@01.software/sdk 0.29.0 → 0.30.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +273 -73
- package/dist/analytics/react.cjs +4 -1
- package/dist/analytics/react.cjs.map +1 -1
- package/dist/analytics/react.js +4 -1
- package/dist/analytics/react.js.map +1 -1
- package/dist/analytics.cjs +4 -1
- package/dist/analytics.cjs.map +1 -1
- package/dist/analytics.js +4 -1
- package/dist/analytics.js.map +1 -1
- package/dist/client.cjs +1476 -0
- package/dist/client.cjs.map +1 -0
- package/dist/client.d.cts +28 -0
- package/dist/client.d.ts +28 -0
- package/dist/client.js +1453 -0
- package/dist/client.js.map +1 -0
- package/dist/collection-client-B9d9kr1d.d.ts +218 -0
- package/dist/collection-client-QPbwimkU.d.cts +218 -0
- package/dist/{const-DAjQYNuM.d.ts → const-B75IFDRi.d.ts} +2 -4
- package/dist/{const-Dsixdi6z.d.cts → const-VZuk2tWc.d.cts} +2 -4
- package/dist/index-B2WbhEgT.d.cts +106 -0
- package/dist/index-B2WbhEgT.d.ts +106 -0
- package/dist/index.cjs +784 -1530
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +11 -115
- package/dist/index.d.ts +11 -115
- package/dist/index.js +784 -1548
- package/dist/index.js.map +1 -1
- package/dist/metadata.cjs +91 -0
- package/dist/metadata.cjs.map +1 -0
- package/dist/metadata.d.cts +58 -0
- package/dist/metadata.d.ts +58 -0
- package/dist/metadata.js +68 -0
- package/dist/metadata.js.map +1 -0
- package/dist/{payload-types-Ci-ZA7aM.d.cts → payload-types-DPjO_IbQ.d.cts} +9 -3
- package/dist/{payload-types-Ci-ZA7aM.d.ts → payload-types-DPjO_IbQ.d.ts} +9 -3
- package/dist/query.cjs +1791 -0
- package/dist/query.cjs.map +1 -0
- package/dist/query.d.cts +244 -0
- package/dist/query.d.ts +244 -0
- package/dist/query.js +1786 -0
- package/dist/query.js.map +1 -0
- package/dist/realtime.cjs +4 -1
- package/dist/realtime.cjs.map +1 -1
- package/dist/realtime.d.cts +2 -2
- package/dist/realtime.d.ts +2 -2
- package/dist/realtime.js +4 -1
- package/dist/realtime.js.map +1 -1
- package/dist/{server-BINWywT8.d.cts → server-CrsPyqEc.d.cts} +14 -31
- package/dist/{server-BINWywT8.d.ts → server-CrsPyqEc.d.ts} +14 -31
- package/dist/server.cjs +299 -840
- package/dist/server.cjs.map +1 -1
- package/dist/server.d.cts +112 -7
- package/dist/server.d.ts +112 -7
- package/dist/server.js +299 -858
- package/dist/server.js.map +1 -1
- package/dist/{types-BWq_WlbB.d.ts → types-1fBLrYU7.d.ts} +1 -1
- package/dist/{types-zKjATmDK.d.cts → types-BwT0eeaz.d.cts} +1 -1
- package/dist/{server-Cv0Q4dPQ.d.ts → types-Dlb2mwpX.d.cts} +228 -741
- package/dist/{server-C0C8dtms.d.cts → types-DuSKPiY5.d.ts} +228 -741
- package/dist/ui/canvas/server.cjs +7 -6
- package/dist/ui/canvas/server.cjs.map +1 -1
- package/dist/ui/canvas/server.d.cts +1 -3
- package/dist/ui/canvas/server.d.ts +1 -3
- package/dist/ui/canvas/server.js +7 -6
- package/dist/ui/canvas/server.js.map +1 -1
- package/dist/ui/canvas.cjs +11 -10
- package/dist/ui/canvas.cjs.map +1 -1
- package/dist/ui/canvas.d.cts +29 -6
- package/dist/ui/canvas.d.ts +29 -6
- package/dist/ui/canvas.js +11 -10
- package/dist/ui/canvas.js.map +1 -1
- package/dist/ui/form.d.cts +1 -1
- package/dist/ui/form.d.ts +1 -1
- package/dist/ui/video.d.cts +1 -1
- package/dist/ui/video.d.ts +1 -1
- package/dist/webhook.d.cts +3 -3
- package/dist/webhook.d.ts +3 -3
- package/package.json +84 -15
package/README.md
CHANGED
|
@@ -45,7 +45,7 @@ export function App() {
|
|
|
45
45
|
// Main entry - browser client, query builder, hooks, utilities
|
|
46
46
|
import { createClient } from '@01.software/sdk'
|
|
47
47
|
|
|
48
|
-
// Server-only entry -
|
|
48
|
+
// Server-only entry - keep Secret Key code out of browser-facing imports
|
|
49
49
|
import { createServerClient } from '@01.software/sdk/server'
|
|
50
50
|
|
|
51
51
|
// Webhook only - webhook handlers
|
|
@@ -67,6 +67,45 @@ import { CanvasRenderer } from '@01.software/sdk/ui/canvas'
|
|
|
67
67
|
import { VideoPlayer } from '@01.software/sdk/ui/video'
|
|
68
68
|
```
|
|
69
69
|
|
|
70
|
+
The root entry keeps `createClient`, commerce helpers, collection helpers, and
|
|
71
|
+
types lightweight. Server, React Query, and UI features live behind explicit
|
|
72
|
+
sub-paths so consumers install feature peers only when they import the matching
|
|
73
|
+
entry.
|
|
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` |
|
|
90
|
+
|
|
91
|
+
If a feature is not listed here, it does not need a separate peer install.
|
|
92
|
+
For the full component-to-peer mapping, see
|
|
93
|
+
`packages/sdk/.claude/rules/components-reference.md`.
|
|
94
|
+
|
|
95
|
+
Migration quick reference:
|
|
96
|
+
|
|
97
|
+
- `createClient` remains available from `@01.software/sdk` and
|
|
98
|
+
`@01.software/sdk/client`.
|
|
99
|
+
- `createServerClient` must be imported from `@01.software/sdk/server`.
|
|
100
|
+
- React Query hooks and cache helpers must be imported from
|
|
101
|
+
`@01.software/sdk/query`.
|
|
102
|
+
- UI components must be imported from the specific `@01.software/sdk/ui/*`
|
|
103
|
+
sub-path and require only that row's peers.
|
|
104
|
+
- Console-shared pure ecommerce helpers live in private
|
|
105
|
+
`@01.software/contracts`. The public SDK keeps customer-facing helpers
|
|
106
|
+
self-contained and must not import private contracts; Console code should
|
|
107
|
+
import shared helpers from contracts directly.
|
|
108
|
+
|
|
70
109
|
## Getting Started
|
|
71
110
|
|
|
72
111
|
### Client
|
|
@@ -89,11 +128,13 @@ const { docs } = await client.collections.from('products').find({
|
|
|
89
128
|
|
|
90
129
|
```typescript
|
|
91
130
|
import { createServerClient } from '@01.software/sdk/server'
|
|
131
|
+
import { createServerQueryHooks } from '@01.software/sdk/query'
|
|
92
132
|
|
|
93
133
|
const server = createServerClient({
|
|
94
134
|
publishableKey: process.env.SOFTWARE_PUBLISHABLE_KEY,
|
|
95
135
|
secretKey: process.env.SOFTWARE_SECRET_KEY, // sk01_... opaque API key from Console
|
|
96
136
|
})
|
|
137
|
+
const serverQuery = createServerQueryHooks(server)
|
|
97
138
|
|
|
98
139
|
// Create order (server only)
|
|
99
140
|
const order = await server.commerce.orders.create({
|
|
@@ -107,12 +148,15 @@ const order = await server.commerce.orders.create({
|
|
|
107
148
|
})
|
|
108
149
|
|
|
109
150
|
// SSR prefetch (server)
|
|
110
|
-
await
|
|
151
|
+
await serverQuery.prefetchQuery({
|
|
111
152
|
collection: 'products',
|
|
112
153
|
options: { limit: 10 },
|
|
113
154
|
})
|
|
114
155
|
```
|
|
115
156
|
|
|
157
|
+
Always import `createServerClient` from `@01.software/sdk/server` so generated
|
|
158
|
+
code and bundlers do not blur the Secret Key boundary.
|
|
159
|
+
|
|
116
160
|
## Getting product detail
|
|
117
161
|
|
|
118
162
|
The recommended way to fetch a single product is the shaped helper:
|
|
@@ -124,7 +168,9 @@ const client = createClient({
|
|
|
124
168
|
publishableKey: process.env.NEXT_PUBLIC_SOFTWARE_PUBLISHABLE_KEY!,
|
|
125
169
|
})
|
|
126
170
|
|
|
127
|
-
const product = await client.commerce.product.detail({
|
|
171
|
+
const product = await client.commerce.product.detail({
|
|
172
|
+
slug: 'every-peach-tee',
|
|
173
|
+
})
|
|
128
174
|
if (!product) {
|
|
129
175
|
return notFound() // returned null — product missing, unpublished, or not in this tenant
|
|
130
176
|
}
|
|
@@ -133,14 +179,80 @@ if (!product) {
|
|
|
133
179
|
|
|
134
180
|
`detail()` returns `ProductDetail | null`. A `null` result covers every "no result" reason: `not_found`, `not_published`, `tenant_mismatch`, `feature_disabled`. Render the same "not available" UI for all four. To recover the exact reason for triage, `404` maps to `null` rather than a thrown error — inspect `client.lastRequestId` and match against backend logs.
|
|
135
181
|
|
|
182
|
+
### Product selection helpers
|
|
183
|
+
|
|
184
|
+
```typescript
|
|
185
|
+
import {
|
|
186
|
+
buildProductHref,
|
|
187
|
+
buildProductOptionMatrixFromDetail,
|
|
188
|
+
getProductSelectionImages,
|
|
189
|
+
resolveProductSelectionFromMatrix,
|
|
190
|
+
} from '@01.software/sdk'
|
|
191
|
+
|
|
192
|
+
const matrix = buildProductOptionMatrixFromDetail(product)
|
|
193
|
+
const selection = resolveProductSelectionFromMatrix(
|
|
194
|
+
matrix,
|
|
195
|
+
{ search: '?opt.color=black&opt.size=s' },
|
|
196
|
+
undefined,
|
|
197
|
+
{ detail: product },
|
|
198
|
+
)
|
|
199
|
+
|
|
200
|
+
const images = getProductSelectionImages(selection) // object media only, deduped
|
|
201
|
+
const href = buildProductHref(product, {
|
|
202
|
+
optionSlug: 'color',
|
|
203
|
+
optionValueSlug: 'black',
|
|
204
|
+
})
|
|
205
|
+
```
|
|
206
|
+
|
|
207
|
+
`availableValuesByOptionSlug` / `availableValuesByOptionId` include
|
|
208
|
+
`availableStock`, `isUnlimited`, and `availableForSale` per value so option UIs
|
|
209
|
+
can render stock state without recalculating from variants.
|
|
210
|
+
|
|
136
211
|
### With React Query
|
|
137
212
|
|
|
138
213
|
```typescript
|
|
139
|
-
|
|
214
|
+
import { createQueryHooks } from '@01.software/sdk/query'
|
|
215
|
+
|
|
216
|
+
const query = createQueryHooks(client)
|
|
217
|
+
const { data: product, isLoading } = query.useProductDetailBySlug(slug)
|
|
140
218
|
```
|
|
141
219
|
|
|
142
220
|
Cache key is `['products', 'detail', { slug }]`. Mutations on products, product-variants, product-options, product-option-values, brands, brand-logos, images, and related collections automatically invalidate this cache.
|
|
143
221
|
|
|
222
|
+
### Selection URL contract
|
|
223
|
+
|
|
224
|
+
Use `createProductSelectionCodec(detail)` when product pages need to keep option
|
|
225
|
+
selection in the URL. Complete selections use `variant=<variantId>`; partial
|
|
226
|
+
selections use `opt.<optionId>=<valueId>`. Older
|
|
227
|
+
`opt.<optionSlug>=<valueSlug>` URLs still parse during Stage 1, but slugs are
|
|
228
|
+
compatibility metadata rather than canonical identity.
|
|
229
|
+
|
|
230
|
+
```typescript
|
|
231
|
+
import {
|
|
232
|
+
createProductSelectionCodec,
|
|
233
|
+
resolveProductSelection,
|
|
234
|
+
} from '@01.software/sdk'
|
|
235
|
+
|
|
236
|
+
const codec = createProductSelectionCodec(product)
|
|
237
|
+
const normalizedSelection = codec.parse('?opt.option-color=color-black')
|
|
238
|
+
const selection = resolveProductSelection(product, normalizedSelection)
|
|
239
|
+
const selectionQuery = codec.stringify(normalizedSelection)
|
|
240
|
+
// selectionQuery === 'opt.option-color=color-black' for partial selections
|
|
241
|
+
// selectionQuery === 'variant=variant-black-large' once a complete variant is selected
|
|
242
|
+
// selection.selectedVariant, selection.price, selection.stock, selection.media
|
|
243
|
+
```
|
|
244
|
+
|
|
245
|
+
Use IDs from `detail.options[].id` and `detail.options[].values[].id` when
|
|
246
|
+
building selection state. Slugs remain useful for display and old inbound URLs,
|
|
247
|
+
but new outbound URLs should use the codec output.
|
|
248
|
+
|
|
249
|
+
Do not use bare option query keys such as `?size=large`. The SDK rejects them
|
|
250
|
+
as ambiguous because product pages commonly share URLs with unrelated search,
|
|
251
|
+
filter, analytics, or framework parameters. Namespacing selection keys under
|
|
252
|
+
`opt.` lets the codec distinguish product-option state from ordinary query
|
|
253
|
+
parameters while still allowing unrelated parameters such as `utm_campaign` to
|
|
254
|
+
coexist without being interpreted as selection state.
|
|
255
|
+
|
|
144
256
|
## Advanced: direct Payload queries (escape hatch)
|
|
145
257
|
|
|
146
258
|
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.
|
|
@@ -244,29 +356,40 @@ export default async function ProductPage({ params }) {
|
|
|
244
356
|
```typescript
|
|
245
357
|
const client = createClient({
|
|
246
358
|
publishableKey: string, // Required
|
|
359
|
+
apiUrl?: string, // Optional API origin override
|
|
247
360
|
})
|
|
248
361
|
|
|
249
362
|
const server = createServerClient({
|
|
250
363
|
publishableKey: string,
|
|
251
364
|
secretKey: string, // sk01_... or pat01_...
|
|
365
|
+
apiUrl?: string, // Optional API origin override
|
|
252
366
|
})
|
|
253
367
|
```
|
|
254
368
|
|
|
255
|
-
| Option | Type | Description
|
|
256
|
-
| ---------------- | -------- |
|
|
257
|
-
| `publishableKey` | `string` | API publishable key
|
|
258
|
-
| `secretKey` | `string` | API secret key (server only)
|
|
369
|
+
| Option | Type | Description |
|
|
370
|
+
| ---------------- | -------- | ------------------------------------------------------------- |
|
|
371
|
+
| `publishableKey` | `string` | API publishable key |
|
|
372
|
+
| `secretKey` | `string` | API secret key or PAT (server only) |
|
|
373
|
+
| `apiUrl` | `string` | Optional API origin override for staging, preview, or proxies |
|
|
374
|
+
|
|
375
|
+
Use `apiUrl: string` when an SDK instance should target a non-default API
|
|
376
|
+
origin.
|
|
259
377
|
|
|
260
|
-
API URL
|
|
378
|
+
API URL resolution order:
|
|
261
379
|
|
|
262
|
-
|
|
263
|
-
|
|
380
|
+
1. Explicit `apiUrl` passed to `createClient()` or `createServerClient()`
|
|
381
|
+
2. `SOFTWARE_API_URL` (server) or `NEXT_PUBLIC_SOFTWARE_API_URL` (browser)
|
|
382
|
+
3. Build-time default: `DEFAULT_API_URL` when injected at build time; otherwise dev-tagged SDK builds (`-dev.` versions) use `https://api.stg.01.software`, and regular releases use `https://api.01.software`
|
|
264
383
|
|
|
265
384
|
### Query Builder
|
|
266
385
|
|
|
267
386
|
Access collections via `client.collections.from(slug)`.
|
|
268
387
|
|
|
269
|
-
> **Note:** `client.collections.from()`
|
|
388
|
+
> **Note:** the root `client.collections.from()` type exposes the lightweight
|
|
389
|
+
> read surface (`find`, `findById`, `count`). Metadata helpers live behind the
|
|
390
|
+
> optional `@01.software/sdk/metadata` entry, and write operations (`create`,
|
|
391
|
+
> `update`, `remove`, `updateMany`, `removeMany`) are only available on
|
|
392
|
+
> `server.collections.from()`.
|
|
270
393
|
|
|
271
394
|
```typescript
|
|
272
395
|
// List query - returns PayloadFindResponse
|
|
@@ -295,11 +418,17 @@ const product = await client.collections.from('products').findById(id, {
|
|
|
295
418
|
|
|
296
419
|
// Single item query - returns document directly
|
|
297
420
|
const product = await client.collections.from('products').findById('id')
|
|
421
|
+
```
|
|
422
|
+
|
|
423
|
+
Raw collection mutations are an escape hatch. For ecommerce product catalog
|
|
424
|
+
writes, prefer `server.commerce.product.upsert()` so options, option-values,
|
|
425
|
+
and variants are written through the domain transaction.
|
|
298
426
|
|
|
427
|
+
```typescript
|
|
299
428
|
// Create (server only) - returns PayloadMutationResponse
|
|
300
429
|
const { doc, message } = await server.collections
|
|
301
|
-
.from('
|
|
302
|
-
.create({
|
|
430
|
+
.from('articles')
|
|
431
|
+
.create({ title: 'Article' })
|
|
303
432
|
|
|
304
433
|
// Create with file upload (server only) - uses multipart/form-data
|
|
305
434
|
const { doc } = await server.collections
|
|
@@ -308,8 +437,8 @@ const { doc } = await server.collections
|
|
|
308
437
|
|
|
309
438
|
// Update (server only) - returns PayloadMutationResponse
|
|
310
439
|
const { doc } = await server.collections
|
|
311
|
-
.from('
|
|
312
|
-
.update('id', {
|
|
440
|
+
.from('articles')
|
|
441
|
+
.update('id', { title: 'Updated article' })
|
|
313
442
|
|
|
314
443
|
// Update with file replacement (server only)
|
|
315
444
|
await server.collections
|
|
@@ -317,28 +446,26 @@ await server.collections
|
|
|
317
446
|
.update('id', { alt: 'New alt' }, { file: newFile })
|
|
318
447
|
|
|
319
448
|
// Delete (server only) - returns document directly
|
|
320
|
-
const deletedDoc = await server.collections.from('
|
|
449
|
+
const deletedDoc = await server.collections.from('articles').remove('id')
|
|
321
450
|
|
|
322
451
|
// Count
|
|
323
452
|
const { totalDocs } = await client.collections.from('products').count()
|
|
324
453
|
|
|
325
|
-
// SEO Metadata (
|
|
326
|
-
|
|
327
|
-
.from('products')
|
|
328
|
-
.findMetadata(
|
|
329
|
-
{ where: { slug: { equals: 'my-product' } } },
|
|
330
|
-
{ siteName: 'My Store' },
|
|
331
|
-
)
|
|
454
|
+
// SEO Metadata (generate from a fetched document)
|
|
455
|
+
import { extractSeo, generateMetadata } from '@01.software/sdk/metadata'
|
|
332
456
|
|
|
333
|
-
const
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
457
|
+
const { docs } = await client.collections.from('products').find({
|
|
458
|
+
where: { slug: { equals: 'my-product' } },
|
|
459
|
+
limit: 1,
|
|
460
|
+
depth: 1,
|
|
461
|
+
})
|
|
462
|
+
const metadata = docs[0]
|
|
463
|
+
? generateMetadata(extractSeo(docs[0]), { siteName: 'My Store' })
|
|
464
|
+
: null
|
|
338
465
|
|
|
339
466
|
// Bulk operations (server only)
|
|
340
|
-
await server.collections.from('
|
|
341
|
-
await server.collections.from('
|
|
467
|
+
await server.collections.from('articles').updateMany(where, data)
|
|
468
|
+
await server.collections.from('articles').removeMany(where)
|
|
342
469
|
```
|
|
343
470
|
|
|
344
471
|
### API Response Types (Payload Native)
|
|
@@ -370,95 +497,112 @@ interface PayloadMutationResponse<T> {
|
|
|
370
497
|
// findById() / remove() returns T (document directly)
|
|
371
498
|
```
|
|
372
499
|
|
|
373
|
-
| Operation
|
|
374
|
-
|
|
|
375
|
-
| `find()`
|
|
376
|
-
| `findById()`
|
|
377
|
-
| `create()`
|
|
378
|
-
| `update()`
|
|
379
|
-
| `remove()`
|
|
380
|
-
| `count()`
|
|
381
|
-
| `findMetadata()` | `Metadata \| null` - Next.js Metadata (null if no match) |
|
|
382
|
-
| `findMetadataById()` | `Metadata` - Next.js Metadata (throws on 404) |
|
|
500
|
+
| Operation | Response Type |
|
|
501
|
+
| ------------ | ------------------------------------------------------------------ |
|
|
502
|
+
| `find()` | `PayloadFindResponse<T>` - `{ docs, totalDocs, hasNextPage, ... }` |
|
|
503
|
+
| `findById()` | `T` - document object directly |
|
|
504
|
+
| `create()` | `PayloadMutationResponse<T>` - `{ doc, message }` |
|
|
505
|
+
| `update()` | `PayloadMutationResponse<T>` - `{ doc, message }` |
|
|
506
|
+
| `remove()` | `T` - deleted document object directly |
|
|
507
|
+
| `count()` | `{ totalDocs: number }` |
|
|
383
508
|
|
|
384
509
|
### React Query Hooks
|
|
385
510
|
|
|
386
|
-
|
|
511
|
+
React Query helpers are opt-in through `@01.software/sdk/query`. Install
|
|
512
|
+
`@tanstack/react-query` (and React peers) only when your app imports this
|
|
513
|
+
sub-path. Browser components should use `createQueryHooks(client)` for
|
|
514
|
+
browser-safe reads and customer auth hooks. Collection writes belong in trusted
|
|
515
|
+
server code via `createServerClient`.
|
|
387
516
|
|
|
388
517
|
```typescript
|
|
518
|
+
import { createQueryHooks } from '@01.software/sdk/query'
|
|
519
|
+
|
|
520
|
+
const query = createQueryHooks(client)
|
|
521
|
+
|
|
389
522
|
// List query
|
|
390
|
-
const { data, isLoading } =
|
|
523
|
+
const { data, isLoading } = query.useQuery({
|
|
391
524
|
collection: 'products',
|
|
392
525
|
options: { limit: 10 },
|
|
393
526
|
})
|
|
394
527
|
|
|
395
528
|
// Suspense mode
|
|
396
|
-
const { data } =
|
|
529
|
+
const { data } = query.useSuspenseQuery({
|
|
397
530
|
collection: 'products',
|
|
398
531
|
options: { limit: 10 },
|
|
399
532
|
})
|
|
400
533
|
|
|
401
534
|
// Query by ID
|
|
402
|
-
const { data } =
|
|
535
|
+
const { data } = query.useQueryById({
|
|
403
536
|
collection: 'products',
|
|
404
537
|
id: 'product_id',
|
|
405
538
|
})
|
|
406
539
|
|
|
407
540
|
// Infinite scroll
|
|
408
|
-
const { data, fetchNextPage, hasNextPage } =
|
|
541
|
+
const { data, fetchNextPage, hasNextPage } = query.useInfiniteQuery({
|
|
409
542
|
collection: 'products',
|
|
410
543
|
options: { limit: 20 },
|
|
411
544
|
})
|
|
412
545
|
|
|
413
|
-
// Mutation hooks — ServerClient only (auto-invalidate cache on success)
|
|
414
|
-
const { mutate: create } = client.query.useCreate({ collection: 'images' })
|
|
415
|
-
const { mutate: update } = client.query.useUpdate({ collection: 'products' })
|
|
416
|
-
const { mutate: remove } = client.query.useRemove({ collection: 'products' })
|
|
417
|
-
|
|
418
|
-
create({ data: { alt: 'Hero' }, file: imageFile, filename: 'hero.jpg' })
|
|
419
|
-
update({ id: 'product_id', data: { title: 'Updated' } })
|
|
420
|
-
remove('product_id')
|
|
421
|
-
|
|
422
546
|
// SSR Prefetch
|
|
423
|
-
await
|
|
547
|
+
await query.prefetchQuery({
|
|
424
548
|
collection: 'products',
|
|
425
549
|
options: { limit: 10 },
|
|
426
550
|
})
|
|
427
|
-
await
|
|
551
|
+
await query.prefetchQueryById({
|
|
428
552
|
collection: 'products',
|
|
429
553
|
id: 'product_id',
|
|
430
554
|
})
|
|
431
|
-
await
|
|
555
|
+
await query.prefetchInfiniteQuery({
|
|
432
556
|
collection: 'products',
|
|
433
557
|
pageSize: 20,
|
|
434
558
|
})
|
|
435
559
|
|
|
436
560
|
// Cache utilities
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
561
|
+
query.invalidateQueries('products')
|
|
562
|
+
query.getQueryData('products', 'list', options)
|
|
563
|
+
query.setQueryData('products', 'detail', id, data)
|
|
440
564
|
|
|
441
565
|
// Customer auth hooks (Client only)
|
|
442
|
-
const { data: profile } =
|
|
443
|
-
const { mutate: login } =
|
|
444
|
-
const { mutate: register } =
|
|
445
|
-
const { mutate: logout } =
|
|
566
|
+
const { data: profile } = query.useCustomerMe()
|
|
567
|
+
const { mutate: login } = query.useCustomerLogin()
|
|
568
|
+
const { mutate: register } = query.useCustomerRegister()
|
|
569
|
+
const { mutate: logout } = query.useCustomerLogout()
|
|
446
570
|
|
|
447
571
|
login({ email: 'user@example.com', password: 'password' })
|
|
448
572
|
|
|
449
573
|
// Other customer mutations
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
574
|
+
query.useCustomerForgotPassword()
|
|
575
|
+
query.useCustomerResetPassword()
|
|
576
|
+
query.useCustomerChangePassword()
|
|
453
577
|
|
|
454
578
|
// Customer cache utilities
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
579
|
+
query.invalidateCustomerQueries()
|
|
580
|
+
query.getCustomerData()
|
|
581
|
+
query.setCustomerData(profile)
|
|
582
|
+
```
|
|
583
|
+
|
|
584
|
+
```typescript
|
|
585
|
+
// Server action / API route for collection writes
|
|
586
|
+
import { createServerClient } from '@01.software/sdk/server'
|
|
587
|
+
|
|
588
|
+
const server = createServerClient({
|
|
589
|
+
publishableKey: process.env.SOFTWARE_PUBLISHABLE_KEY!,
|
|
590
|
+
secretKey: process.env.SOFTWARE_SECRET_KEY!,
|
|
591
|
+
})
|
|
592
|
+
|
|
593
|
+
await server.collections.from('articles').update('article_id', {
|
|
594
|
+
title: 'Updated article',
|
|
595
|
+
})
|
|
458
596
|
```
|
|
459
597
|
|
|
460
598
|
### Customer Auth
|
|
461
599
|
|
|
600
|
+
Customer auth methods currently cover local email/password flows: register,
|
|
601
|
+
login, refresh, password reset, profile read/update, and password change.
|
|
602
|
+
`CustomerProfile.authProvider` may contain `google`, `apple`, `kakao`, or
|
|
603
|
+
`naver` for accounts created through platform/provider integrations, but the
|
|
604
|
+
SDK does not expose social-login initiation or callback helpers yet.
|
|
605
|
+
|
|
462
606
|
Available on Client via `client.customer.auth.*`.
|
|
463
607
|
|
|
464
608
|
```typescript
|
|
@@ -548,7 +692,63 @@ await client.commerce.shipping.calculate({ shippingPolicyId?, orderAmount, posta
|
|
|
548
692
|
|
|
549
693
|
### Commerce Product
|
|
550
694
|
|
|
551
|
-
|
|
695
|
+
Product reads are available on both Client and ServerClient via `commerce.product.*`.
|
|
696
|
+
Product catalog writes are ServerClient-only.
|
|
697
|
+
Use `server.commerce.product.upsert()` for product catalog writes that include
|
|
698
|
+
options, option values, and variants. It is the tenant-admin safe path because
|
|
699
|
+
it applies the product/option/variant transaction that raw collection writes do
|
|
700
|
+
not provide.
|
|
701
|
+
|
|
702
|
+
```typescript
|
|
703
|
+
const result = await server.commerce.product.upsert({
|
|
704
|
+
product: {
|
|
705
|
+
title: 'Every Peach Tee',
|
|
706
|
+
slug: 'every-peach-tee',
|
|
707
|
+
status: 'published',
|
|
708
|
+
},
|
|
709
|
+
options: [
|
|
710
|
+
{
|
|
711
|
+
title: 'Color',
|
|
712
|
+
slug: 'color',
|
|
713
|
+
values: [
|
|
714
|
+
{ value: 'Black', slug: 'black', swatchColor: '#111111' },
|
|
715
|
+
{ value: 'White', slug: 'white', swatchColor: '#ffffff' },
|
|
716
|
+
],
|
|
717
|
+
},
|
|
718
|
+
{
|
|
719
|
+
title: 'Size',
|
|
720
|
+
slug: 'size',
|
|
721
|
+
values: [
|
|
722
|
+
{ value: 'Small', slug: 's' },
|
|
723
|
+
{ value: 'Medium', slug: 'm' },
|
|
724
|
+
],
|
|
725
|
+
},
|
|
726
|
+
],
|
|
727
|
+
variants: [
|
|
728
|
+
{
|
|
729
|
+
optionValues: { color: { valueSlug: 'black' }, size: { valueSlug: 's' } },
|
|
730
|
+
sku: 'TEE-BLK-S',
|
|
731
|
+
price: 29000,
|
|
732
|
+
stock: 10,
|
|
733
|
+
isActive: true,
|
|
734
|
+
},
|
|
735
|
+
{
|
|
736
|
+
optionValues: { color: { valueSlug: 'white' }, size: { valueSlug: 'm' } },
|
|
737
|
+
sku: 'TEE-WHT-M',
|
|
738
|
+
price: 29000,
|
|
739
|
+
stock: 8,
|
|
740
|
+
isActive: true,
|
|
741
|
+
},
|
|
742
|
+
],
|
|
743
|
+
})
|
|
744
|
+
|
|
745
|
+
if (!result.ok) {
|
|
746
|
+
throw new Error(result.message)
|
|
747
|
+
}
|
|
748
|
+
```
|
|
749
|
+
|
|
750
|
+
For updates to existing options or option-values, prefer `id` / `valueId` when
|
|
751
|
+
available so rename-safe updates do not depend on slugs.
|
|
552
752
|
|
|
553
753
|
```typescript
|
|
554
754
|
// Batch stock check (point-in-time read, NOT a reservation)
|
|
@@ -654,7 +854,7 @@ Source of truth: `packages/sdk/src/core/collection/const.ts` (`COLLECTIONS`: 73)
|
|
|
654
854
|
| Live Streams | `live-streams` |
|
|
655
855
|
| Media | `images` |
|
|
656
856
|
| Forms | `forms`, `form-submissions` |
|
|
657
|
-
| Community | `posts`, `comments`, `reactions`, `reaction-types`, `bookmarks`, `post-categories`
|
|
857
|
+
| Community | `posts`, `comments`, `reactions`, `reaction-types`, `bookmarks`, `post-categories` |
|
|
658
858
|
| Events | `event-calendars`, `events`, `event-categories`, `event-occurrences`, `event-tags` |
|
|
659
859
|
|
|
660
860
|
Server-only collections: `customer-groups`, `reports`, and `community-bans`
|
package/dist/analytics/react.cjs
CHANGED
|
@@ -28,7 +28,10 @@ module.exports = __toCommonJS(react_exports);
|
|
|
28
28
|
var import_react = require("react");
|
|
29
29
|
|
|
30
30
|
// src/core/client/types.ts
|
|
31
|
-
function resolveApiUrl() {
|
|
31
|
+
function resolveApiUrl(apiUrl) {
|
|
32
|
+
if (apiUrl) {
|
|
33
|
+
return apiUrl.replace(/\/$/, "");
|
|
34
|
+
}
|
|
32
35
|
if (typeof process !== "undefined" && process.env) {
|
|
33
36
|
const envUrl = process.env.SOFTWARE_API_URL || process.env.NEXT_PUBLIC_SOFTWARE_API_URL;
|
|
34
37
|
if (envUrl) {
|