fimo 0.9.0 → 0.10.0-experimental.1

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.
@@ -48,9 +48,9 @@ export interface <%= model.pascal %> extends WithReferences<<%= model.pascal %>R
48
48
  // Generate interface properties based on fields
49
49
  if (model.fields) {
50
50
  Object.entries(model.fields).forEach(([key, field]) => {
51
- const type = getTypeForField(field);
51
+ const type = isNullableRichTextField(field) ? `${getTypeForField(field)} | null` : getTypeForField(field);
52
52
  -%>
53
- <%= key %>: <%= type %>;
53
+ <%= key %><%= isOptionalOutputField(field) ? '?' : '' %>: <%= type %>;
54
54
  <%
55
55
  });
56
56
  }
@@ -68,7 +68,9 @@ export interface <%= model.pascal %>Input {
68
68
  <%
69
69
  if (model.fields) {
70
70
  Object.entries(model.fields).forEach(([key, field]) => {
71
- const type = getInputTypeForField(field);
71
+ const type = isNullableRichTextField(field)
72
+ ? `${getInputTypeForField(field)} | null`
73
+ : getInputTypeForField(field);
72
74
  -%>
73
75
  <%= key %><%= isRequiredField(field) ? '' : '?' %>: <%= type %>;
74
76
  <%
@@ -85,6 +87,7 @@ if (model.fields) {
85
87
  function getTypeForField(field) {
86
88
  switch(field.type) {
87
89
  case 'string':
90
+ return getStringLiteralUnion(field) || 'FimoString';
88
91
  case 'text':
89
92
  return 'FimoString';
90
93
  case 'richtext':
@@ -113,6 +116,7 @@ function getTypeForField(field) {
113
116
  function getInputTypeForField(field) {
114
117
  switch(field.type) {
115
118
  case 'string':
119
+ return getStringLiteralUnion(field) || 'string';
116
120
  case 'text':
117
121
  return 'string';
118
122
  case 'richtext':
@@ -143,6 +147,7 @@ function getInputTypeForField(field) {
143
147
  function getNestedFieldType(field, input) {
144
148
  switch(field.type) {
145
149
  case 'string':
150
+ return getStringLiteralUnion(field) || 'string';
146
151
  case 'text':
147
152
  return 'string';
148
153
  case 'richtext':
@@ -195,9 +200,31 @@ function getBlocksType(field, input) {
195
200
  return `${item}[]`;
196
201
  }
197
202
 
203
+ function getStringLiteralUnion(field) {
204
+ if (!Array.isArray(field.options) || field.options.length === 0) {
205
+ return null;
206
+ }
207
+ return field.options.map((option) => JSON.stringify(String(option))).join(' | ');
208
+ }
209
+
198
210
  function isRequiredField(field) {
211
+ if (isConditionalField(field)) {
212
+ return false;
213
+ }
199
214
  return Boolean(field.required || (field.type === 'blocks' && Number(field.min || 0) > 0));
200
215
  }
216
+
217
+ function isNullableRichTextField(field) {
218
+ return field.type === 'richtext' && !isRequiredField(field);
219
+ }
220
+
221
+ function isOptionalOutputField(field) {
222
+ return isConditionalField(field) || isNullableRichTextField(field);
223
+ }
224
+
225
+ function isConditionalField(field) {
226
+ return field.showIf !== undefined || field.showIfAny !== undefined;
227
+ }
201
228
  -%>
202
229
 
203
230
  export interface <%= model.pascal %>References {
@@ -94,9 +94,46 @@ fimo entries query --file query.sql
94
94
  cat query.sql | fimo entries query --stdin
95
95
  ```
96
96
 
97
- SQL exposes the raw internal representation. `document_id` is the stable ID used by `fimo entries`, content tools, references, and the API. `id` is the internal ID of one exact locale variant. When query results will feed another Fimo command or tool, select `document_id AS id`; use `locale` to select a variant.
97
+ SQL exposes the raw internal representation. `document_id` is the stable ID used by `fimo entries`, content tools, references, and the API. `id` is the internal ID of one exact locale variant. Localized variants share one `document_id` and have one row per locale; non-localized entries have a null `locale`. When query results will feed another Fimo command or tool, select `document_id AS id`; use `locale` to select a variant.
98
+
99
+ The supported content columns are `id`, `document_id`, `content_type`, `locale`, `slug`, `data`, `created_at`, and `updated_at`. Schema fields live inside the JSONB `data` column. Fimo may add internal columns, so select the fields you need instead of `SELECT *` and do not depend on other columns. If a safe read is blocked because a SQL function is unavailable, tell the user which function is missing and use the built-in `supportReport` tool when the platform gap materially blocks the task. Do not bypass the restriction with a write.
100
+
101
+ For example, find English blog posts that do not yet have a Spanish variant:
102
+
103
+ ```sql
104
+ SELECT source.document_id AS id, source.data
105
+ FROM entries source
106
+ WHERE source.content_type = 'BlogPost'
107
+ AND source.locale = 'en'
108
+ AND NOT EXISTS (
109
+ SELECT 1
110
+ FROM entries target
111
+ WHERE target.content_type = source.content_type
112
+ AND target.document_id = source.document_id
113
+ AND target.locale = 'es'
114
+ )
115
+ ```
116
+
117
+ Find Spanish variants whose English source was edited later:
118
+
119
+ ```sql
120
+ SELECT
121
+ target.document_id AS id,
122
+ target.content_type,
123
+ target.slug,
124
+ source.updated_at AS source_updated_at,
125
+ target.updated_at AS target_updated_at
126
+ FROM entries source
127
+ JOIN entries target
128
+ ON target.content_type = source.content_type
129
+ AND target.document_id = source.document_id
130
+ WHERE source.locale = 'en'
131
+ AND target.locale = 'es'
132
+ AND source.updated_at > target.updated_at
133
+ ORDER BY source.updated_at DESC
134
+ ```
98
135
 
99
- The available columns are `id`, `document_id`, `content_type`, `locale`, `slug`, `data`, `created_at`, and `updated_at`. Schema fields live inside the JSONB `data` column. If a safe read is blocked because a SQL function is unavailable, tell the user which function is missing and use the built-in `supportReport` tool when the platform gap materially blocks the task. Do not bypass the restriction with a write.
136
+ Treat these results as possibly stale, not proof of a bad translation. The query only proves that the source row was edited later; an agent should review the content and link each target with its stable `id`.
100
137
 
101
138
  ### Bulk mutations
102
139
 
@@ -24,6 +24,10 @@ Run this before writing a custom payload. The list shows supported event types,
24
24
  - Analytics: `analytics.report_ready`
25
25
  - Media: uploaded/updated/deleted/generated/processing_failed
26
26
  - Entries: `entry.changed`
27
+ - Domains: `domain.created`, `domain.deleted`
28
+ - Labels: `label.changed`
29
+ - Locales: `locale.created`, `locale.default_changed`, `locale.deleted`
30
+ - Environments: `environment.created`, `environment.deleted`, `environment.merged`, `environment.restored`
27
31
 
28
32
  Do not guess variable paths. The common envelope is:
29
33
 
@@ -61,6 +61,48 @@ something else (e.g. `handle`, `permalink`).
61
61
 
62
62
  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.
63
63
 
64
+ ## Enumerated strings
65
+
66
+ A `string` field may declare `options` to close its value set. The CMS renders a select, the API validates the value as an enum, and the generated client types the field as a literal union instead of `string`.
67
+
68
+ ```json
69
+ "layout": { "type": "string", "options": ["article", "video"], "default": "article" }
70
+ ```
71
+
72
+ - Values must be unique. Set a `default` that is one of them.
73
+ - `options` cannot be combined with `regex`, `minLength`, or `maxLength`, and is rejected on every field type other than `string`.
74
+ - Writes are validated against the set: a value outside it is rejected with a 400. An optional field also accepts `""` and `null` so the editor can clear the choice; a `required` one only accepts a declared value.
75
+ - Removing an option that entries still hold fails the push, the same way an unsafe type change does. Migrate those entries to a remaining value first.
76
+
77
+ ## Conditional fields
78
+
79
+ A field may declare `showIf` (every clause must match) or `showIfAny` (one clause is enough) to apply only in some entries. A clause names a sibling field and a value.
80
+
81
+ ```json
82
+ {
83
+ "layout": { "type": "string", "options": ["article", "video"], "default": "article" },
84
+ "body": { "type": "richtext", "showIf": { "field": "layout", "equals": "article" } },
85
+ "videoUrl": { "type": "string", "required": true, "showIf": { "field": "layout", "equals": "video" } },
86
+ "transcript": {
87
+ "type": "text",
88
+ "showIfAny": [
89
+ { "field": "layout", "equals": "video" },
90
+ { "field": "hasAudio", "equals": true }
91
+ ]
92
+ }
93
+ }
94
+ ```
95
+
96
+ - A clause is `{ "field": "<sibling>", "equals": <value> }` or `{ "field": "<sibling>", "in": [<values>] }`. Use `in` for several values of one field.
97
+ - `showIf` accepts one clause or an array of clauses, and every clause must match. `showIfAny` takes an array and one match is enough. A field declares one key or the other, never both.
98
+ - **A hidden field is not rendered and not validated, so `required` does not apply to it.** An entry whose condition never held can be saved without ever filling the field.
99
+ - **Hiding a field changes nothing about storage or reads.** The value stays in the entry and the API keeps returning it, so an editor who flips the trigger back gets their work back. Narrow on the trigger in site code rather than trusting the value.
100
+ - Because a conditional field can be absent, it is always optional in the generated types even when `required` is set.
101
+ - Conditions resolve against siblings in the same object. A field inside a block variant may only reference fields in that same block item.
102
+ - A clause may reference a `boolean`, `number`, or `string` field. A `string` trigger must declare `options`, which is what lets `fimo validate` reject a clause value that could never match.
103
+ - A trigger cannot itself be conditional — conditions are one level deep, so a chain like A shows B shows C is rejected.
104
+ - `fimo validate` and `fimo schemas push` reject an unknown trigger name, a trigger of the wrong type, a value outside the trigger's `options`, and a trigger that is itself conditional.
105
+
64
106
  ## Blocks
65
107
 
66
108
  `blocks` is the only nested structured-content type and always stores an array. Use `schema.fields` for one homogeneous block shape. Set `max: 1` when the field is a single optional group. Use `schema.oneOf` when editors choose a variant for each item.
@@ -272,7 +314,7 @@ interface BlogPost {
272
314
  };
273
315
  title: FimoString;
274
316
  summary: FimoString;
275
- body: FimoRichText;
317
+ body?: FimoRichText | null;
276
318
  coverImage: FimoMedia;
277
319
  publishDate: FimoString;
278
320
  featured: FimoBoolean;
@@ -282,6 +324,8 @@ interface BlogPost {
282
324
 
283
325
  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`.
284
326
 
327
+ Optional richtext fields are nullable in generated read and write types. `<RichText>` accepts the generated value directly and renders nothing when it is null or undefined. A non-conditional required richtext field stays non-null; conditional fields remain optional because their condition may not apply.
328
+
285
329
  ## Localized and shared content
286
330
 
287
331
  Localized content has one variant per locale. Generated hooks read the active Fimo locale automatically.
@@ -36,6 +36,17 @@ When you open a Collection, the entry list provides several tools:
36
36
  - **Columns** dropdown (the eye button) next to the search bar to show/hide columns and reorder them by dragging each column row by its grip handle.
37
37
  - **Pagination** at the bottom when there are many entries.
38
38
 
39
+ ## Completing Missing Translations
40
+
41
+ Localized Collections show only entries for the locale selected beside the Collection name. When that locale is missing translations that exist in the project's default locale:
42
+
43
+ 1. Click the amber **N missing** warning beside the locale selector.
44
+ 2. Review the source and target languages in the popover.
45
+ 3. Choose **Create missing translations**.
46
+ 4. Review or edit the prompt placed in AI chat, then send it when ready.
47
+
48
+ Fimo does not send the prompt automatically. After the AI finishes, translated entries appear in the selected locale and the warning updates.
49
+
39
50
  ## Creating a New Entry
40
51
 
41
52
  1. Navigate to the Collection you want to add content to.
@@ -15,7 +15,7 @@ Only project members with Full Access can see Webhooks in Settings or manage its
15
15
  5. Optionally narrow form, agent, media, or entry events with the filter shown for that event.
16
16
  6. Review the request and select **Save**.
17
17
 
18
- The event groups are Entries, Forms, Publish, Agents, Analytics, and Media. Webhooks belong to the project, with up to 20 configurations. They listen to main by default or can include every environment.
18
+ The event groups are Entries, Forms, Publish, Agents, Analytics, Media, Domains, Labels, Locales, and Environments. Webhooks belong to the project, with up to 20 configurations. They listen to main by default or can include every environment.
19
19
 
20
20
  Entry webhooks show **Combine rapid changes**. The default waits 5 seconds after direct editing stops and sends one delivery with the combined changes. Immediate, 30-second, and 1-minute windows are also available. Bulk edits and environment merges already produce batches and send immediately.
21
21
 
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "bundled": true,
3
3
  "bundler": "esbuild",
4
- "bundledAt": "2026-07-24T11:14:53.290Z",
4
+ "bundledAt": "2026-07-26T16:18:23.038Z",
5
5
  "cliVersion": "0.9.0",
6
6
  "external": [
7
7
  "oxc-parser",