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