@seliseblocks/cli-os 0.2.1 → 0.2.3
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/AI_USAGE_GUIDE.md +551 -546
- package/LICENSE +21 -21
- package/README.md +171 -171
- package/bin/run.js +2 -2
- package/dist/commands/data/files/delete.js +5 -4
- package/dist/commands/data/files/get-many.js +1 -1
- package/dist/commands/data/files/get.js +1 -1
- package/dist/commands/data/files/info.js +1 -1
- package/dist/commands/data/files/object-tree.d.ts +23 -0
- package/dist/commands/data/files/object-tree.js +238 -0
- package/dist/commands/data/files/presigned-upload-url.js +9 -2
- package/dist/commands/data/files/update-additional-info.js +2 -2
- package/dist/commands/data/files/upload-to-local-storage.js +2 -2
- package/dist/commands/data/files/upload.d.ts +2 -4
- package/dist/commands/data/files/upload.js +14 -28
- package/dist/index.js +685 -647
- package/dist/lib/scaffold-web/root-files.js +158 -0
- package/dist/skills/blocks-data-gateway-configuration/SKILL.md +204 -204
- package/dist/skills/blocks-data-gateway-crud/SKILL.md +223 -223
- package/dist/skills/blocks-data-storage/SKILL.md +253 -161
- package/dist/skills/blocks-data-storage/flows/object-management.md +124 -0
- package/dist/skills/blocks-frontend-local-https/SKILL.md +100 -100
- package/dist/skills/blocks-iam-account/SKILL.md +169 -169
- package/dist/skills/blocks-iam-sso-oidc-implementation/SKILL.md +80 -80
- package/dist/skills/blocks-iam-users/SKILL.md +131 -131
- package/dist/skills/blocks-localization-configuration/SKILL.md +149 -149
- package/dist/skills/blocks-localization-implementation/SKILL.md +63 -63
- package/dist/skills/blocks-onboarding/SKILL.md +77 -77
- package/dist/skills/blocks-storage-configuration/SKILL.md +4 -4
- package/package.json +47 -47
- package/dist/commands/data/files/create-folder.d.ts +0 -1
- package/dist/commands/data/files/create-folder.js +0 -34
- package/dist/commands/data/files/delete-folder.d.ts +0 -1
- package/dist/commands/data/files/delete-folder.js +0 -25
- package/dist/commands/data/files/dms-list.d.ts +0 -1
- package/dist/commands/data/files/dms-list.js +0 -27
- package/dist/commands/data/files/dms-upload.d.ts +0 -6
- package/dist/commands/data/files/dms-upload.js +0 -41
|
@@ -1,223 +1,223 @@
|
|
|
1
|
-
---
|
|
2
|
-
name: blocks-data-gateway-crud
|
|
3
|
-
description: "Implement create/read/update/delete against a SELISE Blocks project's runtime Data Gateway using the @seliseblocks/client SDK. Use data.collection(schemaName) for straightforward per-item CRUD, data.graphql() for joins or custom query shapes, and data.schemas.*/data.validations.* for schema/validation metadata. Shows how to wire CRUD into the React 18 + Vite + TanStack Query app that blocks new web scaffolds. Use whenever the user wants to read or write actual records through a Blocks Data schema from app code."
|
|
4
|
-
---
|
|
5
|
-
|
|
6
|
-
# Blocks Data - Gateway CRUD
|
|
7
|
-
|
|
8
|
-
Once a schema exists and has been reloaded via the blocks-data-gateway-configuration skill, the Data Gateway exposes runtime records through GraphQL. This skill shows how to use the generated app's shared `@seliseblocks/client` instance for CRUD. Do not use raw `fetch` or `curl` against Blocks APIs from app code.
|
|
9
|
-
|
|
10
|
-
Prerequisite: a project selected via `blocks use` and an app scaffolded with `blocks new web <name> ...`. If either is missing, run the blocks-onboarding skill first.
|
|
11
|
-
|
|
12
|
-
## Use the Existing Client
|
|
13
|
-
|
|
14
|
-
The scaffold creates one `createBlocksClient()` call in `src/lib/blocks/client.ts`:
|
|
15
|
-
|
|
16
|
-
```ts
|
|
17
|
-
import { createBlocksClient } from "@seliseblocks/client";
|
|
18
|
-
import { blocksConfig } from "./config";
|
|
19
|
-
import { getValidAccessToken } from "./auth";
|
|
20
|
-
|
|
21
|
-
export const blocksClient = createBlocksClient({
|
|
22
|
-
accessToken: () => getValidAccessToken(),
|
|
23
|
-
apiUrl: blocksConfig.apiUrl,
|
|
24
|
-
appDomain: blocksConfig.appDomain,
|
|
25
|
-
oidc: { clientId: blocksConfig.oidcClientId, scope: blocksConfig.oidcScope, url: blocksConfig.oidcUrl },
|
|
26
|
-
xBlocksKey: blocksConfig.xBlocksKey
|
|
27
|
-
});
|
|
28
|
-
```
|
|
29
|
-
|
|
30
|
-
Import that existing instance from feature code. Do not create a second client.
|
|
31
|
-
|
|
32
|
-
- `xBlocksKey` is the project's public tenant key from `VITE_BLOCKS_X_BLOCKS_KEY`.
|
|
33
|
-
- The SDK does not add `ProjectKey` to runtime Data calls.
|
|
34
|
-
- The HTTP client sends `credentials: "include"` so hosted-login session cookies are included.
|
|
35
|
-
- Never use the CLI's impersonation token or project token in browser code.
|
|
36
|
-
|
|
37
|
-
## Preferred CRUD Helper
|
|
38
|
-
|
|
39
|
-
Use `blocksClient.data.collection<T>(schemaName, options)` for normal CRUD. Pass the schema name, not the collection name. For a schema named `Product` with collection `Products`, pass `"Product"`.
|
|
40
|
-
|
|
41
|
-
```ts
|
|
42
|
-
const products = blocksClient.data.collection<Product>("Product", {
|
|
43
|
-
fields: ["name", "price", "status"]
|
|
44
|
-
});
|
|
45
|
-
|
|
46
|
-
products.list({ pageNo: 1, pageSize: 20 });
|
|
47
|
-
products.get(itemId);
|
|
48
|
-
products.create(payload);
|
|
49
|
-
products.update(itemId, payload);
|
|
50
|
-
products.delete(itemId);
|
|
51
|
-
```
|
|
52
|
-
|
|
53
|
-
What the helper does:
|
|
54
|
-
|
|
55
|
-
- `list` and `get` call `query getProducts($input: DynamicQueryInput)`.
|
|
56
|
-
- `create` calls `mutation insertProduct($input: ProductInsertInput!)`.
|
|
57
|
-
- `update` calls `mutation updateProduct($filter: String, $input: ProductUpdateInput!)`.
|
|
58
|
-
- `delete` calls `mutation deleteProduct($filter: String, $input: ProductDeleteInput!)`.
|
|
59
|
-
- `get`, `update`, and `delete` address records by `ItemId`.
|
|
60
|
-
- `fields` controls the GraphQL selection for returned items; `ItemId` is always selected.
|
|
61
|
-
|
|
62
|
-
`list` accepts paging plus optional GraphQL dynamic input values:
|
|
63
|
-
|
|
64
|
-
```ts
|
|
65
|
-
await products.list({
|
|
66
|
-
pageNo: 1,
|
|
67
|
-
pageSize: 20,
|
|
68
|
-
filter: { status: "Active" },
|
|
69
|
-
sort: { name: 1 }
|
|
70
|
-
});
|
|
71
|
-
```
|
|
72
|
-
|
|
73
|
-
For search, only send filters that the schema/runtime actually supports. If unsure, start with simple paging and client-side filtering, or inspect the schema first.
|
|
74
|
-
|
|
75
|
-
## Product Example
|
|
76
|
-
|
|
77
|
-
```ts
|
|
78
|
-
// src/features/products/productsApi.ts
|
|
79
|
-
import { blocksClient } from "../../lib/blocks/client";
|
|
80
|
-
|
|
81
|
-
export type Product = Record<string, unknown> & {
|
|
82
|
-
itemId?: string;
|
|
83
|
-
id?: string;
|
|
84
|
-
name?: string;
|
|
85
|
-
price?: number;
|
|
86
|
-
status?: string;
|
|
87
|
-
};
|
|
88
|
-
|
|
89
|
-
export type ProductInput = { name: string; price: number; status: string };
|
|
90
|
-
|
|
91
|
-
const products = blocksClient.data.collection<Product>("Product", {
|
|
92
|
-
fields: ["name", "price", "status"]
|
|
93
|
-
});
|
|
94
|
-
|
|
95
|
-
export async function listProducts({ page, pageSize }: { page: number; pageSize: number }) {
|
|
96
|
-
const response = await products.list({ pageNo: page, pageSize });
|
|
97
|
-
return normalizeProductList(response);
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
export function createProduct(input: ProductInput) {
|
|
101
|
-
return products.create(input);
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
export function updateProduct(product: Product) {
|
|
105
|
-
return products.update(productId(product), product);
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
export function deleteProduct(product: Product) {
|
|
109
|
-
return products.delete(productId(product));
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
function productId(product: Product): string {
|
|
113
|
-
const id = product.itemId ?? product.id;
|
|
114
|
-
if (!id) throw new Error("Product item id is missing.");
|
|
115
|
-
return String(id);
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
function normalizeProductList(response: unknown): { items: Product[]; totalCount: number } {
|
|
119
|
-
const record = response as {
|
|
120
|
-
data?: {
|
|
121
|
-
getProducts?: { items?: Product[]; totalCount?: number };
|
|
122
|
-
items?: Product[];
|
|
123
|
-
totalCount?: number;
|
|
124
|
-
};
|
|
125
|
-
items?: Product[];
|
|
126
|
-
totalCount?: number;
|
|
127
|
-
};
|
|
128
|
-
const gateway = record.data?.getProducts;
|
|
129
|
-
const items = gateway?.items ?? record.data?.items ?? record.items ?? [];
|
|
130
|
-
return { items, totalCount: gateway?.totalCount ?? record.data?.totalCount ?? record.totalCount ?? items.length };
|
|
131
|
-
}
|
|
132
|
-
```
|
|
133
|
-
|
|
134
|
-
```ts
|
|
135
|
-
// src/features/products/useProducts.ts
|
|
136
|
-
import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
|
|
137
|
-
import { createProduct, deleteProduct, listProducts, updateProduct } from "./productsApi";
|
|
138
|
-
import type { Product, ProductInput } from "./productsApi";
|
|
139
|
-
|
|
140
|
-
export function useProductsQuery(page: number, pageSize: number) {
|
|
141
|
-
return useQuery({
|
|
142
|
-
queryKey: ["products", page, pageSize],
|
|
143
|
-
queryFn: () => listProducts({ page, pageSize })
|
|
144
|
-
});
|
|
145
|
-
}
|
|
146
|
-
|
|
147
|
-
export function useProductMutations() {
|
|
148
|
-
const queryClient = useQueryClient();
|
|
149
|
-
const invalidate = () => queryClient.invalidateQueries({ queryKey: ["products"] });
|
|
150
|
-
|
|
151
|
-
return {
|
|
152
|
-
create: useMutation({ mutationFn: (input: ProductInput) => createProduct(input), onSuccess: invalidate }),
|
|
153
|
-
update: useMutation({ mutationFn: (product: Product) => updateProduct(product), onSuccess: invalidate }),
|
|
154
|
-
remove: useMutation({ mutationFn: (product: Product) => deleteProduct(product), onSuccess: invalidate })
|
|
155
|
-
};
|
|
156
|
-
}
|
|
157
|
-
```
|
|
158
|
-
|
|
159
|
-
## When to Use `data.graphql()`
|
|
160
|
-
|
|
161
|
-
Use `data.graphql({ query, variables, operationName })` when the collection helper is too small:
|
|
162
|
-
|
|
163
|
-
- joins across schemas;
|
|
164
|
-
- custom nested selections;
|
|
165
|
-
- generated `where`, `order`, or `paging` arguments;
|
|
166
|
-
- bulk operations such as `insertManyProduct`.
|
|
167
|
-
|
|
168
|
-
Example:
|
|
169
|
-
|
|
170
|
-
```ts
|
|
171
|
-
const result = await blocksClient.data.graphql({
|
|
172
|
-
operationName: "getProducts",
|
|
173
|
-
query: `
|
|
174
|
-
query getProducts($input: DynamicQueryInput) {
|
|
175
|
-
getProducts(input: $input) {
|
|
176
|
-
items { ItemId name price status }
|
|
177
|
-
totalCount
|
|
178
|
-
}
|
|
179
|
-
}
|
|
180
|
-
`,
|
|
181
|
-
variables: { input: { pageNo: 1, pageSize: 20 } }
|
|
182
|
-
});
|
|
183
|
-
```
|
|
184
|
-
|
|
185
|
-
## Schema Metadata
|
|
186
|
-
|
|
187
|
-
Use `data.schemas.*` when building dynamic UI:
|
|
188
|
-
|
|
189
|
-
- `data.schemas.list(options)` or `data.schemas.get(schemaName)` for discovery;
|
|
190
|
-
- `data.schemas.infoByName(schemaName)` for field-level metadata;
|
|
191
|
-
- `data.schemas.info()` for general schema info;
|
|
192
|
-
- `data.schemas.aggregation(options)` for summary views;
|
|
193
|
-
- `data.schemas.getById(id)` when you already have the schema id.
|
|
194
|
-
|
|
195
|
-
Use `data.validations.*` to read field-level validation rules at runtime (e.g. to drive client-side form validation from the same rules the backend enforces):
|
|
196
|
-
|
|
197
|
-
- `data.validations.list(options)` for all rules, with optional schema/field/paging filters;
|
|
198
|
-
- `data.validations.bySchemaId(schemaId)` for every rule on one schema;
|
|
199
|
-
- `data.validations.bySchemaAndField({ schemaId, fieldName })` for one field's rule;
|
|
200
|
-
- `data.validations.getById(id)` when you already have the validation id.
|
|
201
|
-
|
|
202
|
-
Validation rules are authored and saved separately via `blocks data validation save` (see the blocks-data-gateway-configuration skill); this SDK namespace only reads them.
|
|
203
|
-
|
|
204
|
-
## Gotchas
|
|
205
|
-
|
|
206
|
-
- Pass schema name, not collection name: `Product`, not `Products`.
|
|
207
|
-
- Generated query names pluralize by appending `s`: `getProducts`.
|
|
208
|
-
- Generated mutation names stay singular: `insertProduct`, `updateProduct`, `deleteProduct`.
|
|
209
|
-
- Dynamic item selections use schema field names such as `ItemId`, `name`, `price`.
|
|
210
|
-
- Mutation response fields are lower camel case: `acknowledged`, `itemId`, `message`, `totalImpactedData`.
|
|
211
|
-
- If GraphQL says the field does not exist, the schema probably has not been created or reloaded.
|
|
212
|
-
- Keep one `blocksClient` per app.
|
|
213
|
-
|
|
214
|
-
## Prompt Routing
|
|
215
|
-
|
|
216
|
-
```text
|
|
217
|
-
Wire create/read/update/delete for Product into my React app -> this skill
|
|
218
|
-
Build a paged table of Orders in the dashboard -> this skill
|
|
219
|
-
I need a GraphQL query that joins Orders with Customer details -> this skill, data.graphql
|
|
220
|
-
Build a dynamic form from my schema's field metadata -> this skill, data.schemas.infoByName
|
|
221
|
-
Create a Product schema with title/price and reload it -> blocks-data-gateway-configuration
|
|
222
|
-
Upload a PDF and get a download link -> blocks-data-storage
|
|
223
|
-
```
|
|
1
|
+
---
|
|
2
|
+
name: blocks-data-gateway-crud
|
|
3
|
+
description: "Implement create/read/update/delete against a SELISE Blocks project's runtime Data Gateway using the @seliseblocks/client SDK. Use data.collection(schemaName) for straightforward per-item CRUD, data.graphql() for joins or custom query shapes, and data.schemas.*/data.validations.* for schema/validation metadata. Shows how to wire CRUD into the React 18 + Vite + TanStack Query app that blocks new web scaffolds. Use whenever the user wants to read or write actual records through a Blocks Data schema from app code."
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Blocks Data - Gateway CRUD
|
|
7
|
+
|
|
8
|
+
Once a schema exists and has been reloaded via the blocks-data-gateway-configuration skill, the Data Gateway exposes runtime records through GraphQL. This skill shows how to use the generated app's shared `@seliseblocks/client` instance for CRUD. Do not use raw `fetch` or `curl` against Blocks APIs from app code.
|
|
9
|
+
|
|
10
|
+
Prerequisite: a project selected via `blocks use` and an app scaffolded with `blocks new web <name> ...`. If either is missing, run the blocks-onboarding skill first.
|
|
11
|
+
|
|
12
|
+
## Use the Existing Client
|
|
13
|
+
|
|
14
|
+
The scaffold creates one `createBlocksClient()` call in `src/lib/blocks/client.ts`:
|
|
15
|
+
|
|
16
|
+
```ts
|
|
17
|
+
import { createBlocksClient } from "@seliseblocks/client";
|
|
18
|
+
import { blocksConfig } from "./config";
|
|
19
|
+
import { getValidAccessToken } from "./auth";
|
|
20
|
+
|
|
21
|
+
export const blocksClient = createBlocksClient({
|
|
22
|
+
accessToken: () => getValidAccessToken(),
|
|
23
|
+
apiUrl: blocksConfig.apiUrl,
|
|
24
|
+
appDomain: blocksConfig.appDomain,
|
|
25
|
+
oidc: { clientId: blocksConfig.oidcClientId, scope: blocksConfig.oidcScope, url: blocksConfig.oidcUrl },
|
|
26
|
+
xBlocksKey: blocksConfig.xBlocksKey
|
|
27
|
+
});
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
Import that existing instance from feature code. Do not create a second client.
|
|
31
|
+
|
|
32
|
+
- `xBlocksKey` is the project's public tenant key from `VITE_BLOCKS_X_BLOCKS_KEY`.
|
|
33
|
+
- The SDK does not add `ProjectKey` to runtime Data calls.
|
|
34
|
+
- The HTTP client sends `credentials: "include"` so hosted-login session cookies are included.
|
|
35
|
+
- Never use the CLI's impersonation token or project token in browser code.
|
|
36
|
+
|
|
37
|
+
## Preferred CRUD Helper
|
|
38
|
+
|
|
39
|
+
Use `blocksClient.data.collection<T>(schemaName, options)` for normal CRUD. Pass the schema name, not the collection name. For a schema named `Product` with collection `Products`, pass `"Product"`.
|
|
40
|
+
|
|
41
|
+
```ts
|
|
42
|
+
const products = blocksClient.data.collection<Product>("Product", {
|
|
43
|
+
fields: ["name", "price", "status"]
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
products.list({ pageNo: 1, pageSize: 20 });
|
|
47
|
+
products.get(itemId);
|
|
48
|
+
products.create(payload);
|
|
49
|
+
products.update(itemId, payload);
|
|
50
|
+
products.delete(itemId);
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
What the helper does:
|
|
54
|
+
|
|
55
|
+
- `list` and `get` call `query getProducts($input: DynamicQueryInput)`.
|
|
56
|
+
- `create` calls `mutation insertProduct($input: ProductInsertInput!)`.
|
|
57
|
+
- `update` calls `mutation updateProduct($filter: String, $input: ProductUpdateInput!)`.
|
|
58
|
+
- `delete` calls `mutation deleteProduct($filter: String, $input: ProductDeleteInput!)`.
|
|
59
|
+
- `get`, `update`, and `delete` address records by `ItemId`.
|
|
60
|
+
- `fields` controls the GraphQL selection for returned items; `ItemId` is always selected.
|
|
61
|
+
|
|
62
|
+
`list` accepts paging plus optional GraphQL dynamic input values:
|
|
63
|
+
|
|
64
|
+
```ts
|
|
65
|
+
await products.list({
|
|
66
|
+
pageNo: 1,
|
|
67
|
+
pageSize: 20,
|
|
68
|
+
filter: { status: "Active" },
|
|
69
|
+
sort: { name: 1 }
|
|
70
|
+
});
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
For search, only send filters that the schema/runtime actually supports. If unsure, start with simple paging and client-side filtering, or inspect the schema first.
|
|
74
|
+
|
|
75
|
+
## Product Example
|
|
76
|
+
|
|
77
|
+
```ts
|
|
78
|
+
// src/features/products/productsApi.ts
|
|
79
|
+
import { blocksClient } from "../../lib/blocks/client";
|
|
80
|
+
|
|
81
|
+
export type Product = Record<string, unknown> & {
|
|
82
|
+
itemId?: string;
|
|
83
|
+
id?: string;
|
|
84
|
+
name?: string;
|
|
85
|
+
price?: number;
|
|
86
|
+
status?: string;
|
|
87
|
+
};
|
|
88
|
+
|
|
89
|
+
export type ProductInput = { name: string; price: number; status: string };
|
|
90
|
+
|
|
91
|
+
const products = blocksClient.data.collection<Product>("Product", {
|
|
92
|
+
fields: ["name", "price", "status"]
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
export async function listProducts({ page, pageSize }: { page: number; pageSize: number }) {
|
|
96
|
+
const response = await products.list({ pageNo: page, pageSize });
|
|
97
|
+
return normalizeProductList(response);
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
export function createProduct(input: ProductInput) {
|
|
101
|
+
return products.create(input);
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
export function updateProduct(product: Product) {
|
|
105
|
+
return products.update(productId(product), product);
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
export function deleteProduct(product: Product) {
|
|
109
|
+
return products.delete(productId(product));
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
function productId(product: Product): string {
|
|
113
|
+
const id = product.itemId ?? product.id;
|
|
114
|
+
if (!id) throw new Error("Product item id is missing.");
|
|
115
|
+
return String(id);
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
function normalizeProductList(response: unknown): { items: Product[]; totalCount: number } {
|
|
119
|
+
const record = response as {
|
|
120
|
+
data?: {
|
|
121
|
+
getProducts?: { items?: Product[]; totalCount?: number };
|
|
122
|
+
items?: Product[];
|
|
123
|
+
totalCount?: number;
|
|
124
|
+
};
|
|
125
|
+
items?: Product[];
|
|
126
|
+
totalCount?: number;
|
|
127
|
+
};
|
|
128
|
+
const gateway = record.data?.getProducts;
|
|
129
|
+
const items = gateway?.items ?? record.data?.items ?? record.items ?? [];
|
|
130
|
+
return { items, totalCount: gateway?.totalCount ?? record.data?.totalCount ?? record.totalCount ?? items.length };
|
|
131
|
+
}
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
```ts
|
|
135
|
+
// src/features/products/useProducts.ts
|
|
136
|
+
import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
|
|
137
|
+
import { createProduct, deleteProduct, listProducts, updateProduct } from "./productsApi";
|
|
138
|
+
import type { Product, ProductInput } from "./productsApi";
|
|
139
|
+
|
|
140
|
+
export function useProductsQuery(page: number, pageSize: number) {
|
|
141
|
+
return useQuery({
|
|
142
|
+
queryKey: ["products", page, pageSize],
|
|
143
|
+
queryFn: () => listProducts({ page, pageSize })
|
|
144
|
+
});
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
export function useProductMutations() {
|
|
148
|
+
const queryClient = useQueryClient();
|
|
149
|
+
const invalidate = () => queryClient.invalidateQueries({ queryKey: ["products"] });
|
|
150
|
+
|
|
151
|
+
return {
|
|
152
|
+
create: useMutation({ mutationFn: (input: ProductInput) => createProduct(input), onSuccess: invalidate }),
|
|
153
|
+
update: useMutation({ mutationFn: (product: Product) => updateProduct(product), onSuccess: invalidate }),
|
|
154
|
+
remove: useMutation({ mutationFn: (product: Product) => deleteProduct(product), onSuccess: invalidate })
|
|
155
|
+
};
|
|
156
|
+
}
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
## When to Use `data.graphql()`
|
|
160
|
+
|
|
161
|
+
Use `data.graphql({ query, variables, operationName })` when the collection helper is too small:
|
|
162
|
+
|
|
163
|
+
- joins across schemas;
|
|
164
|
+
- custom nested selections;
|
|
165
|
+
- generated `where`, `order`, or `paging` arguments;
|
|
166
|
+
- bulk operations such as `insertManyProduct`.
|
|
167
|
+
|
|
168
|
+
Example:
|
|
169
|
+
|
|
170
|
+
```ts
|
|
171
|
+
const result = await blocksClient.data.graphql({
|
|
172
|
+
operationName: "getProducts",
|
|
173
|
+
query: `
|
|
174
|
+
query getProducts($input: DynamicQueryInput) {
|
|
175
|
+
getProducts(input: $input) {
|
|
176
|
+
items { ItemId name price status }
|
|
177
|
+
totalCount
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
`,
|
|
181
|
+
variables: { input: { pageNo: 1, pageSize: 20 } }
|
|
182
|
+
});
|
|
183
|
+
```
|
|
184
|
+
|
|
185
|
+
## Schema Metadata
|
|
186
|
+
|
|
187
|
+
Use `data.schemas.*` when building dynamic UI:
|
|
188
|
+
|
|
189
|
+
- `data.schemas.list(options)` or `data.schemas.get(schemaName)` for discovery;
|
|
190
|
+
- `data.schemas.infoByName(schemaName)` for field-level metadata;
|
|
191
|
+
- `data.schemas.info()` for general schema info;
|
|
192
|
+
- `data.schemas.aggregation(options)` for summary views;
|
|
193
|
+
- `data.schemas.getById(id)` when you already have the schema id.
|
|
194
|
+
|
|
195
|
+
Use `data.validations.*` to read field-level validation rules at runtime (e.g. to drive client-side form validation from the same rules the backend enforces):
|
|
196
|
+
|
|
197
|
+
- `data.validations.list(options)` for all rules, with optional schema/field/paging filters;
|
|
198
|
+
- `data.validations.bySchemaId(schemaId)` for every rule on one schema;
|
|
199
|
+
- `data.validations.bySchemaAndField({ schemaId, fieldName })` for one field's rule;
|
|
200
|
+
- `data.validations.getById(id)` when you already have the validation id.
|
|
201
|
+
|
|
202
|
+
Validation rules are authored and saved separately via `blocks data validation save` (see the blocks-data-gateway-configuration skill); this SDK namespace only reads them.
|
|
203
|
+
|
|
204
|
+
## Gotchas
|
|
205
|
+
|
|
206
|
+
- Pass schema name, not collection name: `Product`, not `Products`.
|
|
207
|
+
- Generated query names pluralize by appending `s`: `getProducts`.
|
|
208
|
+
- Generated mutation names stay singular: `insertProduct`, `updateProduct`, `deleteProduct`.
|
|
209
|
+
- Dynamic item selections use schema field names such as `ItemId`, `name`, `price`.
|
|
210
|
+
- Mutation response fields are lower camel case: `acknowledged`, `itemId`, `message`, `totalImpactedData`.
|
|
211
|
+
- If GraphQL says the field does not exist, the schema probably has not been created or reloaded.
|
|
212
|
+
- Keep one `blocksClient` per app.
|
|
213
|
+
|
|
214
|
+
## Prompt Routing
|
|
215
|
+
|
|
216
|
+
```text
|
|
217
|
+
Wire create/read/update/delete for Product into my React app -> this skill
|
|
218
|
+
Build a paged table of Orders in the dashboard -> this skill
|
|
219
|
+
I need a GraphQL query that joins Orders with Customer details -> this skill, data.graphql
|
|
220
|
+
Build a dynamic form from my schema's field metadata -> this skill, data.schemas.infoByName
|
|
221
|
+
Create a Product schema with title/price and reload it -> blocks-data-gateway-configuration
|
|
222
|
+
Upload a PDF and get a download link -> blocks-data-storage
|
|
223
|
+
```
|