@01.software/sdk 0.43.0 → 0.45.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 (80) hide show
  1. package/CHANGELOG.md +375 -2
  2. package/MIGRATION.md +89 -10
  3. package/README.md +451 -109
  4. package/dist/{server-nXOezi4b.d.cts → catalog-mappers-sIshKNb3.d.cts} +14 -3
  5. package/dist/{server-nXOezi4b.d.ts → catalog-mappers-sIshKNb3.d.ts} +14 -3
  6. package/dist/client.cjs +2680 -345
  7. package/dist/client.cjs.map +1 -1
  8. package/dist/client.d.cts +10 -7
  9. package/dist/client.d.ts +10 -7
  10. package/dist/client.js +2680 -345
  11. package/dist/client.js.map +1 -1
  12. package/dist/{collection-client-U70KizLf.d.cts → collection-client-BSo7VXQE.d.cts} +8 -5
  13. package/dist/{collection-client-gWsA7BVP.d.ts → collection-client-Ckfusuee.d.ts} +8 -5
  14. package/dist/{const-_WWHU-gX.d.ts → const-BAG0MH0u.d.cts} +13 -10
  15. package/dist/{const-BBynifk0.d.cts → const-CaPq2AE4.d.ts} +13 -10
  16. package/dist/errors.cjs +74 -1
  17. package/dist/errors.cjs.map +1 -1
  18. package/dist/errors.d.cts +38 -1
  19. package/dist/errors.d.ts +38 -1
  20. package/dist/errors.js +74 -1
  21. package/dist/errors.js.map +1 -1
  22. package/dist/{index-DPi5NWU8.d.ts → index-BNICJxBL.d.ts} +4 -4
  23. package/dist/{index-Dg4Y6Aq7.d.cts → index-YGrZrxNn.d.cts} +4 -4
  24. package/dist/index.cjs +2741 -368
  25. package/dist/index.cjs.map +1 -1
  26. package/dist/index.d.cts +18 -19
  27. package/dist/index.d.ts +18 -19
  28. package/dist/index.js +2741 -368
  29. package/dist/index.js.map +1 -1
  30. package/dist/{payload-types-DLxkB7Mv.d.cts → payload-types-CJZuXcgF.d.cts} +6873 -4208
  31. package/dist/{payload-types-DLxkB7Mv.d.ts → payload-types-CJZuXcgF.d.ts} +6873 -4208
  32. package/dist/query.cjs +550 -101
  33. package/dist/query.cjs.map +1 -1
  34. package/dist/query.d.cts +43 -45
  35. package/dist/query.d.ts +43 -45
  36. package/dist/query.js +550 -101
  37. package/dist/query.js.map +1 -1
  38. package/dist/realtime.cjs.map +1 -1
  39. package/dist/realtime.d.cts +2 -2
  40. package/dist/realtime.d.ts +2 -2
  41. package/dist/realtime.js.map +1 -1
  42. package/dist/server.cjs +2252 -444
  43. package/dist/server.cjs.map +1 -1
  44. package/dist/server.d.cts +11 -10
  45. package/dist/server.d.ts +11 -10
  46. package/dist/server.js +2252 -444
  47. package/dist/server.js.map +1 -1
  48. package/dist/storefront-cache.cjs +65 -1
  49. package/dist/storefront-cache.cjs.map +1 -1
  50. package/dist/storefront-cache.js +65 -1
  51. package/dist/storefront-cache.js.map +1 -1
  52. package/dist/types-BC6rVmJa.d.ts +47 -0
  53. package/dist/{types-B9lGhZlB.d.ts → types-BlOvjoTX.d.ts} +1095 -504
  54. package/dist/{types-Bpx5sYKo.d.cts → types-C4WuJfw7.d.cts} +1095 -504
  55. package/dist/types-D5p7-m1l.d.cts +47 -0
  56. package/dist/ui/canvas/server.cjs +244 -2
  57. package/dist/ui/canvas/server.cjs.map +1 -1
  58. package/dist/ui/canvas/server.d.cts +27 -1
  59. package/dist/ui/canvas/server.d.ts +27 -1
  60. package/dist/ui/canvas/server.js +244 -2
  61. package/dist/ui/canvas/server.js.map +1 -1
  62. package/dist/ui/canvas.cjs +419 -103
  63. package/dist/ui/canvas.cjs.map +1 -1
  64. package/dist/ui/canvas.d.cts +19 -7
  65. package/dist/ui/canvas.d.ts +19 -7
  66. package/dist/ui/canvas.js +419 -103
  67. package/dist/ui/canvas.js.map +1 -1
  68. package/dist/ui/form.d.cts +1 -1
  69. package/dist/ui/form.d.ts +1 -1
  70. package/dist/ui/video.d.cts +1 -1
  71. package/dist/ui/video.d.ts +1 -1
  72. package/dist/webhook.cjs +2 -1
  73. package/dist/webhook.cjs.map +1 -1
  74. package/dist/webhook.d.cts +4 -4
  75. package/dist/webhook.d.ts +4 -4
  76. package/dist/webhook.js +2 -1
  77. package/dist/webhook.js.map +1 -1
  78. package/package.json +12 -11
  79. package/dist/types-B1ngKbWI.d.cts +0 -33
  80. package/dist/types-BiKNBwRA.d.ts +0 -33
package/README.md CHANGED
@@ -63,6 +63,10 @@ entry.
63
63
  | `@01.software/sdk` | browser-safe `createClient`, commerce helpers, collection helpers, types | none |
64
64
  | `@01.software/sdk/client` | browser-safe `createClient` entry | none |
65
65
  | `@01.software/sdk/server` | `createServerClient`, server-only collection, commerce, and preview APIs | none; keep `secretKey` code on the server |
66
+ | `@01.software/sdk/errors` | SDK error classes and guards | none |
67
+ | `@01.software/sdk/analytics` | browser analytics client and typed event helpers | none |
68
+ | `@01.software/sdk/metadata` | SEO metadata extraction and generation helpers | none |
69
+ | `@01.software/sdk/webhook` | webhook handlers, event guards, and webhook types | none |
66
70
  | `@01.software/sdk/query` | React Query hooks, cache helpers, `getQueryClient` | `@tanstack/react-query`, `react`, `react-dom` |
67
71
  | `@01.software/sdk/realtime` | `RealtimeConnection`, `useRealtimeQuery` | `@tanstack/react-query`, `react`, `react-dom` |
68
72
  | `@01.software/sdk/storefront-cache` | product storefront cache resource name helpers | none |
@@ -75,9 +79,13 @@ entry.
75
79
  | `@01.software/sdk/ui/video` | `VideoPlayer` | `react`, `react-dom`, `@mux/mux-player-react` |
76
80
  | `@01.software/sdk/ui/image` | `Image` | `react`, `react-dom` |
77
81
 
82
+ `CanvasRenderer` renders reserved `shape` nodes (`terminator`, `process`,
83
+ `decision`, `io`, `subprocess`) with first-party SVG geometry, without a tenant
84
+ catalog row or QuickJS template.
85
+
78
86
  If a feature is not listed here, it does not need a separate peer install.
79
87
  For the full component-to-peer mapping, see
80
- `packages/sdk/.claude/rules/components-reference.md`.
88
+ `packages/published/sdk/.claude/rules/components-reference.md`.
81
89
 
82
90
  Migration quick reference:
83
91
 
@@ -211,23 +219,20 @@ const server = createServerClient({
211
219
  })
212
220
  const serverQuery = createServerQueryHooks(server)
213
221
 
214
- // Create order (server only)
215
- const order = await server.commerce.orders.create({
216
- orderNumber: generateOrderNumber(),
217
- customerSnapshot: { email: 'user@example.com' },
218
- shippingAddress: {
219
- recipientName: 'John',
220
- phone: '010-1234-5678',
221
- postalCode: '12345',
222
- address: 'Seoul',
223
- detailAddress: 'Apt 101',
224
- },
225
- items: [
226
- { product: productId, variant: variantId, option: optionId, quantity: 1 },
227
- ],
228
- totalAmount: 10000,
229
- pgPaymentId: 'provider-payment-id', // optional (omit for free orders)
230
- discountCode: 'WELCOME10', // optional
222
+ // Start the canonical Cart -> Checkout -> PaymentSession flow.
223
+ const { cart, cartToken } = await server.commerce.carts.create({ market: 'kr' })
224
+ await server.commerce.carts.addItem({
225
+ cartToken,
226
+ productId,
227
+ variantId,
228
+ quantity: 1,
229
+ expectedRevision: cart.revision,
230
+ idempotencyKey: crypto.randomUUID(),
231
+ })
232
+ const checkout = await server.commerce.checkouts.create({
233
+ cartToken,
234
+ customer: { email: 'user@example.com' },
235
+ idempotencyKey: crypto.randomUUID(),
231
236
  })
232
237
 
233
238
  // SSR prefetch (server)
@@ -237,13 +242,31 @@ await serverQuery.prefetchQuery({
237
242
  })
238
243
  ```
239
244
 
240
- Create-order inputs prefer `items`; existing `orderItems` callers remain
241
- supported and are serialized to the same endpoint field. Order reads expose
242
- joined line items at `order.items.docs`.
245
+ Provider-paid and manual Order creation are not part of the 0.45 SDK surface.
246
+ An Order is emitted only when the Checkout is placed. Canonical Order reads
247
+ expose line snapshots directly at `order.items`.
243
248
 
244
249
  Always import `createServerClient` from `@01.software/sdk/server` so generated
245
250
  code and bundlers do not blur the Secret Key boundary.
246
251
 
252
+ ### Tenant context introspection
253
+
254
+ Trusted server workflows can inspect the resolved tenant capability context
255
+ without calling the Console endpoint directly:
256
+
257
+ ```typescript
258
+ const context = await server.tenant.context()
259
+
260
+ console.log(context.features)
261
+ console.log(context.collections.active)
262
+ console.log(context.fieldConfigs)
263
+ ```
264
+
265
+ Pass `{ includeCounts: true }` only when document counts and webhook
266
+ configuration are needed. That mode bypasses the endpoint cache and performs
267
+ additional collection reads. Tenant introspection is server-only and is not
268
+ exported from the browser-safe root entry.
269
+
247
270
  Server-rendered preview routes can use `server.preview.detail()` with the
248
271
  short-lived preview token issued by Console:
249
272
 
@@ -337,34 +360,34 @@ const { product } = result
337
360
  // featuredImage, priceRange, compareAtPriceRange, availableForSale, selectedOrFirstAvailableVariant }
338
361
  ```
339
362
 
340
- `detail()` returns `ProductDetailResult`, a discriminated union:
341
- `{ found: true, product: ProductDetail } | { found: false, reason }`. The
342
- `reason` value is one of `not_found`, `not_published`, or
343
- `feature_disabled`, so storefronts can choose between a standard 404, preview
344
- CTA, or feature gating UI. Permission/auth errors, including 403 tenant
345
- mismatches, still throw
346
- typed `SDKError` subclasses and preserve request IDs through the existing
347
- `lastRequestId` / `onRequestId` path.
348
-
349
- Public product visibility has two axes. Lifecycle `status` must still be
350
- `published` and `publishedAt` must be current or empty. Then
351
- `storefrontVisibility` decides storefront exposure: `listed` products appear in
352
- listing helpers and direct detail, `unlisted` products are omitted from listing
353
- helpers but can be fetched by direct detail slug/id, and `hidden` products return
354
- `{ found: false, reason: 'not_published' }` from detail and are omitted from
355
- listings. Legacy products without `storefrontVisibility` are treated as
356
- `listed`.
357
-
358
- The successful product payload exposes inventory rollups without sentinel
359
- values: `product.totalInventory` is the tracked stock sum across non-unlimited
360
- variants, `null` when no variants are tracked, and
361
- `product.hasUnlimitedVariant` signals whether any variant is unlimited.
363
+ Browser `detail()` returns `ProductDetailCatalogResult`, the same public-safe
364
+ catalog detail shape as `detailCatalog()`: `{ found: true, product:
365
+ ProductDetailCatalog } | { found: false, reason }`. The `reason` value is one of
366
+ `not_found`, `not_published`, or `feature_disabled`, so storefronts can choose
367
+ between a standard 404, preview CTA, or feature gating UI. Permission/auth
368
+ errors, including 403 tenant mismatches, still throw typed `SDKError` subclasses
369
+ and preserve request IDs through the existing `lastRequestId` / `onRequestId`
370
+ path.
371
+
372
+ Public product visibility is driven by product `status` and `publishedAt`.
373
+ Listing helpers return products with `status: 'published'` when `publishedAt`
374
+ is current or empty. Direct detail and checkout admission also allow
375
+ `status: 'unlisted'`, so an unlisted product can be opened by slug/id without
376
+ appearing in PLP/search/curated listing helpers. Draft, archived, scheduled, or
377
+ malformed lifecycle states return `{ found: false, reason: 'not_published' }`
378
+ from detail and are omitted from listings.
379
+
380
+ The successful browser product payload omits operational inventory fields such as
381
+ `product.totalInventory`, variant `stock`, and variant `reservedStock`. Use
382
+ `stockSnapshot()` / `stockCheck()` for live storefront inventory overlays, or use
383
+ `createServerClient().commerce.product.detail()` from trusted server code when a
384
+ full operational detail read is required.
362
385
 
363
386
  ### Edge-cached catalog + live stock (storefront migration)
364
387
 
365
388
  When a PDP or listing UI is served behind the Console Edge CDN, prefer the
366
- catalog/stock split instead of reading `variant.stock` from a cached
367
- `detail()` response (inventory in that payload can lag for the catalog TTL).
389
+ catalog/stock split instead of treating cached catalog responses as live
390
+ inventory authorities.
368
391
 
369
392
  ```typescript
370
393
  import { createClient, mergeProductDetailWithStock } from '@01.software/sdk'
@@ -388,8 +411,8 @@ const { product, stockMergeStatus } = mergeProductDetailWithStock(
388
411
  Listing UIs use the same pattern: `listingPage()` for PLP/search grids, or
389
412
  `listingGroupsCatalog({ productIds })` when curated product IDs are already
390
413
  known, then `stockSnapshot()` (or `stock-check` at cart/checkout) for live
391
- availability. `detail()` and POST detail/listing endpoints are unchanged during
392
- the migration window; see ADR 0012 addendum in `docs/decisions/0012-sdk-public-commerce-contract.md`.
414
+ availability. Browser `detail()` is now catalog-safe as well; see ADR 0012
415
+ addendum in `docs/decisions/0012-sdk-public-commerce-contract.md`.
393
416
 
394
417
  ### Product Listing Pages (PLP) — join-safe queries
395
418
 
@@ -457,13 +480,6 @@ const { docs } = await server.collections.from('products').find({
457
480
  { publishedAt: { equals: null } },
458
481
  ],
459
482
  },
460
- {
461
- or: [
462
- { storefrontVisibility: { equals: 'listed' } },
463
- { storefrontVisibility: { exists: false } },
464
- { storefrontVisibility: { equals: null } },
465
- ],
466
- },
467
483
  ],
468
484
  },
469
485
  limit: 24,
@@ -684,6 +700,57 @@ selection param. Each swatch carries a hint-only slug-compat href such as
684
700
  `preferCompleteVariantFromHint: true` on `buildProductListingCard()` only when
685
701
  the card should deep-link a complete hint variant.
686
702
 
703
+ ## Memberships
704
+
705
+ Membership self-service is split by credential boundary:
706
+
707
+ - Browser/customer JWT clients can read the signed-in customer's current or
708
+ latest membership and request cancellation for active, trialing, or past-due
709
+ memberships.
710
+ - Server clients with a Secret Key can subscribe a customer after the storefront
711
+ has completed the delegated first charge.
712
+
713
+ ```typescript
714
+ import { createClient } from '@01.software/sdk'
715
+ import { createServerClient } from '@01.software/sdk/server'
716
+
717
+ const client = createClient({ publishableKey: 'pk_live_...' })
718
+
719
+ const membership = await client.memberships.me()
720
+ if (
721
+ membership &&
722
+ ['active', 'trialing', 'past_due'].includes(membership.status)
723
+ ) {
724
+ await client.memberships.cancel({ id: membership.id })
725
+ }
726
+
727
+ const server = createServerClient({
728
+ publishableKey: 'pk_live_...',
729
+ secretKey: process.env.SOFTWARE_SECRET_KEY!,
730
+ })
731
+
732
+ await server.memberships.subscribe({
733
+ planId: 'plan_123',
734
+ customerId: 'customer_123',
735
+ customerRef: 'cus_provider_123',
736
+ pgPaymentId: 'pay_123',
737
+ amount: 9900,
738
+ currency: 'KRW',
739
+ subscribeIntentKey: 'checkout-session-123',
740
+ card: { last4: '4242', brand: 'visa' },
741
+ })
742
+ ```
743
+
744
+ `subscribeIntentKey` is sent as `X-Idempotency-Key`; it is not part of the JSON
745
+ body. Replays must use the same plan, customer reference, amount, currency, and
746
+ paid-start provider payment id snapshot as the original incomplete membership.
747
+ Trial subscriptions may omit `pgPaymentId` because no first-period charge has
748
+ been captured yet; paid starts must include the provider payment id that funded
749
+ the first period.
750
+ `memberships.me()` and browser `memberships.cancel()` require the customer JWT
751
+ configured on the client. Refund webhook `refundedAmount` values are cumulative
752
+ charge totals, not per-event deltas.
753
+
687
754
  ## Storefront performance defaults
688
755
 
689
756
  - **PLP:** prefer `commerce.product.listingPage()` or `useProductListingPage()` (GET `/api/products/listing-groups/query/catalog`, CDN-cacheable, card-ready). Use `listingGroupsCatalog({ productIds })` only when IDs are already known. Treat the full listing-groups response shape as a server-auth escape hatch because it can include operational stock fields. Avoid fetching a product list and then calling `detail()` per card.
@@ -775,7 +842,7 @@ Dotted-path filters (`where: { 'product.slug': { equals } }`) are Payload-native
775
842
 
776
843
  Checklist when `find()` returns `docs: []` unexpectedly, in order of likelihood:
777
844
 
778
- 1. **Access control filtered the document.** Many collections enforce public read filters. Products require `status: 'published'`, a current or unset `publishedAt`, and listable storefront visibility (`listed`, legacy missing, or `null`) for publishable-key raw listing reads. Draft, future, `unlisted`, `hidden`, or malformed products silently disappear from raw listing results even when their slug or ID matches. Use the shaped product detail helper for direct-link `unlisted` products. Correlate with backend logs via `client.lastRequestId` (or catch `SDKError.requestId`).
845
+ 1. **Access control filtered the document.** Many collections enforce public read filters. Product listing reads require `status: 'published'` plus a current or unset `publishedAt`. Draft, future, `unlisted`, archived, or malformed products silently disappear from raw listing results even when their slug or ID matches. Use `commerce.product.detail({ slug | id })` for `unlisted` products. Correlate with backend logs via `client.lastRequestId` (or catch `SDKError.requestId`).
779
846
  2. **Build-time publishable key / API URL differs from runtime.** SSG `generateStaticParams` / `generateMetadata` / the page render must all see the same tenant context. A wrong or missing key at build time produces a baked-in empty response.
780
847
  3. **Next.js SSG fetch cache served a stale empty response.** Use `cache: 'no-store'` or `export const revalidate = 0` on server components that should reflect live data.
781
848
  4. **`where: { slug: 'x' }` string shorthand.** Always use `{ slug: { equals: 'x' } }` — bare strings silently match nothing.
@@ -875,6 +942,24 @@ const product = await server.collections.from('products').findById(id, {
875
942
  const product = await client.collections.from('products').findById('id')
876
943
  ```
877
944
 
945
+ Localized raw collection reads can request a supported Payload locale. The SDK
946
+ currently exposes `en` and `ko`; it does not expose Payload's `locale=all`
947
+ shape. `fallbackLocale` serializes to Payload's REST `fallback-locale` query
948
+ key, and `fallbackLocale: false` serializes as `fallback-locale=none`.
949
+
950
+ ```typescript
951
+ const { docs } = await client.collections.from('articles').find({
952
+ locale: 'ko',
953
+ fallbackLocale: 'en',
954
+ limit: 10,
955
+ })
956
+
957
+ const article = await server.collections.from('articles').findById(id, {
958
+ locale: 'ko',
959
+ fallbackLocale: false,
960
+ })
961
+ ```
962
+
878
963
  Raw collection mutations are an escape hatch. For ecommerce product catalog
879
964
  writes, prefer `server.commerce.product.upsert()` so options, option-values,
880
965
  and variants are written through the domain transaction.
@@ -1052,13 +1137,58 @@ await server.collections.from('articles').update('article_id', {
1052
1137
 
1053
1138
  ### Customer Auth
1054
1139
 
1055
- Customer auth methods currently cover local email/password flows: register,
1056
- login, refresh, password reset, profile read/update, and password change.
1057
- `CustomerProfile.authProvider` may contain `google`, `apple`, `kakao`, or
1058
- `naver` for accounts created through platform/provider integrations, but the
1059
- SDK does not expose social-login initiation or callback helpers yet.
1140
+ New storefronts should use hosted customer OAuth through
1141
+ `client.customer.oauth.*`. The SDK generates PKCE authorization URLs, parses the
1142
+ callback, exchanges codes, rotates refresh tokens, and calls hosted logout, but
1143
+ it does not store tokens for you. Store OAuth access, refresh, and transient
1144
+ state in trusted route handlers, preferably HttpOnly cookies or a server-side
1145
+ session store. Do not put OAuth tokens in browser-readable storage.
1146
+
1147
+ ```typescript
1148
+ const client = createClient({
1149
+ publishableKey: process.env.NEXT_PUBLIC_SOFTWARE_PUBLISHABLE_KEY,
1150
+ customer: {
1151
+ persist: false,
1152
+ oauth: {
1153
+ issuer: process.env.NEXT_PUBLIC_SOFTWARE_CUSTOMER_AUTH_ISSUER!,
1154
+ clientId: process.env.NEXT_PUBLIC_SOFTWARE_CUSTOMER_CLIENT_ID!,
1155
+ },
1156
+ },
1157
+ })
1060
1158
 
1061
- Available on Client via `client.customer.auth.*`.
1159
+ const customerOAuth = client.customer.oauth
1160
+ if (!customerOAuth) throw new Error('Customer OAuth is not configured')
1161
+
1162
+ const authorization = await customerOAuth.createAuthorizationUrl({
1163
+ redirectUri: `${origin}/api/auth/customer/callback`,
1164
+ returnTo: '/account',
1165
+ })
1166
+
1167
+ const callback = customerOAuth.parseCallback(callbackUrl, {
1168
+ expectedState: authorization.state,
1169
+ })
1170
+
1171
+ const tokens = await customerOAuth.exchangeCode({
1172
+ code: callback.code,
1173
+ codeVerifier: authorization.codeVerifier,
1174
+ redirectUri: `${origin}/api/auth/customer/callback`,
1175
+ })
1176
+
1177
+ await customerOAuth.refresh({ refreshToken: tokens.refreshToken })
1178
+ await customerOAuth.logout({ refreshToken: tokens.refreshToken })
1179
+ ```
1180
+
1181
+ `exchangeCode()` may return an `idToken` when the `openid` scope is granted. In
1182
+ this SDK release, treat `idToken` as opaque unless your server verifies issuer,
1183
+ audience, expiry, nonce, and hosted JWKS signature itself. Use `me()` after
1184
+ establishing an access token for trusted customer profile display.
1185
+
1186
+ #### Legacy direct local auth
1187
+
1188
+ `client.customer.auth.*` remains available for existing local email/password
1189
+ flows: register, login, refresh, password reset, profile read/update, and
1190
+ password change. Keep this path as explicit legacy compatibility for existing
1191
+ integrations; do not overload `login()` to sometimes redirect into hosted auth.
1062
1192
 
1063
1193
  ```typescript
1064
1194
  const client = createClient({
@@ -1086,7 +1216,7 @@ client.customer.auth.logout()
1086
1216
  const orders = await client.commerce.orders.listMine({
1087
1217
  page: 1,
1088
1218
  limit: 10,
1089
- displayFinancialStatus: 'paid',
1219
+ financialStatus: 'paid',
1090
1220
  })
1091
1221
 
1092
1222
  // Password
@@ -1095,15 +1225,191 @@ await client.customer.auth.resetPassword(token, newPassword)
1095
1225
  await client.customer.auth.changePassword(currentPassword, newPassword)
1096
1226
  ```
1097
1227
 
1098
- ### Commerce Orders (ServerClient-only writes)
1228
+ `forgotPassword()` keeps the same SDK signature in both delivery modes. A
1229
+ tenant administrator chooses either backward-compatible signed webhook
1230
+ delivery or platform-managed email in the Console customer-authentication
1231
+ settings. Platform email requires the storefront reset URL to be configured;
1232
+ the platform appends the `token` query parameter and sends through the verified
1233
+ tenant sender or the platform fallback. Do not add reset tokens or dynamic
1234
+ redirect URLs to the SDK call.
1235
+
1236
+ ### Market discovery
1237
+
1238
+ Discover a tenant's active buyer markets and resolve the default market. These
1239
+ reads are safe for a publishable key — server-only FX/pricing fields are never
1240
+ returned.
1241
+
1242
+ ```ts
1243
+ const markets = await client.markets.list() // MarketDTO[] (active markets)
1244
+ const primary = await client.markets.default() // MarketDTO | null (active primary)
1245
+
1246
+ // Pass the resolved handle to market-aware commerce calls:
1247
+ const cart = await client.commerce.carts.create({ market: primary?.handle })
1248
+ ```
1249
+
1250
+ `MarketDTO` is `{ id, handle, name, countryCode, targetCountries, currency,
1251
+ isPrimary, isActive }` — `countryCode`/`targetCountries` are ISO 3166-1 alpha-2,
1252
+ `currency` is ISO 4217.
1253
+
1254
+ > `client.markets.default()` resolves the tenant's primary market from server
1255
+ > data. It is distinct from the client's _configured_ default market handle
1256
+ > (`CommerceClient.getDefaultMarket()`), which is the handle you passed to
1257
+ > `createClient({ market })`.
1258
+
1259
+ ### Commerce Market Context
1260
+
1261
+ Pass `market` when constructing a client to default market-aware product and cart
1262
+ helpers to that market. Per-call `market` still wins when supplied.
1263
+
1264
+ ```typescript
1265
+ const client = createClient({
1266
+ publishableKey: process.env.NEXT_PUBLIC_SOFTWARE_PUBLISHABLE_KEY,
1267
+ market: 'kr',
1268
+ })
1269
+
1270
+ await client.commerce.product.detail({ slug: 'shirt' }) // uses kr
1271
+ await client.commerce.carts.create() // creates a kr cart
1272
+ await client.commerce.product.detail({ slug: 'shirt', market: 'us' }) // override
1273
+
1274
+ const usClient = client.withMarket('us')
1275
+ const { cart } = await usClient.commerce.carts.retrieve({ cartToken })
1276
+ await usClient.commerce.carts.addItem({
1277
+ cartToken,
1278
+ productId,
1279
+ variantId,
1280
+ quantity,
1281
+ expectedRevision: cart.revision,
1282
+ idempotencyKey: crypto.randomUUID(),
1283
+ })
1284
+ ```
1099
1285
 
1100
- Available on ServerClient via `server.commerce.orders.*`. `checkout` and `listMine` are also on Client and return sanitized customer-facing order DTOs. Customer order DTOs expose the independent status axes (`displayFinancialStatus`, `displayFulfillmentStatus`, `returnStatus`, `primaryDisplayStatus`, `returnDisplayStatus`); `status` and `displayStatus` are read-only compatibility aliases for older clients. Use `server.collections.from('orders')` for raw operational order documents.
1286
+ `createServerClient({ market })` and `server.withMarket(market)` provide the
1287
+ same request-scoped default for trusted server code.
1288
+
1289
+ ### Commerce Checkout and PaymentSession
1290
+
1291
+ The canonical payment lifecycle is Cart → Checkout → PaymentSession → Order.
1292
+ Checkout and PaymentSession results are required discriminated unions: narrow
1293
+ their state fields before reading state-specific data such as `nextAction` or
1294
+ `order`. Browser calls use publishable-key capability tokens and the matching
1295
+ customer JWT when a resource is customer-bound. Server calls use the secret
1296
+ credential and never expose provider secrets in returned DTOs.
1297
+
1298
+ Browser and server commerce writes use separate transports even when their JSON
1299
+ shapes match. `createServerClient()` routes every Cart operation, Checkout
1300
+ create/retrieve/query, and PaymentSession create/retrieve/cancel/confirm through
1301
+ the resource-owned `/server/*` namespace. Public paths retain only their exact
1302
+ capability/customer bodies and never advertise the server credential.
1303
+
1304
+ Generate one idempotency key per logical mutation, persist it with the pending
1305
+ operation, and reuse it after timeouts or lost responses. Creating a fresh key
1306
+ for each retry starts a different operation. Do not derive ordinary keys from a
1307
+ Cart, Checkout, PaymentSession, Order, or provider id: reuse of that resource for
1308
+ a later operation would collide. Use `createCommerceIdempotencyKey()` and retain
1309
+ the generated value with the pending operation.
1310
+
1311
+ Cart add/update/remove/clear/discount/attribute mutations additionally require
1312
+ the last observed `cart.revision` as `expectedRevision`. A
1313
+ `cart_revision_conflict` means refetch the Cart and start a new logical mutation
1314
+ with a new idempotency key. Cart merge requires the key but not a single revision
1315
+ because it atomically resolves two current Cart states.
1316
+ Treat a replayed Cart result as an operation receipt: never replace a newer
1317
+ cached Cart with a lower `revision`; retrieve when response ordering is unclear.
1318
+
1319
+ ```typescript
1320
+ import {
1321
+ createCommerceIdempotencyKey,
1322
+ isCommerceSDKError,
1323
+ } from '@01.software/sdk'
1324
+
1325
+ const checkoutIdempotencyKey = createCommerceIdempotencyKey()
1326
+ const { checkout, checkoutToken } = await client.commerce.checkouts.create({
1327
+ cartToken,
1328
+ customer: { email: 'buyer@example.com', name: 'Buyer' },
1329
+ shippingAddress,
1330
+ idempotencyKey: checkoutIdempotencyKey,
1331
+ })
1332
+
1333
+ const paymentIdempotencyKey = createCommerceIdempotencyKey()
1334
+ const paymentSession = await client.commerce.paymentSessions.create({
1335
+ checkoutToken,
1336
+ provider: 'toss',
1337
+ providerOptions: { profile: 'widget_v2' },
1338
+ idempotencyKey: paymentIdempotencyKey,
1339
+ })
1340
+
1341
+ if (
1342
+ paymentSession.presentationStatus === 'actionable' &&
1343
+ paymentSession.nextAction.type === 'toss_widget'
1344
+ ) {
1345
+ // Initialize the Toss widget with the returned public client/variant fields.
1346
+ }
1347
+
1348
+ // Browser recovery requires the parent capability.
1349
+ await client.commerce.paymentSessions.retrieve({
1350
+ paymentSessionId: paymentSession.id,
1351
+ checkoutToken,
1352
+ })
1353
+
1354
+ // Toss confirmation is a typed server-only adapter action. There is no generic
1355
+ // `confirmVerified` or provider-paid Order creation escape hatch.
1356
+ const confirmationIdempotencyKey = createCommerceIdempotencyKey()
1357
+ let confirmed
1358
+ try {
1359
+ confirmed = await server.commerce.paymentSessions.confirm({
1360
+ paymentSessionId: paymentSession.id,
1361
+ idempotencyKey: confirmationIdempotencyKey,
1362
+ action: {
1363
+ type: 'toss_authorization',
1364
+ paymentKey,
1365
+ providerOrderId,
1366
+ },
1367
+ })
1368
+ } catch (error) {
1369
+ if (isCommerceSDKError(error) && error.retryable) {
1370
+ // Retry the same logical operation with confirmationIdempotencyKey.
1371
+ }
1372
+ throw error
1373
+ }
1374
+
1375
+ if (confirmed.finalizationStatus === 'committed') {
1376
+ console.log(confirmed.order.orderNumber)
1377
+ }
1378
+
1379
+ const checkoutPage = await server.commerce.checkouts.query({
1380
+ status: ['open'],
1381
+ limit: 20,
1382
+ })
1383
+ void checkoutPage.nextCursor
1384
+ ```
1385
+
1386
+ `checkoutToken` is returned only alongside Checkout creation; ordinary Checkout
1387
+ DTOs never echo the bearer capability. Browser `checkouts.retrieve` and
1388
+ PaymentSession retrieve/cancel calls accept that token. Server
1389
+ `checkouts.retrieve({ checkoutId })` uses tenant-scoped authority, and
1390
+ `checkouts.query` returns lightweight rows without capabilities.
1391
+
1392
+ Canonical commerce error envelopes become `CommerceSDKError`. Branch on its
1393
+ stable `reason`, inspect `retryable`, and log `requestId`; a newer server reason
1394
+ is preserved as `reason: 'unknown_commerce_error'` plus `rawReason`.
1395
+
1396
+ ### Commerce Orders (post-placement operations)
1397
+
1398
+ Provider-paid `orders.create`, `orders.confirmPayment`, and ordinary
1399
+ `orders.updateTransaction` were removed in SDK 0.45. No general manual Order API
1400
+ is exposed. After a PaymentSession commits an Order, trusted server code can use
1401
+ the remaining fulfillment, cancellation, and return operations. Customer order
1402
+ reads remain available through `client.commerce.orders.listMine()`.
1403
+
1404
+ The SDK root no longer exports raw generated `Order`, `Transaction`, `Cart`, or
1405
+ `CartItem` Payload documents. Use the canonical `PublicOrder` / `PublicCart`
1406
+ DTOs for storefront code and explicit server collection types only inside
1407
+ trusted operational code. Order cancellation no longer returns the deprecated
1408
+ inline provider-refund branch: a paid cancellation returns `refundPending`, and
1409
+ the dedicated refund-resolution flow reports provider completion.
1101
1410
 
1102
1411
  ```typescript
1103
- // Orders
1104
- await server.commerce.orders.create(params)
1105
1412
  await server.commerce.orders.update({ orderNumber, status: 'confirmed' })
1106
- await server.commerce.orders.checkout({ cartId, pgPaymentId?, orderNumber, customerSnapshot, discountCode? })
1107
1413
 
1108
1414
  // Single-order lookup (getOrder endpoint removed — use collection find)
1109
1415
  const { docs: [order] } = await server.collections.from('orders').find({
@@ -1118,23 +1424,6 @@ await server.commerce.orders.createFulfillment({ orderNumber, items })
1118
1424
  await server.commerce.orders.updateFulfillment({ fulfillmentId, carrier, trackingNumber })
1119
1425
  await server.commerce.orders.bulkImportFulfillments({ items: [{ orderNumber, carrier, trackingNumber }] })
1120
1426
 
1121
- // Provider-verified payment confirmation
1122
- // Provider webhook handlers should verify with the provider first, then call:
1123
- await server.commerce.orders.confirmPayment({
1124
- orderNumber,
1125
- pgProvider: 'provider-name',
1126
- pgPaymentId,
1127
- amount,
1128
- providerStatus: 'PAID',
1129
- providerEventId,
1130
- })
1131
-
1132
- // Low-level transaction annotation / compatibility path. Prefer confirmPayment()
1133
- // for normal provider-verified paid transitions.
1134
- // status: 'failed' records a retryable PG failure on the Transaction only; the
1135
- // Order stays pending until verified payment succeeds or the merchant cancels it.
1136
- await server.commerce.orders.updateTransaction({ pgPaymentId, status, paymentMethod, receiptUrl })
1137
-
1138
1427
  // Returns
1139
1428
  await server.commerce.orders.createReturn({
1140
1429
  orderNumber,
@@ -1163,8 +1452,14 @@ Available on both Client (customer JWT) and ServerClient (secretKey).
1163
1452
 
1164
1453
  ```typescript
1165
1454
  await client.commerce.discounts.validate({ code, orderAmount })
1166
- await client.commerce.shipping.calculate({ shippingPolicyId?, orderAmount, postalCode? })
1167
- // calculateShipping returns: { shippingAmount, baseShippingAmount, extraShippingAmount, freeShipping, freeShippingMinAmount, isJeju, isRemoteIsland }
1455
+ await client.commerce.shipping.calculate({
1456
+ shippingPolicyId?,
1457
+ orderAmount,
1458
+ postalCode?,
1459
+ countryCode?,
1460
+ regionCode?,
1461
+ })
1462
+ // calculateShipping returns: { shippingAmount, shippingLines, baseShippingAmount, extraShippingAmount, freeShipping, freeShippingMinAmount, isJeju, isRemoteIsland }
1168
1463
  ```
1169
1464
 
1170
1465
  ### Commerce Product
@@ -1282,31 +1577,77 @@ for (const item of results) {
1282
1577
 
1283
1578
  ### Commerce Cart
1284
1579
 
1285
- Available on both Client and ServerClient via `commerce.cart.*`. These helpers return sanitized customer-facing cart DTOs; use `server.collections.from('carts' | 'cart-items')` for raw operational cart documents.
1580
+ Available on both Client and ServerClient via `commerce.carts.*`. These helpers
1581
+ return required, browser-safe DTOs; use
1582
+ `server.collections.from('carts' | 'cart-items')` for raw operational documents.
1286
1583
 
1287
1584
  ```typescript
1288
- // Add item to cart
1289
- await client.commerce.cart.addItem({
1290
- cartId,
1291
- product,
1292
- variant,
1293
- option,
1294
- quantity,
1585
+ const created = await client.commerce.carts.create({
1586
+ market: 'kr',
1587
+ attributes: [{ key: 'channel', value: 'web' }],
1588
+ })
1589
+ const { cartToken, warnings } = created
1590
+ let cart = created.cart
1591
+
1592
+ cart = (
1593
+ await client.commerce.carts.addItem({
1594
+ cartToken,
1595
+ productId,
1596
+ variantId,
1597
+ quantity,
1598
+ admissions: [{ eventId, occurrenceId, admissionTypeId }],
1599
+ expectedRevision: cart.revision,
1600
+ idempotencyKey: crypto.randomUUID(),
1601
+ })
1602
+ ).cart
1603
+
1604
+ cart = (
1605
+ await client.commerce.carts.updateItem({
1606
+ cartToken,
1607
+ cartItemId,
1608
+ quantity,
1609
+ expectedRevision: cart.revision,
1610
+ idempotencyKey: crypto.randomUUID(),
1611
+ })
1612
+ ).cart
1613
+ cart = (
1614
+ await client.commerce.carts.removeItem({
1615
+ cartToken,
1616
+ cartItemId,
1617
+ expectedRevision: cart.revision,
1618
+ idempotencyKey: crypto.randomUUID(),
1619
+ })
1620
+ ).cart
1621
+ await client.commerce.carts.retrieve({ cartToken })
1622
+ cart = (
1623
+ await client.commerce.carts.updateDiscountCodes({
1624
+ cartToken,
1625
+ discountCodes: ['WELCOME10', 'FREESHIP'],
1626
+ expectedRevision: cart.revision,
1627
+ idempotencyKey: crypto.randomUUID(),
1628
+ })
1629
+ ).cart
1630
+ cart = (
1631
+ await client.commerce.carts.updateAttributes({
1632
+ cartToken,
1633
+ attributes: [], // replace/clear
1634
+ expectedRevision: cart.revision,
1635
+ idempotencyKey: crypto.randomUUID(),
1636
+ })
1637
+ ).cart
1638
+ await client.commerce.carts.clear({
1639
+ cartToken,
1640
+ expectedRevision: cart.revision,
1641
+ idempotencyKey: crypto.randomUUID(),
1295
1642
  })
1296
-
1297
- // Update item quantity
1298
- await client.commerce.cart.updateItem({ cartItemId, quantity })
1299
-
1300
- // Remove item from cart
1301
- await client.commerce.cart.removeItem({ cartItemId })
1302
-
1303
- // Other cart operations return sanitized customer-facing cart DTOs.
1304
- await client.commerce.cart.get(cartId)
1305
- await client.commerce.cart.applyDiscount({ cartId, discountCode })
1306
- await client.commerce.cart.removeDiscount({ cartId })
1307
- await client.commerce.cart.clear({ cartId })
1308
1643
  ```
1309
1644
 
1645
+ `updateDiscountCodes({ cartToken, discountCodes })` is the canonical full-list
1646
+ replacement API. Pass an empty list to clear codes. Singular `admission`,
1647
+ `applyDiscount()`, and `removeDiscount()` are not part of the 0.45 surface.
1648
+ Browser `mine()`/`merge({ guestCartToken })` derive customer identity from the
1649
+ JWT; server calls require an explicit `customerToken`.
1650
+
1310
1651
  ### Community Moderation (ServerClient-only)
1311
1652
 
1312
1653
  Available only on ServerClient via `server.community.moderation.*`.
@@ -1412,7 +1753,7 @@ join-order surface and does not emit a semantic order-change webhook.
1412
1753
 
1413
1754
  ## Supported Collections
1414
1755
 
1415
- Source of truth: `packages/sdk/src/core/collection/const.ts` (`COLLECTIONS`).
1756
+ Source of truth: `packages/published/sdk/src/core/collection/const.ts` (`COLLECTIONS`).
1416
1757
 
1417
1758
  | Category | Browser-public generic collections |
1418
1759
  | ------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
@@ -1435,8 +1776,9 @@ operational records, raw cart/cart-item records, form submissions, raw community
1435
1776
  Canvas graph shell rows (`canvas-nodes`, `canvas-edges`), segmentation records,
1436
1777
  and moderation records. They remain available from
1437
1778
  `createServerClient().collections` with secret/PAT credentials. Shaped
1438
- browser/customer helpers such as `commerce.cart.*`,
1439
- `commerce.orders.listMine()`, and `commerce.orders.checkout()` remain available
1779
+ browser/customer helpers such as `commerce.carts.*`,
1780
+ `commerce.checkouts.create/retrieve`, `commerce.paymentSessions.*`, and
1781
+ `commerce.orders.listMine()` remain available
1440
1782
  where customer-facing DTOs are needed, but raw slugs are intentionally absent
1441
1783
  from browser collection discovery.
1442
1784