simple-strapi 1.0.0-alpha.26 → 1.0.0-alpha.27
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 +724 -0
- package/dist/client.d.ts +3 -3
- package/dist/client.js +1 -0
- package/dist/fields/media.d.ts +7 -0
- package/dist/fields/media.js +10 -3
- package/dist/utils/schema.js +6 -1
- package/package.json +16 -14
package/README.md
ADDED
|
@@ -0,0 +1,724 @@
|
|
|
1
|
+
# simple-strapi
|
|
2
|
+
|
|
3
|
+
> A lightweight, type-safe Strapi v5 client for Node.js and TypeScript. Define schemas once, get fully-typed responses and automatic populate queries — no manual configuration needed.
|
|
4
|
+
|
|
5
|
+
## Features
|
|
6
|
+
|
|
7
|
+
- Schema-driven: define a schema and get fully-typed response data
|
|
8
|
+
- Automatic `populate` generation from schemas (deep, nested)
|
|
9
|
+
- Zod-based validation with safe parsing and warnings on mismatches
|
|
10
|
+
- Full CRUD: `getCollection`, `getSingle`, `create`, `update`, `delete`
|
|
11
|
+
- Auto-pagination: fetch all pages automatically or control pagination manually
|
|
12
|
+
- Authentication via API token or email/password credentials
|
|
13
|
+
- All fields support `required` option for strict TypeScript types
|
|
14
|
+
- Upload files to the Media Library
|
|
15
|
+
|
|
16
|
+
## Installation
|
|
17
|
+
|
|
18
|
+
```bash
|
|
19
|
+
npm install simple-strapi
|
|
20
|
+
# or
|
|
21
|
+
yarn add simple-strapi
|
|
22
|
+
# or
|
|
23
|
+
bun add simple-strapi
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
## Quick start
|
|
27
|
+
|
|
28
|
+
```ts
|
|
29
|
+
import { StrapiClient } from "simple-strapi";
|
|
30
|
+
|
|
31
|
+
const client = await StrapiClient.create("https://my-strapi.example.com/api", {
|
|
32
|
+
auth: process.env.STRAPI_TOKEN, // API token (string)
|
|
33
|
+
});
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
---
|
|
37
|
+
|
|
38
|
+
## Client API
|
|
39
|
+
|
|
40
|
+
### `StrapiClient.create(endpoint, options?)`
|
|
41
|
+
|
|
42
|
+
Creates and returns a new `StrapiClient` instance. This is the only way to instantiate the client.
|
|
43
|
+
|
|
44
|
+
```ts
|
|
45
|
+
const client = await StrapiClient.create(endpoint, options?)
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
| Parameter | Type | Description |
|
|
49
|
+
| ----------------- | ----------------------------------------------- | ---------------------------------------------------------- |
|
|
50
|
+
| `endpoint` | `string \| URL` | Full URL including base path (e.g. `https://host.com/api`) |
|
|
51
|
+
| `options.auth` | `string \| { email: string; password: string }` | API token string, or credentials object for JWT auth |
|
|
52
|
+
| `options.params` | `Record<string, any>` | Default query params for every request |
|
|
53
|
+
| `options.headers` | `Record<string, string>` | Default extra headers for every request |
|
|
54
|
+
|
|
55
|
+
**Examples:**
|
|
56
|
+
|
|
57
|
+
```ts
|
|
58
|
+
// With API token
|
|
59
|
+
const client = await StrapiClient.create("https://host.com/api", {
|
|
60
|
+
auth: process.env.STRAPI_TOKEN,
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
// With email/password (fetches JWT automatically)
|
|
64
|
+
const client = await StrapiClient.create("https://host.com/api", {
|
|
65
|
+
auth: { email: "user@example.com", password: "secret" },
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
// Without auth (public API)
|
|
69
|
+
const client = await StrapiClient.create("https://host.com/api");
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
---
|
|
73
|
+
|
|
74
|
+
### `client.getCollection(pluralId, options?)`
|
|
75
|
+
|
|
76
|
+
Fetches a collection of entries. By default fetches all pages automatically.
|
|
77
|
+
|
|
78
|
+
```ts
|
|
79
|
+
const { data, meta } = await client.getCollection(pluralId, options?)
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
| Parameter | Type | Default | Description |
|
|
83
|
+
| -------------------------- | ----------------------------------------------- | ------------- | ----------------------------------------------------------------- |
|
|
84
|
+
| `pluralId` | `string` | — | Strapi collection plural API ID (e.g. `"articles"`) |
|
|
85
|
+
| `options.schema` | `Schema` | — | Field schema for typed response and auto-populate |
|
|
86
|
+
| `options.pagination` | `false \| { page?: number; pageSize?: number }` | `{ page: 1 }` | `false` = fetch all pages; object = single page with given params |
|
|
87
|
+
| `options.sort` | `string \| string[]` | — | Sort expression(s) (e.g. `"createdAt:desc"`) |
|
|
88
|
+
| `options.filters` | `Record<string, any>` | — | Strapi filter object |
|
|
89
|
+
| `options.populate` | `any` | — | Manual populate (ignored if `schema` is provided) |
|
|
90
|
+
| `options.params` | `Record<string, any>` | — | Additional raw query params |
|
|
91
|
+
| `options.headers` | `Record<string, string>` | — | Request-specific extra headers |
|
|
92
|
+
| `options.where.documentId` | `string` | — | Append a documentId segment to the URL path |
|
|
93
|
+
|
|
94
|
+
**Returns:** `Promise<{ data: InferSchemaWithDefaults<S>[], meta: any }>`
|
|
95
|
+
|
|
96
|
+
**Examples:**
|
|
97
|
+
|
|
98
|
+
```ts
|
|
99
|
+
import {
|
|
100
|
+
StrapiClient,
|
|
101
|
+
text,
|
|
102
|
+
number,
|
|
103
|
+
enumeration,
|
|
104
|
+
media,
|
|
105
|
+
component,
|
|
106
|
+
dynamic,
|
|
107
|
+
richText,
|
|
108
|
+
boolean,
|
|
109
|
+
} from "simple-strapi";
|
|
110
|
+
|
|
111
|
+
// Typed response with schema
|
|
112
|
+
const { data } = await client.getCollection("articles", {
|
|
113
|
+
pagination: false, // fetch all pages
|
|
114
|
+
sort: "publishedAt:desc",
|
|
115
|
+
filters: { status: { $eq: "published" } },
|
|
116
|
+
schema: {
|
|
117
|
+
title: text({ required: true }),
|
|
118
|
+
slug: text({ required: true }),
|
|
119
|
+
views: number(),
|
|
120
|
+
published: boolean(),
|
|
121
|
+
cover: media.single(),
|
|
122
|
+
category: enumeration(["news", "blog", "tutorial"]),
|
|
123
|
+
body: richText.blocks(),
|
|
124
|
+
},
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
// data is typed as Array<{ title: string; slug: string; views: number | null | undefined; ... }>
|
|
128
|
+
|
|
129
|
+
// Untyped (no schema)
|
|
130
|
+
const { data: raw } = await client.getCollection("articles");
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
---
|
|
134
|
+
|
|
135
|
+
### `client.getSingle(pluralId, options?)`
|
|
136
|
+
|
|
137
|
+
Fetches a single entry. Works with both Strapi single types and collections (with filters to identify one entry).
|
|
138
|
+
|
|
139
|
+
```ts
|
|
140
|
+
const { data, meta } = await client.getSingle(pluralId, options?)
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
| Parameter | Type | Description |
|
|
144
|
+
| ------------------ | ------------------------ | ------------------------------------------------- |
|
|
145
|
+
| `pluralId` | `string` | Strapi collection or single type plural API ID |
|
|
146
|
+
| `options.schema` | `Schema` | Field schema for typed response and auto-populate |
|
|
147
|
+
| `options.populate` | `any` | Manual populate (ignored if `schema` is provided) |
|
|
148
|
+
| `options.params` | `Record<string, any>` | Query params, including `filters` |
|
|
149
|
+
| `options.headers` | `Record<string, string>` | Request-specific extra headers |
|
|
150
|
+
|
|
151
|
+
**Returns:** `Promise<{ data: InferSchemaWithDefaults<S>; meta: any }>`
|
|
152
|
+
|
|
153
|
+
**Example:**
|
|
154
|
+
|
|
155
|
+
```ts
|
|
156
|
+
const { data } = await client.getSingle("homepage", {
|
|
157
|
+
schema: {
|
|
158
|
+
title: text({ required: true }),
|
|
159
|
+
subtitle: text(),
|
|
160
|
+
hero: media.single({ required: true }),
|
|
161
|
+
},
|
|
162
|
+
});
|
|
163
|
+
|
|
164
|
+
// With filters to identify one entry from a collection
|
|
165
|
+
const { data: article } = await client.getSingle("articles", {
|
|
166
|
+
params: { filters: { slug: { $eq: "my-article" } } },
|
|
167
|
+
schema: { title: text({ required: true }), body: richText.blocks() },
|
|
168
|
+
});
|
|
169
|
+
```
|
|
170
|
+
|
|
171
|
+
---
|
|
172
|
+
|
|
173
|
+
### `client.update(pluralId, documentId, payload, options?)`
|
|
174
|
+
|
|
175
|
+
Updates an existing entry by `documentId`.
|
|
176
|
+
|
|
177
|
+
```ts
|
|
178
|
+
const { data, meta } = await client.update(pluralId, documentId, payload, options?)
|
|
179
|
+
```
|
|
180
|
+
|
|
181
|
+
| Parameter | Type | Description |
|
|
182
|
+
| ----------------- | ------------------------ | ------------------------------------------------------- |
|
|
183
|
+
| `pluralId` | `string` | Strapi collection plural API ID |
|
|
184
|
+
| `documentId` | `string` | The Strapi v5 document ID |
|
|
185
|
+
| `payload` | `any` | Data to update (will be wrapped in `{ data: payload }`) |
|
|
186
|
+
| `options.schema` | `Schema` | Schema for parsing the response |
|
|
187
|
+
| `options.params` | `Record<string, any>` | Additional query params |
|
|
188
|
+
| `options.headers` | `Record<string, string>` | Request-specific extra headers |
|
|
189
|
+
|
|
190
|
+
**Returns:** `Promise<{ data: InferSchemaWithDefaults<S>; meta: any }>`
|
|
191
|
+
|
|
192
|
+
**Example:**
|
|
193
|
+
|
|
194
|
+
```ts
|
|
195
|
+
const { data } = await client.update(
|
|
196
|
+
"articles",
|
|
197
|
+
"abc123",
|
|
198
|
+
{ title: "New Title" },
|
|
199
|
+
{
|
|
200
|
+
schema: { title: text({ required: true }) },
|
|
201
|
+
},
|
|
202
|
+
);
|
|
203
|
+
```
|
|
204
|
+
|
|
205
|
+
---
|
|
206
|
+
|
|
207
|
+
### `client.create(pluralId, payload, options?)`
|
|
208
|
+
|
|
209
|
+
Creates a new entry in the collection.
|
|
210
|
+
|
|
211
|
+
```ts
|
|
212
|
+
const { data, meta } = await client.create(pluralId, payload, options?)
|
|
213
|
+
```
|
|
214
|
+
|
|
215
|
+
| Parameter | Type | Description |
|
|
216
|
+
| ----------------- | ------------------------ | --------------------------------------------------------------- |
|
|
217
|
+
| `pluralId` | `string` | Strapi collection plural API ID |
|
|
218
|
+
| `payload` | `any` | Data for the new entry (will be wrapped in `{ data: payload }`) |
|
|
219
|
+
| `options.schema` | `Schema` | Schema for parsing the response |
|
|
220
|
+
| `options.params` | `Record<string, any>` | Additional query params |
|
|
221
|
+
| `options.headers` | `Record<string, string>` | Request-specific extra headers |
|
|
222
|
+
|
|
223
|
+
**Returns:** `Promise<{ data: InferSchemaWithDefaults<S>; meta: any }>`
|
|
224
|
+
|
|
225
|
+
**Example:**
|
|
226
|
+
|
|
227
|
+
```ts
|
|
228
|
+
const { data } = await client.create("articles", { title: "Hello World", slug: "hello-world" });
|
|
229
|
+
```
|
|
230
|
+
|
|
231
|
+
---
|
|
232
|
+
|
|
233
|
+
### `client.delete(pluralId, documentId, options?)`
|
|
234
|
+
|
|
235
|
+
Deletes an entry by `documentId`.
|
|
236
|
+
|
|
237
|
+
```ts
|
|
238
|
+
const { data, meta } = await client.delete(pluralId, documentId, options?)
|
|
239
|
+
```
|
|
240
|
+
|
|
241
|
+
| Parameter | Type | Description |
|
|
242
|
+
| ----------------- | ------------------------ | ------------------------------- |
|
|
243
|
+
| `pluralId` | `string` | Strapi collection plural API ID |
|
|
244
|
+
| `documentId` | `string` | The Strapi v5 document ID |
|
|
245
|
+
| `options.params` | `Record<string, any>` | Additional query params |
|
|
246
|
+
| `options.headers` | `Record<string, string>` | Request-specific extra headers |
|
|
247
|
+
|
|
248
|
+
**Returns:** `Promise<{ data: { documentId: string }; meta: {} }>` (on 204) or `Promise<{ data: any; meta: any }>`
|
|
249
|
+
|
|
250
|
+
**Example:**
|
|
251
|
+
|
|
252
|
+
```ts
|
|
253
|
+
await client.delete("articles", "abc123");
|
|
254
|
+
```
|
|
255
|
+
|
|
256
|
+
---
|
|
257
|
+
|
|
258
|
+
### `client.upload(file, options?)`
|
|
259
|
+
|
|
260
|
+
Uploads a file to the Strapi Media Library. Returns an array of the uploaded media objects.
|
|
261
|
+
|
|
262
|
+
```ts
|
|
263
|
+
const media = await client.upload(file, options?)
|
|
264
|
+
```
|
|
265
|
+
|
|
266
|
+
| Parameter | Type | Description |
|
|
267
|
+
| ------------------ | ------------------------ | -------------------------------------------------------------------------------------------------------------------------------------- |
|
|
268
|
+
| `file` | `Blob \| File \| string` | File source: a `Blob`, a browser `File`, a base64 data URI (`data:mime;base64,...`), or a raw base64 string |
|
|
269
|
+
| `options.filename` | `string` | File name in the upload form. Required for `Blob` and raw base64; auto-extracted from `File` |
|
|
270
|
+
| `options.ref` | `string` | Content Type name (e.g. `"product"` → auto-resolves to `api::product.product`) or a full UID (e.g. `"plugin::users-permissions.user"`) |
|
|
271
|
+
| `options.refId` | `string \| number` | `documentId` of the entity to attach the file to |
|
|
272
|
+
| `options.field` | `string` | Top-level field name on the entity. Nested fields (dot-notation) are not supported by Strapi's `/upload` endpoint |
|
|
273
|
+
| `options.headers` | `Record<string, string>` | Extra request headers |
|
|
274
|
+
|
|
275
|
+
**Returns:** `Promise<ZodMediaType[]>`
|
|
276
|
+
|
|
277
|
+
**Examples:**
|
|
278
|
+
|
|
279
|
+
```ts
|
|
280
|
+
// Upload a File from a browser input
|
|
281
|
+
const [file] = inputEl.files!;
|
|
282
|
+
const [uploaded] = await client.upload(file, { path: "products/2024" });
|
|
283
|
+
console.log(uploaded.url);
|
|
284
|
+
|
|
285
|
+
// Upload a base64 data URI and attach it to an entity
|
|
286
|
+
const [media] = await client.upload("data:image/png;base64,iVBORw0KGgo...", {
|
|
287
|
+
filename: "cover.png",
|
|
288
|
+
ref: "article", // auto-resolved to api::article.article
|
|
289
|
+
refId: "abc123",
|
|
290
|
+
field: "cover",
|
|
291
|
+
});
|
|
292
|
+
|
|
293
|
+
// Upload a Blob with a custom filename
|
|
294
|
+
const blob = new Blob([buffer], { type: "image/jpeg" });
|
|
295
|
+
const [result] = await client.upload(blob, { filename: "photo.jpg" });
|
|
296
|
+
```
|
|
297
|
+
|
|
298
|
+
> **Note:** Strapi's `/upload` endpoint does not support attaching files to nested fields via dot-notation. Upload the file first, then use `client.update()` to set the field.
|
|
299
|
+
|
|
300
|
+
---
|
|
301
|
+
|
|
302
|
+
## Schema field helpers
|
|
303
|
+
|
|
304
|
+
Schemas are plain objects where each key maps to a field definition tuple. All field helpers accept an optional `options` object.
|
|
305
|
+
|
|
306
|
+
> **Default nullability**: all fields default to `T | null | undefined` unless `{ required: true }` is passed. This reflects Strapi's real-world behavior where fields are frequently optional.
|
|
307
|
+
|
|
308
|
+
---
|
|
309
|
+
|
|
310
|
+
### `text(options?)`
|
|
311
|
+
|
|
312
|
+
A string field.
|
|
313
|
+
|
|
314
|
+
```ts
|
|
315
|
+
import { text } from "simple-strapi";
|
|
316
|
+
|
|
317
|
+
text(); // string | null | undefined
|
|
318
|
+
text({ required: true }); // string
|
|
319
|
+
```
|
|
320
|
+
|
|
321
|
+
| Option | Type | Default | Description |
|
|
322
|
+
| ---------- | --------- | ------- | ------------------------------------------------------------------ |
|
|
323
|
+
| `required` | `boolean` | `false` | If true, type is `string` instead of `string \| null \| undefined` |
|
|
324
|
+
|
|
325
|
+
TypeScript types: `TextField`, `TextOptions`, `InferText<O>`
|
|
326
|
+
|
|
327
|
+
---
|
|
328
|
+
|
|
329
|
+
### `number(options?)`
|
|
330
|
+
|
|
331
|
+
A numeric field.
|
|
332
|
+
|
|
333
|
+
```ts
|
|
334
|
+
import { number } from "simple-strapi";
|
|
335
|
+
|
|
336
|
+
number(); // number | null | undefined
|
|
337
|
+
number({ required: true }); // number
|
|
338
|
+
```
|
|
339
|
+
|
|
340
|
+
| Option | Type | Default | Description |
|
|
341
|
+
| ---------- | --------- | ------- | ------------------------------------------------------------------ |
|
|
342
|
+
| `required` | `boolean` | `false` | If true, type is `number` instead of `number \| null \| undefined` |
|
|
343
|
+
|
|
344
|
+
TypeScript types: `NumberField`, `NumberOptions`, `InferNumber<O>`
|
|
345
|
+
|
|
346
|
+
---
|
|
347
|
+
|
|
348
|
+
### `boolean(options?)`
|
|
349
|
+
|
|
350
|
+
A boolean field.
|
|
351
|
+
|
|
352
|
+
```ts
|
|
353
|
+
import { boolean } from "simple-strapi";
|
|
354
|
+
|
|
355
|
+
boolean(); // boolean | null | undefined
|
|
356
|
+
boolean({ required: true }); // boolean
|
|
357
|
+
```
|
|
358
|
+
|
|
359
|
+
| Option | Type | Default | Description |
|
|
360
|
+
| ---------- | --------- | ------- | -------------------------------------------------------------------- |
|
|
361
|
+
| `required` | `boolean` | `false` | If true, type is `boolean` instead of `boolean \| null \| undefined` |
|
|
362
|
+
|
|
363
|
+
TypeScript types: `BooleanField`, `BooleanOptions`, `InferBoolean<O>`
|
|
364
|
+
|
|
365
|
+
---
|
|
366
|
+
|
|
367
|
+
### `json(options?)`
|
|
368
|
+
|
|
369
|
+
A JSON field (typed as `any`).
|
|
370
|
+
|
|
371
|
+
```ts
|
|
372
|
+
import { json } from "simple-strapi";
|
|
373
|
+
|
|
374
|
+
json(); // any | null | undefined
|
|
375
|
+
json({ required: true }); // any
|
|
376
|
+
```
|
|
377
|
+
|
|
378
|
+
| Option | Type | Default | Description |
|
|
379
|
+
| ---------- | --------- | ------- | ------------------------------------------------------------ |
|
|
380
|
+
| `required` | `boolean` | `false` | If true, type is `any` instead of `any \| null \| undefined` |
|
|
381
|
+
|
|
382
|
+
TypeScript types: `JSONField`, `JSONOptions`, `InferJSON<O>`
|
|
383
|
+
|
|
384
|
+
---
|
|
385
|
+
|
|
386
|
+
### `enumeration(values, options?)`
|
|
387
|
+
|
|
388
|
+
An enum field constrained to a fixed list of string values.
|
|
389
|
+
|
|
390
|
+
```ts
|
|
391
|
+
import { enumeration } from "simple-strapi";
|
|
392
|
+
|
|
393
|
+
enumeration(["draft", "published", "archived"]);
|
|
394
|
+
// "draft" | "published" | "archived" | null | undefined
|
|
395
|
+
|
|
396
|
+
enumeration(["draft", "published"], { required: true });
|
|
397
|
+
// "draft" | "published"
|
|
398
|
+
```
|
|
399
|
+
|
|
400
|
+
| Parameter | Type | Description |
|
|
401
|
+
| --------- | -------------------------------- | ---------------------------------------- |
|
|
402
|
+
| `values` | `readonly [string, ...string[]]` | Non-empty tuple of allowed string values |
|
|
403
|
+
|
|
404
|
+
| Option | Type | Default | Description |
|
|
405
|
+
| ---------- | --------- | ------- | ------------------------------------------------------------------------ |
|
|
406
|
+
| `required` | `boolean` | `false` | If true, type is `V[number]` instead of `V[number] \| null \| undefined` |
|
|
407
|
+
|
|
408
|
+
TypeScript types: `EnumerationField`, `EnumerationOptions`, `InferEnumeration<V, O>`
|
|
409
|
+
|
|
410
|
+
---
|
|
411
|
+
|
|
412
|
+
### `media.single(options?)`
|
|
413
|
+
|
|
414
|
+
A single Strapi media upload field. Automatically adds the correct `populate` entry.
|
|
415
|
+
|
|
416
|
+
```ts
|
|
417
|
+
import { media } from "simple-strapi";
|
|
418
|
+
|
|
419
|
+
media.single(); // MediaType | null | undefined
|
|
420
|
+
media.single({ required: true }); // MediaType
|
|
421
|
+
```
|
|
422
|
+
|
|
423
|
+
The resolved `MediaType` shape:
|
|
424
|
+
|
|
425
|
+
```ts
|
|
426
|
+
{
|
|
427
|
+
id: number;
|
|
428
|
+
name: string;
|
|
429
|
+
alternativeText: string | null;
|
|
430
|
+
caption: string | null;
|
|
431
|
+
width: number | null;
|
|
432
|
+
height: number | null;
|
|
433
|
+
formats: Record<string, { name; hash?; ext?; mime; path?; size; url; width; height }> |
|
|
434
|
+
null |
|
|
435
|
+
undefined;
|
|
436
|
+
hash: string;
|
|
437
|
+
ext: string;
|
|
438
|
+
mime: string;
|
|
439
|
+
size: number;
|
|
440
|
+
url: string;
|
|
441
|
+
previewUrl: string | null;
|
|
442
|
+
provider: string;
|
|
443
|
+
provider_metadata: unknown | null;
|
|
444
|
+
createdAt: string;
|
|
445
|
+
updatedAt: string;
|
|
446
|
+
}
|
|
447
|
+
```
|
|
448
|
+
|
|
449
|
+
| Option | Type | Default | Description |
|
|
450
|
+
| ---------- | --------- | ------- | ------------------------------------------------------------------------ |
|
|
451
|
+
| `required` | `boolean` | `false` | If true, type is `MediaType` instead of `MediaType \| null \| undefined` |
|
|
452
|
+
|
|
453
|
+
TypeScript types: `MediaSingleField`, `MediaSingleOptions`, `InferMediaSingle<O>`, `ZodMediaType`, `zodMediaSchema`
|
|
454
|
+
|
|
455
|
+
---
|
|
456
|
+
|
|
457
|
+
### `richText.blocks(options?)`
|
|
458
|
+
|
|
459
|
+
A Strapi rich text blocks field (Strapi v5 block editor format).
|
|
460
|
+
|
|
461
|
+
```ts
|
|
462
|
+
import { richText } from "simple-strapi";
|
|
463
|
+
|
|
464
|
+
richText.blocks(); // RichTextBlocks | null | undefined
|
|
465
|
+
richText.blocks({ required: true }); // RichTextBlocks
|
|
466
|
+
```
|
|
467
|
+
|
|
468
|
+
`RichTextBlocks` is an array of block nodes:
|
|
469
|
+
|
|
470
|
+
```ts
|
|
471
|
+
type RichTextBlocks = Array<
|
|
472
|
+
| { type: "paragraph"; children: ParagraphChild[] }
|
|
473
|
+
| { type: "heading"; level: number; children: ParagraphChild[] }
|
|
474
|
+
| { type: "list"; format: "ordered" | "unordered"; children: ListItemBlock[] }
|
|
475
|
+
>;
|
|
476
|
+
|
|
477
|
+
type ParagraphChild =
|
|
478
|
+
| {
|
|
479
|
+
type: "text";
|
|
480
|
+
text: string;
|
|
481
|
+
bold?: boolean;
|
|
482
|
+
italic?: boolean;
|
|
483
|
+
underline?: boolean;
|
|
484
|
+
strikethrough?: boolean;
|
|
485
|
+
code?: boolean;
|
|
486
|
+
}
|
|
487
|
+
| { type: "link"; url: string; children: ParagraphChild[] };
|
|
488
|
+
```
|
|
489
|
+
|
|
490
|
+
| Option | Type | Default | Description |
|
|
491
|
+
| ---------- | --------- | ------- | ---------------------------------------------------------------------------------- |
|
|
492
|
+
| `required` | `boolean` | `false` | If true, type is `RichTextBlocks` instead of `RichTextBlocks \| null \| undefined` |
|
|
493
|
+
|
|
494
|
+
TypeScript types: `RichTextBlocksField`, `RichTextBlocksOptions`, `InferRichTextBlocks<O>`, `RichTextBlocks`, `ParagraphChild`, `zodRichTextBlocksSchema`, `paragraphChild`
|
|
495
|
+
|
|
496
|
+
---
|
|
497
|
+
|
|
498
|
+
### `component.single(schema, options?)`
|
|
499
|
+
|
|
500
|
+
A Strapi single (non-repeatable) component. Automatically populates nested fields.
|
|
501
|
+
|
|
502
|
+
```ts
|
|
503
|
+
import { component, text, media } from "simple-strapi";
|
|
504
|
+
|
|
505
|
+
component.single({ title: text(), image: media.single() });
|
|
506
|
+
// { id: number; documentId?: string; title: string | null | undefined; image: MediaType | null | undefined; ... } | null | undefined
|
|
507
|
+
|
|
508
|
+
component.single({ title: text() }, { required: true });
|
|
509
|
+
// { id: number; ...; title: string | null | undefined } — not nullable
|
|
510
|
+
```
|
|
511
|
+
|
|
512
|
+
| Parameter | Type | Description |
|
|
513
|
+
| --------- | -------- | ----------------------------- |
|
|
514
|
+
| `schema` | `Schema` | Field schema of the component |
|
|
515
|
+
|
|
516
|
+
| Option | Type | Default | Description |
|
|
517
|
+
| ---------- | --------- | ------- | ------------------------------------------- |
|
|
518
|
+
| `required` | `boolean` | `false` | If true, component is not nullable/optional |
|
|
519
|
+
|
|
520
|
+
TypeScript types: `ComponentSingleField`, `ComponentSingleOptions`, `InferComponentSingle<S, O>`
|
|
521
|
+
|
|
522
|
+
---
|
|
523
|
+
|
|
524
|
+
### `component.repeatable(schema, options?)`
|
|
525
|
+
|
|
526
|
+
A Strapi repeatable component (array). Automatically populates nested fields.
|
|
527
|
+
|
|
528
|
+
```ts
|
|
529
|
+
import { component, text } from "simple-strapi";
|
|
530
|
+
|
|
531
|
+
component.repeatable({ label: text(), value: text() });
|
|
532
|
+
// Array<{ id: number; label: string | null | undefined; ... }> | null | undefined
|
|
533
|
+
|
|
534
|
+
component.repeatable({ label: text() }, { required: true });
|
|
535
|
+
// Array<{ id: number; label: string | null | undefined; ... }>
|
|
536
|
+
```
|
|
537
|
+
|
|
538
|
+
| Parameter | Type | Description |
|
|
539
|
+
| --------- | -------- | ----------------------------------- |
|
|
540
|
+
| `schema` | `Schema` | Field schema of the component items |
|
|
541
|
+
|
|
542
|
+
| Option | Type | Default | Description |
|
|
543
|
+
| ---------- | --------- | ------- | --------------------------------------- |
|
|
544
|
+
| `required` | `boolean` | `false` | If true, array is not nullable/optional |
|
|
545
|
+
|
|
546
|
+
TypeScript types: `ComponentRepeatableField`, `ComponentRepeatableOptions`, `InferComponentRepeatable<S, O>`
|
|
547
|
+
|
|
548
|
+
---
|
|
549
|
+
|
|
550
|
+
### `relation.hasMany(schema, options?)`
|
|
551
|
+
|
|
552
|
+
A Strapi relation that returns multiple related entries. Automatically populates the relation.
|
|
553
|
+
|
|
554
|
+
```ts
|
|
555
|
+
import { relation, text } from "simple-strapi";
|
|
556
|
+
|
|
557
|
+
relation.hasMany({ name: text() });
|
|
558
|
+
// Array<{ id: number; name: string | null | undefined; ... }>
|
|
559
|
+
|
|
560
|
+
relation.hasMany({ name: text() }, { nullable: true, optional: true });
|
|
561
|
+
// Array<...> | null | undefined
|
|
562
|
+
```
|
|
563
|
+
|
|
564
|
+
| Parameter | Type | Description |
|
|
565
|
+
| --------- | -------- | ----------------------------------- |
|
|
566
|
+
| `schema` | `Schema` | Field schema of the related entries |
|
|
567
|
+
|
|
568
|
+
| Option | Type | Default | Description |
|
|
569
|
+
| ---------- | --------- | ------- | ---------------------------------- |
|
|
570
|
+
| `nullable` | `boolean` | `false` | If true, result can be `null` |
|
|
571
|
+
| `optional` | `boolean` | `false` | If true, result can be `undefined` |
|
|
572
|
+
|
|
573
|
+
TypeScript types: `RelationHasManyField`, `RelationHasManyOptions`, `InferRelationHasMany<S, O>`
|
|
574
|
+
|
|
575
|
+
---
|
|
576
|
+
|
|
577
|
+
### `relation.hasOne(schema, options?)`
|
|
578
|
+
|
|
579
|
+
A Strapi relation that returns a single related entry. Automatically populates the relation.
|
|
580
|
+
|
|
581
|
+
```ts
|
|
582
|
+
import { relation, text } from "simple-strapi";
|
|
583
|
+
|
|
584
|
+
relation.hasOne({ name: text() });
|
|
585
|
+
// { id: number; name: string | null | undefined; ... }
|
|
586
|
+
|
|
587
|
+
relation.hasOne({ name: text() }, { nullable: true });
|
|
588
|
+
// { id: number; ... } | null
|
|
589
|
+
```
|
|
590
|
+
|
|
591
|
+
| Parameter | Type | Description |
|
|
592
|
+
| --------- | -------- | --------------------------------- |
|
|
593
|
+
| `schema` | `Schema` | Field schema of the related entry |
|
|
594
|
+
|
|
595
|
+
| Option | Type | Default | Description |
|
|
596
|
+
| ---------- | --------- | ------- | ---------------------------------- |
|
|
597
|
+
| `nullable` | `boolean` | `false` | If true, result can be `null` |
|
|
598
|
+
| `optional` | `boolean` | `false` | If true, result can be `undefined` |
|
|
599
|
+
|
|
600
|
+
TypeScript types: `RelationHasOneField`, `RelationHasOneOptions`, `InferRelationHasOne<S, O>`
|
|
601
|
+
|
|
602
|
+
---
|
|
603
|
+
|
|
604
|
+
### `dynamic(blocks, options?)`
|
|
605
|
+
|
|
606
|
+
A Strapi dynamic zone. Each key is a component UID, each value is the component's schema. The result is a discriminated union array tagged with `__component`.
|
|
607
|
+
|
|
608
|
+
```ts
|
|
609
|
+
import { dynamic, text, media, enumeration, component, richText } from "simple-strapi";
|
|
610
|
+
|
|
611
|
+
dynamic({
|
|
612
|
+
"blocks.hero": { title: text({ required: true }), image: media.single() },
|
|
613
|
+
"blocks.content": { body: richText.blocks() },
|
|
614
|
+
});
|
|
615
|
+
// Array<
|
|
616
|
+
// | { __component: "blocks.hero"; title: string; image: MediaType | null | undefined }
|
|
617
|
+
// | { __component: "blocks.content"; body: RichTextBlocks | null | undefined }
|
|
618
|
+
// >
|
|
619
|
+
```
|
|
620
|
+
|
|
621
|
+
| Parameter | Type | Description |
|
|
622
|
+
| --------- | ------------------------ | -------------------------------------- |
|
|
623
|
+
| `blocks` | `Record<string, Schema>` | Map of component UIDs to their schemas |
|
|
624
|
+
|
|
625
|
+
| Option | Type | Default | Description |
|
|
626
|
+
| ---------- | --------- | ------- | ---------------------------------- |
|
|
627
|
+
| `nullable` | `boolean` | `false` | If true, result can be `null` |
|
|
628
|
+
| `optional` | `boolean` | `false` | If true, result can be `undefined` |
|
|
629
|
+
|
|
630
|
+
TypeScript types: `DynamicField`, `DynamicOptions`, `InferDynamic<B, O>`
|
|
631
|
+
|
|
632
|
+
---
|
|
633
|
+
|
|
634
|
+
## TypeScript type utilities
|
|
635
|
+
|
|
636
|
+
All types exported from `simple-strapi`:
|
|
637
|
+
|
|
638
|
+
| Type | Description |
|
|
639
|
+
| ---------------------------- | ----------------------------------------------------------------------------------------- |
|
|
640
|
+
| `Schema` | `Record<string, SchemaField>` — the shape of a schema definition |
|
|
641
|
+
| `SchemaField` | Union of all field tuple types |
|
|
642
|
+
| `InferSchema<S>` | Infers the TypeScript shape from a `Schema` (without default Strapi fields) |
|
|
643
|
+
| `InferSchemaWithDefaults<S>` | Same as `InferSchema<S>` plus `id`, `documentId`, `createdAt`, `updatedAt`, `publishedAt` |
|
|
644
|
+
|
|
645
|
+
---
|
|
646
|
+
|
|
647
|
+
## Default Strapi fields
|
|
648
|
+
|
|
649
|
+
Every entity returned by the client automatically includes these fields (regardless of schema):
|
|
650
|
+
|
|
651
|
+
| Field | Type |
|
|
652
|
+
| ------------- | -------------------------------------------- |
|
|
653
|
+
| `id` | `number` |
|
|
654
|
+
| `documentId` | `string \| undefined` |
|
|
655
|
+
| `createdAt` | `string \| undefined` (ISO datetime) |
|
|
656
|
+
| `updatedAt` | `string \| undefined` (ISO datetime) |
|
|
657
|
+
| `publishedAt` | `string \| null \| undefined` (ISO datetime) |
|
|
658
|
+
|
|
659
|
+
---
|
|
660
|
+
|
|
661
|
+
## Full example
|
|
662
|
+
|
|
663
|
+
```ts
|
|
664
|
+
import {
|
|
665
|
+
StrapiClient,
|
|
666
|
+
text,
|
|
667
|
+
number,
|
|
668
|
+
boolean,
|
|
669
|
+
json,
|
|
670
|
+
enumeration,
|
|
671
|
+
media,
|
|
672
|
+
richText,
|
|
673
|
+
component,
|
|
674
|
+
dynamic,
|
|
675
|
+
relation,
|
|
676
|
+
} from "simple-strapi";
|
|
677
|
+
|
|
678
|
+
const client = await StrapiClient.create(process.env.STRAPI_URL!, {
|
|
679
|
+
auth: process.env.STRAPI_TOKEN,
|
|
680
|
+
});
|
|
681
|
+
|
|
682
|
+
const { data: events } = await client.getCollection("events", {
|
|
683
|
+
pagination: false,
|
|
684
|
+
sort: "publishedAt:desc",
|
|
685
|
+
filters: { status: { $eq: "published" } },
|
|
686
|
+
schema: {
|
|
687
|
+
title: text({ required: true }),
|
|
688
|
+
slug: text({ required: true }),
|
|
689
|
+
status: enumeration(["draft", "published", "archived"], { required: true }),
|
|
690
|
+
featured: boolean(),
|
|
691
|
+
price: number(),
|
|
692
|
+
metadata: json(),
|
|
693
|
+
cover: media.single({ required: true }),
|
|
694
|
+
tags: relation.hasMany({ name: text({ required: true }) }),
|
|
695
|
+
location: relation.hasOne({ city: text(), country: text() }),
|
|
696
|
+
header: component.single({
|
|
697
|
+
headline: text({ required: true }),
|
|
698
|
+
background: media.single(),
|
|
699
|
+
}),
|
|
700
|
+
speakers: component.repeatable({
|
|
701
|
+
name: text({ required: true }),
|
|
702
|
+
bio: richText.blocks(),
|
|
703
|
+
avatar: media.single(),
|
|
704
|
+
}),
|
|
705
|
+
blocks: dynamic({
|
|
706
|
+
"blocks.text": { body: richText.blocks({ required: true }) },
|
|
707
|
+
"blocks.gallery": {
|
|
708
|
+
layout: enumeration(["grid", "masonry"]),
|
|
709
|
+
images: component.repeatable({ image: media.single({ required: true }), caption: text() }),
|
|
710
|
+
},
|
|
711
|
+
}),
|
|
712
|
+
},
|
|
713
|
+
});
|
|
714
|
+
```
|
|
715
|
+
|
|
716
|
+
---
|
|
717
|
+
|
|
718
|
+
## License
|
|
719
|
+
|
|
720
|
+
[ISC](../LICENSE)
|
|
721
|
+
|
|
722
|
+
## Maintainer
|
|
723
|
+
|
|
724
|
+
[@hund-ernesto](https://github.com/hund-ernesto)
|
package/dist/client.d.ts
CHANGED
|
@@ -6,7 +6,7 @@ import z from "zod";
|
|
|
6
6
|
import { DynamicField, DynamicOptions, InferDynamic } from "./fields/dynamic";
|
|
7
7
|
import { defaultStrapiFieldsSchema } from "./utils/schema";
|
|
8
8
|
import { ComponentRepeatableField, ComponentRepeatableOptions, ComponentSingleField, ComponentSingleOptions, InferComponentRepeatable, InferComponentSingle } from "./fields/component";
|
|
9
|
-
import { InferMediaSingle, MediaSingleField, MediaSingleOptions, ZodMediaType } from "./fields/media";
|
|
9
|
+
import { InferMediaSingle, MediaSingleField, MediaSingleOptions, InferMediaMultiple, MediaMultipleField, MediaMultipleOptions, ZodMediaType } from "./fields/media";
|
|
10
10
|
import { EnumerationField, EnumerationOptions, InferEnumeration } from "./fields/enumeration";
|
|
11
11
|
import { InferRichTextBlocks, RichTextBlocksField, RichTextBlocksOptions } from "./fields/richText";
|
|
12
12
|
import { InferJSON, JSONField, JSONOptions } from "./fields/json";
|
|
@@ -16,7 +16,7 @@ type EntityRequest<P = {}> = {
|
|
|
16
16
|
params?: RequestParams;
|
|
17
17
|
headers?: Record<string, string>;
|
|
18
18
|
} & P;
|
|
19
|
-
export type SchemaField = TextField | NumberField | BooleanField | RelationHasManyField | RelationHasOneField | DynamicField | ComponentSingleField | ComponentRepeatableField | MediaSingleField | EnumerationField | RichTextBlocksField | JSONField;
|
|
19
|
+
export type SchemaField = TextField | NumberField | BooleanField | RelationHasManyField | RelationHasOneField | DynamicField | ComponentSingleField | ComponentRepeatableField | MediaSingleField | MediaMultipleField | EnumerationField | RichTextBlocksField | JSONField;
|
|
20
20
|
export type Schema = Record<string, SchemaField>;
|
|
21
21
|
export type InferSchema<S extends Schema> = {
|
|
22
22
|
[K in keyof S]: S[K] extends ["text", infer O extends TextOptions] ? InferText<O> : S[K] extends ["number", infer O extends NumberOptions] ? InferNumber<O> : S[K] extends ["boolean", infer O extends BooleanOptions] ? InferBoolean<O> : S[K] extends ["json", infer O extends JSONOptions] ? InferJSON<O> : S[K] extends [
|
|
@@ -39,7 +39,7 @@ export type InferSchema<S extends Schema> = {
|
|
|
39
39
|
"dynamic",
|
|
40
40
|
infer B extends Record<string, Schema>,
|
|
41
41
|
infer O extends DynamicOptions
|
|
42
|
-
] ? InferDynamic<B, O> : S[K] extends ["media.single", infer O extends MediaSingleOptions] ? InferMediaSingle<O> : S[K] extends [
|
|
42
|
+
] ? InferDynamic<B, O> : S[K] extends ["media.single", infer O extends MediaSingleOptions] ? InferMediaSingle<O> : S[K] extends ["media.multiple", infer O extends MediaMultipleOptions] ? InferMediaMultiple<O> : S[K] extends [
|
|
43
43
|
"enumeration",
|
|
44
44
|
infer V extends readonly [string, ...string[]],
|
|
45
45
|
infer O extends EnumerationOptions
|
package/dist/client.js
CHANGED
package/dist/fields/media.d.ts
CHANGED
|
@@ -35,6 +35,13 @@ export type MediaSingleOptions = {
|
|
|
35
35
|
export type InferMediaSingle<O extends MediaSingleOptions> = O["required"] extends true ? ZodMediaType : ZodMediaType | null | undefined;
|
|
36
36
|
export declare const mediaSingleSchema: (opts: MediaSingleOptions) => ZodType;
|
|
37
37
|
export type MediaSingleField = readonly ["media.single", MediaSingleOptions];
|
|
38
|
+
export type MediaMultipleOptions = {
|
|
39
|
+
required?: boolean;
|
|
40
|
+
};
|
|
41
|
+
export type InferMediaMultiple<O extends MediaMultipleOptions> = O["required"] extends true ? ZodMediaType[] : ZodMediaType[] | null | undefined;
|
|
42
|
+
export declare const mediaMultipleSchema: (opts: MediaMultipleOptions) => ZodType;
|
|
43
|
+
export type MediaMultipleField = readonly ["media.multiple", MediaMultipleOptions];
|
|
38
44
|
export declare const media: {
|
|
39
45
|
single: <O extends MediaSingleOptions = {}>(options?: O) => ["media.single", O];
|
|
46
|
+
multiple: <O extends MediaMultipleOptions = {}>(options?: O) => ["media.multiple", O];
|
|
40
47
|
};
|
package/dist/fields/media.js
CHANGED
|
@@ -36,10 +36,17 @@ const single = (options = {}) => {
|
|
|
36
36
|
};
|
|
37
37
|
export const mediaSingleSchema = (opts) => {
|
|
38
38
|
let schema = zodMediaSchema;
|
|
39
|
-
// if (opts.nullable) schema = schema.nullable();
|
|
40
|
-
// if (opts.optional) schema = schema.optional();
|
|
41
39
|
if (!opts.required)
|
|
42
40
|
schema = schema.nullable().optional();
|
|
43
41
|
return schema;
|
|
44
42
|
};
|
|
45
|
-
|
|
43
|
+
const multiple = (options = {}) => {
|
|
44
|
+
return ["media.multiple", options];
|
|
45
|
+
};
|
|
46
|
+
export const mediaMultipleSchema = (opts) => {
|
|
47
|
+
let schema = z.array(zodMediaSchema);
|
|
48
|
+
if (!opts.required)
|
|
49
|
+
schema = schema.nullable().optional();
|
|
50
|
+
return schema;
|
|
51
|
+
};
|
|
52
|
+
export const media = { single, multiple };
|
package/dist/utils/schema.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { booleanSchema } from "../fields/boolean";
|
|
2
2
|
import { dynamicSchema } from "../fields/dynamic";
|
|
3
3
|
import { enumerationSchema } from "../fields/enumeration";
|
|
4
|
-
import { mediaSingleSchema } from "../fields/media";
|
|
4
|
+
import { mediaSingleSchema, mediaMultipleSchema } from "../fields/media";
|
|
5
5
|
import { numberSchema } from "../fields/number";
|
|
6
6
|
import { repeatableSchema, singleSchema } from "../fields/component";
|
|
7
7
|
import { richTextBlocksSchema } from "../fields/richText";
|
|
@@ -60,6 +60,11 @@ export const schemaToParser = (schema) => {
|
|
|
60
60
|
shape[key] = mediaSingleSchema(args);
|
|
61
61
|
break;
|
|
62
62
|
}
|
|
63
|
+
case "media.multiple": {
|
|
64
|
+
const [, args] = field;
|
|
65
|
+
shape[key] = mediaMultipleSchema(args);
|
|
66
|
+
break;
|
|
67
|
+
}
|
|
63
68
|
case "enumeration": {
|
|
64
69
|
const [, values, options] = field;
|
|
65
70
|
shape[key] = enumerationSchema(values, options);
|
package/package.json
CHANGED
|
@@ -1,16 +1,23 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "simple-strapi",
|
|
3
|
-
"version": "1.0.0-alpha.
|
|
3
|
+
"version": "1.0.0-alpha.27",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"main": "./dist/index.js",
|
|
6
6
|
"types": "./dist/index.d.ts",
|
|
7
|
-
"readme": "docs/NPM.md",
|
|
8
7
|
"exports": {
|
|
9
8
|
".": {
|
|
10
9
|
"import": "./dist/index.js",
|
|
11
10
|
"types": "./dist/index.d.ts"
|
|
12
11
|
}
|
|
13
12
|
},
|
|
13
|
+
"files": [
|
|
14
|
+
"dist",
|
|
15
|
+
"README.md"
|
|
16
|
+
],
|
|
17
|
+
"publishConfig": {
|
|
18
|
+
"access": "public",
|
|
19
|
+
"provenance": true
|
|
20
|
+
},
|
|
14
21
|
"repository": {
|
|
15
22
|
"type": "git",
|
|
16
23
|
"url": "git+https://github.com/hund-studio/simple-strapi.git"
|
|
@@ -22,20 +29,15 @@
|
|
|
22
29
|
"scripts": {
|
|
23
30
|
"build": "tsc",
|
|
24
31
|
"dev": "tsc --watch",
|
|
25
|
-
"
|
|
26
|
-
"
|
|
27
|
-
"
|
|
28
|
-
"
|
|
29
|
-
"
|
|
30
|
-
"
|
|
31
|
-
"
|
|
32
|
-
"publish:premajor": "tsx scripts/publish.ts premajor alpha",
|
|
33
|
-
"publish:major": "tsx scripts/publish.ts major",
|
|
32
|
+
"copy:readme": "cp docs/readme.md ./README.md",
|
|
33
|
+
"postpublish": "rm ./README.md",
|
|
34
|
+
"prepublishOnly": "npm run build && npm run copy:readme",
|
|
35
|
+
"release:alpha": "npm version prerelease --preid alpha && git push --follow-tags",
|
|
36
|
+
"release:beta": "npm version prerelease --preid beta && git push --follow-tags",
|
|
37
|
+
"release:minor": "npm version minor && git push --follow-tags",
|
|
38
|
+
"release:patch": "npm version patch && git push --follow-tags",
|
|
34
39
|
"test": "echo \"Error: no test specified\" && exit 1"
|
|
35
40
|
},
|
|
36
|
-
"files": [
|
|
37
|
-
"dist"
|
|
38
|
-
],
|
|
39
41
|
"keywords": [],
|
|
40
42
|
"author": "",
|
|
41
43
|
"license": "ISC",
|