@craftware/crafty-sdk 0.6.0 → 0.8.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.
Files changed (39) hide show
  1. package/README.md +47 -3
  2. package/dist/client.d.ts +5 -0
  3. package/dist/client.d.ts.map +1 -1
  4. package/dist/client.js +5 -0
  5. package/dist/client.js.map +1 -1
  6. package/dist/crafty-visual-editing.global.js +1 -1
  7. package/dist/crafty-visual-editing.global.js.map +1 -1
  8. package/dist/http.d.ts +8 -1
  9. package/dist/http.d.ts.map +1 -1
  10. package/dist/http.js +50 -11
  11. package/dist/http.js.map +1 -1
  12. package/dist/image.d.ts +27 -0
  13. package/dist/image.d.ts.map +1 -0
  14. package/dist/image.js +110 -0
  15. package/dist/image.js.map +1 -0
  16. package/dist/index.d.ts +3 -0
  17. package/dist/index.d.ts.map +1 -1
  18. package/dist/index.js +2 -0
  19. package/dist/index.js.map +1 -1
  20. package/dist/resources/assistant.d.ts +30 -0
  21. package/dist/resources/assistant.d.ts.map +1 -0
  22. package/dist/resources/assistant.js +51 -0
  23. package/dist/resources/assistant.js.map +1 -0
  24. package/dist/resources/entries.js +2 -2
  25. package/dist/resources/entries.js.map +1 -1
  26. package/dist/resources/forms.d.ts.map +1 -1
  27. package/dist/resources/forms.js +4 -4
  28. package/dist/resources/forms.js.map +1 -1
  29. package/dist/resources/pages.js +2 -2
  30. package/dist/resources/pages.js.map +1 -1
  31. package/dist/types/public.d.ts +101 -1
  32. package/dist/types/public.d.ts.map +1 -1
  33. package/dist/version.d.ts +1 -1
  34. package/dist/version.js +1 -1
  35. package/package.json +1 -1
  36. package/skills/crafty-cms/SKILL.md +40 -7
  37. package/skills/crafty-cms/references/mcp-configuration.md +23 -7
  38. package/skills/crafty-cms/references/migrate-to-crafty.md +85 -0
  39. package/skills/crafty-cms/references/sdk-development.md +59 -7
@@ -23,7 +23,7 @@ const crafty = createCraftyClient({
23
23
  });
24
24
  ```
25
25
 
26
- `createCraftyClient(config)` returns a plain object with six resources (`collections`, `entries`, `pages`, `forms`, `mediaCollections`, `mediaFiles`) and the visual editor helpers `attrs`, `attrsHtml` and `previewFromRequest`.
26
+ `createCraftyClient(config)` returns a plain object with six resources (`collections`, `entries`, `pages`, `forms`, `mediaCollections`, `mediaFiles`), the image helpers `image` and `srcset`, and the visual editor helpers `attrs`, `attrsHtml` and `previewFromRequest`.
27
27
 
28
28
  ### Config options
29
29
 
@@ -70,7 +70,7 @@ crafty.entries.list(collectionSlug, query?, options?) // => { data: CraftyEntry
70
70
  crafty.entries.get(collectionSlug, id, options?) // => { data: CraftyEntry }
71
71
  ```
72
72
 
73
- `query`: `sort_by`, `sort_order` (`asc`/`desc`), `per_page`, `page`, plus any extra key/value filters passed through to the API.
73
+ `query`: `q` (text search over the collection's text fields), `sort_by`, `sort_order` (`asc`/`desc`), `per_page`, `page`, plus any extra key/value filters passed through to the API.
74
74
 
75
75
  ### `pages`
76
76
 
@@ -113,7 +113,7 @@ const cta = landing.requireBlock("cta"); // throws plain Error if abs
113
113
  | `boolean` | boolean |
114
114
  | `stringList` | `string[]` (e.g. `["AI", "Cloud"]`) |
115
115
  | `repeater` | array of objects; every declared subfield is present, `null` when empty (e.g. `[{ icon, title, text, tags: string[] }]`) |
116
- | `reference` | collection reference: entry summary `{ id, slug, label }` or `null`; media reference (`model: "Media"`): the media id, plus `<key>_media` with `{ id, url, file_name, mime_type, size, is_public }` or `null` |
116
+ | `reference` | collection reference: entry summary `{ id, slug, label }` or `null`; media reference (`model: "Media"`): the media id, plus `<key>_media` with `{ id, url, file_name, mime_type, size, is_public, image_transform_base }` or `null` |
117
117
  | `multiReference` | collection: array of entries; media (`model: "Media"`): array of media ids, plus `<key>_media` with the files in the same order (missing files are skipped) |
118
118
 
119
119
  Entries expose media references the same way (`entry.image` is the id, `entry.image_media.url` the file).
@@ -124,21 +124,72 @@ const items = solutions?.data.items ?? [];
124
124
  items.map((item) => `${item.title}: ${item.tags.join(", ")}`);
125
125
 
126
126
  const hero = home.block("hero");
127
- const heroUrl = hero?.data.image_media?.url; // reference, model "Media"
127
+ const heroUrl = crafty.image(hero?.data.image_media, { width: 1600, format: "auto" }); // reference, model "Media"
128
128
  const slides = hero?.data.gallery_media ?? []; // multiReference, model "Media"
129
129
  ```
130
130
 
131
+ ### Images: always go through `crafty.image()`
132
+
133
+ Render every Crafty image with `crafty.image(media, { width, format: "auto" })` (and `crafty.srcset(media, widths, { format: "auto" })` for responsive images) instead of the raw `url`. Sites that ship the original upload fail Core Web Vitals (LCP).
134
+
135
+ ```tsx
136
+ const photo = block.data.image_media;
137
+
138
+ <img
139
+ src={crafty.image(photo, { width: 800, format: "auto" })}
140
+ srcSet={crafty.srcset(photo, [400, 800, 1200, 1600], { format: "auto" })}
141
+ sizes="(min-width: 768px) 50vw, 100vw"
142
+ width={800}
143
+ height={450}
144
+ loading="lazy"
145
+ alt={block.data.title ?? ""}
146
+ />
147
+ ```
148
+
149
+ - `crafty.image(media, { width?, height?, fit?, quality?, format? })` → string. `media` is a `<key>_media` object, a `CraftyMediaFile`, or a URL string. `width`/`height` 1–4096, `quality` 1–100, `fit`: `scale-down` | `contain` | `cover` | `crop` | `pad`, `format`: `auto` | `webp` | `avif` | `jpeg` | `png`. Invalid options are dropped.
150
+ - `crafty.srcset(media, widths, options?)` → `"<url> 400w, <url> 800w"`, or `""` when the image cannot be transformed.
151
+ - Pick `width` from the rendered size (about 2x for retina on small images); use `fit: "cover"` with both `width` and `height` for fixed-ratio cards and thumbnails.
152
+ - Transforms only apply to **public** raster images when the Crafty server has Cloudflare Image Transformations enabled (the media then carries `image_transform_base`). Otherwise `image()` returns the original `url` and `srcset()` returns `""`, so the same code always works. Upload images to a public repository (`manage_media_collection` with `is_public: true`) when the site needs permanent, resizable URLs.
153
+ - Use `og:image` with `crafty.image(media, { width: 1200, height: 630, fit: "cover", format: "jpeg" })`: social scrapers do not support AVIF/WebP negotiation.
154
+ - Do not hand-build `/cdn-cgi/image/...` URLs or append `?w=` query parameters.
155
+
131
156
  Prefer modelling lists as `repeater` / `stringList` fields on a block type over collections or `json` fields; see the `crafty-cms` skill.
132
157
 
158
+ ### Generated types (`crafty types`)
159
+
160
+ Do not guess field keys or shapes: generate them from the site's real content models with the CLI.
161
+
162
+ ```bash
163
+ npx @craftware/crafty-cli types # CRAFTY_API_URL + CRAFTY_API_KEY from .env
164
+ npx @craftware/crafty-cli types --api-key=$KEY --url=https://<tenant>.crafty.do --out=src/crafty.types.ts
165
+ ```
166
+
167
+ It reads `GET /api/v1/schema` (JSON Schema of every collection and block type of the API key's environment, versioned by `format_version`) and writes `crafty.types.ts`:
168
+
169
+ - `<Slug>Entry` per collection (entry metadata + fields), `<Slug>BlockData` and `<Slug>Block` per block type, the `CraftyPageBlock` union (discriminated by `type`), the `CraftyCollectionTypes` / `CraftyBlockDataTypes` slug maps, and `CraftyEntryReference` / `CraftyMediaSummary`.
170
+ - `select` → union of option values, `stringList` → `string[]`, `repeater` → array of item objects, `json` → `unknown`; optional fields are `| null`.
171
+
172
+ ```ts
173
+ import type { CarsEntry, CraftyPageBlock } from "./crafty.types";
174
+
175
+ const cars = (await crafty.entries.list("cars")).data as unknown as CarsEntry[];
176
+ const page = await crafty.pages.get("home");
177
+ for (const block of page.blocks as unknown as CraftyPageBlock[]) {
178
+ if (block.type === "hero") block.data.heading; // HeroBlockData
179
+ }
180
+ ```
181
+
182
+ Types depend on the environment (generate with the key of the environment the code targets) and go stale when a collection or block type changes — re-run the command after `create_block_type`, `update_block_type`, `create_collection` or `update_collection`.
183
+
133
184
  ### `forms`
134
185
 
135
186
  ```ts
136
187
  crafty.forms.list(options?) // => { data: CraftyForm[] } (requires apiKey)
137
188
  crafty.forms.get(slug, options?) // => { data: CraftyForm } (requires apiKey)
138
- crafty.forms.submit(formSlug, data, options?) // => { id, form_id, submitted_at } (public, no apiKey)
189
+ crafty.forms.submit(formSlug, data, options?) // => { id, form_id, submitted_at } (public; apiKey optional, picks the environment)
139
190
  ```
140
191
 
141
- `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`.
192
+ `forms.submit()` uses a **public** endpoint: the `X-API-Key`, when configured, only selects the environment (a staging key submits to staging, no key submits to production), but it **requires `siteSlug` in the client config** — calling it without one throws a synchronous `CraftyError` with code `BAD_REQUEST`.
142
193
 
143
194
  ```ts
144
195
  const crafty = createCraftyClient({
@@ -170,7 +221,7 @@ crafty.mediaCollections.list(query?, options?) // => { data: CraftyMediaCollect
170
221
  crafty.mediaFiles.list(query?, options?) // => { data: CraftyMediaFile[], links, meta: { ...pagination, ttl_minutes } }
171
222
  ```
172
223
 
173
- `query`: `collection`, `directory_id`, `ttl_minutes` (signed-URL lifetime), `per_page`, `page`.
224
+ `query`: `collection`, `directory_id`, `ttl_minutes` (signed-URL lifetime), `per_page`, `page`. Each file has `url`, `is_public`, `expires_at` and `image_transform_base`; pass the file to `crafty.image()` to render it.
174
225
 
175
226
  ## `llms.txt`
176
227
 
@@ -284,6 +335,7 @@ try {
284
335
 
285
336
  - `attrs(target, field, { label? })` / `crafty.attrs(...)` → `{ "data-crafty-block" | "data-crafty-entry", "data-crafty-field", "data-crafty-label"? }` to spread on the element that renders the value; `{}` when the target has no valid id. `field` is a field key or a path into a list or JSON value (`sections[3].title`, `items[2].title` for a block-type `repeater`, `tags[1]` for a `stringList`, `items[0].tags[1]` for a stringList inside a repeater item). The index is the item's position in the stored data.
286
337
  - `attrsHtml(target, field, { label? })` → the same attributes as an escaped HTML string.
338
+ - `image(media, options?)` / `crafty.image(...)` and `srcset(media, widths, options?)` / `crafty.srcset(...)` → resized image URLs; see "Images: always go through `crafty.image()`".
287
339
  - `previewFromRequest(req)` → preview token of an incoming request (URL string, `URL`, Web `Request`, Node `req.url`, Express `req.query`) or `null`.
288
340
  - `readPreviewToken()` → token in the browser, or `null`.
289
341
  - `enableVisualEditing(options)` from `@craftware/crafty-sdk/visual-editing` → connects the page to the editor; no-op outside the admin iframe. Options: `onRefresh`, `adminOrigins`, `page`, `entries`, `touchHighlightMs`.