fimo 0.4.0-experimental.2 → 0.4.0-experimental.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/assets/content-templates/hooks-template.eta +193 -121
- package/assets/content-templates/types-template.eta +162 -0
- package/assets/skills/fimo/references/content.md +92 -39
- package/assets/skills/fimo/references/locales.md +1 -1
- package/assets/skills/fimo-cli/references/content.md +17 -9
- package/assets/skills/fimo-cli/references/locales.md +2 -2
- package/dist/cli/bundle.json +2 -2
- package/dist/cli/index.js +188 -64
- package/dist/runtime/primitives/index.d.ts +2 -2
- package/dist/runtime/primitives/index.d.ts.map +1 -1
- package/dist/runtime/primitives/lib/index.d.ts +2 -2
- package/dist/runtime/primitives/lib/index.d.ts.map +1 -1
- package/dist/runtime/primitives/lib/primitives.d.ts +3 -0
- package/dist/runtime/primitives/lib/primitives.d.ts.map +1 -1
- package/dist/runtime/primitives/lib/query.d.ts +46 -8
- package/dist/runtime/primitives/lib/query.d.ts.map +1 -1
- package/dist/runtime/primitives/public-surface.test.js +27 -1
- package/dist/scripts/sync-schemas.js +91 -27
- package/dist/scripts/sync-schemas.test.d.ts +1 -0
- package/dist/scripts/sync-schemas.test.js +121 -0
- package/dist/scripts/validate-schemas.js +31 -4
- package/dist/scripts/validate-schemas.test.js +49 -0
- package/package.json +1 -1
- package/templates/react-router/package.json +1 -1
|
@@ -10,6 +10,7 @@ This file covers the **package surface**: the JSON shape you write, the generate
|
|
|
10
10
|
{
|
|
11
11
|
"uid": "BlogPost",
|
|
12
12
|
"name": "Blog Posts",
|
|
13
|
+
"i18n": { "enabled": true },
|
|
13
14
|
"fields": {
|
|
14
15
|
"title": { "type": "string" },
|
|
15
16
|
"body": { "type": "richtext" },
|
|
@@ -25,6 +26,8 @@ This file covers the **package surface**: the JSON shape you write, the generate
|
|
|
25
26
|
|
|
26
27
|
**Required**: `uid` (PascalCase, matches filename), `name`, `fields`.
|
|
27
28
|
|
|
29
|
+
`i18n` is an optional type-level policy. Omit it or use `{ "enabled": true }` for localized content. Use `{ "enabled": false }` for one shared entry that appears in every locale, such as an Author, Category, or Settings type. No other `i18n` keys and no field-level localization settings are supported.
|
|
30
|
+
|
|
28
31
|
### Reserved field names — do NOT declare these in `fields`
|
|
29
32
|
|
|
30
33
|
Every entry already carries this metadata automatically. Declaring any of them as a
|
|
@@ -33,6 +36,8 @@ reserved entry metadata and cannot be used as a content field.`):
|
|
|
33
36
|
|
|
34
37
|
`id`, `documentId`, `slug`, `locale`, `createdAt`, `updatedAt`, `__fimo`.
|
|
35
38
|
|
|
39
|
+
Localized entries expose `locale`; non-localized entries omit it. It remains reserved in both schemas.
|
|
40
|
+
|
|
36
41
|
In particular **do not add a `slug` field** — every entry gets a `slug` for free. You still
|
|
37
42
|
pass `slug` inside `data` when _creating an entry_ (see "Entry body shape" below); it just
|
|
38
43
|
must not appear in the schema's `fields`. If you need an additional URL-ish string, name it
|
|
@@ -40,16 +45,16 @@ something else (e.g. `handle`, `permalink`).
|
|
|
40
45
|
|
|
41
46
|
## Field types
|
|
42
47
|
|
|
43
|
-
| Type | `data` JSON shape
|
|
44
|
-
| ---------- |
|
|
45
|
-
| `string` | `"..."`
|
|
46
|
-
| `richtext` | Tiptap JSONContent (see below)
|
|
47
|
-
| `number` | `42`
|
|
48
|
-
| `boolean` | `true` / `false`
|
|
49
|
-
| `date` | ISO date string, e.g. `"2026-06-03"`
|
|
50
|
-
| `media` | `{ "source": "local"\|"external", "url": "https://...", "alt": "..." }`
|
|
51
|
-
| `json` | any JSON
|
|
52
|
-
| `relation` |
|
|
48
|
+
| Type | `data` JSON shape | Use for |
|
|
49
|
+
| ---------- | ------------------------------------------------------------------------------------- | --------------------------------- |
|
|
50
|
+
| `string` | `"..."` | Titles, short text |
|
|
51
|
+
| `richtext` | Tiptap JSONContent (see below) | Blog body, descriptions, articles |
|
|
52
|
+
| `number` | `42` | Counts, quantities |
|
|
53
|
+
| `boolean` | `true` / `false` | Flags |
|
|
54
|
+
| `date` | ISO date string, e.g. `"2026-06-03"` | Publish dates, timestamps |
|
|
55
|
+
| `media` | `{ "source": "local"\|"external", "url": "https://...", "alt": "..." }` | Images, video |
|
|
56
|
+
| `json` | any JSON | Structured metadata |
|
|
57
|
+
| `relation` | `{ "documentId": "..." }`, `null`, or an array of reference objects when `many: true` | References to another schema |
|
|
53
58
|
|
|
54
59
|
A `date` field currently generates as `any` in the TS client, so for typed access (and the `<Date>` component) prefer a `string` field holding an ISO date — which is what the `publishDate` examples below do.
|
|
55
60
|
|
|
@@ -74,7 +79,10 @@ Rules:
|
|
|
74
79
|
- `target` is required and must be the PascalCase schema `uid` of the referenced content type.
|
|
75
80
|
- Omit `many` for a to-one relation. Use `"many": true` for a to-many relation.
|
|
76
81
|
- Self-references are allowed, e.g. a `Category` schema can have `parent: { "type": "relation", "target": "Category" }`.
|
|
77
|
-
- Entry values store target `documentId
|
|
82
|
+
- Entry values store reference objects with the target `documentId`, not row `id` values: `{ "documentId": "..." }`.
|
|
83
|
+
- Do not include `contentType` for normal fixed-target relations; the schema's `target` already defines it.
|
|
84
|
+
- Do not add `localized` or field-level `i18n` to a relation. The relation value belongs to the entry variant that stores it.
|
|
85
|
+
- Use `null` for an empty to-one relation and `[]` for an empty to-many relation.
|
|
78
86
|
- The API validates that referenced target documents exist and rejects duplicate values in to-many relation arrays.
|
|
79
87
|
|
|
80
88
|
Entry payload example:
|
|
@@ -84,8 +92,8 @@ Entry payload example:
|
|
|
84
92
|
"data": {
|
|
85
93
|
"slug": "relation-example",
|
|
86
94
|
"title": "Relation example",
|
|
87
|
-
"author": "750e8400-e29b-41d4-a716-446655440000",
|
|
88
|
-
"contributors": ["750e8400-e29b-41d4-a716-446655440000"]
|
|
95
|
+
"author": { "documentId": "750e8400-e29b-41d4-a716-446655440000" },
|
|
96
|
+
"contributors": [{ "documentId": "750e8400-e29b-41d4-a716-446655440000" }]
|
|
89
97
|
}
|
|
90
98
|
}
|
|
91
99
|
```
|
|
@@ -159,7 +167,12 @@ For `media` fields, prefer `source: "local"` with assets uploaded via `fimo asse
|
|
|
159
167
|
|
|
160
168
|
## Generated React Query hooks
|
|
161
169
|
|
|
162
|
-
After pre-build (or `fimo deploy`),
|
|
170
|
+
After pre-build (or `fimo deploy`), each schema generates two files:
|
|
171
|
+
|
|
172
|
+
- `src/schemas/{Uid}.types.ts` — pure TypeScript types such as `BlogPost`, `BlogPostInput`, `BlogPostQuery`, and `BlogPostResult`.
|
|
173
|
+
- `src/schemas/{Uid}.ts` — runtime helpers, React Query hooks, default export, and re-exported public types.
|
|
174
|
+
|
|
175
|
+
Import from the public schema module, e.g. `import BlogPost from '@/schemas/BlogPost'` and `import type { BlogPostResult } from '@/schemas/BlogPost'`. Do not import from `.types.ts` directly in app code. There is no generated `src/schemas/index.ts` barrel.
|
|
163
176
|
|
|
164
177
|
**Entity shape — fields live at the top level of the entry**, not under a nested `data` key. The generated `BlogPost` interface looks like:
|
|
165
178
|
|
|
@@ -168,7 +181,7 @@ interface BlogPost {
|
|
|
168
181
|
id: string;
|
|
169
182
|
documentId: string;
|
|
170
183
|
slug: FimoString;
|
|
171
|
-
locale: string;
|
|
184
|
+
locale: string; // localized schemas only; omitted when i18n.enabled is false
|
|
172
185
|
createdAt: string;
|
|
173
186
|
updatedAt: string;
|
|
174
187
|
__fimo: {
|
|
@@ -185,35 +198,56 @@ interface BlogPost {
|
|
|
185
198
|
|
|
186
199
|
Content fields live at the top level. Fimo-owned metadata that is not part of the user schema lives under `__fimo`, so a schema can safely define fields named `contentType`.
|
|
187
200
|
|
|
188
|
-
## Localized content
|
|
201
|
+
## Localized and shared content
|
|
189
202
|
|
|
190
|
-
|
|
203
|
+
Localized content is stored per locale. Generated hooks read the active Fimo locale automatically.
|
|
191
204
|
|
|
192
205
|
When a project has multiple configured locales, create and update each localized content variant explicitly. Use the default locale first, then create non-default variants with the same `documentId` and the target `locale` through the content tools or `fimo entries` commands.
|
|
193
206
|
|
|
207
|
+
For shared content, set `i18n: { "enabled": false }` on the schema and create each entry once. Shared entries:
|
|
208
|
+
|
|
209
|
+
- have no public `locale` property;
|
|
210
|
+
- are returned in every locale context without duplication;
|
|
211
|
+
- ignore locale on owner reads and writes;
|
|
212
|
+
- still use the active/requested locale when a populated relation targets a localized schema.
|
|
213
|
+
|
|
214
|
+
Do not duplicate shared entries across locales, add field-level localization settings, or implement locale fallbacks.
|
|
215
|
+
|
|
194
216
|
**Generated API:**
|
|
195
217
|
|
|
196
218
|
```ts
|
|
197
219
|
// low-level fetch helpers
|
|
198
|
-
getById(id: string): Promise<
|
|
199
|
-
getBySlug(slug: string): Promise<
|
|
200
|
-
getByField(fieldName: string, value: string): Promise<
|
|
201
|
-
get<TFields extends BlogPostFields | undefined>(params?: BlogPostQuery<TFields>): Promise<BlogPostResult<TFields>[]>
|
|
202
|
-
create(payload:
|
|
203
|
-
update(id: string, payload: Partial<
|
|
220
|
+
getById<TPopulate extends BlogPostPopulate | undefined>(id: string, options?: { populate?: TPopulate }): Promise<BlogPostResult<undefined, TPopulate>>
|
|
221
|
+
getBySlug<TPopulate extends BlogPostPopulate | undefined>(slug: string, options?: { populate?: TPopulate }): Promise<BlogPostResult<undefined, TPopulate> | null>
|
|
222
|
+
getByField<TPopulate extends BlogPostPopulate | undefined>(fieldName: string, value: string, options?: { populate?: TPopulate }): Promise<BlogPostResult<undefined, TPopulate> | null>
|
|
223
|
+
get<TFields extends BlogPostFields | undefined, TPopulate extends BlogPostPopulate | undefined>(params?: BlogPostQuery<TFields, TPopulate>): Promise<BlogPostResult<TFields, TPopulate>[]>
|
|
224
|
+
create(payload: BlogPostInput): Promise<BlogPost>
|
|
225
|
+
update(id: string, payload: Partial<BlogPostInput>): Promise<BlogPost>
|
|
204
226
|
remove(id: string): Promise<void>
|
|
205
227
|
|
|
206
228
|
// react-query hooks
|
|
207
|
-
useGetById(id)
|
|
208
|
-
useGetBySlug(slug)
|
|
209
|
-
useGetByField(fieldName, value
|
|
210
|
-
useGet<TFields>(params?)
|
|
229
|
+
useGetById(id, { populate? }) // → { data: BlogPostResult<undefined, TPopulate> | undefined, isLoading, ... }
|
|
230
|
+
useGetBySlug(slug, { populate? }) // → { data: BlogPostResult<undefined, TPopulate> | null | undefined, ... } — ideal for /:slug detail routes
|
|
231
|
+
useGetByField(fieldName, value, { populate? })
|
|
232
|
+
useGet<TFields, TPopulate>(params?) // → { data: BlogPostResult<TFields, TPopulate>[] | undefined, isLoading, ... }
|
|
211
233
|
useCreate() / useUpdate() / useRemove()
|
|
212
234
|
|
|
213
235
|
// Suspense variants (same signatures, throw to a <Suspense> boundary instead of returning isLoading)
|
|
214
236
|
useSuspenseGetById(id) / useSuspenseGetBySlug(slug) / useSuspenseGetByField(fieldName, value) / useSuspenseGet(params?)
|
|
215
237
|
```
|
|
216
238
|
|
|
239
|
+
Write inputs contain plain JSON values. Read models contain Fimo wrapper values for source tracking, so use `BlogPostInput` rather than sending a `BlogPost` back to `create` or `update`.
|
|
240
|
+
|
|
241
|
+
For a schema with `"isSingleton": true`, codegen omits collection methods and generates this smaller API:
|
|
242
|
+
|
|
243
|
+
```ts
|
|
244
|
+
get<TPopulate extends SettingsPopulate | undefined>(options?: { locale?: string; populate?: TPopulate }): Promise<SettingsResult<undefined, TPopulate>>
|
|
245
|
+
update(payload: Partial<SettingsInput>): Promise<Settings>
|
|
246
|
+
useGet(options?) / useSuspenseGet(options?) / useUpdate()
|
|
247
|
+
```
|
|
248
|
+
|
|
249
|
+
Singleton clients do not expose `create`, `remove`, `getById`, or list queries. Localized singletons have one variant per locale; non-localized singletons have one locale-free row.
|
|
250
|
+
|
|
217
251
|
Access pattern — the hooks **unwrap** the API envelope, so `get()` / `useGet()` resolve to a `BlogPost[]` directly (not a `{ data, meta }` wrapper):
|
|
218
252
|
|
|
219
253
|
```tsx
|
|
@@ -223,6 +257,7 @@ posts.map((post) => /* ... */); // BlogPost[]
|
|
|
223
257
|
```
|
|
224
258
|
|
|
225
259
|
Query params supported: `limit` (1-100, default 20), `offset` (default 0), `search`, `sort`, `where`, `fields`, and `populate`. Use these canonical names in new code.
|
|
260
|
+
`fields` and `populate` change the returned item type. `where`, `search`, `sort`, `limit`, and `offset` only change selection or ordering. `locale` selects a localized owner variant, or the localized target variant when shared content is populated; raw shared reads omit it.
|
|
226
261
|
|
|
227
262
|
`where` filters on the server. Use scalar values for equality and operator objects for ranges, lists, text matches, or null checks. Keep boolean, string, and number values typed in the object:
|
|
228
263
|
|
|
@@ -242,6 +277,8 @@ BlogPost.useGet({
|
|
|
242
277
|
```
|
|
243
278
|
|
|
244
279
|
Operators: scalar equality, `$eq`, `$ne`, `$gt`, `$gte`, `$lt`, `$lte`, `$in`, `$nin`, `$contains`, `$startsWith`, `$null`.
|
|
280
|
+
Relation field values are reference objects, but relation filters use the target `documentId` string, for example `where: { author: "750e..." }`; for to-many relations that means "contains this target document".
|
|
281
|
+
For to-many relations, `$eq` and `$contains` mean membership, `$in` means any listed document is present, `$ne` and `$nin` mean none are present, and `$null: true` means empty or missing. Relation fields cannot be sorted.
|
|
245
282
|
|
|
246
283
|
Use `fields` to keep list payloads small. The API always keeps `id`, and returns only the requested fields beyond that:
|
|
247
284
|
|
|
@@ -261,10 +298,26 @@ const { data: posts = [] } = BlogPost.useGet({
|
|
|
261
298
|
fields: ['slug', 'title', 'author'] as const,
|
|
262
299
|
populate: ['author'] as const,
|
|
263
300
|
});
|
|
264
|
-
// Without populate: post.author is
|
|
301
|
+
// Without populate: post.author is { documentId: "..." }.
|
|
265
302
|
// With populate: post.author is the target Author entry object, or null.
|
|
266
303
|
```
|
|
267
304
|
|
|
305
|
+
Keep `fields` and `populate` arrays `as const` so TypeScript can infer the precise projected and populated shape. Prefer hook inference. If a helper/component needs a named type, use the generated result type:
|
|
306
|
+
|
|
307
|
+
```ts
|
|
308
|
+
import type { BlogPostResult } from '@/schemas/BlogPost';
|
|
309
|
+
|
|
310
|
+
type PostWithAuthor = BlogPostResult<undefined, 'author'>;
|
|
311
|
+
type PostCardItem = BlogPostResult<
|
|
312
|
+
readonly ['slug', 'title', 'author', 'contributors'],
|
|
313
|
+
readonly ['author', 'contributors']
|
|
314
|
+
>;
|
|
315
|
+
```
|
|
316
|
+
|
|
317
|
+
Do not type populated hook data as `BlogPost[]`: plain `BlogPost` keeps relation fields as `FimoReference` objects, while `BlogPostResult<TFields, TPopulate>` models the projected/populated read result.
|
|
318
|
+
|
|
319
|
+
`BlogPostPopulate` is the generated input type for allowed populate values. It comes from the schema's relation fields, so a post schema with `author` and `contributors` only accepts those names in `populate`. Most app code never imports it directly; it is mainly used by `BlogPostQuery` and hook generics.
|
|
320
|
+
|
|
268
321
|
Populate is depth-one. It only works for relation fields; trying to populate a scalar field returns 400. To-many relation order is preserved.
|
|
269
322
|
|
|
270
323
|
The generated hooks do **not** surface pagination meta (`total`, `hasMore`, `limit`, `offset`). If you need it, read it from a raw fetch to `/entries/<typeName>` (see the **Read path** section below), which returns the full `{ data, meta }` envelope.
|
|
@@ -273,16 +326,16 @@ The generated hooks do **not** surface pagination meta (`total`, `hasMore`, `lim
|
|
|
273
326
|
|
|
274
327
|
The entry objects a hook returns are **not** plain JS values. The generated client wraps each _trackable_ field in a `fimo/ui` primitive that carries source metadata for inline editing. Which fields wrap, and which stay plain:
|
|
275
328
|
|
|
276
|
-
| Schema field type | Field value you get
|
|
277
|
-
| ----------------- |
|
|
278
|
-
| `string` | `FimoString`
|
|
279
|
-
| `richtext` | `FimoRichText`
|
|
280
|
-
| `boolean` | `FimoBoolean`
|
|
281
|
-
| `media` | `FimoMedia` / `FimoMedia[]`
|
|
282
|
-
| `number` | `number`
|
|
283
|
-
| `json` | `Record<string, any>`
|
|
284
|
-
| `date` | `any` (raw ISO date string)
|
|
285
|
-
| `relation` | `
|
|
329
|
+
| Schema field type | Field value you get | Notes |
|
|
330
|
+
| ----------------- | ----------------------------------- | ------------------------------------------------------------------------------------------------- |
|
|
331
|
+
| `string` | `FimoString` | `extends String` — **not** a primitive `string` |
|
|
332
|
+
| `richtext` | `FimoRichText` | Tiptap JSONContent + source metadata |
|
|
333
|
+
| `boolean` | `FimoBoolean` | class; `.valueOf()` → `boolean` |
|
|
334
|
+
| `media` | `FimoMedia` / `FimoMedia[]` | shape + rendering in `references/assets.md` |
|
|
335
|
+
| `number` | `number` | plain — not wrapped |
|
|
336
|
+
| `json` | `Record<string, any>` | plain — not wrapped |
|
|
337
|
+
| `date` | `any` (raw ISO date string) | not wrapped (`FimoDate` is not emitted — see below) |
|
|
338
|
+
| `relation` | `FimoReference` / `FimoReference[]` | reference object(s) with target `documentId`; use `populate` when embedded target rows are needed |
|
|
286
339
|
|
|
287
340
|
Rules of thumb:
|
|
288
341
|
|
|
@@ -346,7 +399,7 @@ const { data, meta } = await res.json();
|
|
|
346
399
|
// meta: { limit, offset, total, hasMore }
|
|
347
400
|
```
|
|
348
401
|
|
|
349
|
-
Prefer the generated React Query hooks from `@/schemas/{Uid}` over hand-rolled fetch — they handle caching and types (returning
|
|
402
|
+
Prefer the generated React Query hooks from `@/schemas/{Uid}` over hand-rolled fetch — they handle caching and types (returning typed `BlogPostResult<TFields, TPopulate>[]` results). Reach for this raw fetch only when you need the pagination `meta`, which the hooks don't surface.
|
|
350
403
|
|
|
351
404
|
## Forms vs. schemas
|
|
352
405
|
|
|
@@ -22,7 +22,7 @@ Fimo provides locale utilities that follow the Fimo standard, but they are helpe
|
|
|
22
22
|
- `defaultLocale` is the source/default locale.
|
|
23
23
|
- `locales` lists enabled locales and must include `defaultLocale`. Do not add non-default locales unless the user asks for multiple languages, localized routes, translations, or a specific locale.
|
|
24
24
|
- `routing.strategy` currently supports `path-prefix`.
|
|
25
|
-
- If multiple locales are configured, create or update labels and content for every configured locale.
|
|
25
|
+
- If multiple locales are configured, create or update labels and localized content for every configured locale. Create schemas with `i18n: { "enabled": false }` only once; those entries are shared across locales.
|
|
26
26
|
|
|
27
27
|
Locale tags should be structurally valid, for example `en`, `es`, `pt-BR`, or `zh-Hans`.
|
|
28
28
|
|
|
@@ -10,7 +10,7 @@ This file covers the **CLI workflow** for content. Use it when the user wants to
|
|
|
10
10
|
|
|
11
11
|
1. Write `src/schemas/{Uid}.json` — PascalCase filename (e.g. `BlogPost.json`). For the JSON shape and field types, load `fimo/references/content.md`.
|
|
12
12
|
2. Register the schema with `fimo schemas push {Uid}`.
|
|
13
|
-
3. Pre-build generates `src/schemas/{Uid}.ts` — typed React Query hooks. See `fimo/references/content.md` for the hook API.
|
|
13
|
+
3. Pre-build generates `src/schemas/{Uid}.types.ts` and `src/schemas/{Uid}.ts` — type definitions plus typed React Query hooks. App code imports from `@/schemas/{Uid}`, not from `.types.ts` directly. See `fimo/references/content.md` for the hook API.
|
|
14
14
|
4. Seed entries via `fimo entries create`. Generate cover images first with `fimo generate <model>` if needed (see `media.md` / `assets.md`).
|
|
15
15
|
5. Write React pages that import from `@/schemas/{Uid}` and render with `fimo/ui` components.
|
|
16
16
|
|
|
@@ -57,11 +57,13 @@ fimo entries list BlogPost --fields slug,title,excerpt,coverImage --sort -publis
|
|
|
57
57
|
fimo entries list BlogPost --fields slug,title,author --populate author
|
|
58
58
|
```
|
|
59
59
|
|
|
60
|
-
Use the canonical read query flags: `--search`, `--sort`, `--where`, `--limit`, `--offset`, and `--
|
|
61
|
-
`--where` is a JSON object keyed by schema field name. Simple values mean equality, so booleans, strings, and numbers should stay typed inside the JSON (`{"featured":true}`, `{"category":"react"}`, `{"rank":{"$gte":3}}`). Public Fimo-owned metadata lives under `__fimo`; currently `__fimo.contentType` is filterable. Operator objects support `$eq`, `$ne`, `$gt`, `$gte`, `$lt`, `$lte`, `$in`, `$nin`, `$contains`, `$startsWith`, and `$null`, subject to the field type. Use `--sort field` for ascending and `--sort -field` for descending.
|
|
60
|
+
Use the canonical read query flags: `--search`, `--sort`, `--where`, `--limit`, `--offset`, `--fields`, and `--populate`.
|
|
61
|
+
`--where` is a JSON object keyed by schema field name. Simple values mean equality, so booleans, strings, and numbers should stay typed inside the JSON (`{"featured":true}`, `{"category":"react"}`, `{"rank":{"$gte":3}}`). Relation field values are reference objects, but relation filters use the target `documentId` string, for example `{"author":"750e..."}`; for to-many relations that means "contains this target document". Public Fimo-owned metadata lives under `__fimo`; currently `__fimo.contentType` is filterable. Operator objects support `$eq`, `$ne`, `$gt`, `$gte`, `$lt`, `$lte`, `$in`, `$nin`, `$contains`, `$startsWith`, and `$null`, subject to the field type. For to-many relations, `$eq` and `$contains` mean membership, `$in` means any listed document is present, `$ne` and `$nin` mean none are present, and `$null: true` means empty or missing. Relation fields cannot be sorted. Use `--sort field` for ascending and `--sort -field` for descending on scalar fields.
|
|
62
62
|
`--fields` is a comma-separated projection for list payloads. The API always keeps `id`; pass only the fields your view needs, such as `slug,title,excerpt,coverImage`.
|
|
63
63
|
|
|
64
|
-
`--populate` is a comma-separated list of relation fields to embed. Without `--populate`, relation fields return
|
|
64
|
+
`--populate` is a comma-separated list of relation fields to embed. Without `--populate`, relation fields return reference objects like `{ "documentId": "..." }`. With `--populate author,contributors`, those fields return the target entry objects for the requested locale. Only relation fields can be populated; asking to populate a scalar field returns 400.
|
|
65
|
+
|
|
66
|
+
In generated React code, the equivalent is `populate: ['author', 'contributors'] as const` in the schema hook/query params. Let hooks infer the result when possible; if a named TypeScript shape is needed, use the generated `BlogPostResult<TFields, TPopulate>` type documented in `fimo/references/content.md`. Writes use the separate generated `BlogPostInput` type.
|
|
65
67
|
|
|
66
68
|
For relation schema JSON and entry value shape, load `fimo/references/content.md` § Relation fields. The short version:
|
|
67
69
|
|
|
@@ -74,18 +76,24 @@ For relation schema JSON and entry value shape, load `fimo/references/content.md
|
|
|
74
76
|
}
|
|
75
77
|
```
|
|
76
78
|
|
|
77
|
-
Entry bodies store target `documentId` values:
|
|
79
|
+
Entry bodies store reference objects with target `documentId` values:
|
|
78
80
|
|
|
79
81
|
```bash
|
|
80
|
-
fimo entries create BlogPost --body '{"data":{"slug":"hello","title":"Hello","author":"750e8400-e29b-41d4-a716-446655440000","contributors":[]}}'
|
|
82
|
+
fimo entries create BlogPost --body '{"data":{"slug":"hello","title":"Hello","author":{"documentId":"750e8400-e29b-41d4-a716-446655440000"},"contributors":[]}}'
|
|
81
83
|
```
|
|
82
84
|
|
|
83
|
-
|
|
85
|
+
Do not add `localized` or field-level `i18n` to relation fields. The relation value belongs to the entry variant that stores it.
|
|
86
|
+
|
|
87
|
+
## Localized and shared content
|
|
84
88
|
|
|
85
|
-
|
|
89
|
+
Schemas are localized by default. Omit `--locale` for default-locale entries. Pass `--locale <locale>` when creating, listing, reading, or updating a specific non-default locale.
|
|
86
90
|
|
|
87
91
|
When creating a localized variant of an existing entry, pass the source entry's `documentId` with `--document-id`. Keep projects single-locale unless the user asks for multilingual or localized output; when multiple locales are configured, create and update every locale explicitly.
|
|
88
92
|
|
|
93
|
+
For content shared across locales, define `"i18n": { "enabled": false }` at the schema root and create each entry once. Do not pass `--document-id` to manufacture locale variants. Shared entry responses omit `locale`; a read locale is only used to resolve populated relations that target localized schemas. No other `i18n` options and no field-level localization settings are supported.
|
|
94
|
+
|
|
95
|
+
Use `"isSingleton": true` for a type with exactly one logical document. Generated singleton clients expose `get`/`update` and their React Query hooks, not collection create/remove APIs.
|
|
96
|
+
|
|
89
97
|
## "Add a blog" recipe
|
|
90
98
|
|
|
91
99
|
1. Load `fimo/references/content.md` for the JSON shape and field types.
|
|
@@ -108,7 +116,7 @@ fimo validate # runs all phases: codegen, lint, route validatio
|
|
|
108
116
|
Advanced/internal (hidden from `fimo --help`) — for the bare regen phase:
|
|
109
117
|
|
|
110
118
|
```bash
|
|
111
|
-
fimo scripts sync-schemas # regenerate src/schemas/*.ts
|
|
119
|
+
fimo scripts sync-schemas # regenerate src/schemas/*.types.ts and src/schemas/*.ts
|
|
112
120
|
```
|
|
113
121
|
|
|
114
122
|
## Non-interactive use
|
|
@@ -25,7 +25,7 @@ Edit `fimo-config.json`. Keep one locale unless the user asks for multilingual o
|
|
|
25
25
|
- `locales` must include `defaultLocale`. Do not add non-default locales unless the user asks for multiple languages, localized routes, translations, or a specific locale.
|
|
26
26
|
- Locale tags should be structurally valid, for example `en`, `es`, `pt-BR`, or `zh-Hans`.
|
|
27
27
|
- `routing.strategy` currently supports `path-prefix`.
|
|
28
|
-
- If multiple locales are configured, create or update labels and content for every configured locale.
|
|
28
|
+
- If multiple locales are configured, create or update labels and localized content for every configured locale. Create schemas with `i18n: { "enabled": false }` only once.
|
|
29
29
|
|
|
30
30
|
## Validate
|
|
31
31
|
|
|
@@ -74,4 +74,4 @@ Labels and content are separate translated surfaces:
|
|
|
74
74
|
- labels: static UI copy declared with `t('key')`, managed with `fimo labels`
|
|
75
75
|
- content: schema entries, managed with `fimo entries`
|
|
76
76
|
|
|
77
|
-
Omit `--locale` for default-locale writes. Pass `--locale <locale>` for each non-default
|
|
77
|
+
Omit `--locale` for default-locale writes. Pass `--locale <locale>` for each non-default localized entry you need to create or update. Shared schemas (`i18n.enabled: false`) have one locale-free entry instead.
|
package/dist/cli/bundle.json
CHANGED