@01.software/sdk 0.44.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 +358 -2
  2. package/MIGRATION.md +89 -10
  3. package/README.md +441 -98
  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-Dyl-CDcj.d.cts → collection-client-BSo7VXQE.d.cts} +8 -5
  13. package/dist/{collection-client-DWooaiTr.d.ts → collection-client-Ckfusuee.d.ts} +8 -5
  14. package/dist/{const-pGDnsy0o.d.ts → const-BAG0MH0u.d.cts} +13 -10
  15. package/dist/{const-1vleZ6ov.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-DUNySyxV.d.ts → index-BNICJxBL.d.ts} +4 -4
  23. package/dist/{index-a6g7vqAD.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-CDsuOEfM.d.cts → payload-types-CJZuXcgF.d.cts} +6671 -4001
  31. package/dist/{payload-types-CDsuOEfM.d.ts → payload-types-CJZuXcgF.d.ts} +6671 -4001
  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-CDND3hzq.d.cts → types-BlOvjoTX.d.ts} +1095 -509
  54. package/dist/{types-Dt9FJisi.d.ts → types-C4WuJfw7.d.cts} +1095 -509
  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-SoXsbKhj.d.cts +0 -33
  80. package/dist/types-oiae_LMm.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,24 +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
- pgProvider: 'portone', // required with pgPaymentId
230
- pgPaymentId: 'provider-payment-id', // omit both fields for free orders
231
- 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(),
232
236
  })
233
237
 
234
238
  // SSR prefetch (server)
@@ -238,13 +242,31 @@ await serverQuery.prefetchQuery({
238
242
  })
239
243
  ```
240
244
 
241
- Create-order inputs prefer `items`; existing `orderItems` callers remain
242
- supported and are serialized to the same endpoint field. Order reads expose
243
- 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`.
244
248
 
245
249
  Always import `createServerClient` from `@01.software/sdk/server` so generated
246
250
  code and bundlers do not blur the Secret Key boundary.
247
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
+
248
270
  Server-rendered preview routes can use `server.preview.detail()` with the
249
271
  short-lived preview token issued by Console:
250
272
 
@@ -338,14 +360,14 @@ const { product } = result
338
360
  // featuredImage, priceRange, compareAtPriceRange, availableForSale, selectedOrFirstAvailableVariant }
339
361
  ```
340
362
 
341
- `detail()` returns `ProductDetailResult`, a discriminated union:
342
- `{ found: true, product: ProductDetail } | { found: false, reason }`. The
343
- `reason` value is one of `not_found`, `not_published`, or
344
- `feature_disabled`, so storefronts can choose between a standard 404, preview
345
- CTA, or feature gating UI. Permission/auth errors, including 403 tenant
346
- mismatches, still throw
347
- typed `SDKError` subclasses and preserve request IDs through the existing
348
- `lastRequestId` / `onRequestId` path.
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.
349
371
 
350
372
  Public product visibility is driven by product `status` and `publishedAt`.
351
373
  Listing helpers return products with `status: 'published'` when `publishedAt`
@@ -355,16 +377,17 @@ appearing in PLP/search/curated listing helpers. Draft, archived, scheduled, or
355
377
  malformed lifecycle states return `{ found: false, reason: 'not_published' }`
356
378
  from detail and are omitted from listings.
357
379
 
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.
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
 
@@ -677,6 +700,57 @@ selection param. Each swatch carries a hint-only slug-compat href such as
677
700
  `preferCompleteVariantFromHint: true` on `buildProductListingCard()` only when
678
701
  the card should deep-link a complete hint variant.
679
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
+
680
754
  ## Storefront performance defaults
681
755
 
682
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.
@@ -868,6 +942,24 @@ const product = await server.collections.from('products').findById(id, {
868
942
  const product = await client.collections.from('products').findById('id')
869
943
  ```
870
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
+
871
963
  Raw collection mutations are an escape hatch. For ecommerce product catalog
872
964
  writes, prefer `server.commerce.product.upsert()` so options, option-values,
873
965
  and variants are written through the domain transaction.
@@ -1045,13 +1137,58 @@ await server.collections.from('articles').update('article_id', {
1045
1137
 
1046
1138
  ### Customer Auth
1047
1139
 
1048
- Customer auth methods currently cover local email/password flows: register,
1049
- login, refresh, password reset, profile read/update, and password change.
1050
- `CustomerProfile.authProvider` may contain `google`, `apple`, `kakao`, or
1051
- `naver` for accounts created through platform/provider integrations, but the
1052
- 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
+ })
1158
+
1159
+ const customerOAuth = client.customer.oauth
1160
+ if (!customerOAuth) throw new Error('Customer OAuth is not configured')
1053
1161
 
1054
- Available on Client via `client.customer.auth.*`.
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.
1055
1192
 
1056
1193
  ```typescript
1057
1194
  const client = createClient({
@@ -1079,7 +1216,7 @@ client.customer.auth.logout()
1079
1216
  const orders = await client.commerce.orders.listMine({
1080
1217
  page: 1,
1081
1218
  limit: 10,
1082
- displayFinancialStatus: 'paid',
1219
+ financialStatus: 'paid',
1083
1220
  })
1084
1221
 
1085
1222
  // Password
@@ -1088,15 +1225,191 @@ await client.customer.auth.resetPassword(token, newPassword)
1088
1225
  await client.customer.auth.changePassword(currentPassword, newPassword)
1089
1226
  ```
1090
1227
 
1091
- ### 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.
1092
1253
 
1093
- 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.
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
+ ```
1285
+
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.
1094
1410
 
1095
1411
  ```typescript
1096
- // Orders
1097
- await server.commerce.orders.create(params)
1098
1412
  await server.commerce.orders.update({ orderNumber, status: 'confirmed' })
1099
- await server.commerce.orders.checkout({ cartId, orderNumber, customerSnapshot, pgProvider, pgPaymentId, discountCode? })
1100
1413
 
1101
1414
  // Single-order lookup (getOrder endpoint removed — use collection find)
1102
1415
  const { docs: [order] } = await server.collections.from('orders').find({
@@ -1111,29 +1424,6 @@ await server.commerce.orders.createFulfillment({ orderNumber, items })
1111
1424
  await server.commerce.orders.updateFulfillment({ fulfillmentId, carrier, trackingNumber })
1112
1425
  await server.commerce.orders.bulkImportFulfillments({ items: [{ orderNumber, carrier, trackingNumber }] })
1113
1426
 
1114
- // Provider-verified payment confirmation
1115
- // Provider webhook handlers should verify with the provider first, then call:
1116
- await server.commerce.orders.confirmPayment({
1117
- orderNumber,
1118
- pgProvider: 'provider-name',
1119
- pgPaymentId,
1120
- amount,
1121
- providerStatus: 'PAID',
1122
- providerEventId,
1123
- })
1124
-
1125
- // For paid create/checkout flows, pass pgProvider with the same provider slug
1126
- // you will later use for confirmPayment and getByPaymentId. pgProvider and
1127
- // pgPaymentId are a pair: omit both for free orders, or provide both for paid
1128
- // payment windows. getByPaymentId({ pgProvider, pgPaymentId }) is strict and
1129
- // only resolves payment transactions stored with the same provider.
1130
-
1131
- // Low-level transaction annotation / compatibility path. Prefer confirmPayment()
1132
- // for normal provider-verified paid transitions.
1133
- // status: 'failed' records a retryable PG failure on the Transaction only; the
1134
- // Order stays pending until verified payment succeeds or the merchant cancels it.
1135
- await server.commerce.orders.updateTransaction({ pgPaymentId, status, paymentMethod, receiptUrl })
1136
-
1137
1427
  // Returns
1138
1428
  await server.commerce.orders.createReturn({
1139
1429
  orderNumber,
@@ -1162,8 +1452,14 @@ Available on both Client (customer JWT) and ServerClient (secretKey).
1162
1452
 
1163
1453
  ```typescript
1164
1454
  await client.commerce.discounts.validate({ code, orderAmount })
1165
- await client.commerce.shipping.calculate({ shippingPolicyId?, orderAmount, postalCode? })
1166
- // 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 }
1167
1463
  ```
1168
1464
 
1169
1465
  ### Commerce Product
@@ -1281,31 +1577,77 @@ for (const item of results) {
1281
1577
 
1282
1578
  ### Commerce Cart
1283
1579
 
1284
- 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.
1285
1583
 
1286
1584
  ```typescript
1287
- // Add item to cart
1288
- await client.commerce.cart.addItem({
1289
- cartId,
1290
- product,
1291
- variant,
1292
- option,
1293
- 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(),
1294
1642
  })
1295
-
1296
- // Update item quantity
1297
- await client.commerce.cart.updateItem({ cartItemId, quantity })
1298
-
1299
- // Remove item from cart
1300
- await client.commerce.cart.removeItem({ cartItemId })
1301
-
1302
- // Other cart operations return sanitized customer-facing cart DTOs.
1303
- await client.commerce.cart.get(cartId)
1304
- await client.commerce.cart.applyDiscount({ cartId, discountCode })
1305
- await client.commerce.cart.removeDiscount({ cartId })
1306
- await client.commerce.cart.clear({ cartId })
1307
1643
  ```
1308
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
+
1309
1651
  ### Community Moderation (ServerClient-only)
1310
1652
 
1311
1653
  Available only on ServerClient via `server.community.moderation.*`.
@@ -1411,7 +1753,7 @@ join-order surface and does not emit a semantic order-change webhook.
1411
1753
 
1412
1754
  ## Supported Collections
1413
1755
 
1414
- Source of truth: `packages/sdk/src/core/collection/const.ts` (`COLLECTIONS`).
1756
+ Source of truth: `packages/published/sdk/src/core/collection/const.ts` (`COLLECTIONS`).
1415
1757
 
1416
1758
  | Category | Browser-public generic collections |
1417
1759
  | ------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
@@ -1434,8 +1776,9 @@ operational records, raw cart/cart-item records, form submissions, raw community
1434
1776
  Canvas graph shell rows (`canvas-nodes`, `canvas-edges`), segmentation records,
1435
1777
  and moderation records. They remain available from
1436
1778
  `createServerClient().collections` with secret/PAT credentials. Shaped
1437
- browser/customer helpers such as `commerce.cart.*`,
1438
- `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
1439
1782
  where customer-facing DTOs are needed, but raw slugs are intentionally absent
1440
1783
  from browser collection discovery.
1441
1784