@01.software/cli 0.10.3 → 0.10.5
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/dist/index.js +256 -36
- package/dist/index.js.map +1 -1
- package/dist/mcp/{chunk-VSMPWKVX.js → chunk-2EPYMNHW.js} +256 -221
- package/dist/mcp/chunk-2EPYMNHW.js.map +1 -0
- package/dist/mcp/http.js +1 -1
- package/dist/mcp/stdio.js +1 -1
- package/dist/mcp/vercel.js +255 -220
- package/package.json +2 -2
- package/dist/mcp/chunk-VSMPWKVX.js.map +0 -1
package/dist/mcp/vercel.js
CHANGED
|
@@ -1106,7 +1106,7 @@ async function swallow(promise) {
|
|
|
1106
1106
|
import { z as z3 } from "zod";
|
|
1107
1107
|
|
|
1108
1108
|
// src/lib/client.ts
|
|
1109
|
-
import { createServerClient } from "@01.software/sdk";
|
|
1109
|
+
import { createServerClient } from "@01.software/sdk/server";
|
|
1110
1110
|
var MISSING_HTTP_AUTH_CONTEXT_ERROR2 = "MCP HTTP requests require a validated OAuth tenant context before tool execution.";
|
|
1111
1111
|
var HTTP_OAUTH_SDK_CLIENT_ERROR = "MCP HTTP OAuth requests cannot use SDK-backed tools. Use reviewed Console service endpoints for OAuth transport.";
|
|
1112
1112
|
function getClient() {
|
|
@@ -1939,18 +1939,22 @@ async function productDetail({
|
|
|
1939
1939
|
// src/tools/product-upsert.ts
|
|
1940
1940
|
import { z as z23 } from "zod";
|
|
1941
1941
|
var optionValueSchema = z23.object({
|
|
1942
|
-
id: z23.string().optional().describe("
|
|
1942
|
+
id: z23.string().optional().describe("Stable existing option-value ID for rename-safe updates"),
|
|
1943
1943
|
value: z23.string().describe("Display label (e.g. Black, S)"),
|
|
1944
|
-
slug: z23.string().optional().describe(
|
|
1944
|
+
slug: z23.string().optional().describe(
|
|
1945
|
+
"Optional compatibility value token. The server generates one from value on create when omitted; not canonical identity."
|
|
1946
|
+
),
|
|
1945
1947
|
swatchColor: z23.string().nullable().optional(),
|
|
1946
1948
|
thumbnail: z23.string().nullable().optional(),
|
|
1947
1949
|
images: z23.array(z23.string()).optional(),
|
|
1948
1950
|
metadata: z23.unknown().optional()
|
|
1949
1951
|
});
|
|
1950
1952
|
var optionSchema = z23.object({
|
|
1951
|
-
id: z23.string().optional().describe("
|
|
1953
|
+
id: z23.string().optional().describe("Stable existing option ID for rename-safe updates"),
|
|
1952
1954
|
title: z23.string().describe("Option name (e.g. Color, Size)"),
|
|
1953
|
-
slug: z23.string().optional().describe(
|
|
1955
|
+
slug: z23.string().optional().describe(
|
|
1956
|
+
"Optional compatibility option token. The server generates one from title on create when omitted; not canonical identity."
|
|
1957
|
+
),
|
|
1954
1958
|
values: z23.array(optionValueSchema).describe("Allowed option values")
|
|
1955
1959
|
});
|
|
1956
1960
|
var variantOptionValueSchema = z23.object({
|
|
@@ -1964,7 +1968,7 @@ var variantSchema = z23.object({
|
|
|
1964
1968
|
z23.record(z23.string(), z23.union([z23.string(), variantOptionValueSchema])),
|
|
1965
1969
|
z23.array(z23.string())
|
|
1966
1970
|
]).optional().describe(
|
|
1967
|
-
"Option-value selection. Prefer
|
|
1971
|
+
"Option-value selection. Prefer stable option-value IDs, either as an array or object values using { valueId }. Slug maps and exact { OptionTitle: ValueLabel } maps remain compatibility-only and fail when labels are ambiguous."
|
|
1968
1972
|
),
|
|
1969
1973
|
sku: z23.string().nullable().optional(),
|
|
1970
1974
|
title: z23.string().nullable().optional(),
|
|
@@ -1986,10 +1990,10 @@ var schema23 = {
|
|
|
1986
1990
|
"Product fields. Include `id` to update an existing product; omit for create (then `title` is required)."
|
|
1987
1991
|
),
|
|
1988
1992
|
options: z23.array(optionSchema).optional().describe(
|
|
1989
|
-
"Option definitions.
|
|
1993
|
+
"Option definitions. Include stable option/value IDs when updating or renaming existing rows. Slugs are optional compatibility metadata generated from title/value on create when omitted; omitted options on an existing product are deleted (with their values)."
|
|
1990
1994
|
),
|
|
1991
1995
|
variants: z23.array(variantSchema).optional().describe(
|
|
1992
|
-
"Variant rows. Prefer
|
|
1996
|
+
"Variant rows. Prefer stable option-value IDs in optionValues. Slug/title maps are compatibility inputs. Omitted variants on an existing product are deleted, unless referenced by an active cart or non-terminal order \u2014 those are soft-deactivated (isActive: false)."
|
|
1993
1997
|
)
|
|
1994
1998
|
};
|
|
1995
1999
|
var metadata23 = {
|
|
@@ -2290,14 +2294,16 @@ var recipes = {
|
|
|
2290
2294
|
recommendedSurface: "react-query",
|
|
2291
2295
|
runtime: "browser",
|
|
2292
2296
|
code: `import { createClient } from '@01.software/sdk'
|
|
2297
|
+
import { createQueryHooks } from '@01.software/sdk/query'
|
|
2293
2298
|
|
|
2294
2299
|
const client = createClient({
|
|
2295
2300
|
publishableKey: process.env.NEXT_PUBLIC_SOFTWARE_PUBLISHABLE_KEY!,
|
|
2296
2301
|
})
|
|
2302
|
+
const query = createQueryHooks(client)
|
|
2297
2303
|
|
|
2298
2304
|
// React Query hook \u2014 handles caching and background updates
|
|
2299
2305
|
function ProductList() {
|
|
2300
|
-
const { data, isLoading, error } =
|
|
2306
|
+
const { data, isLoading, error } = query.useQuery({
|
|
2301
2307
|
collection: 'products',
|
|
2302
2308
|
options: {
|
|
2303
2309
|
where: { status: { equals: 'published' } },
|
|
@@ -2329,7 +2335,7 @@ function ProductList() {
|
|
|
2329
2335
|
title: "Fetch collection list (server)",
|
|
2330
2336
|
recommendedSurface: "query-builder",
|
|
2331
2337
|
runtime: "server",
|
|
2332
|
-
code: `import { createServerClient } from '@01.software/sdk'
|
|
2338
|
+
code: `import { createServerClient } from '@01.software/sdk/server'
|
|
2333
2339
|
|
|
2334
2340
|
const client = createServerClient({
|
|
2335
2341
|
publishableKey: process.env.SOFTWARE_PUBLISHABLE_KEY!,
|
|
@@ -2362,13 +2368,15 @@ const result = await client.collections.from('products').find({
|
|
|
2362
2368
|
recommendedSurface: "react-query",
|
|
2363
2369
|
runtime: "browser",
|
|
2364
2370
|
code: `import { createClient } from '@01.software/sdk'
|
|
2371
|
+
import { createQueryHooks } from '@01.software/sdk/query'
|
|
2365
2372
|
|
|
2366
2373
|
const client = createClient({
|
|
2367
2374
|
publishableKey: process.env.NEXT_PUBLIC_SOFTWARE_PUBLISHABLE_KEY!,
|
|
2368
2375
|
})
|
|
2376
|
+
const query = createQueryHooks(client)
|
|
2369
2377
|
|
|
2370
2378
|
function ProductDetail({ id }: { id: string }) {
|
|
2371
|
-
const { data, isLoading } =
|
|
2379
|
+
const { data, isLoading } = query.useQueryById({
|
|
2372
2380
|
collection: 'products',
|
|
2373
2381
|
id,
|
|
2374
2382
|
})
|
|
@@ -2388,7 +2396,7 @@ function ProductDetail({ id }: { id: string }) {
|
|
|
2388
2396
|
title: "Fetch single item by ID (server)",
|
|
2389
2397
|
recommendedSurface: "query-builder",
|
|
2390
2398
|
runtime: "server",
|
|
2391
|
-
code: `import { createServerClient } from '@01.software/sdk'
|
|
2399
|
+
code: `import { createServerClient } from '@01.software/sdk/server'
|
|
2392
2400
|
|
|
2393
2401
|
const client = createServerClient({
|
|
2394
2402
|
publishableKey: process.env.SOFTWARE_PUBLISHABLE_KEY!,
|
|
@@ -2411,7 +2419,7 @@ console.log(product.title)`,
|
|
|
2411
2419
|
title: "Create a new item (server only)",
|
|
2412
2420
|
recommendedSurface: "server-api",
|
|
2413
2421
|
runtime: "server",
|
|
2414
|
-
code: `import { createServerClient } from '@01.software/sdk'
|
|
2422
|
+
code: `import { createServerClient } from '@01.software/sdk/server'
|
|
2415
2423
|
|
|
2416
2424
|
const client = createServerClient({
|
|
2417
2425
|
publishableKey: process.env.SOFTWARE_PUBLISHABLE_KEY!,
|
|
@@ -2440,7 +2448,7 @@ const result = await client.collections.from('products').create({
|
|
|
2440
2448
|
title: "Update an existing item (server only)",
|
|
2441
2449
|
recommendedSurface: "server-api",
|
|
2442
2450
|
runtime: "server",
|
|
2443
|
-
code: `import { createServerClient } from '@01.software/sdk'
|
|
2451
|
+
code: `import { createServerClient } from '@01.software/sdk/server'
|
|
2444
2452
|
|
|
2445
2453
|
const client = createServerClient({
|
|
2446
2454
|
publishableKey: process.env.SOFTWARE_PUBLISHABLE_KEY!,
|
|
@@ -2469,7 +2477,7 @@ const result = await client.collections.from('products').update('product-id', {
|
|
|
2469
2477
|
title: "Delete an item (server only)",
|
|
2470
2478
|
recommendedSurface: "server-api",
|
|
2471
2479
|
runtime: "server",
|
|
2472
|
-
code: `import { createServerClient } from '@01.software/sdk'
|
|
2480
|
+
code: `import { createServerClient } from '@01.software/sdk/server'
|
|
2473
2481
|
|
|
2474
2482
|
const client = createServerClient({
|
|
2475
2483
|
publishableKey: process.env.SOFTWARE_PUBLISHABLE_KEY!,
|
|
@@ -2494,10 +2502,12 @@ console.log('Deleted:', deleted.title)`,
|
|
|
2494
2502
|
recommendedSurface: "react-query",
|
|
2495
2503
|
runtime: "browser",
|
|
2496
2504
|
code: `import { createClient } from '@01.software/sdk'
|
|
2505
|
+
import { createQueryHooks } from '@01.software/sdk/query'
|
|
2497
2506
|
|
|
2498
2507
|
const client = createClient({
|
|
2499
2508
|
publishableKey: process.env.NEXT_PUBLIC_SOFTWARE_PUBLISHABLE_KEY!,
|
|
2500
2509
|
})
|
|
2510
|
+
const query = createQueryHooks(client)
|
|
2501
2511
|
|
|
2502
2512
|
function InfiniteProductList() {
|
|
2503
2513
|
const {
|
|
@@ -2506,7 +2516,7 @@ function InfiniteProductList() {
|
|
|
2506
2516
|
hasNextPage,
|
|
2507
2517
|
isFetchingNextPage,
|
|
2508
2518
|
isLoading,
|
|
2509
|
-
} =
|
|
2519
|
+
} = query.useInfiniteQuery({
|
|
2510
2520
|
collection: 'products',
|
|
2511
2521
|
options: { where: { status: { equals: 'published' } } },
|
|
2512
2522
|
pageSize: 20,
|
|
@@ -2541,7 +2551,8 @@ function InfiniteProductList() {
|
|
|
2541
2551
|
title: "SSR data prefetching (server)",
|
|
2542
2552
|
recommendedSurface: "react-query",
|
|
2543
2553
|
runtime: "server",
|
|
2544
|
-
code: `import { createServerClient } from '@01.software/sdk'
|
|
2554
|
+
code: `import { createServerClient } from '@01.software/sdk/server'
|
|
2555
|
+
import { createServerQueryHooks, getQueryClient } from '@01.software/sdk/query'
|
|
2545
2556
|
import { dehydrate, HydrationBoundary } from '@tanstack/react-query'
|
|
2546
2557
|
|
|
2547
2558
|
// In a Next.js Server Component or getServerSideProps:
|
|
@@ -2549,27 +2560,29 @@ const client = createServerClient({
|
|
|
2549
2560
|
publishableKey: process.env.SOFTWARE_PUBLISHABLE_KEY!,
|
|
2550
2561
|
secretKey: process.env.SOFTWARE_SECRET_KEY!,
|
|
2551
2562
|
})
|
|
2563
|
+
const queryClient = getQueryClient()
|
|
2564
|
+
const serverQuery = createServerQueryHooks(client, queryClient)
|
|
2552
2565
|
|
|
2553
2566
|
// Prefetch list \u2014 client hydrates instantly without a loading state
|
|
2554
|
-
await
|
|
2567
|
+
await serverQuery.prefetchQuery({
|
|
2555
2568
|
collection: 'products',
|
|
2556
2569
|
options: { limit: 20 },
|
|
2557
2570
|
})
|
|
2558
2571
|
|
|
2559
2572
|
// Prefetch single item
|
|
2560
|
-
await
|
|
2573
|
+
await serverQuery.prefetchQueryById({
|
|
2561
2574
|
collection: 'products',
|
|
2562
2575
|
id: 'product-id',
|
|
2563
2576
|
})
|
|
2564
2577
|
|
|
2565
2578
|
// Prefetch infinite list
|
|
2566
|
-
await
|
|
2579
|
+
await serverQuery.prefetchInfiniteQuery({
|
|
2567
2580
|
collection: 'products',
|
|
2568
2581
|
pageSize: 20,
|
|
2569
2582
|
})
|
|
2570
2583
|
|
|
2571
2584
|
// Dehydrate and pass to client
|
|
2572
|
-
const state = dehydrate(
|
|
2585
|
+
const state = dehydrate(queryClient)
|
|
2573
2586
|
|
|
2574
2587
|
export default function Page() {
|
|
2575
2588
|
return (
|
|
@@ -2639,7 +2652,7 @@ await client.customer.resetPassword(token, 'newPassword123')`,
|
|
|
2639
2652
|
title: "File upload pattern (server)",
|
|
2640
2653
|
recommendedSurface: "server-api",
|
|
2641
2654
|
runtime: "server",
|
|
2642
|
-
code: `import { createServerClient } from '@01.software/sdk'
|
|
2655
|
+
code: `import { createServerClient } from '@01.software/sdk/server'
|
|
2643
2656
|
|
|
2644
2657
|
const client = createServerClient({
|
|
2645
2658
|
publishableKey: process.env.SOFTWARE_PUBLISHABLE_KEY!,
|
|
@@ -2670,7 +2683,7 @@ const result = await client.collections.from('images').create(formData as unknow
|
|
|
2670
2683
|
title: "Bulk update/delete (server only)",
|
|
2671
2684
|
recommendedSurface: "server-api",
|
|
2672
2685
|
runtime: "server",
|
|
2673
|
-
code: `import { createServerClient } from '@01.software/sdk'
|
|
2686
|
+
code: `import { createServerClient } from '@01.software/sdk/server'
|
|
2674
2687
|
|
|
2675
2688
|
const client = createServerClient({
|
|
2676
2689
|
publishableKey: process.env.SOFTWARE_PUBLISHABLE_KEY!,
|
|
@@ -2822,15 +2835,15 @@ var docIndex = [
|
|
|
2822
2835
|
},
|
|
2823
2836
|
{
|
|
2824
2837
|
title: "Query Builder \u2014 Metadata Generation",
|
|
2825
|
-
keywords: ["metadata", "
|
|
2826
|
-
summary: "
|
|
2838
|
+
keywords: ["metadata", "generateMetadata", "extractSeo", "next.js metadata", "seo", "open graph", "query builder"],
|
|
2839
|
+
summary: "Use @01.software/sdk/metadata helpers with fetched documents to generate Metadata-shaped SEO objects without pulling Next.js into the root SDK entry.",
|
|
2827
2840
|
resourceUri: "docs://sdk/query-builder"
|
|
2828
2841
|
},
|
|
2829
2842
|
// React Query Hooks
|
|
2830
2843
|
{
|
|
2831
2844
|
title: "React Query \u2014 useQuery()",
|
|
2832
2845
|
keywords: ["useQuery", "react query", "hook", "list", "fetch", "collection", "cache", "react"],
|
|
2833
|
-
summary: "client.
|
|
2846
|
+
summary: "createQueryHooks(client).useQuery({ collection, options }) \u2014 fetches a list with caching and background updates. Use inside a React component.",
|
|
2834
2847
|
resourceUri: "docs://sdk/react-query"
|
|
2835
2848
|
},
|
|
2836
2849
|
{
|
|
@@ -2842,7 +2855,7 @@ var docIndex = [
|
|
|
2842
2855
|
{
|
|
2843
2856
|
title: "React Query \u2014 useInfiniteQuery()",
|
|
2844
2857
|
keywords: ["useInfiniteQuery", "useSuspenseInfiniteQuery", "infinite scroll", "load more", "pagination", "react query", "hook"],
|
|
2845
|
-
summary: "client.
|
|
2858
|
+
summary: "createQueryHooks(client).useInfiniteQuery({ collection, options, pageSize }) \u2014 infinite scrolling. data.pages is an array of pages; flatten with flatMap for all docs.",
|
|
2846
2859
|
resourceUri: "docs://sdk/react-query"
|
|
2847
2860
|
},
|
|
2848
2861
|
{
|
|
@@ -2861,7 +2874,7 @@ var docIndex = [
|
|
|
2861
2874
|
{
|
|
2862
2875
|
title: "Cache Invalidation and Manual Cache Management",
|
|
2863
2876
|
keywords: ["invalidateQueries", "getQueryData", "setQueryData", "cache", "invalidate", "optimistic update", "react query"],
|
|
2864
|
-
summary: "
|
|
2877
|
+
summary: "query.invalidateQueries(collection, type) triggers refetch. getQueryData/setQueryData allow manual optimistic updates.",
|
|
2865
2878
|
resourceUri: "docs://sdk/react-query"
|
|
2866
2879
|
},
|
|
2867
2880
|
// Filtering
|
|
@@ -3012,23 +3025,25 @@ var AUTH_GUIDES = {
|
|
|
3012
3025
|
title: "Browser Client Setup",
|
|
3013
3026
|
envVars: ["NEXT_PUBLIC_SOFTWARE_PUBLISHABLE_KEY"],
|
|
3014
3027
|
code: `import { createClient } from '@01.software/sdk'
|
|
3028
|
+
import { createQueryHooks } from '@01.software/sdk/query'
|
|
3015
3029
|
|
|
3016
3030
|
const client = createClient({
|
|
3017
3031
|
publishableKey: process.env.NEXT_PUBLIC_SOFTWARE_PUBLISHABLE_KEY!
|
|
3018
3032
|
})
|
|
3033
|
+
const query = createQueryHooks(client)
|
|
3019
3034
|
|
|
3020
3035
|
// Read-only operations + React Query hooks + Customer Auth
|
|
3021
|
-
const { data } =
|
|
3036
|
+
const { data } = query.useQuery({ collection: 'products' })`,
|
|
3022
3037
|
notes: [
|
|
3023
3038
|
"Client is read-only \u2014 no create/update/delete operations",
|
|
3024
3039
|
"publishableKey is safe to expose in browser (prefixed with NEXT_PUBLIC_)",
|
|
3025
|
-
"
|
|
3040
|
+
"Use createQueryHooks(client) for React Query hooks and client.customer for Customer Auth"
|
|
3026
3041
|
]
|
|
3027
3042
|
},
|
|
3028
3043
|
"server-client": {
|
|
3029
3044
|
title: "Server Client Setup",
|
|
3030
3045
|
envVars: ["SOFTWARE_PUBLISHABLE_KEY", "SOFTWARE_SECRET_KEY"],
|
|
3031
|
-
code: `import { createServerClient } from '@01.software/sdk'
|
|
3046
|
+
code: `import { createServerClient } from '@01.software/sdk/server'
|
|
3032
3047
|
|
|
3033
3048
|
const client = createServerClient({
|
|
3034
3049
|
publishableKey: process.env.SOFTWARE_PUBLISHABLE_KEY!,
|
|
@@ -3041,7 +3056,7 @@ const result = await client.collections.from('products').create({ title: 'New Pr
|
|
|
3041
3056
|
"ServerClient has full CRUD access and must run only in trusted server code",
|
|
3042
3057
|
"Store server credentials in environment variables and rotate them from the Console",
|
|
3043
3058
|
"Use in API routes, server actions, or backend services only",
|
|
3044
|
-
"
|
|
3059
|
+
"Use createServerClient in API routes, server actions, or backend services; keep React Query hooks for browser-safe reads and server prefetching"
|
|
3045
3060
|
]
|
|
3046
3061
|
},
|
|
3047
3062
|
"customer-auth": {
|
|
@@ -3179,37 +3194,35 @@ function generatePattern(collection, operation, surface) {
|
|
|
3179
3194
|
);
|
|
3180
3195
|
}
|
|
3181
3196
|
const parts2 = [];
|
|
3182
|
-
if (operation === "read") {
|
|
3197
|
+
if (operation === "read" || operation === "full-crud") {
|
|
3183
3198
|
parts2.push(
|
|
3184
3199
|
`import { createClient } from '@01.software/sdk'`,
|
|
3185
|
-
|
|
3186
|
-
`const client = createClient({`,
|
|
3187
|
-
` publishableKey: process.env.NEXT_PUBLIC_SOFTWARE_PUBLISHABLE_KEY!`,
|
|
3188
|
-
`})`,
|
|
3189
|
-
``
|
|
3200
|
+
`import { createQueryHooks } from '@01.software/sdk/query'`
|
|
3190
3201
|
);
|
|
3191
|
-
}
|
|
3202
|
+
}
|
|
3203
|
+
if (operation === "write" || operation === "full-crud") {
|
|
3204
|
+
parts2.push(`import { createServerClient } from '@01.software/sdk/server'`);
|
|
3205
|
+
}
|
|
3206
|
+
parts2.push(``);
|
|
3207
|
+
if (operation === "read" || operation === "full-crud") {
|
|
3192
3208
|
parts2.push(
|
|
3193
|
-
`
|
|
3194
|
-
|
|
3195
|
-
`// Mutation hooks require ServerClient`,
|
|
3196
|
-
`const client = createServerClient({`,
|
|
3197
|
-
` publishableKey: process.env.SOFTWARE_PUBLISHABLE_KEY!,`,
|
|
3198
|
-
` secretKey: process.env.SOFTWARE_SECRET_KEY!`,
|
|
3209
|
+
`const client = createClient({`,
|
|
3210
|
+
` publishableKey: process.env.NEXT_PUBLIC_SOFTWARE_PUBLISHABLE_KEY!`,
|
|
3199
3211
|
`})`,
|
|
3212
|
+
`const query = createQueryHooks(client)`,
|
|
3200
3213
|
``
|
|
3201
3214
|
);
|
|
3202
3215
|
}
|
|
3203
3216
|
if (operation === "read" || operation === "full-crud") {
|
|
3204
3217
|
parts2.push(
|
|
3205
3218
|
`// List query`,
|
|
3206
|
-
`const { data, isLoading } =
|
|
3219
|
+
`const { data, isLoading } = query.useQuery({`,
|
|
3207
3220
|
` collection: '${collection}',`,
|
|
3208
3221
|
` options: { limit: 10 }`,
|
|
3209
3222
|
`})`,
|
|
3210
3223
|
``,
|
|
3211
3224
|
`// Single item`,
|
|
3212
|
-
`const { data: item } =
|
|
3225
|
+
`const { data: item } = query.useQueryById({`,
|
|
3213
3226
|
` collection: '${collection}',`,
|
|
3214
3227
|
` id: itemId`,
|
|
3215
3228
|
`})`
|
|
@@ -3218,31 +3231,28 @@ function generatePattern(collection, operation, surface) {
|
|
|
3218
3231
|
if (operation === "write" || operation === "full-crud") {
|
|
3219
3232
|
parts2.push(
|
|
3220
3233
|
``,
|
|
3221
|
-
`//
|
|
3222
|
-
`const
|
|
3223
|
-
`
|
|
3224
|
-
|
|
3225
|
-
|
|
3226
|
-
`const { mutate: update } = client.query.useUpdate({ collection: '${collection}' })`,
|
|
3227
|
-
`update({ id: itemId, data: { title: 'Updated' } })`,
|
|
3234
|
+
`// Writes belong in a server action or API route, not a client component.`,
|
|
3235
|
+
`const server = createServerClient({`,
|
|
3236
|
+
` publishableKey: process.env.SOFTWARE_PUBLISHABLE_KEY!,`,
|
|
3237
|
+
` secretKey: process.env.SOFTWARE_SECRET_KEY!`,
|
|
3238
|
+
`})`,
|
|
3228
3239
|
``,
|
|
3229
|
-
|
|
3230
|
-
`
|
|
3231
|
-
`remove(itemId)`
|
|
3240
|
+
`await server.collections.from('${collection}').create({ title: 'New Item' })`,
|
|
3241
|
+
`await server.collections.from('${collection}').update(itemId, { title: 'Updated' })`,
|
|
3242
|
+
`await server.collections.from('${collection}').remove(itemId)`
|
|
3232
3243
|
);
|
|
3233
3244
|
}
|
|
3234
3245
|
return {
|
|
3235
3246
|
code: parts2.join("\n"),
|
|
3236
3247
|
notes: [
|
|
3237
|
-
"React Query hooks provide automatic caching and background updates",
|
|
3238
|
-
"
|
|
3239
|
-
operation === "write" || operation === "full-crud" ? "Mutation hooks (useCreate, useUpdate, useRemove) require ServerClient with SOFTWARE_PUBLISHABLE_KEY + SOFTWARE_SECRET_KEY" : "Read hooks work in browser components"
|
|
3248
|
+
"React Query hooks provide automatic caching and background updates for browser-safe reads",
|
|
3249
|
+
operation === "write" || operation === "full-crud" ? "Writes require trusted server code with SOFTWARE_PUBLISHABLE_KEY + SOFTWARE_SECRET_KEY; invalidate browser caches after the server action returns" : "Read hooks work in browser components"
|
|
3240
3250
|
]
|
|
3241
3251
|
};
|
|
3242
3252
|
}
|
|
3243
3253
|
if (surface === "server-api") {
|
|
3244
3254
|
const parts2 = [
|
|
3245
|
-
`import { createServerClient } from '@01.software/sdk'`,
|
|
3255
|
+
`import { createServerClient } from '@01.software/sdk/server'`,
|
|
3246
3256
|
``,
|
|
3247
3257
|
`const client = createServerClient({`,
|
|
3248
3258
|
` publishableKey: process.env.SOFTWARE_PUBLISHABLE_KEY!,`,
|
|
@@ -3409,8 +3419,12 @@ const result = await client.collections.from('products').find({
|
|
|
3409
3419
|
})
|
|
3410
3420
|
|
|
3411
3421
|
// Use React hooks
|
|
3422
|
+
import { createQueryHooks } from '@01.software/sdk/query'
|
|
3423
|
+
|
|
3424
|
+
const query = createQueryHooks(client)
|
|
3425
|
+
|
|
3412
3426
|
function ProductList() {
|
|
3413
|
-
const { data, isLoading } =
|
|
3427
|
+
const { data, isLoading } = query.useQuery({
|
|
3414
3428
|
collection: 'products',
|
|
3415
3429
|
options: { limit: 10 }
|
|
3416
3430
|
})
|
|
@@ -3427,7 +3441,7 @@ function ProductList() {
|
|
|
3427
3441
|
}
|
|
3428
3442
|
\`\`\`
|
|
3429
3443
|
|
|
3430
|
-
### React Query Hooks (
|
|
3444
|
+
### React Query Hooks (@01.software/sdk/query)
|
|
3431
3445
|
|
|
3432
3446
|
| Hook | Description |
|
|
3433
3447
|
|------|-------------|
|
|
@@ -3462,13 +3476,17 @@ await client.collections.from('products').remove('id')
|
|
|
3462
3476
|
// count() returns { totalDocs }
|
|
3463
3477
|
const { totalDocs } = await client.collections.from('products').count()
|
|
3464
3478
|
|
|
3465
|
-
// Metadata - generate
|
|
3466
|
-
|
|
3467
|
-
|
|
3468
|
-
const
|
|
3469
|
-
|
|
3470
|
-
|
|
3471
|
-
|
|
3479
|
+
// Metadata - generate Metadata-shaped SEO objects from fetched documents
|
|
3480
|
+
import { extractSeo, generateMetadata } from '@01.software/sdk/metadata'
|
|
3481
|
+
|
|
3482
|
+
const productResult = await client.collections.from('products').find({
|
|
3483
|
+
where: { slug: { equals: 'my-product' } },
|
|
3484
|
+
limit: 1,
|
|
3485
|
+
depth: 1,
|
|
3486
|
+
})
|
|
3487
|
+
const productMeta = productResult.docs[0]
|
|
3488
|
+
? generateMetadata(extractSeo(productResult.docs[0]), { siteName: 'My Store' })
|
|
3489
|
+
: null
|
|
3472
3490
|
\`\`\`
|
|
3473
3491
|
|
|
3474
3492
|
### Filtering (Payload Query Syntax)
|
|
@@ -3491,7 +3509,7 @@ For ecommerce collections, use \`products.listing.*\` for browse/search pricing
|
|
|
3491
3509
|
### Server Client (Server-side)
|
|
3492
3510
|
|
|
3493
3511
|
\`\`\`typescript
|
|
3494
|
-
import { createServerClient } from '@01.software/sdk'
|
|
3512
|
+
import { createServerClient } from '@01.software/sdk/server'
|
|
3495
3513
|
|
|
3496
3514
|
const client = createServerClient({
|
|
3497
3515
|
publishableKey: process.env.SOFTWARE_PUBLISHABLE_KEY!,
|
|
@@ -3511,7 +3529,7 @@ if (!product) return notFound()
|
|
|
3511
3529
|
// product: { product, variants, options, brand, categories, tags, images, videos, listing }
|
|
3512
3530
|
\`\`\`
|
|
3513
3531
|
|
|
3514
|
-
For React: \`const { data } = client.
|
|
3532
|
+
For React: \`const { data } = createQueryHooks(client).useProductDetailBySlug(slug)\`.
|
|
3515
3533
|
|
|
3516
3534
|
### Product listing (grouped)
|
|
3517
3535
|
|
|
@@ -3566,7 +3584,8 @@ ${filters}
|
|
|
3566
3584
|
### ${operation === "find" ? "Query" : operation === "create" ? "Create" : operation === "update" ? "Update" : "Delete"} Example
|
|
3567
3585
|
|
|
3568
3586
|
\`\`\`typescript
|
|
3569
|
-
import { createClient
|
|
3587
|
+
import { createClient } from '@01.software/sdk'
|
|
3588
|
+
import { createServerClient } from '@01.software/sdk/server'
|
|
3570
3589
|
|
|
3571
3590
|
// Client (read-only public collections)
|
|
3572
3591
|
const client = createClient({
|
|
@@ -3587,14 +3606,18 @@ const result = await ${readClientName}.collections.from('${collection}').find(${
|
|
|
3587
3606
|
// result.docs - array of items
|
|
3588
3607
|
// result.totalDocs, result.page, result.totalPages, result.hasNextPage, ...
|
|
3589
3608
|
|
|
3590
|
-
${isPublicCollection ? `// Using React
|
|
3591
|
-
|
|
3609
|
+
${isPublicCollection ? `// Using React Query hooks
|
|
3610
|
+
import { createQueryHooks } from '@01.software/sdk/query'
|
|
3611
|
+
|
|
3612
|
+
const query = createQueryHooks(client)
|
|
3613
|
+
|
|
3614
|
+
const { data, isLoading, error } = query.useQuery({
|
|
3592
3615
|
collection: '${collection}',
|
|
3593
3616
|
options: { limit: 10 }
|
|
3594
3617
|
})
|
|
3595
3618
|
|
|
3596
3619
|
// With Suspense
|
|
3597
|
-
const { data } =
|
|
3620
|
+
const { data } = query.useSuspenseQuery({
|
|
3598
3621
|
collection: '${collection}',
|
|
3599
3622
|
options: { limit: 10 }
|
|
3600
3623
|
})` : `// React hooks are browser/public only and do not support '${collection}'.`}` : operation === "create" ? `// Create ${collection} item (ServerClient only)
|
|
@@ -3663,7 +3686,7 @@ var SCENARIOS = {
|
|
|
3663
3686
|
|
|
3664
3687
|
### Code Example (ServerClient)
|
|
3665
3688
|
\`\`\`typescript
|
|
3666
|
-
import { createServerClient } from '@01.software/sdk'
|
|
3689
|
+
import { createServerClient } from '@01.software/sdk/server'
|
|
3667
3690
|
|
|
3668
3691
|
const client = createServerClient({
|
|
3669
3692
|
publishableKey: process.env.SOFTWARE_PUBLISHABLE_KEY!,
|
|
@@ -3888,7 +3911,6 @@ customer-addresses
|
|
|
3888
3911
|
### Optional Collections
|
|
3889
3912
|
|
|
3890
3913
|
- customer-groups \u2014 Console/server-scoped segmentation for VIP coupons and campaigns. Use \`createServerClient().collections.from('customer-groups')\`.
|
|
3891
|
-
- customer-profile-lists \u2014 Public profile display/ranking lists for storefronts. Browser reads may use \`client.collections.from('customer-profile-lists')\`.
|
|
3892
3914
|
|
|
3893
3915
|
### Config
|
|
3894
3916
|
|
|
@@ -4029,7 +4051,9 @@ comments, reactions, bookmarks, reports, community-bans
|
|
|
4029
4051
|
|
|
4030
4052
|
### Optional Collections
|
|
4031
4053
|
|
|
4032
|
-
post-categories, customer-profile-lists
|
|
4054
|
+
post-categories, customer-profile-lists
|
|
4055
|
+
|
|
4056
|
+
- \`customer-profile-lists\` is Community-owned public profile curation. It composes \`customer-profiles\`, so effective Community access also requires Customers.`
|
|
4033
4057
|
};
|
|
4034
4058
|
function featureSetupGuide({
|
|
4035
4059
|
feature
|
|
@@ -4142,12 +4166,7 @@ var COLLECTIONS_BY_CATEGORY = {
|
|
|
4142
4166
|
"shipping-policies",
|
|
4143
4167
|
"shipping-zones"
|
|
4144
4168
|
],
|
|
4145
|
-
Customers: [
|
|
4146
|
-
"customers",
|
|
4147
|
-
"customer-profiles",
|
|
4148
|
-
"customer-profile-lists",
|
|
4149
|
-
"customer-addresses"
|
|
4150
|
-
],
|
|
4169
|
+
Customers: ["customers", "customer-profiles", "customer-addresses"],
|
|
4151
4170
|
Carts: ["carts", "cart-items"],
|
|
4152
4171
|
Discounts: ["discounts"],
|
|
4153
4172
|
Documents: ["documents", "document-categories", "document-types"],
|
|
@@ -4163,7 +4182,8 @@ var COLLECTIONS_BY_CATEGORY = {
|
|
|
4163
4182
|
"reactions",
|
|
4164
4183
|
"reaction-types",
|
|
4165
4184
|
"bookmarks",
|
|
4166
|
-
"post-categories"
|
|
4185
|
+
"post-categories",
|
|
4186
|
+
"customer-profile-lists"
|
|
4167
4187
|
],
|
|
4168
4188
|
Playlists: [
|
|
4169
4189
|
"playlists",
|
|
@@ -4273,6 +4293,22 @@ yarn add @01.software/sdk
|
|
|
4273
4293
|
pnpm add @01.software/sdk
|
|
4274
4294
|
\`\`\`
|
|
4275
4295
|
|
|
4296
|
+
## Optional peers
|
|
4297
|
+
|
|
4298
|
+
You do not need extra packages for the root SDK entry. Install peer
|
|
4299
|
+
dependencies only when you import a feature sub-path:
|
|
4300
|
+
|
|
4301
|
+
- \`@01.software/sdk/query\` -> \`@tanstack/react-query\`, \`react\`, \`react-dom\`
|
|
4302
|
+
- \`@01.software/sdk/realtime\` -> \`@tanstack/react-query\`
|
|
4303
|
+
- \`@01.software/sdk/analytics/react\` -> \`react\`, \`react-dom\`
|
|
4304
|
+
- \`@01.software/sdk/ui/rich-text\` -> \`@payloadcms/richtext-lexical\`
|
|
4305
|
+
- \`@01.software/sdk/ui/form\` -> none
|
|
4306
|
+
- \`@01.software/sdk/ui/code-block\` -> \`shiki\`, \`hast-util-to-jsx-runtime\`
|
|
4307
|
+
- \`@01.software/sdk/ui/canvas\` -> \`@tanstack/react-query\`, \`@xyflow/react\`, \`quickjs-emscripten\`, \`postcss\`, \`sucrase\`
|
|
4308
|
+
- \`@01.software/sdk/ui/canvas/server\` -> none
|
|
4309
|
+
- \`@01.software/sdk/ui/video\` -> \`@mux/mux-player-react\`
|
|
4310
|
+
- \`@01.software/sdk/ui/image\` -> none
|
|
4311
|
+
|
|
4276
4312
|
## Basic Usage
|
|
4277
4313
|
|
|
4278
4314
|
\`\`\`typescript
|
|
@@ -4310,7 +4346,7 @@ Comprehensive guides to master the 01.software SDK.
|
|
|
4310
4346
|
|
|
4311
4347
|
## Data Fetching
|
|
4312
4348
|
|
|
4313
|
-
Use the Query Builder or React Query hooks to fetch data efficiently.
|
|
4349
|
+
Use the Query Builder or opt-in React Query hooks to fetch data efficiently.
|
|
4314
4350
|
|
|
4315
4351
|
### Query Builder
|
|
4316
4352
|
\`\`\`typescript
|
|
@@ -4338,7 +4374,10 @@ const { totalDocs } = await client.collections.from('products').count()
|
|
|
4338
4374
|
|
|
4339
4375
|
### React Query Hook
|
|
4340
4376
|
\`\`\`typescript
|
|
4341
|
-
|
|
4377
|
+
import { createQueryHooks } from '@01.software/sdk/query'
|
|
4378
|
+
|
|
4379
|
+
const query = createQueryHooks(client)
|
|
4380
|
+
const { data, isLoading, error } = query.useQuery({
|
|
4342
4381
|
collection: 'products',
|
|
4343
4382
|
options: {
|
|
4344
4383
|
where: { status: { equals: 'published' } },
|
|
@@ -4349,7 +4388,7 @@ const { data, isLoading, error } = client.query.useQuery({
|
|
|
4349
4388
|
|
|
4350
4389
|
### Suspense Mode
|
|
4351
4390
|
\`\`\`typescript
|
|
4352
|
-
const { data } =
|
|
4391
|
+
const { data } = query.useSuspenseQuery({
|
|
4353
4392
|
collection: 'products',
|
|
4354
4393
|
options: { limit: 10 }
|
|
4355
4394
|
})
|
|
@@ -4358,7 +4397,7 @@ const { data } = client.query.useSuspenseQuery({
|
|
|
4358
4397
|
### Infinite Scroll
|
|
4359
4398
|
\`\`\`typescript
|
|
4360
4399
|
const { data, fetchNextPage, hasNextPage, isFetchingNextPage } =
|
|
4361
|
-
|
|
4400
|
+
query.useInfiniteQuery({
|
|
4362
4401
|
collection: 'products',
|
|
4363
4402
|
pageSize: 20
|
|
4364
4403
|
})
|
|
@@ -4405,19 +4444,18 @@ const result = await serverClient.from('products').removeMany(
|
|
|
4405
4444
|
)
|
|
4406
4445
|
\`\`\`
|
|
4407
4446
|
|
|
4408
|
-
###
|
|
4447
|
+
### Server Mutations
|
|
4409
4448
|
\`\`\`typescript
|
|
4410
|
-
|
|
4411
|
-
const { mutate: create } = client.query.useCreate({ collection: 'products' })
|
|
4412
|
-
create({ title: 'New', status: 'draft' })
|
|
4449
|
+
import { createServerClient } from '@01.software/sdk/server'
|
|
4413
4450
|
|
|
4414
|
-
|
|
4415
|
-
|
|
4416
|
-
|
|
4451
|
+
const server = createServerClient({
|
|
4452
|
+
publishableKey: process.env.SOFTWARE_PUBLISHABLE_KEY!,
|
|
4453
|
+
secretKey: process.env.SOFTWARE_SECRET_KEY!,
|
|
4454
|
+
})
|
|
4417
4455
|
|
|
4418
|
-
|
|
4419
|
-
|
|
4420
|
-
remove('product-id')
|
|
4456
|
+
await server.collections.from('products').create({ title: 'New', status: 'draft' })
|
|
4457
|
+
await server.collections.from('products').update('product-id', { title: 'Updated' })
|
|
4458
|
+
await server.collections.from('products').remove('product-id')
|
|
4421
4459
|
\`\`\`
|
|
4422
4460
|
|
|
4423
4461
|
## Caching Strategies
|
|
@@ -4427,17 +4465,17 @@ The SDK uses React Query for caching and background updates.
|
|
|
4427
4465
|
### SSR Prefetching
|
|
4428
4466
|
\`\`\`typescript
|
|
4429
4467
|
// Prefetch in server component for instant client hydration
|
|
4430
|
-
await
|
|
4468
|
+
await query.prefetchQuery({
|
|
4431
4469
|
collection: 'products',
|
|
4432
4470
|
options: { limit: 10 }
|
|
4433
4471
|
})
|
|
4434
4472
|
|
|
4435
|
-
await
|
|
4473
|
+
await query.prefetchQueryById({
|
|
4436
4474
|
collection: 'products',
|
|
4437
4475
|
id: 'product-id'
|
|
4438
4476
|
})
|
|
4439
4477
|
|
|
4440
|
-
await
|
|
4478
|
+
await query.prefetchInfiniteQuery({
|
|
4441
4479
|
collection: 'products',
|
|
4442
4480
|
pageSize: 20
|
|
4443
4481
|
})
|
|
@@ -4446,19 +4484,19 @@ await client.query.prefetchInfiniteQuery({
|
|
|
4446
4484
|
### Cache Invalidation
|
|
4447
4485
|
\`\`\`typescript
|
|
4448
4486
|
// Invalidate list cache for a collection
|
|
4449
|
-
|
|
4487
|
+
query.invalidateQueries('products', 'list')
|
|
4450
4488
|
|
|
4451
4489
|
// Invalidate all caches for a collection
|
|
4452
|
-
|
|
4490
|
+
query.invalidateQueries('products')
|
|
4453
4491
|
\`\`\`
|
|
4454
4492
|
|
|
4455
4493
|
### Manual Cache Management
|
|
4456
4494
|
\`\`\`typescript
|
|
4457
4495
|
// Read cached data
|
|
4458
|
-
const cached =
|
|
4496
|
+
const cached = query.getQueryData('products', 'list')
|
|
4459
4497
|
|
|
4460
4498
|
// Write to cache (optimistic updates)
|
|
4461
|
-
|
|
4499
|
+
query.setQueryData('products', 'list', newData)
|
|
4462
4500
|
\`\`\`
|
|
4463
4501
|
|
|
4464
4502
|
## Error Handling
|
|
@@ -4536,7 +4574,7 @@ const client = createClient({
|
|
|
4536
4574
|
For server-side operations (full CRUD)
|
|
4537
4575
|
|
|
4538
4576
|
\`\`\`typescript
|
|
4539
|
-
import { createServerClient } from '@01.software/sdk'
|
|
4577
|
+
import { createServerClient } from '@01.software/sdk/server'
|
|
4540
4578
|
|
|
4541
4579
|
const client = createServerClient({
|
|
4542
4580
|
publishableKey: string,
|
|
@@ -4615,13 +4653,19 @@ const result = await client.collections.from('collection').removeMany(where)
|
|
|
4615
4653
|
// Returns PayloadFindResponse with deleted docs
|
|
4616
4654
|
\`\`\`
|
|
4617
4655
|
|
|
4618
|
-
## React Query Hooks (
|
|
4656
|
+
## React Query Hooks (\`@01.software/sdk/query\`)
|
|
4657
|
+
|
|
4658
|
+
\`\`\`typescript
|
|
4659
|
+
import { createQueryHooks } from '@01.software/sdk/query'
|
|
4660
|
+
|
|
4661
|
+
const query = createQueryHooks(client)
|
|
4662
|
+
\`\`\`
|
|
4619
4663
|
|
|
4620
4664
|
### useQuery()
|
|
4621
4665
|
Query a collection list.
|
|
4622
4666
|
|
|
4623
4667
|
\`\`\`typescript
|
|
4624
|
-
const { data, isLoading, error } =
|
|
4668
|
+
const { data, isLoading, error } = query.useQuery({
|
|
4625
4669
|
collection: 'products',
|
|
4626
4670
|
options: { limit: 10, where: { status: { equals: 'published' } } }
|
|
4627
4671
|
})
|
|
@@ -4631,7 +4675,7 @@ const { data, isLoading, error } = client.query.useQuery({
|
|
|
4631
4675
|
Suspense mode list query.
|
|
4632
4676
|
|
|
4633
4677
|
\`\`\`typescript
|
|
4634
|
-
const { data } =
|
|
4678
|
+
const { data } = query.useSuspenseQuery({
|
|
4635
4679
|
collection: 'products',
|
|
4636
4680
|
options: { limit: 10 }
|
|
4637
4681
|
})
|
|
@@ -4641,7 +4685,7 @@ const { data } = client.query.useSuspenseQuery({
|
|
|
4641
4685
|
Get a single item by ID.
|
|
4642
4686
|
|
|
4643
4687
|
\`\`\`typescript
|
|
4644
|
-
const { data } =
|
|
4688
|
+
const { data } = query.useQueryById({
|
|
4645
4689
|
collection: 'products',
|
|
4646
4690
|
id: 'product-id'
|
|
4647
4691
|
})
|
|
@@ -4651,7 +4695,7 @@ const { data } = client.query.useQueryById({
|
|
|
4651
4695
|
Suspense mode single item query.
|
|
4652
4696
|
|
|
4653
4697
|
\`\`\`typescript
|
|
4654
|
-
const { data } =
|
|
4698
|
+
const { data } = query.useSuspenseQueryById({
|
|
4655
4699
|
collection: 'products',
|
|
4656
4700
|
id: 'product-id'
|
|
4657
4701
|
})
|
|
@@ -4661,7 +4705,7 @@ const { data } = client.query.useSuspenseQueryById({
|
|
|
4661
4705
|
Infinite scroll.
|
|
4662
4706
|
|
|
4663
4707
|
\`\`\`typescript
|
|
4664
|
-
const { data, fetchNextPage, hasNextPage } =
|
|
4708
|
+
const { data, fetchNextPage, hasNextPage } = query.useInfiniteQuery({
|
|
4665
4709
|
collection: 'products',
|
|
4666
4710
|
options: { limit: 20 },
|
|
4667
4711
|
pageSize: 20
|
|
@@ -4672,52 +4716,43 @@ const { data, fetchNextPage, hasNextPage } = client.query.useInfiniteQuery({
|
|
|
4672
4716
|
Suspense mode infinite scroll.
|
|
4673
4717
|
|
|
4674
4718
|
\`\`\`typescript
|
|
4675
|
-
const { data, fetchNextPage } =
|
|
4719
|
+
const { data, fetchNextPage } = query.useSuspenseInfiniteQuery({
|
|
4676
4720
|
collection: 'products',
|
|
4677
4721
|
pageSize: 20
|
|
4678
4722
|
})
|
|
4679
4723
|
\`\`\`
|
|
4680
4724
|
|
|
4681
|
-
##
|
|
4682
|
-
|
|
4683
|
-
### useCreate()
|
|
4684
|
-
Create a document with automatic cache invalidation.
|
|
4685
|
-
|
|
4686
|
-
\`\`\`typescript
|
|
4687
|
-
const { mutate } = client.query.useCreate({ collection: 'products' })
|
|
4688
|
-
mutate({ title: 'New Product', status: 'draft' })
|
|
4689
|
-
\`\`\`
|
|
4725
|
+
## Server Mutations
|
|
4690
4726
|
|
|
4691
|
-
|
|
4692
|
-
Update a document with automatic cache invalidation.
|
|
4727
|
+
Writes require trusted server code with \`createServerClient\`; do not run server credentials in React client components.
|
|
4693
4728
|
|
|
4694
4729
|
\`\`\`typescript
|
|
4695
|
-
|
|
4696
|
-
mutate({ id: 'product-id', data: { title: 'Updated' } })
|
|
4697
|
-
\`\`\`
|
|
4730
|
+
import { createServerClient } from '@01.software/sdk/server'
|
|
4698
4731
|
|
|
4699
|
-
|
|
4700
|
-
|
|
4732
|
+
const server = createServerClient({
|
|
4733
|
+
publishableKey: process.env.SOFTWARE_PUBLISHABLE_KEY!,
|
|
4734
|
+
secretKey: process.env.SOFTWARE_SECRET_KEY!,
|
|
4735
|
+
})
|
|
4701
4736
|
|
|
4702
|
-
|
|
4703
|
-
|
|
4704
|
-
|
|
4737
|
+
await server.collections.from('products').create({ title: 'New Product', status: 'draft' })
|
|
4738
|
+
await server.collections.from('products').update('product-id', { title: 'Updated' })
|
|
4739
|
+
await server.collections.from('products').remove('product-id')
|
|
4705
4740
|
\`\`\`
|
|
4706
4741
|
|
|
4707
4742
|
## Cache Utilities
|
|
4708
4743
|
|
|
4709
4744
|
\`\`\`typescript
|
|
4710
4745
|
// Invalidate cache
|
|
4711
|
-
|
|
4746
|
+
query.invalidateQueries('products', 'list')
|
|
4712
4747
|
|
|
4713
4748
|
// SSR prefetch
|
|
4714
|
-
await
|
|
4715
|
-
await
|
|
4716
|
-
await
|
|
4749
|
+
await query.prefetchQuery({ collection: 'products', options: { limit: 10 } })
|
|
4750
|
+
await query.prefetchQueryById({ collection: 'products', id: 'id' })
|
|
4751
|
+
await query.prefetchInfiniteQuery({ collection: 'products', pageSize: 20 })
|
|
4717
4752
|
|
|
4718
4753
|
// Get/set cached data
|
|
4719
|
-
const cached =
|
|
4720
|
-
|
|
4754
|
+
const cached = query.getQueryData('products', 'list')
|
|
4755
|
+
query.setQueryData('products', 'list', newData)
|
|
4721
4756
|
\`\`\`
|
|
4722
4757
|
|
|
4723
4758
|
For ecommerce reads, prefer \`products.listing.*\` for card/search pricing and keep authoritative sellable pricing on \`product-variants.price\`.
|
|
@@ -4964,7 +4999,7 @@ const result3 = await client.collections.from('products').find({ sort: '-isFeatu
|
|
|
4964
4999
|
## Full Example
|
|
4965
5000
|
|
|
4966
5001
|
\`\`\`typescript
|
|
4967
|
-
import { createServerClient } from '@01.software/sdk'
|
|
5002
|
+
import { createServerClient } from '@01.software/sdk/server'
|
|
4968
5003
|
|
|
4969
5004
|
const serverClient = createServerClient({
|
|
4970
5005
|
publishableKey: process.env.SOFTWARE_PUBLISHABLE_KEY!,
|
|
@@ -4992,14 +5027,22 @@ console.log(result.hasNextPage) // true
|
|
|
4992
5027
|
var metadata43 = {
|
|
4993
5028
|
name: "docs-react-query",
|
|
4994
5029
|
title: "React Query Hooks",
|
|
4995
|
-
description: "01.software SDK React Query hooks reference (
|
|
5030
|
+
description: "01.software SDK React Query hooks reference (@01.software/sdk/query)"
|
|
4996
5031
|
};
|
|
4997
5032
|
function handler13() {
|
|
4998
5033
|
return `# React Query Hooks
|
|
4999
5034
|
|
|
5000
|
-
React Query hooks are
|
|
5035
|
+
React Query hooks are opt-in through \`@01.software/sdk/query\`. They provide automatic caching, background refetching, and cache invalidation without making root \`createClient\` consumers install React Query.
|
|
5001
5036
|
|
|
5002
|
-
|
|
5037
|
+
Install \`@tanstack/react-query\` and React peers only when importing this sub-path.
|
|
5038
|
+
|
|
5039
|
+
\`\`\`typescript
|
|
5040
|
+
import { createClient } from '@01.software/sdk'
|
|
5041
|
+
import { createQueryHooks } from '@01.software/sdk/query'
|
|
5042
|
+
|
|
5043
|
+
const client = createClient({ publishableKey })
|
|
5044
|
+
const query = createQueryHooks(client)
|
|
5045
|
+
\`\`\`
|
|
5003
5046
|
|
|
5004
5047
|
## Query Hooks
|
|
5005
5048
|
|
|
@@ -5007,7 +5050,7 @@ React Query hooks are available on the browser-side \`Client\` via \`client.quer
|
|
|
5007
5050
|
Query a collection list with automatic caching.
|
|
5008
5051
|
|
|
5009
5052
|
\`\`\`typescript
|
|
5010
|
-
const { data, isLoading, error } =
|
|
5053
|
+
const { data, isLoading, error } = query.useQuery({
|
|
5011
5054
|
collection: 'products',
|
|
5012
5055
|
options: {
|
|
5013
5056
|
where: { status: { equals: 'published' } },
|
|
@@ -5025,7 +5068,7 @@ Suspense-mode list query. Throws a promise while loading (use with React Suspens
|
|
|
5025
5068
|
|
|
5026
5069
|
\`\`\`typescript
|
|
5027
5070
|
// Inside a Suspense boundary
|
|
5028
|
-
const { data } =
|
|
5071
|
+
const { data } = query.useSuspenseQuery({
|
|
5029
5072
|
collection: 'products',
|
|
5030
5073
|
options: { limit: 10 },
|
|
5031
5074
|
})
|
|
@@ -5036,7 +5079,7 @@ const { data } = client.query.useSuspenseQuery({
|
|
|
5036
5079
|
Get a single document by ID.
|
|
5037
5080
|
|
|
5038
5081
|
\`\`\`typescript
|
|
5039
|
-
const { data, isLoading } =
|
|
5082
|
+
const { data, isLoading } = query.useQueryById({
|
|
5040
5083
|
collection: 'products',
|
|
5041
5084
|
id: 'product-id',
|
|
5042
5085
|
})
|
|
@@ -5047,7 +5090,7 @@ const { data, isLoading } = client.query.useQueryById({
|
|
|
5047
5090
|
Suspense-mode single document query.
|
|
5048
5091
|
|
|
5049
5092
|
\`\`\`typescript
|
|
5050
|
-
const { data } =
|
|
5093
|
+
const { data } = query.useSuspenseQueryById({
|
|
5051
5094
|
collection: 'products',
|
|
5052
5095
|
id: 'product-id',
|
|
5053
5096
|
})
|
|
@@ -5063,7 +5106,7 @@ const {
|
|
|
5063
5106
|
fetchNextPage,
|
|
5064
5107
|
hasNextPage,
|
|
5065
5108
|
isFetchingNextPage,
|
|
5066
|
-
} =
|
|
5109
|
+
} = query.useInfiniteQuery({
|
|
5067
5110
|
collection: 'products',
|
|
5068
5111
|
options: {
|
|
5069
5112
|
where: { status: { equals: 'published' } },
|
|
@@ -5079,55 +5122,33 @@ const {
|
|
|
5079
5122
|
Suspense-mode infinite scroll.
|
|
5080
5123
|
|
|
5081
5124
|
\`\`\`typescript
|
|
5082
|
-
const { data, fetchNextPage, hasNextPage } =
|
|
5125
|
+
const { data, fetchNextPage, hasNextPage } = query.useSuspenseInfiniteQuery({
|
|
5083
5126
|
collection: 'products',
|
|
5084
5127
|
pageSize: 20,
|
|
5085
5128
|
})
|
|
5086
5129
|
\`\`\`
|
|
5087
5130
|
|
|
5088
|
-
##
|
|
5089
|
-
|
|
5090
|
-
Mutation hooks automatically invalidate relevant cache keys after success.
|
|
5131
|
+
## Writes
|
|
5091
5132
|
|
|
5092
|
-
|
|
5093
|
-
Create a document and invalidate list cache.
|
|
5133
|
+
Keep writes in trusted server code. Use a server action, API route, or backend service with \`createServerClient\`, then invalidate browser React Query caches after the action completes.
|
|
5094
5134
|
|
|
5095
5135
|
\`\`\`typescript
|
|
5096
|
-
|
|
5097
|
-
|
|
5098
|
-
})
|
|
5099
|
-
|
|
5100
|
-
// Fire and forget
|
|
5101
|
-
mutate({ title: 'New Product', status: 'draft' })
|
|
5102
|
-
|
|
5103
|
-
// Await result
|
|
5104
|
-
const result = await mutateAsync({ title: 'New Product', status: 'draft' })
|
|
5105
|
-
// result.doc - created document
|
|
5106
|
-
\`\`\`
|
|
5107
|
-
|
|
5108
|
-
For ecommerce reads, price-oriented product cards should consume \`products.listing.minPrice/maxPrice\`. Authoritative sellable pricing still lives on \`product-variants.price\`.
|
|
5136
|
+
// app/products/actions.ts
|
|
5137
|
+
'use server'
|
|
5109
5138
|
|
|
5110
|
-
|
|
5111
|
-
Update a document and invalidate list + detail cache.
|
|
5139
|
+
import { createServerClient } from '@01.software/sdk/server'
|
|
5112
5140
|
|
|
5113
|
-
|
|
5114
|
-
|
|
5115
|
-
|
|
5141
|
+
const server = createServerClient({
|
|
5142
|
+
publishableKey: process.env.SOFTWARE_PUBLISHABLE_KEY!,
|
|
5143
|
+
secretKey: process.env.SOFTWARE_SECRET_KEY!,
|
|
5116
5144
|
})
|
|
5117
5145
|
|
|
5118
|
-
|
|
5146
|
+
export async function createProduct(data: { title: string; status: 'draft' | 'published' }) {
|
|
5147
|
+
return server.collections.from('products').create(data)
|
|
5148
|
+
}
|
|
5119
5149
|
\`\`\`
|
|
5120
5150
|
|
|
5121
|
-
|
|
5122
|
-
Remove a document and invalidate list cache.
|
|
5123
|
-
|
|
5124
|
-
\`\`\`typescript
|
|
5125
|
-
const { mutate, mutateAsync } = client.query.useRemove({
|
|
5126
|
-
collection: 'products',
|
|
5127
|
-
})
|
|
5128
|
-
|
|
5129
|
-
mutate('product-id')
|
|
5130
|
-
\`\`\`
|
|
5151
|
+
For ecommerce reads, price-oriented product cards should consume \`products.listing.minPrice/maxPrice\`. Authoritative sellable pricing still lives on \`product-variants.price\`.
|
|
5131
5152
|
|
|
5132
5153
|
## Cache Utilities
|
|
5133
5154
|
|
|
@@ -5136,10 +5157,10 @@ Manually invalidate cached queries for a collection.
|
|
|
5136
5157
|
|
|
5137
5158
|
\`\`\`typescript
|
|
5138
5159
|
// Invalidate all list queries for a collection
|
|
5139
|
-
|
|
5160
|
+
query.invalidateQueries('products', 'list')
|
|
5140
5161
|
|
|
5141
5162
|
// Invalidate all queries for a collection (list + detail)
|
|
5142
|
-
|
|
5163
|
+
query.invalidateQueries('products')
|
|
5143
5164
|
\`\`\`
|
|
5144
5165
|
|
|
5145
5166
|
### getQueryData() / setQueryData()
|
|
@@ -5147,10 +5168,10 @@ Read and write the React Query cache directly.
|
|
|
5147
5168
|
|
|
5148
5169
|
\`\`\`typescript
|
|
5149
5170
|
// Read cached data
|
|
5150
|
-
const cached =
|
|
5171
|
+
const cached = query.getQueryData('products', 'list')
|
|
5151
5172
|
|
|
5152
5173
|
// Write to cache (useful for optimistic updates)
|
|
5153
|
-
|
|
5174
|
+
query.setQueryData('products', 'list', newData)
|
|
5154
5175
|
\`\`\`
|
|
5155
5176
|
|
|
5156
5177
|
## SSR Prefetching
|
|
@@ -5160,34 +5181,37 @@ Prefetch data in server components for instant hydration on the client.
|
|
|
5160
5181
|
\`\`\`typescript
|
|
5161
5182
|
// app/products/page.tsx (Next.js App Router Server Component)
|
|
5162
5183
|
import { HydrationBoundary, dehydrate } from '@tanstack/react-query'
|
|
5163
|
-
import { createServerClient } from '@01.software/sdk'
|
|
5184
|
+
import { createServerClient } from '@01.software/sdk/server'
|
|
5185
|
+
import { createServerQueryHooks, getQueryClient } from '@01.software/sdk/query'
|
|
5164
5186
|
|
|
5165
5187
|
export default async function ProductsPage() {
|
|
5166
5188
|
const serverClient = createServerClient({
|
|
5167
5189
|
publishableKey: process.env.SOFTWARE_PUBLISHABLE_KEY!,
|
|
5168
5190
|
secretKey: process.env.SOFTWARE_SECRET_KEY!,
|
|
5169
5191
|
})
|
|
5192
|
+
const queryClient = getQueryClient()
|
|
5193
|
+
const serverQuery = createServerQueryHooks(serverClient, queryClient)
|
|
5170
5194
|
|
|
5171
5195
|
// Prefetch list
|
|
5172
|
-
await
|
|
5196
|
+
await serverQuery.prefetchQuery({
|
|
5173
5197
|
collection: 'products',
|
|
5174
5198
|
options: { limit: 20, where: { status: { equals: 'published' } } },
|
|
5175
5199
|
})
|
|
5176
5200
|
|
|
5177
5201
|
// Prefetch single item
|
|
5178
|
-
await
|
|
5202
|
+
await serverQuery.prefetchQueryById({
|
|
5179
5203
|
collection: 'products',
|
|
5180
5204
|
id: 'product-id',
|
|
5181
5205
|
})
|
|
5182
5206
|
|
|
5183
5207
|
// Prefetch infinite query
|
|
5184
|
-
await
|
|
5208
|
+
await serverQuery.prefetchInfiniteQuery({
|
|
5185
5209
|
collection: 'products',
|
|
5186
5210
|
pageSize: 20,
|
|
5187
5211
|
})
|
|
5188
5212
|
|
|
5189
5213
|
return (
|
|
5190
|
-
<HydrationBoundary state={dehydrate(
|
|
5214
|
+
<HydrationBoundary state={dehydrate(queryClient)}>
|
|
5191
5215
|
<ProductList />
|
|
5192
5216
|
</HydrationBoundary>
|
|
5193
5217
|
)
|
|
@@ -5200,13 +5224,15 @@ export default async function ProductsPage() {
|
|
|
5200
5224
|
'use client'
|
|
5201
5225
|
|
|
5202
5226
|
import { createClient } from '@01.software/sdk'
|
|
5227
|
+
import { createQueryHooks } from '@01.software/sdk/query'
|
|
5203
5228
|
|
|
5204
5229
|
const client = createClient({
|
|
5205
5230
|
publishableKey: process.env.NEXT_PUBLIC_SOFTWARE_PUBLISHABLE_KEY!,
|
|
5206
5231
|
})
|
|
5232
|
+
const query = createQueryHooks(client)
|
|
5207
5233
|
|
|
5208
5234
|
export function ProductList() {
|
|
5209
|
-
const { data, isLoading, error } =
|
|
5235
|
+
const { data, isLoading, error } = query.useQuery({
|
|
5210
5236
|
collection: 'products',
|
|
5211
5237
|
options: {
|
|
5212
5238
|
where: { status: { equals: 'published' } },
|
|
@@ -5215,20 +5241,13 @@ export function ProductList() {
|
|
|
5215
5241
|
},
|
|
5216
5242
|
})
|
|
5217
5243
|
|
|
5218
|
-
const { mutate: removeProduct } = client.query.useRemove({
|
|
5219
|
-
collection: 'products',
|
|
5220
|
-
})
|
|
5221
|
-
|
|
5222
5244
|
if (isLoading) return <div>Loading...</div>
|
|
5223
5245
|
if (error) return <div>Error: {error.message}</div>
|
|
5224
5246
|
|
|
5225
5247
|
return (
|
|
5226
5248
|
<ul>
|
|
5227
5249
|
{data?.docs.map((product) => (
|
|
5228
|
-
<li key={product.id}>
|
|
5229
|
-
{product.title}
|
|
5230
|
-
<button onClick={() => removeProduct(product.id)}>Delete</button>
|
|
5231
|
-
</li>
|
|
5250
|
+
<li key={product.id}>{product.title}</li>
|
|
5232
5251
|
))}
|
|
5233
5252
|
</ul>
|
|
5234
5253
|
)
|
|
@@ -5248,7 +5267,7 @@ function handler14() {
|
|
|
5248
5267
|
Server-side operations are available via \`client.commerce\` on \`ServerClient\`. Use \`createServerClient\` with both \`publishableKey\` and \`secretKey\`.
|
|
5249
5268
|
|
|
5250
5269
|
\`\`\`typescript
|
|
5251
|
-
import { createServerClient } from '@01.software/sdk'
|
|
5270
|
+
import { createServerClient } from '@01.software/sdk/server'
|
|
5252
5271
|
|
|
5253
5272
|
const client = createServerClient({
|
|
5254
5273
|
publishableKey: process.env.SOFTWARE_PUBLISHABLE_KEY!,
|
|
@@ -5697,9 +5716,9 @@ The SDK provides two client types for different execution environments.
|
|
|
5697
5716
|
| Read (\`find\`, \`findById\`, \`count\`) | Yes | Yes |
|
|
5698
5717
|
| Write (\`create\`, \`update\`, \`remove\`) | No | Yes |
|
|
5699
5718
|
| Bulk (\`updateMany\`, \`removeMany\`) | No | Yes |
|
|
5700
|
-
| React Query hooks (
|
|
5719
|
+
| React Query hooks (\`@01.software/sdk/query\`) | Yes | Yes (SSR prefetch) |
|
|
5701
5720
|
| Customer auth (\`client.customer\`) | Yes | No |
|
|
5702
|
-
|
|
|
5721
|
+
| Commerce/server APIs (\`server.commerce\`, server CRUD) | No | Yes |
|
|
5703
5722
|
|
|
5704
5723
|
## Client (createClient)
|
|
5705
5724
|
|
|
@@ -5707,16 +5726,18 @@ Use in browser code, React client components, and anywhere the secret key must n
|
|
|
5707
5726
|
|
|
5708
5727
|
\`\`\`typescript
|
|
5709
5728
|
import { createClient } from '@01.software/sdk'
|
|
5729
|
+
import { createQueryHooks } from '@01.software/sdk/query'
|
|
5710
5730
|
|
|
5711
5731
|
const client = createClient({
|
|
5712
5732
|
publishableKey: process.env.NEXT_PUBLIC_SOFTWARE_PUBLISHABLE_KEY!,
|
|
5713
5733
|
})
|
|
5734
|
+
const query = createQueryHooks(client)
|
|
5714
5735
|
|
|
5715
5736
|
// Read data
|
|
5716
5737
|
const products = await client.collections.from('products').find({ limit: 10 })
|
|
5717
5738
|
|
|
5718
5739
|
// React Query hooks
|
|
5719
|
-
const { data } =
|
|
5740
|
+
const { data } = query.useQuery({ collection: 'products' })
|
|
5720
5741
|
|
|
5721
5742
|
// Customer auth
|
|
5722
5743
|
await client.customer.login({ email, password })
|
|
@@ -5729,7 +5750,7 @@ await client.customer.login({ email, password })
|
|
|
5729
5750
|
Use in server components, API routes, server actions, and background jobs. Has full CRUD access.
|
|
5730
5751
|
|
|
5731
5752
|
\`\`\`typescript
|
|
5732
|
-
import { createServerClient } from '@01.software/sdk'
|
|
5753
|
+
import { createServerClient } from '@01.software/sdk/server'
|
|
5733
5754
|
|
|
5734
5755
|
const client = createServerClient({
|
|
5735
5756
|
publishableKey: process.env.SOFTWARE_PUBLISHABLE_KEY!,
|
|
@@ -5750,11 +5771,14 @@ await client.collections.from('product-variants').create({
|
|
|
5750
5771
|
})
|
|
5751
5772
|
await client.collections.from('products').remove('product-id')
|
|
5752
5773
|
|
|
5753
|
-
//
|
|
5774
|
+
// Commerce API (orders, carts, etc.)
|
|
5754
5775
|
await client.commerce.orders.create({ ... })
|
|
5755
5776
|
await client.commerce.orders.checkout({ ... })
|
|
5756
5777
|
\`\`\`
|
|
5757
5778
|
|
|
5779
|
+
Server-only code must import \`createServerClient\` from the \`/server\`
|
|
5780
|
+
sub-path.
|
|
5781
|
+
|
|
5758
5782
|
**Environment variables**:
|
|
5759
5783
|
- \`SOFTWARE_PUBLISHABLE_KEY\` \u2014 publishable key (no NEXT_PUBLIC prefix, server-only)
|
|
5760
5784
|
- \`SOFTWARE_SECRET_KEY\` \u2014 server credential
|
|
@@ -5784,7 +5808,7 @@ deploying again.
|
|
|
5784
5808
|
|
|
5785
5809
|
\`\`\`typescript
|
|
5786
5810
|
// lib/sdk.ts \u2014 server-only module
|
|
5787
|
-
import { createServerClient } from '@01.software/sdk'
|
|
5811
|
+
import { createServerClient } from '@01.software/sdk/server'
|
|
5788
5812
|
|
|
5789
5813
|
export function getServerClient() {
|
|
5790
5814
|
return createServerClient({
|
|
@@ -5797,10 +5821,12 @@ export function getServerClient() {
|
|
|
5797
5821
|
\`\`\`typescript
|
|
5798
5822
|
// lib/sdk-client.ts \u2014 browser-safe module
|
|
5799
5823
|
import { createClient } from '@01.software/sdk'
|
|
5824
|
+
import { createQueryHooks } from '@01.software/sdk/query'
|
|
5800
5825
|
|
|
5801
5826
|
export const browserClient = createClient({
|
|
5802
5827
|
publishableKey: process.env.NEXT_PUBLIC_SOFTWARE_PUBLISHABLE_KEY!,
|
|
5803
5828
|
})
|
|
5829
|
+
export const browserQuery = createQueryHooks(browserClient)
|
|
5804
5830
|
\`\`\`
|
|
5805
5831
|
|
|
5806
5832
|
\`\`\`typescript
|
|
@@ -5817,10 +5843,10 @@ export default async function ProductsPage() {
|
|
|
5817
5843
|
\`\`\`typescript
|
|
5818
5844
|
// components/product-list.tsx \u2014 Client Component
|
|
5819
5845
|
'use client'
|
|
5820
|
-
import {
|
|
5846
|
+
import { browserQuery } from '@/lib/sdk-client'
|
|
5821
5847
|
|
|
5822
5848
|
export function ProductList() {
|
|
5823
|
-
const { data } =
|
|
5849
|
+
const { data } = browserQuery.useQuery({ collection: 'products' })
|
|
5824
5850
|
return <ul>{data?.docs.map(p => <li key={p.id}>{p.title}</li>)}</ul>
|
|
5825
5851
|
}
|
|
5826
5852
|
\`\`\`
|
|
@@ -5851,7 +5877,7 @@ Upload files using the \`images\` collection (tenant-scoped, unified image store
|
|
|
5851
5877
|
Use \`ServerClient\` with \`FormData\` to upload images server-side.
|
|
5852
5878
|
|
|
5853
5879
|
\`\`\`typescript
|
|
5854
|
-
import { createServerClient } from '@01.software/sdk'
|
|
5880
|
+
import { createServerClient } from '@01.software/sdk/server'
|
|
5855
5881
|
|
|
5856
5882
|
const client = createServerClient({
|
|
5857
5883
|
publishableKey: process.env.SOFTWARE_PUBLISHABLE_KEY!,
|
|
@@ -5874,7 +5900,7 @@ async function uploadImage(file: File) {
|
|
|
5874
5900
|
\`\`\`typescript
|
|
5875
5901
|
// app/api/upload/route.ts
|
|
5876
5902
|
import { NextRequest, NextResponse } from 'next/server'
|
|
5877
|
-
import { createServerClient } from '@01.software/sdk'
|
|
5903
|
+
import { createServerClient } from '@01.software/sdk/server'
|
|
5878
5904
|
|
|
5879
5905
|
const client = createServerClient({
|
|
5880
5906
|
publishableKey: process.env.SOFTWARE_PUBLISHABLE_KEY!,
|
|
@@ -5904,7 +5930,7 @@ export async function POST(req: NextRequest) {
|
|
|
5904
5930
|
// actions/upload.ts
|
|
5905
5931
|
'use server'
|
|
5906
5932
|
|
|
5907
|
-
import { createServerClient } from '@01.software/sdk'
|
|
5933
|
+
import { createServerClient } from '@01.software/sdk/server'
|
|
5908
5934
|
|
|
5909
5935
|
const client = createServerClient({
|
|
5910
5936
|
publishableKey: process.env.SOFTWARE_PUBLISHABLE_KEY!,
|
|
@@ -6127,29 +6153,38 @@ const client = createClient({
|
|
|
6127
6153
|
const detail = await client.commerce.product.detail({ slug: 'my-product' })
|
|
6128
6154
|
if (!detail) return notFound()
|
|
6129
6155
|
const selection = resolveProductSelection(detail, {
|
|
6130
|
-
search: '?opt.color=black
|
|
6156
|
+
search: '?opt.option-color=color-black',
|
|
6131
6157
|
})
|
|
6132
6158
|
// selection.selectedVariant, selection.price, selection.stock, selection.media
|
|
6133
6159
|
\`\`\`
|
|
6134
6160
|
|
|
6135
6161
|
## URL round-trip
|
|
6136
6162
|
|
|
6137
|
-
Use the SDK codec for
|
|
6163
|
+
Use the SDK codec for canonical selection URLs. Complete selections use
|
|
6164
|
+
\`variant=<variantId>\`; partial selections use
|
|
6165
|
+
\`opt.<optionId>=<valueId>\`. Older
|
|
6166
|
+
\`opt.<optionSlug>=<valueSlug>\` URLs still decode during Stage 1, but slugs are
|
|
6167
|
+
compatibility metadata rather than canonical identity.
|
|
6138
6168
|
|
|
6139
6169
|
\`\`\`typescript
|
|
6140
6170
|
import { createProductSelectionCodec } from '@01.software/sdk'
|
|
6141
6171
|
|
|
6142
6172
|
const codec = createProductSelectionCodec(detail)
|
|
6143
|
-
const selection = codec.parse('?opt.color=black')
|
|
6144
|
-
const
|
|
6173
|
+
const selection = codec.parse('?opt.option-color=color-black')
|
|
6174
|
+
const selectionQuery = codec.stringify(selection)
|
|
6145
6175
|
\`\`\`
|
|
6146
6176
|
|
|
6147
|
-
|
|
6177
|
+
Use IDs from \`detail.options[].id\` and \`detail.options[].values[].id\` when building new selection links. Slugs remain useful for display and older inbound URLs, but new outbound URLs should use the codec output.
|
|
6178
|
+
|
|
6179
|
+
Value-slug-only URLs such as \`?black\` or \`?color=black\` are rejected because option titles and values are not stable identifiers.
|
|
6148
6180
|
|
|
6149
6181
|
## React Query hook variant
|
|
6150
6182
|
|
|
6151
6183
|
\`\`\`typescript
|
|
6152
|
-
|
|
6184
|
+
import { createQueryHooks } from '@01.software/sdk/query'
|
|
6185
|
+
|
|
6186
|
+
const query = createQueryHooks(client)
|
|
6187
|
+
const { data: detail, isLoading } = query.useProductDetailBySlug(slug)
|
|
6153
6188
|
\`\`\`
|
|
6154
6189
|
|
|
6155
6190
|
Cache invalidates automatically when any of 10 detail-relevant collections is mutated through SDK mutation hooks.
|