@craftware/crafty-sdk 0.0.0 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +106 -24
- package/dist/client.d.ts +2 -0
- package/dist/client.d.ts.map +1 -1
- package/dist/client.js +2 -0
- package/dist/client.js.map +1 -1
- package/dist/http.d.ts +8 -0
- package/dist/http.d.ts.map +1 -1
- package/dist/http.js +27 -3
- package/dist/http.js.map +1 -1
- package/dist/resources/forms.d.ts +10 -0
- package/dist/resources/forms.d.ts.map +1 -0
- package/dist/resources/forms.js +24 -0
- package/dist/resources/forms.js.map +1 -0
- package/dist/resources/pages.d.ts +3 -2
- package/dist/resources/pages.d.ts.map +1 -1
- package/dist/resources/pages.js +35 -4
- package/dist/resources/pages.js.map +1 -1
- package/dist/types/public.d.ts +47 -3
- package/dist/types/public.d.ts.map +1 -1
- package/package.json +3 -2
- package/skills/crafty-cms/SKILL.md +111 -0
- package/skills/crafty-cms/references/mcp-configuration.md +183 -0
- package/skills/crafty-cms/references/sdk-development.md +198 -0
package/README.md
CHANGED
|
@@ -2,6 +2,18 @@
|
|
|
2
2
|
|
|
3
3
|
Official JavaScript SDK for the Crafty public API (current local implementation).
|
|
4
4
|
|
|
5
|
+
## AI agent skill
|
|
6
|
+
|
|
7
|
+
This package ships an agent **skill** that teaches Claude, Cursor, and other agents how to configure a Crafty site through its MCP server and build against it with this SDK. It is included in the published package under `skills/crafty-cms/`.
|
|
8
|
+
|
|
9
|
+
To activate it, copy it into your project's skills directory after installing the SDK:
|
|
10
|
+
|
|
11
|
+
```bash
|
|
12
|
+
cp -r node_modules/@craftware/crafty-sdk/skills/crafty-cms .claude/skills/
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
(Use the equivalent skills directory for your agent, e.g. `.cursor/skills/`.) The skill is self-contained — it includes `SKILL.md` plus `references/mcp-configuration.md` and `references/sdk-development.md`.
|
|
16
|
+
|
|
5
17
|
## Requirements
|
|
6
18
|
|
|
7
19
|
- Node `>=20` (package engine)
|
|
@@ -13,8 +25,8 @@ Official JavaScript SDK for the Crafty public API (current local implementation)
|
|
|
13
25
|
import { createCraftyClient } from "@craftware/crafty-sdk";
|
|
14
26
|
|
|
15
27
|
const crafty = createCraftyClient({
|
|
16
|
-
baseUrl: "https://tenant.crafty.
|
|
17
|
-
apiKey: "
|
|
28
|
+
baseUrl: "https://tenant.crafty.do",
|
|
29
|
+
apiKey: "ENVIRONMENT_API_TOKEN",
|
|
18
30
|
timeoutMs: 10_000
|
|
19
31
|
});
|
|
20
32
|
```
|
|
@@ -22,10 +34,13 @@ const crafty = createCraftyClient({
|
|
|
22
34
|
### Client config
|
|
23
35
|
|
|
24
36
|
- `baseUrl` (required): tenant base URL. The SDK appends `/api/v1` automatically if missing.
|
|
25
|
-
- `apiKey` (optional):
|
|
37
|
+
- `apiKey` (optional): environment API token sent as `X-API-Key`.
|
|
38
|
+
- When provided, the API resolves the active site from this token.
|
|
39
|
+
- `pages.*` requests use this site context automatically (you do not pass `siteSlug`).
|
|
40
|
+
- `siteSlug` (optional): site slug used for public form submissions (`forms.submit()`).
|
|
26
41
|
- `fetch` (optional): custom fetch implementation.
|
|
27
42
|
- `headers` (optional): default headers applied to all requests.
|
|
28
|
-
- `timeoutMs` (optional): default timeout for all requests.
|
|
43
|
+
- `timeoutMs` (optional): default timeout (ms) for all requests. A timeout is only applied when the value is a number greater than `0`.
|
|
29
44
|
|
|
30
45
|
## Available resources
|
|
31
46
|
|
|
@@ -40,7 +55,7 @@ const crafty = createCraftyClient({
|
|
|
40
55
|
- `crafty.entries.get(collectionSlug, id, options?)`
|
|
41
56
|
|
|
42
57
|
Supported common `query` params:
|
|
43
|
-
- `sort_by` (
|
|
58
|
+
- `sort_by` (any sortable field, including collection fields like `title`)
|
|
44
59
|
- `sort_order` (`asc` | `desc`)
|
|
45
60
|
- `per_page`
|
|
46
61
|
- `page`
|
|
@@ -48,10 +63,51 @@ Supported common `query` params:
|
|
|
48
63
|
|
|
49
64
|
### `pages`
|
|
50
65
|
|
|
51
|
-
- `crafty.pages.list(
|
|
52
|
-
- `crafty.pages.get(
|
|
66
|
+
- `crafty.pages.list(query?, options?)`
|
|
67
|
+
- `crafty.pages.get(pageSlug, options?)`
|
|
68
|
+
|
|
69
|
+
`pages.*` endpoints are scoped by the environment resolved from `apiKey`.
|
|
70
|
+
|
|
71
|
+
`pages.get()` returns a page response enriched with block helpers:
|
|
53
72
|
|
|
54
|
-
`
|
|
73
|
+
- `page.data` => the raw `CraftyPage`
|
|
74
|
+
- `page.blocks` => all blocks (`CraftyBlock[]`; a non-array `data.blocks` is coerced to `[]`)
|
|
75
|
+
- `page.block(type)` => first block of that type or `null`
|
|
76
|
+
- `page.blocksOf(type)` => all blocks of that type (`[]` if none)
|
|
77
|
+
- `page.hasBlock(type)` => boolean
|
|
78
|
+
- `page.requireBlock(type)` => first block, or throws a plain `Error` (not a `CraftyError`)
|
|
79
|
+
- `page.byType.<type>` => array of blocks for that type
|
|
80
|
+
- `page.firstByType.<type>` => first block for that type or `null`
|
|
81
|
+
|
|
82
|
+
### `forms`
|
|
83
|
+
|
|
84
|
+
- `crafty.forms.list(options?)` — list all forms (requires API key)
|
|
85
|
+
- `crafty.forms.get(slug, options?)` — get a single form by slug (requires API key)
|
|
86
|
+
- `crafty.forms.submit(formSlug, data, options?)` — submit a public form (no API key needed)
|
|
87
|
+
|
|
88
|
+
The `submit()` method uses a public endpoint that does not require authentication. You must provide `siteSlug` in the client config for submissions to work — calling `submit()` without it throws a synchronous `CraftyError` with code `BAD_REQUEST`:
|
|
89
|
+
|
|
90
|
+
```ts
|
|
91
|
+
const crafty = createCraftyClient({
|
|
92
|
+
baseUrl: "https://tenant.crafty.do",
|
|
93
|
+
apiKey: "ENVIRONMENT_API_TOKEN",
|
|
94
|
+
siteSlug: "my-site",
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
// List forms (authenticated)
|
|
98
|
+
const forms = await crafty.forms.list();
|
|
99
|
+
|
|
100
|
+
// Get form details (authenticated)
|
|
101
|
+
const contactForm = await crafty.forms.get("contact");
|
|
102
|
+
|
|
103
|
+
// Submit a form (public, no API key sent)
|
|
104
|
+
const result = await crafty.forms.submit("contact", {
|
|
105
|
+
name: "Jane Doe",
|
|
106
|
+
email: "jane@example.com",
|
|
107
|
+
message: "Hello!",
|
|
108
|
+
});
|
|
109
|
+
// result => { id, form_id, submitted_at }
|
|
110
|
+
```
|
|
55
111
|
|
|
56
112
|
### `mediaCollections`
|
|
57
113
|
|
|
@@ -60,7 +116,7 @@ Supported common `query` params:
|
|
|
60
116
|
Supported `query.include` values:
|
|
61
117
|
- `"stats"`
|
|
62
118
|
- `"directories"`
|
|
63
|
-
- Array form is supported and serialized as comma-separated values
|
|
119
|
+
- Array form is supported, de-duplicated, and serialized as comma-separated values; a raw string is passed through unchanged
|
|
64
120
|
|
|
65
121
|
### `mediaFiles`
|
|
66
122
|
|
|
@@ -96,19 +152,22 @@ const posts = await crafty.entries.list(
|
|
|
96
152
|
- `entries.list()` => paginated `{ data, links, meta }`
|
|
97
153
|
- `entries.get()` => `{ data: CraftyEntry }`
|
|
98
154
|
- `pages.list()` => paginated `{ data, links, meta }`
|
|
99
|
-
- `pages.get()` => `{ data: CraftyPage }`
|
|
155
|
+
- `pages.get()` => `{ data: CraftyPage }` plus the block helpers listed above
|
|
100
156
|
- `mediaCollections.list()` => `{ data, meta: { includes } }`
|
|
101
157
|
- `mediaFiles.list()` => `{ data, links, meta }` (`meta` includes `ttl_minutes`)
|
|
158
|
+
- `forms.list()` => `{ data: CraftyForm[] }`
|
|
159
|
+
- `forms.get()` => `{ data: CraftyForm }`
|
|
160
|
+
- `forms.submit()` => `{ id, form_id, submitted_at }`
|
|
102
161
|
|
|
103
162
|
## Errors
|
|
104
163
|
|
|
105
|
-
The SDK throws `CraftyError` for HTTP, timeout, abort, and
|
|
164
|
+
The SDK throws `CraftyError` for HTTP, timeout, abort, network, and configuration failures (e.g. no `fetch` implementation available at construction).
|
|
106
165
|
|
|
107
166
|
```ts
|
|
108
167
|
import { CraftyError } from "@craftware/crafty-sdk";
|
|
109
168
|
|
|
110
169
|
try {
|
|
111
|
-
await crafty.pages.get("
|
|
170
|
+
await crafty.pages.get("home");
|
|
112
171
|
} catch (error) {
|
|
113
172
|
if (error instanceof CraftyError) {
|
|
114
173
|
console.error(error.code, error.status, error.message);
|
|
@@ -117,17 +176,17 @@ try {
|
|
|
117
176
|
}
|
|
118
177
|
```
|
|
119
178
|
|
|
120
|
-
Current error codes:
|
|
179
|
+
Current error codes (with their HTTP-status mapping):
|
|
121
180
|
|
|
122
|
-
- `NETWORK_ERROR`
|
|
123
|
-
- `TIMEOUT`
|
|
124
|
-
- `BAD_REQUEST`
|
|
125
|
-
- `UNAUTHORIZED`
|
|
126
|
-
- `FORBIDDEN`
|
|
127
|
-
- `NOT_FOUND`
|
|
128
|
-
- `RATE_LIMITED`
|
|
129
|
-
- `SERVER_ERROR`
|
|
130
|
-
- `UNKNOWN_ERROR`
|
|
181
|
+
- `NETWORK_ERROR` — network failure, or no `fetch` available at construction
|
|
182
|
+
- `TIMEOUT` — request exceeded `timeoutMs`
|
|
183
|
+
- `BAD_REQUEST` — HTTP 400 or 422
|
|
184
|
+
- `UNAUTHORIZED` — HTTP 401
|
|
185
|
+
- `FORBIDDEN` — HTTP 403
|
|
186
|
+
- `NOT_FOUND` — HTTP 404
|
|
187
|
+
- `RATE_LIMITED` — HTTP 429
|
|
188
|
+
- `SERVER_ERROR` — HTTP 5xx
|
|
189
|
+
- `UNKNOWN_ERROR` — any other / unmapped status
|
|
131
190
|
|
|
132
191
|
## End-to-end example
|
|
133
192
|
|
|
@@ -135,8 +194,9 @@ Current error codes:
|
|
|
135
194
|
import { createCraftyClient } from "@craftware/crafty-sdk";
|
|
136
195
|
|
|
137
196
|
const crafty = createCraftyClient({
|
|
138
|
-
baseUrl: "https://
|
|
197
|
+
baseUrl: "https://tenant.crafty.do",
|
|
139
198
|
apiKey: process.env.CRAFTY_API_KEY,
|
|
199
|
+
siteSlug: "my-site",
|
|
140
200
|
timeoutMs: 10_000
|
|
141
201
|
});
|
|
142
202
|
|
|
@@ -144,7 +204,29 @@ const collections = await crafty.collections.list();
|
|
|
144
204
|
const posts = await crafty.entries.list("posts", { per_page: 15 });
|
|
145
205
|
const mediaCollections = await crafty.mediaCollections.list({ include: ["stats", "directories"] });
|
|
146
206
|
const mediaFiles = await crafty.mediaFiles.list({ collection: "library", per_page: 24 });
|
|
147
|
-
const homePage = await crafty.pages.get("
|
|
207
|
+
const homePage = await crafty.pages.get("home");
|
|
208
|
+
const forms = await crafty.forms.list();
|
|
209
|
+
const submission = await crafty.forms.submit("contact", { name: "Jane", email: "jane@test.com" });
|
|
210
|
+
```
|
|
211
|
+
|
|
212
|
+
## Pages scoping (site+environment via API key)
|
|
213
|
+
|
|
214
|
+
`pages` requests no longer require a `siteSlug` parameter. The site is inferred server-side from the
|
|
215
|
+
`X-API-Key` header (set from `apiKey` in `createCraftyClient()`).
|
|
216
|
+
|
|
217
|
+
```ts
|
|
218
|
+
const crafty = createCraftyClient({
|
|
219
|
+
baseUrl: "https://tenant.crafty.do",
|
|
220
|
+
apiKey: "ENVIRONMENT_API_TOKEN",
|
|
221
|
+
});
|
|
222
|
+
|
|
223
|
+
await crafty.pages.list({ per_page: 12 });
|
|
224
|
+
|
|
225
|
+
const landing = await crafty.pages.get("landing");
|
|
226
|
+
const processBlock = landing.block("process");
|
|
227
|
+
const allProcessBlocks = landing.blocksOf("process");
|
|
228
|
+
const processFirst = landing.firstByType.process;
|
|
229
|
+
const processList = landing.byType.process ?? [];
|
|
148
230
|
```
|
|
149
231
|
|
|
150
232
|
## Notes
|
package/dist/client.d.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { CollectionsResource } from "./resources/collections";
|
|
2
2
|
import { EntriesResource } from "./resources/entries";
|
|
3
|
+
import { FormsResource } from "./resources/forms";
|
|
3
4
|
import { MediaCollectionsResource } from "./resources/mediaCollections";
|
|
4
5
|
import { MediaFilesResource } from "./resources/mediaFiles";
|
|
5
6
|
import { PagesResource } from "./resources/pages";
|
|
@@ -7,6 +8,7 @@ import type { CraftyClientConfig } from "./types/public";
|
|
|
7
8
|
export type CraftyClient = {
|
|
8
9
|
collections: CollectionsResource;
|
|
9
10
|
entries: EntriesResource;
|
|
11
|
+
forms: FormsResource;
|
|
10
12
|
mediaCollections: MediaCollectionsResource;
|
|
11
13
|
mediaFiles: MediaFilesResource;
|
|
12
14
|
pages: PagesResource;
|
package/dist/client.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../src/client.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,mBAAmB,EAAE,MAAM,yBAAyB,CAAC;AAC9D,OAAO,EAAE,eAAe,EAAE,MAAM,qBAAqB,CAAC;AACtD,OAAO,EAAE,wBAAwB,EAAE,MAAM,8BAA8B,CAAC;AACxE,OAAO,EAAE,kBAAkB,EAAE,MAAM,wBAAwB,CAAC;AAC5D,OAAO,EAAE,aAAa,EAAE,MAAM,mBAAmB,CAAC;AAClD,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,gBAAgB,CAAC;AAEzD,MAAM,MAAM,YAAY,GAAG;IACzB,WAAW,EAAE,mBAAmB,CAAC;IACjC,OAAO,EAAE,eAAe,CAAC;IACzB,gBAAgB,EAAE,wBAAwB,CAAC;IAC3C,UAAU,EAAE,kBAAkB,CAAC;IAC/B,KAAK,EAAE,aAAa,CAAC;CACtB,CAAC;AAEF,wBAAgB,kBAAkB,CAAC,MAAM,EAAE,kBAAkB,GAAG,YAAY,
|
|
1
|
+
{"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../src/client.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,mBAAmB,EAAE,MAAM,yBAAyB,CAAC;AAC9D,OAAO,EAAE,eAAe,EAAE,MAAM,qBAAqB,CAAC;AACtD,OAAO,EAAE,aAAa,EAAE,MAAM,mBAAmB,CAAC;AAClD,OAAO,EAAE,wBAAwB,EAAE,MAAM,8BAA8B,CAAC;AACxE,OAAO,EAAE,kBAAkB,EAAE,MAAM,wBAAwB,CAAC;AAC5D,OAAO,EAAE,aAAa,EAAE,MAAM,mBAAmB,CAAC;AAClD,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,gBAAgB,CAAC;AAEzD,MAAM,MAAM,YAAY,GAAG;IACzB,WAAW,EAAE,mBAAmB,CAAC;IACjC,OAAO,EAAE,eAAe,CAAC;IACzB,KAAK,EAAE,aAAa,CAAC;IACrB,gBAAgB,EAAE,wBAAwB,CAAC;IAC3C,UAAU,EAAE,kBAAkB,CAAC;IAC/B,KAAK,EAAE,aAAa,CAAC;CACtB,CAAC;AAEF,wBAAgB,kBAAkB,CAAC,MAAM,EAAE,kBAAkB,GAAG,YAAY,CAW3E"}
|
package/dist/client.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { HttpClient } from "./http";
|
|
2
2
|
import { CollectionsResource } from "./resources/collections";
|
|
3
3
|
import { EntriesResource } from "./resources/entries";
|
|
4
|
+
import { FormsResource } from "./resources/forms";
|
|
4
5
|
import { MediaCollectionsResource } from "./resources/mediaCollections";
|
|
5
6
|
import { MediaFilesResource } from "./resources/mediaFiles";
|
|
6
7
|
import { PagesResource } from "./resources/pages";
|
|
@@ -9,6 +10,7 @@ export function createCraftyClient(config) {
|
|
|
9
10
|
return {
|
|
10
11
|
collections: new CollectionsResource(http),
|
|
11
12
|
entries: new EntriesResource(http),
|
|
13
|
+
forms: new FormsResource(http),
|
|
12
14
|
mediaCollections: new MediaCollectionsResource(http),
|
|
13
15
|
mediaFiles: new MediaFilesResource(http),
|
|
14
16
|
pages: new PagesResource(http)
|
package/dist/client.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"client.js","sourceRoot":"","sources":["../src/client.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,QAAQ,CAAC;AACpC,OAAO,EAAE,mBAAmB,EAAE,MAAM,yBAAyB,CAAC;AAC9D,OAAO,EAAE,eAAe,EAAE,MAAM,qBAAqB,CAAC;AACtD,OAAO,EAAE,wBAAwB,EAAE,MAAM,8BAA8B,CAAC;AACxE,OAAO,EAAE,kBAAkB,EAAE,MAAM,wBAAwB,CAAC;AAC5D,OAAO,EAAE,aAAa,EAAE,MAAM,mBAAmB,CAAC;
|
|
1
|
+
{"version":3,"file":"client.js","sourceRoot":"","sources":["../src/client.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,QAAQ,CAAC;AACpC,OAAO,EAAE,mBAAmB,EAAE,MAAM,yBAAyB,CAAC;AAC9D,OAAO,EAAE,eAAe,EAAE,MAAM,qBAAqB,CAAC;AACtD,OAAO,EAAE,aAAa,EAAE,MAAM,mBAAmB,CAAC;AAClD,OAAO,EAAE,wBAAwB,EAAE,MAAM,8BAA8B,CAAC;AACxE,OAAO,EAAE,kBAAkB,EAAE,MAAM,wBAAwB,CAAC;AAC5D,OAAO,EAAE,aAAa,EAAE,MAAM,mBAAmB,CAAC;AAYlD,MAAM,UAAU,kBAAkB,CAAC,MAA0B;IAC3D,MAAM,IAAI,GAAG,UAAU,CAAC,gBAAgB,CAAC,MAAM,CAAC,CAAC;IAEjD,OAAO;QACL,WAAW,EAAE,IAAI,mBAAmB,CAAC,IAAI,CAAC;QAC1C,OAAO,EAAE,IAAI,eAAe,CAAC,IAAI,CAAC;QAClC,KAAK,EAAE,IAAI,aAAa,CAAC,IAAI,CAAC;QAC9B,gBAAgB,EAAE,IAAI,wBAAwB,CAAC,IAAI,CAAC;QACpD,UAAU,EAAE,IAAI,kBAAkB,CAAC,IAAI,CAAC;QACxC,KAAK,EAAE,IAAI,aAAa,CAAC,IAAI,CAAC;KAC/B,CAAC;AACJ,CAAC"}
|
package/dist/http.d.ts
CHANGED
|
@@ -9,6 +9,7 @@ type RequiredTransportConfig = {
|
|
|
9
9
|
fetchImpl: typeof fetch;
|
|
10
10
|
defaultHeaders: HeadersInit | undefined;
|
|
11
11
|
apiKey: string | undefined;
|
|
12
|
+
siteSlug: string | undefined;
|
|
12
13
|
timeoutMs: number | undefined;
|
|
13
14
|
};
|
|
14
15
|
export declare class HttpClient {
|
|
@@ -16,12 +17,19 @@ export declare class HttpClient {
|
|
|
16
17
|
private readonly fetchImpl;
|
|
17
18
|
private readonly defaultHeaders;
|
|
18
19
|
private readonly apiKey;
|
|
20
|
+
private readonly siteSlug;
|
|
19
21
|
private readonly defaultTimeoutMs;
|
|
20
22
|
constructor(config: RequiredTransportConfig);
|
|
21
23
|
static fromClientConfig(config: CraftyClientConfig): HttpClient;
|
|
24
|
+
getSiteSlug(): string | undefined;
|
|
22
25
|
get<T>(path: string, options?: HttpRequestOptions): Promise<T>;
|
|
26
|
+
post<T>(path: string, body: unknown, options?: HttpRequestOptions & {
|
|
27
|
+
skipApiKey?: boolean;
|
|
28
|
+
}): Promise<T>;
|
|
23
29
|
request<T>(path: string, options: HttpRequestOptions & {
|
|
24
30
|
method: "GET" | "POST" | "PUT" | "PATCH" | "DELETE";
|
|
31
|
+
body?: unknown;
|
|
32
|
+
skipApiKey?: boolean;
|
|
25
33
|
}): Promise<T>;
|
|
26
34
|
private buildUrl;
|
|
27
35
|
}
|
package/dist/http.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"http.d.ts","sourceRoot":"","sources":["../src/http.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,kBAAkB,EAAc,cAAc,EAAE,MAAM,gBAAgB,CAAC;AAErF,KAAK,UAAU,GAAG,MAAM,GAAG,MAAM,GAAG,OAAO,GAAG,IAAI,GAAG,SAAS,CAAC;AAC/D,KAAK,WAAW,GAAG,MAAM,CAAC,MAAM,EAAE,UAAU,CAAC,CAAC;AAE9C,KAAK,kBAAkB,GAAG,cAAc,GAAG;IACzC,KAAK,CAAC,EAAE,WAAW,CAAC;CACrB,CAAC;AAEF,KAAK,uBAAuB,GAAG;IAC7B,UAAU,EAAE,MAAM,CAAC;IACnB,SAAS,EAAE,OAAO,KAAK,CAAC;IACxB,cAAc,EAAE,WAAW,GAAG,SAAS,CAAC;IACxC,MAAM,EAAE,MAAM,GAAG,SAAS,CAAC;IAC3B,SAAS,EAAE,MAAM,GAAG,SAAS,CAAC;CAC/B,CAAC;AAEF,qBAAa,UAAU;IACrB,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAS;IACpC,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAe;IACzC,OAAO,CAAC,QAAQ,CAAC,cAAc,CAA0B;IACzD,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAqB;IAC5C,OAAO,CAAC,QAAQ,CAAC,gBAAgB,CAAqB;gBAEnC,MAAM,EAAE,uBAAuB;
|
|
1
|
+
{"version":3,"file":"http.d.ts","sourceRoot":"","sources":["../src/http.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,kBAAkB,EAAc,cAAc,EAAE,MAAM,gBAAgB,CAAC;AAErF,KAAK,UAAU,GAAG,MAAM,GAAG,MAAM,GAAG,OAAO,GAAG,IAAI,GAAG,SAAS,CAAC;AAC/D,KAAK,WAAW,GAAG,MAAM,CAAC,MAAM,EAAE,UAAU,CAAC,CAAC;AAE9C,KAAK,kBAAkB,GAAG,cAAc,GAAG;IACzC,KAAK,CAAC,EAAE,WAAW,CAAC;CACrB,CAAC;AAEF,KAAK,uBAAuB,GAAG;IAC7B,UAAU,EAAE,MAAM,CAAC;IACnB,SAAS,EAAE,OAAO,KAAK,CAAC;IACxB,cAAc,EAAE,WAAW,GAAG,SAAS,CAAC;IACxC,MAAM,EAAE,MAAM,GAAG,SAAS,CAAC;IAC3B,QAAQ,EAAE,MAAM,GAAG,SAAS,CAAC;IAC7B,SAAS,EAAE,MAAM,GAAG,SAAS,CAAC;CAC/B,CAAC;AAEF,qBAAa,UAAU;IACrB,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAS;IACpC,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAe;IACzC,OAAO,CAAC,QAAQ,CAAC,cAAc,CAA0B;IACzD,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAqB;IAC5C,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAqB;IAC9C,OAAO,CAAC,QAAQ,CAAC,gBAAgB,CAAqB;gBAEnC,MAAM,EAAE,uBAAuB;WASpC,gBAAgB,CAAC,MAAM,EAAE,kBAAkB,GAAG,UAAU;IAyB/D,WAAW,IAAI,MAAM,GAAG,SAAS;IAI3B,GAAG,CAAC,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,kBAAkB,GAAG,OAAO,CAAC,CAAC,CAAC;IAI9D,IAAI,CAAC,CAAC,EACjB,IAAI,EAAE,MAAM,EACZ,IAAI,EAAE,OAAO,EACb,OAAO,CAAC,EAAE,kBAAkB,GAAG;QAAE,UAAU,CAAC,EAAE,OAAO,CAAA;KAAE,GACtD,OAAO,CAAC,CAAC,CAAC;IAcA,OAAO,CAAC,CAAC,EACpB,IAAI,EAAE,MAAM,EACZ,OAAO,EAAE,kBAAkB,GAAG;QAC5B,MAAM,EAAE,KAAK,GAAG,MAAM,GAAG,KAAK,GAAG,OAAO,GAAG,QAAQ,CAAC;QACpD,IAAI,CAAC,EAAE,OAAO,CAAC;QACf,UAAU,CAAC,EAAE,OAAO,CAAC;KACtB,GACA,OAAO,CAAC,CAAC,CAAC;IA2Fb,OAAO,CAAC,QAAQ;CAejB"}
|
package/dist/http.js
CHANGED
|
@@ -4,12 +4,14 @@ export class HttpClient {
|
|
|
4
4
|
fetchImpl;
|
|
5
5
|
defaultHeaders;
|
|
6
6
|
apiKey;
|
|
7
|
+
siteSlug;
|
|
7
8
|
defaultTimeoutMs;
|
|
8
9
|
constructor(config) {
|
|
9
10
|
this.apiBaseUrl = config.apiBaseUrl;
|
|
10
11
|
this.fetchImpl = config.fetchImpl;
|
|
11
12
|
this.defaultHeaders = config.defaultHeaders;
|
|
12
13
|
this.apiKey = config.apiKey;
|
|
14
|
+
this.siteSlug = config.siteSlug;
|
|
13
15
|
this.defaultTimeoutMs = config.timeoutMs;
|
|
14
16
|
}
|
|
15
17
|
static fromClientConfig(config) {
|
|
@@ -29,12 +31,27 @@ export class HttpClient {
|
|
|
29
31
|
fetchImpl,
|
|
30
32
|
defaultHeaders: config.headers,
|
|
31
33
|
apiKey: config.apiKey,
|
|
34
|
+
siteSlug: config.siteSlug,
|
|
32
35
|
timeoutMs: config.timeoutMs
|
|
33
36
|
});
|
|
34
37
|
}
|
|
38
|
+
getSiteSlug() {
|
|
39
|
+
return this.siteSlug;
|
|
40
|
+
}
|
|
35
41
|
async get(path, options) {
|
|
36
42
|
return this.request(path, { ...options, method: "GET" });
|
|
37
43
|
}
|
|
44
|
+
async post(path, body, options) {
|
|
45
|
+
const requestOptions = {
|
|
46
|
+
...options,
|
|
47
|
+
method: "POST",
|
|
48
|
+
body
|
|
49
|
+
};
|
|
50
|
+
if (options?.skipApiKey) {
|
|
51
|
+
requestOptions.skipApiKey = true;
|
|
52
|
+
}
|
|
53
|
+
return this.request(path, requestOptions);
|
|
54
|
+
}
|
|
38
55
|
async request(path, options) {
|
|
39
56
|
const url = this.buildUrl(path, options.query);
|
|
40
57
|
const headers = new Headers(this.defaultHeaders);
|
|
@@ -45,9 +62,12 @@ export class HttpClient {
|
|
|
45
62
|
if (!headers.has("Accept")) {
|
|
46
63
|
headers.set("Accept", "application/json");
|
|
47
64
|
}
|
|
48
|
-
if (this.apiKey && !headers.has("X-API-Key")) {
|
|
65
|
+
if (this.apiKey && !headers.has("X-API-Key") && !options.skipApiKey) {
|
|
49
66
|
headers.set("X-API-Key", this.apiKey);
|
|
50
67
|
}
|
|
68
|
+
if (options.body !== undefined && !headers.has("Content-Type")) {
|
|
69
|
+
headers.set("Content-Type", "application/json");
|
|
70
|
+
}
|
|
51
71
|
const timeoutMs = options.timeoutMs ?? this.defaultTimeoutMs;
|
|
52
72
|
const timeoutController = typeof timeoutMs === "number" && timeoutMs > 0
|
|
53
73
|
? new AbortController()
|
|
@@ -58,11 +78,15 @@ export class HttpClient {
|
|
|
58
78
|
timeoutHandle = setTimeout(() => timeoutController.abort("timeout"), timeoutMs);
|
|
59
79
|
}
|
|
60
80
|
try {
|
|
61
|
-
const
|
|
81
|
+
const fetchInit = {
|
|
62
82
|
method: options.method,
|
|
63
83
|
headers,
|
|
64
84
|
signal: signal ?? null
|
|
65
|
-
}
|
|
85
|
+
};
|
|
86
|
+
if (options.body !== undefined) {
|
|
87
|
+
fetchInit.body = JSON.stringify(options.body);
|
|
88
|
+
}
|
|
89
|
+
const response = await this.fetchImpl(url, fetchInit);
|
|
66
90
|
const payload = await parseResponsePayload(response);
|
|
67
91
|
if (!response.ok) {
|
|
68
92
|
const requestId = response.headers.get("x-request-id") ?? undefined;
|
package/dist/http.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"http.js","sourceRoot":"","sources":["../src/http.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,oBAAoB,EAAE,MAAM,UAAU,CAAC;
|
|
1
|
+
{"version":3,"file":"http.js","sourceRoot":"","sources":["../src/http.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { HttpClient } from "../http";
|
|
2
|
+
import type { FormListResponse, FormShowResponse, FormSubmitData, FormSubmitResponse, RequestOptions } from "../types/public";
|
|
3
|
+
export declare class FormsResource {
|
|
4
|
+
private readonly http;
|
|
5
|
+
constructor(http: HttpClient);
|
|
6
|
+
list(options?: RequestOptions): Promise<FormListResponse>;
|
|
7
|
+
get(slug: string, options?: RequestOptions): Promise<FormShowResponse>;
|
|
8
|
+
submit(formSlug: string, data: FormSubmitData, options?: RequestOptions): Promise<FormSubmitResponse>;
|
|
9
|
+
}
|
|
10
|
+
//# sourceMappingURL=forms.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"forms.d.ts","sourceRoot":"","sources":["../../src/resources/forms.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,UAAU,EAAE,MAAM,SAAS,CAAC;AACrC,OAAO,KAAK,EACV,gBAAgB,EAChB,gBAAgB,EAChB,cAAc,EACd,kBAAkB,EAClB,cAAc,EACf,MAAM,iBAAiB,CAAC;AAEzB,qBAAa,aAAa;IACL,OAAO,CAAC,QAAQ,CAAC,IAAI;gBAAJ,IAAI,EAAE,UAAU;IAE7C,IAAI,CAAC,OAAO,CAAC,EAAE,cAAc,GAAG,OAAO,CAAC,gBAAgB,CAAC;IAIzD,GAAG,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,cAAc,GAAG,OAAO,CAAC,gBAAgB,CAAC;IAItE,MAAM,CACX,QAAQ,EAAE,MAAM,EAChB,IAAI,EAAE,cAAc,EACpB,OAAO,CAAC,EAAE,cAAc,GACvB,OAAO,CAAC,kBAAkB,CAAC;CAgB/B"}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { CraftyError } from "../errors";
|
|
2
|
+
export class FormsResource {
|
|
3
|
+
http;
|
|
4
|
+
constructor(http) {
|
|
5
|
+
this.http = http;
|
|
6
|
+
}
|
|
7
|
+
list(options) {
|
|
8
|
+
return this.http.get("/forms", options);
|
|
9
|
+
}
|
|
10
|
+
get(slug, options) {
|
|
11
|
+
return this.http.get(`/forms/${encodeURIComponent(slug)}`, options);
|
|
12
|
+
}
|
|
13
|
+
submit(formSlug, data, options) {
|
|
14
|
+
const siteSlug = this.http.getSiteSlug();
|
|
15
|
+
if (!siteSlug) {
|
|
16
|
+
throw new CraftyError({
|
|
17
|
+
code: "BAD_REQUEST",
|
|
18
|
+
message: "siteSlug is required in client config to submit forms."
|
|
19
|
+
});
|
|
20
|
+
}
|
|
21
|
+
return this.http.post(`/sites/${encodeURIComponent(siteSlug)}/forms/${encodeURIComponent(formSlug)}/submit`, data, { ...options, skipApiKey: true });
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
//# sourceMappingURL=forms.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"forms.js","sourceRoot":"","sources":["../../src/resources/forms.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,MAAM,WAAW,CAAC;AAUxC,MAAM,OAAO,aAAa;IACY;IAApC,YAAoC,IAAgB;QAAhB,SAAI,GAAJ,IAAI,CAAY;IAAG,CAAC;IAEjD,IAAI,CAAC,OAAwB;QAClC,OAAO,IAAI,CAAC,IAAI,CAAC,GAAG,CAAmB,QAAQ,EAAE,OAAO,CAAC,CAAC;IAC5D,CAAC;IAEM,GAAG,CAAC,IAAY,EAAE,OAAwB;QAC/C,OAAO,IAAI,CAAC,IAAI,CAAC,GAAG,CAAmB,UAAU,kBAAkB,CAAC,IAAI,CAAC,EAAE,EAAE,OAAO,CAAC,CAAC;IACxF,CAAC;IAEM,MAAM,CACX,QAAgB,EAChB,IAAoB,EACpB,OAAwB;QAExB,MAAM,QAAQ,GAAG,IAAI,CAAC,IAAI,CAAC,WAAW,EAAE,CAAC;QAEzC,IAAI,CAAC,QAAQ,EAAE,CAAC;YACd,MAAM,IAAI,WAAW,CAAC;gBACpB,IAAI,EAAE,aAAa;gBACnB,OAAO,EAAE,wDAAwD;aAClE,CAAC,CAAC;QACL,CAAC;QAED,OAAO,IAAI,CAAC,IAAI,CAAC,IAAI,CACnB,UAAU,kBAAkB,CAAC,QAAQ,CAAC,UAAU,kBAAkB,CAAC,QAAQ,CAAC,SAAS,EACrF,IAAI,EACJ,EAAE,GAAG,OAAO,EAAE,UAAU,EAAE,IAAI,EAAE,CACjC,CAAC;IACJ,CAAC;CACF"}
|
|
@@ -3,7 +3,8 @@ import type { PageListResponse, PageShowResponse, PagesListQuery, RequestOptions
|
|
|
3
3
|
export declare class PagesResource {
|
|
4
4
|
private readonly http;
|
|
5
5
|
constructor(http: HttpClient);
|
|
6
|
-
list(
|
|
7
|
-
get(
|
|
6
|
+
list(query?: PagesListQuery, options?: RequestOptions): Promise<PageListResponse>;
|
|
7
|
+
get(pageSlug: string, options?: RequestOptions): Promise<PageShowResponse>;
|
|
8
|
+
private enrichPageResponse;
|
|
8
9
|
}
|
|
9
10
|
//# sourceMappingURL=pages.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"pages.d.ts","sourceRoot":"","sources":["../../src/resources/pages.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,SAAS,CAAC;AACrC,OAAO,KAAK,
|
|
1
|
+
{"version":3,"file":"pages.d.ts","sourceRoot":"","sources":["../../src/resources/pages.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,SAAS,CAAC;AACrC,OAAO,KAAK,EAGV,gBAAgB,EAChB,gBAAgB,EAChB,cAAc,EAEd,cAAc,EACf,MAAM,iBAAiB,CAAC;AAEzB,qBAAa,aAAa;IACL,OAAO,CAAC,QAAQ,CAAC,IAAI;gBAAJ,IAAI,EAAE,UAAU;IAE7C,IAAI,CAAC,KAAK,CAAC,EAAE,cAAc,EAAE,OAAO,CAAC,EAAE,cAAc,GAAG,OAAO,CAAC,gBAAgB,CAAC;IAM3E,GAAG,CAAC,QAAQ,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,cAAc,GAAG,OAAO,CAAC,gBAAgB,CAAC;IAMvF,OAAO,CAAC,kBAAkB;CAmC3B"}
|
package/dist/resources/pages.js
CHANGED
|
@@ -3,12 +3,43 @@ export class PagesResource {
|
|
|
3
3
|
constructor(http) {
|
|
4
4
|
this.http = http;
|
|
5
5
|
}
|
|
6
|
-
list(
|
|
6
|
+
list(query, options) {
|
|
7
7
|
const requestOptions = query ? { ...options, query } : options;
|
|
8
|
-
return this.http.get(
|
|
8
|
+
return this.http.get("/pages", requestOptions);
|
|
9
9
|
}
|
|
10
|
-
get(
|
|
11
|
-
|
|
10
|
+
async get(pageSlug, options) {
|
|
11
|
+
const response = await this.http.get(`/pages/${encodeURIComponent(pageSlug)}`, options);
|
|
12
|
+
return this.enrichPageResponse(response);
|
|
13
|
+
}
|
|
14
|
+
enrichPageResponse(response) {
|
|
15
|
+
const blocks = Array.isArray(response.data.blocks) ? response.data.blocks : [];
|
|
16
|
+
const byType = {};
|
|
17
|
+
const firstByType = {};
|
|
18
|
+
for (const block of blocks) {
|
|
19
|
+
const type = typeof block.type === "string" ? block.type.trim() : "";
|
|
20
|
+
if (!type) {
|
|
21
|
+
continue;
|
|
22
|
+
}
|
|
23
|
+
byType[type] ??= [];
|
|
24
|
+
byType[type].push(block);
|
|
25
|
+
firstByType[type] ??= block;
|
|
26
|
+
}
|
|
27
|
+
return {
|
|
28
|
+
...response,
|
|
29
|
+
blocks,
|
|
30
|
+
byType,
|
|
31
|
+
firstByType,
|
|
32
|
+
block: (type) => firstByType[type] ?? null,
|
|
33
|
+
blocksOf: (type) => byType[type] ?? [],
|
|
34
|
+
hasBlock: (type) => (byType[type]?.length ?? 0) > 0,
|
|
35
|
+
requireBlock: (type) => {
|
|
36
|
+
const block = firstByType[type] ?? null;
|
|
37
|
+
if (block === null) {
|
|
38
|
+
throw new Error(`Crafty page block not found: ${type}`);
|
|
39
|
+
}
|
|
40
|
+
return block;
|
|
41
|
+
}
|
|
42
|
+
};
|
|
12
43
|
}
|
|
13
44
|
}
|
|
14
45
|
//# sourceMappingURL=pages.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"pages.js","sourceRoot":"","sources":["../../src/resources/pages.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"pages.js","sourceRoot":"","sources":["../../src/resources/pages.ts"],"names":[],"mappings":"AAWA,MAAM,OAAO,aAAa;IACY;IAApC,YAAoC,IAAgB;QAAhB,SAAI,GAAJ,IAAI,CAAY;IAAG,CAAC;IAEjD,IAAI,CAAC,KAAsB,EAAE,OAAwB;QAC1D,MAAM,cAAc,GAAG,KAAK,CAAC,CAAC,CAAC,EAAE,GAAG,OAAO,EAAE,KAAK,EAAE,CAAC,CAAC,CAAC,OAAO,CAAC;QAE/D,OAAO,IAAI,CAAC,IAAI,CAAC,GAAG,CAAmB,QAAQ,EAAE,cAAc,CAAC,CAAC;IACnE,CAAC;IAEM,KAAK,CAAC,GAAG,CAAC,QAAgB,EAAE,OAAwB;QACzD,MAAM,QAAQ,GAAG,MAAM,IAAI,CAAC,IAAI,CAAC,GAAG,CAA6B,UAAU,kBAAkB,CAAC,QAAQ,CAAC,EAAE,EAAE,OAAO,CAAC,CAAC;QAEpH,OAAO,IAAI,CAAC,kBAAkB,CAAC,QAAQ,CAAC,CAAC;IAC3C,CAAC;IAEO,kBAAkB,CAAC,QAAoC;QAC7D,MAAM,MAAM,GAAG,KAAK,CAAC,OAAO,CAAC,QAAQ,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC;QAC/E,MAAM,MAAM,GAAkC,EAAE,CAAC;QACjD,MAAM,WAAW,GAAuC,EAAE,CAAC;QAE3D,KAAK,MAAM,KAAK,IAAI,MAAM,EAAE,CAAC;YAC3B,MAAM,IAAI,GAAG,OAAO,KAAK,CAAC,IAAI,KAAK,QAAQ,CAAC,CAAC,CAAC,KAAK,CAAC,IAAI,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;YACrE,IAAI,CAAC,IAAI,EAAE,CAAC;gBACV,SAAS;YACX,CAAC;YAED,MAAM,CAAC,IAAI,CAAC,KAAK,EAAE,CAAC;YACpB,MAAM,CAAC,IAAI,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;YACzB,WAAW,CAAC,IAAI,CAAC,KAAK,KAAK,CAAC;QAC9B,CAAC;QAED,OAAO;YACL,GAAG,QAAQ;YACX,MAAM;YACN,MAAM;YACN,WAAW;YACX,KAAK,EAAE,CAAC,IAAY,EAAE,EAAE,CAAC,WAAW,CAAC,IAAI,CAAC,IAAI,IAAI;YAClD,QAAQ,EAAE,CAAC,IAAY,EAAE,EAAE,CAAC,MAAM,CAAC,IAAI,CAAC,IAAI,EAAE;YAC9C,QAAQ,EAAE,CAAC,IAAY,EAAE,EAAE,CAAC,CAAC,MAAM,CAAC,IAAI,CAAC,EAAE,MAAM,IAAI,CAAC,CAAC,GAAG,CAAC;YAC3D,YAAY,EAAE,CAAC,IAAY,EAAE,EAAE;gBAC7B,MAAM,KAAK,GAAG,WAAW,CAAC,IAAI,CAAC,IAAI,IAAI,CAAC;gBAExC,IAAI,KAAK,KAAK,IAAI,EAAE,CAAC;oBACnB,MAAM,IAAI,KAAK,CAAC,gCAAgC,IAAI,EAAE,CAAC,CAAC;gBAC1D,CAAC;gBAED,OAAO,KAAK,CAAC;YACf,CAAC;SACF,CAAC;IACJ,CAAC;CACF"}
|
package/dist/types/public.d.ts
CHANGED
|
@@ -93,7 +93,18 @@ export type CollectionShowResponse = CraftyCollection;
|
|
|
93
93
|
export type EntryListResponse = PaginatedResponse<CraftyEntry>;
|
|
94
94
|
export type EntryShowResponse = SingleResponse<CraftyEntry>;
|
|
95
95
|
export type PageListResponse = PaginatedResponse<CraftyPage>;
|
|
96
|
-
export type
|
|
96
|
+
export type PageBlocksByType = Record<string, CraftyBlock[]>;
|
|
97
|
+
export type PageFirstBlocksByType = Record<string, CraftyBlock | null>;
|
|
98
|
+
export type EnrichedCraftyPageResponse = SingleResponse<CraftyPage> & {
|
|
99
|
+
blocks: CraftyBlock[];
|
|
100
|
+
byType: PageBlocksByType;
|
|
101
|
+
firstByType: PageFirstBlocksByType;
|
|
102
|
+
block: (type: string) => CraftyBlock | null;
|
|
103
|
+
blocksOf: (type: string) => CraftyBlock[];
|
|
104
|
+
hasBlock: (type: string) => boolean;
|
|
105
|
+
requireBlock: (type: string) => CraftyBlock;
|
|
106
|
+
};
|
|
107
|
+
export type PageShowResponse = EnrichedCraftyPageResponse;
|
|
97
108
|
export type MediaCollectionListResponse = {
|
|
98
109
|
data: CraftyMediaCollection[];
|
|
99
110
|
meta: {
|
|
@@ -108,7 +119,7 @@ export type MediaFileListResponse = {
|
|
|
108
119
|
};
|
|
109
120
|
};
|
|
110
121
|
export type EntriesListQuery = {
|
|
111
|
-
sort_by?:
|
|
122
|
+
sort_by?: string;
|
|
112
123
|
sort_order?: "asc" | "desc";
|
|
113
124
|
per_page?: number;
|
|
114
125
|
page?: number;
|
|
@@ -130,12 +141,45 @@ export type MediaFilesListQuery = {
|
|
|
130
141
|
per_page?: number;
|
|
131
142
|
page?: number;
|
|
132
143
|
};
|
|
144
|
+
export type CraftyForm = {
|
|
145
|
+
id: number;
|
|
146
|
+
name: string;
|
|
147
|
+
slug: string;
|
|
148
|
+
description: string | null;
|
|
149
|
+
fields: Record<string, CraftyFormField>;
|
|
150
|
+
status: "draft" | "published";
|
|
151
|
+
hcaptcha_enabled: boolean;
|
|
152
|
+
created_at: string;
|
|
153
|
+
updated_at: string;
|
|
154
|
+
};
|
|
155
|
+
export type CraftyFormField = {
|
|
156
|
+
label: string;
|
|
157
|
+
type: "text" | "textarea" | "email" | "tel" | "number" | "select" | "checkbox" | "radio" | "date";
|
|
158
|
+
required: boolean;
|
|
159
|
+
options?: string[];
|
|
160
|
+
order: number;
|
|
161
|
+
};
|
|
162
|
+
export type FormListResponse = {
|
|
163
|
+
data: CraftyForm[];
|
|
164
|
+
};
|
|
165
|
+
export type FormShowResponse = SingleResponse<CraftyForm>;
|
|
166
|
+
export type FormSubmitData = Record<string, unknown>;
|
|
167
|
+
export type FormSubmitResponse = {
|
|
168
|
+
id: number;
|
|
169
|
+
form_id: number;
|
|
170
|
+
submitted_at: string;
|
|
171
|
+
};
|
|
133
172
|
export type CraftyClientConfig = {
|
|
134
173
|
baseUrl: string;
|
|
135
174
|
/**
|
|
136
|
-
*
|
|
175
|
+
* Environment API token. The SDK sends it as `X-API-Key: <token>`.
|
|
176
|
+
* The backend resolves site + environment from this key.
|
|
137
177
|
*/
|
|
138
178
|
apiKey?: string;
|
|
179
|
+
/**
|
|
180
|
+
* Site slug, required for public endpoints like form submissions.
|
|
181
|
+
*/
|
|
182
|
+
siteSlug?: string;
|
|
139
183
|
fetch?: typeof fetch;
|
|
140
184
|
headers?: HeadersInit;
|
|
141
185
|
timeoutMs?: number;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"public.d.ts","sourceRoot":"","sources":["../../src/types/public.ts"],"names":[],"mappings":"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
|
|
1
|
+
{"version":3,"file":"public.d.ts","sourceRoot":"","sources":["../../src/types/public.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@craftware/crafty-sdk",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.0",
|
|
4
4
|
"private": false,
|
|
5
5
|
"type": "module",
|
|
6
6
|
"sideEffects": false,
|
|
@@ -18,7 +18,8 @@
|
|
|
18
18
|
"module": "./dist/index.js",
|
|
19
19
|
"types": "./dist/index.d.ts",
|
|
20
20
|
"files": [
|
|
21
|
-
"dist"
|
|
21
|
+
"dist",
|
|
22
|
+
"skills"
|
|
22
23
|
],
|
|
23
24
|
"engines": {
|
|
24
25
|
"node": ">=20"
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: crafty-cms
|
|
3
|
+
description: "Build on Crafty CMS — configure sites, pages, blocks, collections, entries, forms, and media through the Crafty MCP server, then develop a frontend with the @craftware/crafty-sdk JavaScript SDK. Use when connecting an agent to a Crafty MCP server; calling Crafty MCP tools like list_sites, create_page, publish_form, or upload_media; installing or importing @craftware/crafty-sdk or createCraftyClient; fetching Crafty pages, entries, collections, or media; rendering page blocks; or submitting Crafty forms."
|
|
4
|
+
license: MIT
|
|
5
|
+
metadata:
|
|
6
|
+
author: craftware
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
# Crafty CMS (MCP + JavaScript SDK)
|
|
10
|
+
|
|
11
|
+
Crafty is a headless, multi-tenant CMS. There are two integration surfaces, and they are used at different stages:
|
|
12
|
+
|
|
13
|
+
| Surface | Purpose | Auth | Use it to |
|
|
14
|
+
|---|---|---|---|
|
|
15
|
+
| **MCP server** | Author and configure content | Bearer token (PAT or OAuth), scoped | Create sites, pages, blocks, collections, entries, forms; upload media; publish |
|
|
16
|
+
| **`@craftware/crafty-sdk`** | Read published content into an app | `X-API-Key` environment token | Fetch pages/blocks, entries, collections, media; submit forms from a site |
|
|
17
|
+
|
|
18
|
+
The normal flow is **configure with the MCP server → build a frontend with the SDK**.
|
|
19
|
+
|
|
20
|
+
## When to Use
|
|
21
|
+
|
|
22
|
+
- Connecting Claude, Cursor, VS Code, Codex, or ChatGPT to a Crafty tenant's MCP server.
|
|
23
|
+
- Driving Crafty MCP tools (`list_sites`, `create_page`, `create_entry`, `create_form`, `publish_page`, `upload_media`, …).
|
|
24
|
+
- Installing or importing `@craftware/crafty-sdk` / `createCraftyClient`.
|
|
25
|
+
- Fetching Crafty pages, entries, collections, or media in app code, or submitting a Crafty form.
|
|
26
|
+
|
|
27
|
+
## Scope
|
|
28
|
+
|
|
29
|
+
- **In scope:** using a *deployed* Crafty tenant via its MCP server and the published SDK.
|
|
30
|
+
- **Out of scope:** building or modifying the Crafty backend itself, the SDK's internals, or the publish pipeline (that's the `publish-sdk` skill).
|
|
31
|
+
|
|
32
|
+
## Domain model
|
|
33
|
+
|
|
34
|
+
- **Site** — top-level tenant resource. Everything else belongs to a site.
|
|
35
|
+
- **Page** — a web page in a site; `draft` or `published`; composed of ordered **Blocks**.
|
|
36
|
+
- **Block** — a content unit on a page; its **BlockType** defines the data schema.
|
|
37
|
+
- **Collection** — a structured content schema (e.g. "Blog Posts"); defines the fields **Entries** must have.
|
|
38
|
+
- **Entry** — one record in a collection (e.g. a single post). Belongs to a site and a collection.
|
|
39
|
+
- **Form** — a contact/feedback form in a site; `draft` or `published`. Published forms accept **public** submissions without auth.
|
|
40
|
+
- **Form Submission** — visitor-submitted data; read-only after creation.
|
|
41
|
+
- **Media** — files attached to a site via the Spatie Media Library.
|
|
42
|
+
|
|
43
|
+
## Phase 1 — Configure content via the MCP server
|
|
44
|
+
|
|
45
|
+
Connect once, then drive the tools. Minimal connect with a Personal Access Token (Claude Code shown):
|
|
46
|
+
|
|
47
|
+
```bash
|
|
48
|
+
claude mcp add crafty \
|
|
49
|
+
--transport http \
|
|
50
|
+
--url https://<your-tenant-domain>/mcp \
|
|
51
|
+
--header "Authorization: Bearer YOUR_PERSONAL_ACCESS_TOKEN"
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
The server URL is **per-tenant**: `https://<your-tenant-domain>/mcp`. Tokens carry scopes — `crafty:read`, `crafty:write`, `crafty:publish`, and `crafty:submissions` (reading form submissions, which may contain personal data) — and they are **enforced per tool** (a tool without its scope is hidden and errors if called). Request the scopes the agent needs. **Full connection guide for every client (PAT and OAuth), scopes, and the complete 25-tool catalog: read `references/mcp-configuration.md`.**
|
|
55
|
+
|
|
56
|
+
Canonical authoring workflow (exact tool names):
|
|
57
|
+
|
|
58
|
+
1. `list_sites` → pick a `site_id` (or `create_site`).
|
|
59
|
+
2. `list_block_types` → learn the available block types and their data schemas.
|
|
60
|
+
3. `create_page` with `site_id`, `title`, and optional `blocks: [{ block_type_id, data }]`.
|
|
61
|
+
4. `publish_page`.
|
|
62
|
+
|
|
63
|
+
Collections/entries: `list_collections` → `create_entry` (`collection_slug`, `site_id`, `slug`, `data`) → `update_entry` (snapshots a revision).
|
|
64
|
+
Forms: `create_form` (`site_id`, `name`, `slug`, `fields`) → `publish_form` → `list_form_submissions`.
|
|
65
|
+
Media: `upload_media` (`url`, `site_id`, optional `collection_name`).
|
|
66
|
+
|
|
67
|
+
> `update_page` **replaces all blocks** when `blocks` is supplied. `update_entry` creates a revision. `create_page` always starts as `draft`.
|
|
68
|
+
|
|
69
|
+
## Phase 2 — Build a frontend with the SDK
|
|
70
|
+
|
|
71
|
+
```bash
|
|
72
|
+
npm install @craftware/crafty-sdk
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
```ts
|
|
76
|
+
import { createCraftyClient } from "@craftware/crafty-sdk";
|
|
77
|
+
|
|
78
|
+
const crafty = createCraftyClient({
|
|
79
|
+
baseUrl: "https://<your-tenant>.crafty.do", // SDK appends /api/v1
|
|
80
|
+
apiKey: process.env.CRAFTY_API_KEY, // environment API token (sent as X-API-Key)
|
|
81
|
+
siteSlug: "main-site", // required ONLY for forms.submit()
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
// pages are scoped by the site the apiKey resolves to — no siteSlug argument
|
|
85
|
+
const page = await crafty.pages.get("home");
|
|
86
|
+
const hero = page.block("hero"); // first block of that type, or null
|
|
87
|
+
const posts = await crafty.entries.list("posts", { per_page: 15 });
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
`pages.get()` returns the page enriched with block helpers (`page.block(type)`, `page.blocksOf(type)`, `page.byType`, …). **Full client config, every resource and return shape, block helpers, errors, and an end-to-end example: read `references/sdk-development.md`.**
|
|
91
|
+
|
|
92
|
+
## Key distinction: MCP auth vs SDK API key
|
|
93
|
+
|
|
94
|
+
These are two different credentials — do not mix them:
|
|
95
|
+
|
|
96
|
+
- **MCP** uses a **Bearer** token (Personal Access Token or OAuth access token) with `crafty:*` scopes. Used by an agent/editor to *author* content.
|
|
97
|
+
- **SDK** uses an **environment API token** sent as the `X-API-Key` header. It resolves the site + environment server-side, so `pages.*` need no `siteSlug`. Used by app code to *read* published content.
|
|
98
|
+
|
|
99
|
+
## Common pitfalls
|
|
100
|
+
|
|
101
|
+
- **Wrong tenant host** → the MCP endpoint won't resolve. Use the exact `https://<tenant-domain>/mcp`; each tenant has its own endpoint.
|
|
102
|
+
- **401 from the MCP server** → the Bearer token is missing, invalid, or expired; reconnect or regenerate it.
|
|
103
|
+
- **A tool is missing or fails with "Insufficient OAuth scope"** → the token lacks that tool's scope; re-issue it with `crafty:write` / `crafty:publish` / `crafty:submissions` as needed.
|
|
104
|
+
- **`forms.submit()` throws `BAD_REQUEST`** → `siteSlug` was not set in `createCraftyClient()`. Submissions hit the public endpoint and deliberately omit the API key.
|
|
105
|
+
- **Content not visible via the SDK** → it isn't published. Pages/forms must be published (`publish_page` / `publish_form`).
|
|
106
|
+
- **Confusing the two tokens** → the SDK `apiKey` (X-API-Key) is not an MCP Bearer token.
|
|
107
|
+
|
|
108
|
+
## References
|
|
109
|
+
|
|
110
|
+
- `references/mcp-configuration.md` — connecting every client, scopes, and the full MCP tool catalog with workflows.
|
|
111
|
+
- `references/sdk-development.md` — the complete SDK surface, block helpers, error handling, and examples.
|
|
@@ -0,0 +1,183 @@
|
|
|
1
|
+
# Configuring Crafty via the MCP server
|
|
2
|
+
|
|
3
|
+
Use this when an agent should **author and configure** content in a Crafty tenant: sites, pages/blocks, collections/entries, forms, media, and publishing.
|
|
4
|
+
|
|
5
|
+
## Server URL
|
|
6
|
+
|
|
7
|
+
The MCP endpoint is **per-tenant** and lives at the `/mcp` path on the tenant's own domain:
|
|
8
|
+
|
|
9
|
+
```
|
|
10
|
+
https://<your-tenant-domain>/mcp
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
The tenant is identified by the request **host**, so each tenant has its own endpoint. Hitting the wrong host fails to resolve the tenant. Every request must be authenticated (Bearer token).
|
|
14
|
+
|
|
15
|
+
## Authentication
|
|
16
|
+
|
|
17
|
+
Two families, both carrying a **Bearer** token on every request:
|
|
18
|
+
|
|
19
|
+
### Personal Access Token (PAT) — simplest
|
|
20
|
+
|
|
21
|
+
Generate a long-lived Personal Access Token from your Crafty profile, choosing the scopes you need, then paste it into the editor config as an `Authorization: Bearer …` header. Best for CLIs and local setups. Regenerate and update the config if it stops working.
|
|
22
|
+
|
|
23
|
+
### OAuth 2.0 — via an MCP client
|
|
24
|
+
|
|
25
|
+
Create an "MCP client" in your Crafty profile and pick a grant type:
|
|
26
|
+
|
|
27
|
+
- **Authorization Code** — standard browser-redirect flow for tools that open a browser (Claude.ai, Cursor, VS Code). Select the redirect URI matching your tool.
|
|
28
|
+
- **Device Code (RFC 8628)** — for CLIs that can't open a browser; the editor shows a code in the terminal that you approve in the browser.
|
|
29
|
+
|
|
30
|
+
When you create the client manually in your profile you are shown the `client_id` and a **one-time** `client_secret` — copy it immediately (PATs likewise show the token once). Clients that **auto-register** through discovery (e.g. Claude.ai, Cursor) are registered as public clients and receive no client secret.
|
|
31
|
+
|
|
32
|
+
OAuth clients that support MCP auto-discovery (Claude.ai, Cursor) can find everything from the server URL alone: discovery is published at `/.well-known/oauth-protected-resource` and `/.well-known/oauth-authorization-server`, with `authorization_endpoint` `/oauth/authorize`, `token_endpoint` `/oauth/token`, dynamic `registration_endpoint` `/oauth/register`, PKCE (`S256`), and grants `authorization_code` + `refresh_token`. These OAuth endpoints live at the tenant **root**, not under `/mcp`.
|
|
33
|
+
|
|
34
|
+
## Scopes
|
|
35
|
+
|
|
36
|
+
When you create a token (PAT or OAuth client) you grant one or more scopes. Scopes are **enforced per tool**: a tool is only advertised when your token carries its required scope, and invoking one without the scope fails with an MCP error (`Insufficient OAuth scope`). Grant the scopes the agent needs:
|
|
37
|
+
|
|
38
|
+
| Scope | Unlocks | Tools |
|
|
39
|
+
|---|---|---|
|
|
40
|
+
| `crafty:read` | Read sites, pages, block types, collections, entries, media, and form definitions | all `list_*` / `get_*` except form submissions |
|
|
41
|
+
| `crafty:write` | Create and update sites, pages, entries, media, and forms | all `create_*` / `update_*`, `upload_media` |
|
|
42
|
+
| `crafty:publish` | Publish and unpublish pages and forms | `publish_page`, `unpublish_page`, `publish_form`, `unpublish_form` |
|
|
43
|
+
| `crafty:submissions` | Read form submissions (may contain personal data) | `list_form_submissions`, `get_form_submission` |
|
|
44
|
+
|
|
45
|
+
Request all four for full authoring plus submission access. Scopes are also advertised in OAuth discovery; the `/mcp` endpoint additionally requires a valid authenticated token on every request.
|
|
46
|
+
|
|
47
|
+
## Connecting each client
|
|
48
|
+
|
|
49
|
+
Replace `https://<tenant>/mcp` with your tenant URL and `YOUR_PAT` with a Personal Access Token.
|
|
50
|
+
|
|
51
|
+
**Claude.ai (Authorization Code):** create an Authorization Code MCP client with redirect URI `https://claude.ai/api/mcp/auth_callback`; in Claude.ai go to **Settings → Integrations → Add integration**, enter the server URL, and authorize in the browser.
|
|
52
|
+
|
|
53
|
+
**Claude Code (CLI):**
|
|
54
|
+
|
|
55
|
+
```bash
|
|
56
|
+
# Option A — PAT
|
|
57
|
+
claude mcp add crafty --transport http \
|
|
58
|
+
--url https://<tenant>/mcp \
|
|
59
|
+
--header "Authorization: Bearer YOUR_PAT"
|
|
60
|
+
|
|
61
|
+
# Option B — Device Code (create a Device Code MCP client first)
|
|
62
|
+
claude mcp add crafty --transport http \
|
|
63
|
+
--url https://<tenant>/mcp \
|
|
64
|
+
--oauth2 device_code \
|
|
65
|
+
--client-id YOUR_CLIENT_ID \
|
|
66
|
+
--client-secret YOUR_CLIENT_SECRET
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
**Cursor (Authorization Code):** create an Authorization Code client with redirect URI `cursor://anysphere.cursor-deeplink/mcp/auth_callback`, then add to `~/.cursor/mcp.json` and authorize when prompted:
|
|
70
|
+
|
|
71
|
+
```json
|
|
72
|
+
{ "mcpServers": { "crafty": { "url": "https://<tenant>/mcp" } } }
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
**VS Code (GitHub Copilot, PAT):** add to `.vscode/mcp.json`, then open Copilot Chat → **Agent** mode and select the Crafty tools. (OAuth alternative: Authorization Code client with redirect URI `vscode://github.copilot-chat/mcp/auth_callback`.)
|
|
76
|
+
|
|
77
|
+
```json
|
|
78
|
+
{ "servers": { "crafty": { "type": "http", "url": "https://<tenant>/mcp",
|
|
79
|
+
"headers": { "Authorization": "Bearer YOUR_PAT" } } } }
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
**Codex CLI (`~/.codex/config.yaml`):**
|
|
83
|
+
|
|
84
|
+
```yaml
|
|
85
|
+
# Option A — PAT
|
|
86
|
+
mcp_servers:
|
|
87
|
+
crafty:
|
|
88
|
+
type: sse
|
|
89
|
+
url: https://<tenant>/mcp
|
|
90
|
+
headers:
|
|
91
|
+
Authorization: "Bearer YOUR_PAT"
|
|
92
|
+
# Option B — Device Code: add
|
|
93
|
+
# auth: { type: device_code, client_id: ..., client_secret: ...,
|
|
94
|
+
# token_url: https://<tenant>/oauth/token,
|
|
95
|
+
# device_authorization_url: https://<tenant>/oauth/device/code }
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
**ChatGPT (Authorization Code, Plus/Pro/Enterprise):** create an Authorization Code client with redirect URI `https://chatgpt.com/aip/g-*/oauth/callback`; in **Explore GPTs → Create → Configure → Actions** enter the server URL and configure OAuth with Authorization URL `/oauth/authorize` and Token URL `/oauth/token`.
|
|
99
|
+
|
|
100
|
+
## Tool catalog
|
|
101
|
+
|
|
102
|
+
All tools return a JSON text response. IDs are integers; slugs are strings. Pagination defaults to 25 per page (`per_page` max 100 where supported).
|
|
103
|
+
|
|
104
|
+
### Read (`crafty:read`)
|
|
105
|
+
|
|
106
|
+
| Tool | Required | Optional | Returns |
|
|
107
|
+
|---|---|---|---|
|
|
108
|
+
| `list_sites` | — | — | all sites (id, name, slug, domain, is_active) |
|
|
109
|
+
| `get_site` | `site_id` | — | one site |
|
|
110
|
+
| `list_pages` | `site_id` | `status` (`draft`/`published`) | paginated pages, ordered by title |
|
|
111
|
+
| `get_page` | `page_id` | — | page with its blocks (incl. block types) |
|
|
112
|
+
| `list_block_types` | — | — | all block types and their schemas |
|
|
113
|
+
| `list_collections` | — | `site_id` | active collections |
|
|
114
|
+
| `list_entries` | `collection_slug` | `site_id`, `per_page` | paginated entries (newest id first) |
|
|
115
|
+
| `get_entry` | `entry_id` | — | one entry with its collection |
|
|
116
|
+
| `list_media` | `site_id` | `collection`, `per_page` | paginated media for the site |
|
|
117
|
+
| `list_forms` | — | `site_id`, `status`, `per_page` | paginated forms, ordered by name |
|
|
118
|
+
| `get_form` | `form_id` | — | one form with its field definitions |
|
|
119
|
+
|
|
120
|
+
### Submissions (`crafty:submissions`)
|
|
121
|
+
|
|
122
|
+
Form submissions may contain personal data, so they require their own scope.
|
|
123
|
+
|
|
124
|
+
| Tool | Required | Optional | Returns |
|
|
125
|
+
|---|---|---|---|
|
|
126
|
+
| `list_form_submissions` | `form_slug` | `site_id`, `per_page` | paginated submissions, newest first |
|
|
127
|
+
| `get_form_submission` | `form_slug`, `submission_id` | `site_id` | one submission with its data |
|
|
128
|
+
|
|
129
|
+
### Write (`crafty:write`)
|
|
130
|
+
|
|
131
|
+
| Tool | Required | Optional | Notes |
|
|
132
|
+
|---|---|---|---|
|
|
133
|
+
| `create_site` | `name` | `slug` | slug auto-generated from name; returns the site (with a generated `api_token`) |
|
|
134
|
+
| `create_page` | `site_id`, `title` | `slug`, `blocks` | starts as `draft`; `blocks: [{ block_type_id, data }]` created in order |
|
|
135
|
+
| `update_page` | `page_id` | `title`, `slug`, `blocks` | **if `blocks` is supplied, ALL existing blocks are replaced** |
|
|
136
|
+
| `create_entry` | `collection_slug`, `site_id`, `slug` | `data` | `data` = field key/value pairs |
|
|
137
|
+
| `update_entry` | `entry_id`, `data` | `slug` | snapshots the prior data into a new revision |
|
|
138
|
+
| `upload_media` | `url`, `site_id` | `collection_name` (default `library`), `file_name` | downloads the file from the URL |
|
|
139
|
+
| `create_form` | `site_id`, `name`, `slug` | `description`, `fields` | `fields: [{ key, type, label, required? }]`; starts as `draft` |
|
|
140
|
+
| `update_form` | `form_id` | `name`, `slug`, `description`, `fields`, `notification_emails` | only supplied fields change; `fields`/`notification_emails` replace |
|
|
141
|
+
|
|
142
|
+
### Publish (`crafty:publish`)
|
|
143
|
+
|
|
144
|
+
| Tool | Required | Effect |
|
|
145
|
+
|---|---|---|
|
|
146
|
+
| `publish_page` | `page_id` | status → `published`, sets `published_at` |
|
|
147
|
+
| `unpublish_page` | `page_id` | status → `draft`, clears `published_at` |
|
|
148
|
+
| `publish_form` | `form_id` | status → `published` (now accepts public submissions) |
|
|
149
|
+
| `unpublish_form` | `form_id` | status → `draft` (stops accepting submissions) |
|
|
150
|
+
|
|
151
|
+
## Canonical workflows
|
|
152
|
+
|
|
153
|
+
**Build and publish a page**
|
|
154
|
+
|
|
155
|
+
1. `list_sites` → choose a `site_id` (or `create_site`).
|
|
156
|
+
2. `list_block_types` → note each `id` and its data schema.
|
|
157
|
+
3. `create_page` with `site_id`, `title`, and `blocks: [{ block_type_id, data }, …]`.
|
|
158
|
+
4. `publish_page` with the returned `page_id`.
|
|
159
|
+
|
|
160
|
+
**Structured content**
|
|
161
|
+
|
|
162
|
+
1. `list_collections` (optionally `site_id`) → pick a `collection_slug`.
|
|
163
|
+
2. `create_entry` with `collection_slug`, `site_id`, `slug`, and `data`.
|
|
164
|
+
3. `update_entry` to edit (each edit stores a revision).
|
|
165
|
+
|
|
166
|
+
**Forms**
|
|
167
|
+
|
|
168
|
+
1. `create_form` with `site_id`, `name`, `slug`, and `fields`.
|
|
169
|
+
2. `publish_form` so it accepts public submissions.
|
|
170
|
+
3. `list_form_submissions` / `get_form_submission` to read what visitors sent.
|
|
171
|
+
|
|
172
|
+
**Media**
|
|
173
|
+
|
|
174
|
+
1. `upload_media` with `url` and `site_id` (optionally `collection_name`).
|
|
175
|
+
2. `list_media` to confirm and retrieve URLs.
|
|
176
|
+
|
|
177
|
+
## Troubleshooting
|
|
178
|
+
|
|
179
|
+
- **401 / authentication error** — the Bearer token is missing, invalid, or expired; reconnect or regenerate it. Every `/mcp` request must carry a valid token.
|
|
180
|
+
- **A tool is missing, or fails with `Insufficient OAuth scope`** — the token lacks that tool's scope. Tools are only advertised when the token carries the matching scope (`crafty:write` to create/edit, `crafty:publish` to publish, `crafty:submissions` to read form submissions). Re-issue the token with the scopes you need.
|
|
181
|
+
- **Tenant not found** — wrong host; use the exact tenant domain. OAuth token endpoints are at the tenant root (`/oauth/...`), not under `/mcp`.
|
|
182
|
+
- **Device code never appears** — the MCP client must be created as type **Device Code**, with matching `client_id`/`client_secret`.
|
|
183
|
+
- **Token/secret lost** — `client_secret` and PATs are shown only once at creation; regenerate if lost.
|
|
@@ -0,0 +1,198 @@
|
|
|
1
|
+
# Developing with `@craftware/crafty-sdk`
|
|
2
|
+
|
|
3
|
+
Use this when writing application code that **reads published Crafty content** (pages/blocks, entries, collections, media) or **submits forms**. Authoring/configuration is done via the MCP server (see `mcp-configuration.md`), not the SDK.
|
|
4
|
+
|
|
5
|
+
## Install
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npm install @craftware/crafty-sdk
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
Requires Node `>=20`, or any runtime with `fetch` (browser, Bun, Deno, modern edge runtimes).
|
|
12
|
+
|
|
13
|
+
## Create a client
|
|
14
|
+
|
|
15
|
+
```ts
|
|
16
|
+
import { createCraftyClient } from "@craftware/crafty-sdk";
|
|
17
|
+
|
|
18
|
+
const crafty = createCraftyClient({
|
|
19
|
+
baseUrl: "https://<your-tenant>.crafty.do",
|
|
20
|
+
apiKey: process.env.CRAFTY_API_KEY,
|
|
21
|
+
siteSlug: "main-site",
|
|
22
|
+
timeoutMs: 10_000,
|
|
23
|
+
});
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
`createCraftyClient(config)` returns a plain object with six resources: `collections`, `entries`, `pages`, `forms`, `mediaCollections`, `mediaFiles`.
|
|
27
|
+
|
|
28
|
+
### Config options
|
|
29
|
+
|
|
30
|
+
| Option | Required | Notes |
|
|
31
|
+
|---|---|---|
|
|
32
|
+
| `baseUrl` | **yes** | Tenant base URL. Trailing slashes are stripped; `/api/v1` is appended automatically unless already present. |
|
|
33
|
+
| `apiKey` | no | Environment API token. Sent as `X-API-Key`. Resolves the site + environment server-side, so `pages.*` need no `siteSlug`. |
|
|
34
|
+
| `siteSlug` | no | Site slug. **Required for `forms.submit()`** (the public submit path is `/sites/{siteSlug}/forms/{formSlug}/submit`). |
|
|
35
|
+
| `fetch` | no | Custom fetch. Defaults to `globalThis.fetch`. If none is available, construction throws `CraftyError` (`NETWORK_ERROR`). |
|
|
36
|
+
| `headers` | no | Default headers merged into every request. |
|
|
37
|
+
| `timeoutMs` | no | Default per-request timeout. A timeout is only armed when the value is a number `> 0`. |
|
|
38
|
+
|
|
39
|
+
## Per-call options
|
|
40
|
+
|
|
41
|
+
Every method takes an optional final `options` argument:
|
|
42
|
+
|
|
43
|
+
- `signal: AbortSignal` — merged with the internal timeout signal.
|
|
44
|
+
- `headers: HeadersInit` — merged over the client's default headers.
|
|
45
|
+
- `timeoutMs: number` — overrides the client timeout for this call.
|
|
46
|
+
|
|
47
|
+
```ts
|
|
48
|
+
const posts = await crafty.entries.list(
|
|
49
|
+
"posts",
|
|
50
|
+
{ per_page: 15, page: 1, sort_by: "updated_at", sort_order: "desc" },
|
|
51
|
+
{ timeoutMs: 5_000 }
|
|
52
|
+
);
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
## Resources
|
|
56
|
+
|
|
57
|
+
### `collections`
|
|
58
|
+
|
|
59
|
+
```ts
|
|
60
|
+
crafty.collections.list(options?) // => { data: CraftyCollection[] }
|
|
61
|
+
crafty.collections.get(slug, options?) // => CraftyCollection (BARE object, not wrapped)
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
### `entries`
|
|
65
|
+
|
|
66
|
+
```ts
|
|
67
|
+
crafty.entries.list(collectionSlug, query?, options?) // => { data: CraftyEntry[], links, meta } (paginated)
|
|
68
|
+
crafty.entries.get(collectionSlug, id, options?) // => { data: CraftyEntry }
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
`query`: `sort_by`, `sort_order` (`asc`/`desc`), `per_page`, `page`, plus any extra key/value filters passed through to the API.
|
|
72
|
+
|
|
73
|
+
### `pages`
|
|
74
|
+
|
|
75
|
+
```ts
|
|
76
|
+
crafty.pages.list(query?, options?) // => { data: CraftyPage[], links, meta } (paginated)
|
|
77
|
+
crafty.pages.get(pageSlug, options?) // => enriched page (see below)
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
Pages are scoped server-side by the site the `apiKey` resolves to — there is **no `siteSlug` argument**. `query` accepts `per_page` and `page`.
|
|
81
|
+
|
|
82
|
+
`pages.get()` resolves to `{ data: CraftyPage }` **plus** block helpers:
|
|
83
|
+
|
|
84
|
+
| Helper | Returns |
|
|
85
|
+
|---|---|
|
|
86
|
+
| `page.blocks` | `CraftyBlock[]` (all blocks; non-array data coerced to `[]`) |
|
|
87
|
+
| `page.block(type)` | first block of `type`, or `null` |
|
|
88
|
+
| `page.blocksOf(type)` | all blocks of `type` (`[]` if none) |
|
|
89
|
+
| `page.hasBlock(type)` | `boolean` |
|
|
90
|
+
| `page.requireBlock(type)` | first block, or **throws a plain `Error`** (`Crafty page block not found: …`) — not a `CraftyError` |
|
|
91
|
+
| `page.byType` | `Record<type, CraftyBlock[]>` |
|
|
92
|
+
| `page.firstByType` | `Record<type, CraftyBlock \| null>` |
|
|
93
|
+
| `page.data` | the raw `CraftyPage` |
|
|
94
|
+
|
|
95
|
+
```ts
|
|
96
|
+
const landing = await crafty.pages.get("landing");
|
|
97
|
+
const hero = landing.block("hero"); // CraftyBlock | null
|
|
98
|
+
const features = landing.blocksOf("feature"); // CraftyBlock[]
|
|
99
|
+
const cta = landing.requireBlock("cta"); // throws plain Error if absent
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
### `forms`
|
|
103
|
+
|
|
104
|
+
```ts
|
|
105
|
+
crafty.forms.list(options?) // => { data: CraftyForm[] } (requires apiKey)
|
|
106
|
+
crafty.forms.get(slug, options?) // => { data: CraftyForm } (requires apiKey)
|
|
107
|
+
crafty.forms.submit(formSlug, data, options?) // => { id, form_id, submitted_at } (public, no apiKey)
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
`forms.submit()` uses a **public** endpoint: it sends no `X-API-Key`, but it **requires `siteSlug` in the client config** — calling it without one throws a synchronous `CraftyError` with code `BAD_REQUEST`.
|
|
111
|
+
|
|
112
|
+
```ts
|
|
113
|
+
const crafty = createCraftyClient({
|
|
114
|
+
baseUrl: "https://<your-tenant>.crafty.do",
|
|
115
|
+
siteSlug: "main-site", // required for submit()
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
const result = await crafty.forms.submit("contact", {
|
|
119
|
+
name: "Jane Doe",
|
|
120
|
+
email: "jane@example.com",
|
|
121
|
+
message: "Hello!",
|
|
122
|
+
});
|
|
123
|
+
// => { id, form_id, submitted_at }
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
> Note the asymmetry: `collections.get()` returns the bare object, while `forms.get()` and `entries.get()` are wrapped in `{ data }`.
|
|
127
|
+
|
|
128
|
+
### `mediaCollections`
|
|
129
|
+
|
|
130
|
+
```ts
|
|
131
|
+
crafty.mediaCollections.list(query?, options?) // => { data: CraftyMediaCollection[], meta: { includes } }
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
`query.include` accepts `"stats"`, `"directories"`, or an array of them (de-duplicated and serialized comma-separated).
|
|
135
|
+
|
|
136
|
+
### `mediaFiles`
|
|
137
|
+
|
|
138
|
+
```ts
|
|
139
|
+
crafty.mediaFiles.list(query?, options?) // => { data: CraftyMediaFile[], links, meta: { ...pagination, ttl_minutes } }
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
`query`: `collection`, `directory_id`, `ttl_minutes` (signed-URL lifetime), `per_page`, `page`.
|
|
143
|
+
|
|
144
|
+
## Error handling
|
|
145
|
+
|
|
146
|
+
The SDK throws `CraftyError` for HTTP, timeout, abort, network, and config failures.
|
|
147
|
+
|
|
148
|
+
```ts
|
|
149
|
+
import { CraftyError } from "@craftware/crafty-sdk";
|
|
150
|
+
|
|
151
|
+
try {
|
|
152
|
+
await crafty.pages.get("home");
|
|
153
|
+
} catch (error) {
|
|
154
|
+
if (error instanceof CraftyError) {
|
|
155
|
+
console.error(error.code, error.status, error.message, error.requestId);
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
Codes: `NETWORK_ERROR`, `TIMEOUT`, `BAD_REQUEST` (HTTP 400 **and** 422), `UNAUTHORIZED` (401), `FORBIDDEN` (403), `NOT_FOUND` (404), `RATE_LIMITED` (429), `SERVER_ERROR` (5xx), `UNKNOWN_ERROR` (any other / unmapped status). `error.requestId` is populated when the API returns an `x-request-id` header.
|
|
161
|
+
|
|
162
|
+
> `page.requireBlock()` throws a plain `Error`, not a `CraftyError` — handle it separately.
|
|
163
|
+
|
|
164
|
+
## End-to-end example
|
|
165
|
+
|
|
166
|
+
```ts
|
|
167
|
+
import { createCraftyClient, CraftyError } from "@craftware/crafty-sdk";
|
|
168
|
+
|
|
169
|
+
const crafty = createCraftyClient({
|
|
170
|
+
baseUrl: "https://<your-tenant>.crafty.do",
|
|
171
|
+
apiKey: process.env.CRAFTY_API_KEY,
|
|
172
|
+
siteSlug: "main-site",
|
|
173
|
+
timeoutMs: 10_000,
|
|
174
|
+
});
|
|
175
|
+
|
|
176
|
+
const home = await crafty.pages.get("home");
|
|
177
|
+
const hero = home.block("hero");
|
|
178
|
+
|
|
179
|
+
const { data: posts } = await crafty.entries.list("posts", { per_page: 15, sort_order: "desc" });
|
|
180
|
+
const { data: collections } = await crafty.collections.list();
|
|
181
|
+
|
|
182
|
+
const media = await crafty.mediaCollections.list({ include: ["stats", "directories"] });
|
|
183
|
+
const files = await crafty.mediaFiles.list({ collection: "library", per_page: 24 });
|
|
184
|
+
|
|
185
|
+
try {
|
|
186
|
+
await crafty.forms.submit("contact", { name: "Jane", email: "jane@test.com" });
|
|
187
|
+
} catch (e) {
|
|
188
|
+
if (e instanceof CraftyError && e.code === "BAD_REQUEST") {
|
|
189
|
+
// missing siteSlug, or validation failed
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
```
|
|
193
|
+
|
|
194
|
+
## Notes
|
|
195
|
+
|
|
196
|
+
- `Accept: application/json` is set automatically unless overridden.
|
|
197
|
+
- `baseUrl` is normalized so `/api/v1` is never duplicated.
|
|
198
|
+
- The `apiKey` (`X-API-Key`) is an **environment** token and is distinct from the **Bearer** tokens used for the MCP server.
|