fimo 0.6.0-experimental.1 → 0.6.0-experimental.2

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.
@@ -11,7 +11,7 @@
11
11
  // AUTO-GENERATED ⚠️ – do not edit by hand
12
12
  import { <% if (!model.isSingleton) { %>useInfiniteQuery, <% } %>useSuspenseQuery, useQuery, useMutation, useQueryClient } from "@tanstack/react-query";
13
13
  <% if (!model.isSingleton) { -%>
14
- import type { InfiniteData, UseInfiniteQueryResult, UseQueryResult } from "@tanstack/react-query";
14
+ import type { InfiniteData, UseInfiniteQueryResult, UseQueryResult, UseSuspenseQueryResult } from "@tanstack/react-query";
15
15
  <% } -%>
16
16
  import { FimoString, FimoMedia, FimoBoolean, FimoRichText, useLocale<% if (!model.isSingleton) { %>, parseCollectionResponse<% } %> } from "fimo/ui";
17
17
  // @ts-ignore - virtual module provided by Fimo's Vite plugin
@@ -36,16 +36,35 @@ export type { <%= model.pascal %>, <%= model.pascal %>Fields, <%= model.pascal %
36
36
  * Wraps raw API data with Fimo types for source tracking
37
37
  */
38
38
  export function wrapWithSource(entry: Record<string, any>): <%= model.pascal %> {
39
+ <% if (!model.isSingleton) { -%>
40
+ const documentId = entry.documentId;
41
+ const locale = entry.locale;
42
+ const sourcePrefix = `<%= model.pascal %>.v2:${documentId}:${encodeURIComponent(locale)}`;
43
+ <% } else { -%>
39
44
  const id = entry.id;
40
- const sourcePrefix = `<%= model.pascal %>.${id}`;
45
+ const documentId = entry.documentId;
46
+ const locale = entry.locale ?? defaultLocale();
47
+ const usesV2Identity = typeof id !== "string";
48
+ const sourcePrefix = usesV2Identity
49
+ ? `<%= model.pascal %>.v2:${documentId}:${encodeURIComponent(locale)}`
50
+ : `<%= model.pascal %>.${id}`;
51
+ <% } -%>
41
52
  const entryFimo = entry.__fimo && typeof entry.__fimo === "object" ? entry.__fimo : {};
42
53
 
43
54
  return {
44
- id,
45
- documentId: entry.documentId,
55
+ <% if (!model.isSingleton) { -%>
56
+ documentId,
57
+ <% } else { -%>
58
+ ...(typeof id === "string" ? { id } : {}),
59
+ documentId,
60
+ <% } -%>
46
61
  slug: entry.slug != null ? new FimoString(entry.slug, `${sourcePrefix}.slug`) : entry.slug,
47
- <% if (model.localized) { -%>
48
- locale: entry.locale,
62
+ <% if (!model.isSingleton) { -%>
63
+ locale,
64
+ <% } else if (model.localized) { -%>
65
+ locale,
66
+ <% } else { -%>
67
+ ...(usesV2Identity ? { locale } : {}),
49
68
  <% } -%>
50
69
  createdAt: entry.createdAt,
51
70
  updatedAt: entry.updatedAt,
@@ -101,11 +120,11 @@ if (model.fields) {
101
120
  const wrapper = field.target === model.uid ? 'wrapWithSource' : `wrap${field.targetPascal}WithSource`;
102
121
  if (field.many) {
103
122
  -%>
104
- <%= key %>: Array.isArray(entry.<%= key %>) ? entry.<%= key %>.map((value: any) => value != null && typeof value === "object" && typeof value.id === "string" ? <%= wrapper %>(value) : value) : entry.<%= key %>,
123
+ <%= key %>: Array.isArray(entry.<%= key %>) ? entry.<%= key %>.map((value: any) => isPopulatedEntry(value) ? <%= wrapper %>(value) : value) : entry.<%= key %>,
105
124
  <%
106
125
  } else {
107
126
  -%>
108
- <%= key %>: entry.<%= key %> != null && typeof entry.<%= key %> === "object" && typeof entry.<%= key %>.id === "string" ? <%= wrapper %>(entry.<%= key %>) : entry.<%= key %>,
127
+ <%= key %>: isPopulatedEntry(entry.<%= key %>) ? <%= wrapper %>(entry.<%= key %>) : entry.<%= key %>,
109
128
  <%
110
129
  }
111
130
  break;
@@ -121,6 +140,23 @@ if (model.fields) {
121
140
  } as <%= model.pascal %>;
122
141
  }
123
142
 
143
+ interface PopulatedEntryRecord extends Record<string, unknown> {
144
+ documentId: string;
145
+ __fimo: { contentType: string };
146
+ }
147
+
148
+ function isPopulatedEntry(value: unknown): value is PopulatedEntryRecord {
149
+ if (value == null || typeof value !== "object") {
150
+ return false;
151
+ }
152
+ const entry = value as Record<string, unknown>;
153
+ const fimo = entry.__fimo;
154
+ return typeof entry.documentId === "string"
155
+ && fimo != null
156
+ && typeof fimo === "object"
157
+ && typeof (fimo as Record<string, unknown>).contentType === "string";
158
+ }
159
+
124
160
  <%
125
161
  const hasPolymorphicReferences = model.fields && Object.values(model.fields).some((field) => field.type === 'reference' && Array.isArray(field.targetRefs) && field.targetRefs.length > 0);
126
162
  if (hasPolymorphicReferences) {
@@ -132,7 +168,9 @@ if (hasPolymorphicReferences) {
132
168
  * (unpopulated) values pass through untouched.
133
169
  */
134
170
  function wrapPolymorphic(value: any, wrappers: Record<string, (entry: Record<string, any>) => any>): any {
135
- if (value == null || typeof value !== "object" || typeof value.id !== "string") return value;
171
+ if (!isPopulatedEntry(value)) {
172
+ return value;
173
+ }
136
174
  const contentType = value.__fimo && typeof value.__fimo.contentType === "string" ? value.__fimo.contentType : null;
137
175
  const wrapper = contentType ? wrappers[contentType] : undefined;
138
176
  if (!wrapper) return value;
@@ -172,8 +210,12 @@ function readLocale(
172
210
  ownerUsesLocale: boolean,
173
211
  fallbackLocale = defaultLocale()
174
212
  ): string | undefined {
213
+ <% if (!model.isSingleton) { -%>
214
+ return options.locale ?? fallbackLocale;
215
+ <% } else { -%>
175
216
  const populatesLocalizedTarget = populateFields(options.populate).some((field) => LOCALIZED_REFERENCE_FIELDS.has(field));
176
217
  return ownerUsesLocale || populatesLocalizedTarget ? options.locale ?? fallbackLocale : undefined;
218
+ <% } -%>
177
219
  }
178
220
 
179
221
  function fimoHeaders(extra?: Record<string, string>): Record<string, string> {
@@ -249,11 +291,11 @@ export type InfiniteListResult<TItem> = UseInfiniteQueryResult<
249
291
  readonly isLoadingMore: boolean;
250
292
  };
251
293
 
252
- export async function getById<TPopulate extends <%= model.pascal %>Populate | undefined = undefined>(
253
- id: string,
294
+ export async function getByDocumentId<TPopulate extends <%= model.pascal %>Populate | undefined = undefined>(
295
+ documentId: string,
254
296
  options: { locale?: string; populate?: TPopulate; localeFallbacks?: boolean } = {}
255
297
  ): Promise<<%= model.pascal %>Result<undefined, TPopulate>> {
256
- const res = await fetch(`${base}${withReadOptions(`/entries/<%= model.uid %>/${id}`, options, false)}`, { headers: fimoHeaders() });
298
+ const res = await fetch(`${base}${withReadOptions(`/v2/entries/<%= model.uid %>/${documentId}`, options)}`, { headers: fimoHeaders() });
257
299
  if (!res.ok) throw new Error(await res.text());
258
300
  const data = await res.json();
259
301
  return wrapWithSource(data.data) as <%= model.pascal %>Result<undefined, TPopulate>;
@@ -263,7 +305,7 @@ export async function getBySlug<TPopulate extends <%= model.pascal %>Populate |
263
305
  slug: string,
264
306
  options: { locale?: string; populate?: TPopulate; localeFallbacks?: boolean } = {}
265
307
  ): Promise<<%= model.pascal %>Result<undefined, TPopulate> | null> {
266
- const res = await fetch(`${base}${withReadOptions(`/entries/<%= model.uid %>/slug/${encodeURIComponent(slug)}`, options)}`, { headers: fimoHeaders() });
308
+ const res = await fetch(`${base}${withReadOptions(`/v2/entries/<%= model.uid %>/slug/${encodeURIComponent(slug)}`, options)}`, { headers: fimoHeaders() });
267
309
  if (res.status === 404) return null;
268
310
  if (!res.ok) throw new Error(await res.text());
269
311
  const data = await res.json();
@@ -275,7 +317,7 @@ export async function getByField<TPopulate extends <%= model.pascal %>Populate |
275
317
  value: string,
276
318
  options: { locale?: string; populate?: TPopulate; localeFallbacks?: boolean } = {}
277
319
  ): Promise<<%= model.pascal %>Result<undefined, TPopulate> | null> {
278
- const res = await fetch(`${base}${withReadOptions(`/entries/<%= model.uid %>/field/${encodeURIComponent(fieldName)}/${encodeURIComponent(value)}`, options)}`, { headers: fimoHeaders() });
320
+ const res = await fetch(`${base}${withReadOptions(`/v2/entries/<%= model.uid %>/field/${encodeURIComponent(fieldName)}/${encodeURIComponent(value)}`, options)}`, { headers: fimoHeaders() });
279
321
  if (res.status === 404) return null;
280
322
  if (!res.ok) throw new Error(await res.text());
281
323
  const data = await res.json();
@@ -321,7 +363,7 @@ export async function list<
321
363
  TPopulate extends <%= model.pascal %>Populate | undefined = undefined,
322
364
  >(params: <%= model.pascal %>Query<TFields, TPopulate> = {} as <%= model.pascal %>Query<TFields, TPopulate>): Promise<CollectionPage<<%= model.pascal %>Result<TFields, TPopulate>>> {
323
365
  const qs = toQueryString(params);
324
- const res = await fetch(`${base}/entries/<%= model.uid %>${qs ? "?" + qs : ""}`, { headers: fimoHeaders() });
366
+ const res = await fetch(`${base}/v2/entries/<%= model.uid %>${qs ? "?" + qs : ""}`, { headers: fimoHeaders() });
325
367
  if (!res.ok) throw new Error(await res.text());
326
368
  const data = parseCollectionResponse(await res.json());
327
369
  const items = data.data.map(wrapWithSource) as <%= model.pascal %>Result<TFields, TPopulate>[];
@@ -343,7 +385,7 @@ export async function get<
343
385
  }
344
386
 
345
387
  export async function create(payload: <%= model.pascal %>Input): Promise<<%= model.pascal %>> {
346
- const res = await fetch(`${base}/entries/<%= model.uid %>`, {
388
+ const res = await fetch(`${base}/v2/entries/<%= model.uid %>`, {
347
389
  method: "POST",
348
390
  headers: fimoHeaders({ "Content-Type": "application/json" }),
349
391
  body: JSON.stringify(splitWritePayload(payload))
@@ -353,19 +395,22 @@ export async function create(payload: <%= model.pascal %>Input): Promise<<%= mod
353
395
  return wrapWithSource(data.data);
354
396
  }
355
397
 
356
- export async function update(id: string, payload: Partial<<%= model.pascal %>Input>): Promise<<%= model.pascal %>> {
357
- const res = await fetch(`${base}/entries/<%= model.uid %>/${id}`, {
398
+ export async function update(documentId: string, payload: Partial<<%= model.pascal %>Input>): Promise<<%= model.pascal %>> {
399
+ const write = splitWritePayload(payload) as { data: Record<string, unknown>; locale?: string };
400
+ const url = withReadOptions(`/v2/entries/<%= model.uid %>/${documentId}`, { locale: write.locale });
401
+ const res = await fetch(`${base}${url}`, {
358
402
  method: "PUT",
359
403
  headers: fimoHeaders({ "Content-Type": "application/json" }),
360
- body: JSON.stringify(splitWritePayload(payload))
404
+ body: JSON.stringify({ data: write.data })
361
405
  });
362
406
  if (!res.ok) throw new Error(await res.text());
363
407
  const data = await res.json();
364
408
  return wrapWithSource(data.data);
365
409
  }
366
410
 
367
- export async function remove(id: string): Promise<void> {
368
- const res = await fetch(`${base}/entries/<%= model.uid %>/${id}`, { method: "DELETE", headers: fimoHeaders() });
411
+ export async function remove(documentId: string, options: { locale?: string } = {}): Promise<void> {
412
+ const url = withReadOptions(`/v2/entries/<%= model.uid %>/${documentId}`, options);
413
+ const res = await fetch(`${base}${url}`, { method: "DELETE", headers: fimoHeaders() });
369
414
  if (!res.ok) throw new Error(await res.text());
370
415
  }
371
416
  <% } else { -%>
@@ -399,7 +444,7 @@ export async function update(payload: Partial<<%= model.pascal %>Input>): Promis
399
444
  <% if (!model.isSingleton) { -%>
400
445
  const qk = {
401
446
  root : ["<%= model.pascal %>"] as const,
402
- byId : <TPopulate extends <%= model.pascal %>Populate | undefined = undefined>(id: string, options: { locale?: string; populate?: TPopulate; localeFallbacks?: boolean } = {}) => ["<%= model.pascal %>", id, options.locale, options.populate] as const,
447
+ byDocumentId: <TPopulate extends <%= model.pascal %>Populate | undefined = undefined>(documentId: string, options: { locale?: string; populate?: TPopulate; localeFallbacks?: boolean } = {}) => ["<%= model.pascal %>", "document", documentId, options.locale, options.populate] as const,
403
448
  bySlug : <TPopulate extends <%= model.pascal %>Populate | undefined = undefined>(slug: string, options: { locale?: string; populate?: TPopulate; localeFallbacks?: boolean } = {}) => ["<%= model.pascal %>", "slug", slug, options.locale, options.populate] as const,
404
449
  byField : <TPopulate extends <%= model.pascal %>Populate | undefined = undefined>(field: string, value: string, options: { locale?: string; populate?: TPopulate; localeFallbacks?: boolean } = {}) => ["<%= model.pascal %>", field, value, options.locale, options.populate] as const,
405
450
  list : (params: <%= model.pascal %>Query) => ["<%= model.pascal %>", params] as const,
@@ -409,10 +454,10 @@ const qk = {
409
454
  >(params: InfiniteListQuery<TFields, TPopulate>) => ["<%= model.pascal %>", "infinite", params] as const
410
455
  };
411
456
 
412
- export function useGetById<TPopulate extends <%= model.pascal %>Populate | undefined = undefined>(id: string, options: { locale?: string; populate?: TPopulate; localeFallbacks?: boolean } = {}) {
413
- const locale = readLocale(options, false, useQueryLocale(options.locale));
457
+ export function useGetByDocumentId<TPopulate extends <%= model.pascal %>Populate | undefined = undefined>(documentId: string, options: { locale?: string; populate?: TPopulate; localeFallbacks?: boolean } = {}): UseQueryResult<<%= model.pascal %>Result<undefined, TPopulate>, Error> {
458
+ const locale = useQueryLocale(options.locale);
414
459
  const localizedOptions = { ...options, locale };
415
- return useQuery<<%= model.pascal %>Result<undefined, TPopulate>, Error>({ queryKey: qk.byId(id, localizedOptions), queryFn: () => getById(id, localizedOptions) });
460
+ return useQuery<<%= model.pascal %>Result<undefined, TPopulate>, Error>({ queryKey: qk.byDocumentId(documentId, localizedOptions), queryFn: () => getByDocumentId(documentId, localizedOptions) });
416
461
  }
417
462
 
418
463
  export function useGetBySlug<TPopulate extends <%= model.pascal %>Populate | undefined = undefined>(slug: string, options: { locale?: string; populate?: TPopulate; localeFallbacks?: boolean } = {}) {
@@ -475,10 +520,10 @@ export function useInfiniteList<
475
520
  };
476
521
  }
477
522
 
478
- export function useSuspenseGetById<TPopulate extends <%= model.pascal %>Populate | undefined = undefined>(id: string, options: { locale?: string; populate?: TPopulate; localeFallbacks?: boolean } = {}) {
479
- const locale = readLocale(options, false, useQueryLocale(options.locale));
523
+ export function useSuspenseGetByDocumentId<TPopulate extends <%= model.pascal %>Populate | undefined = undefined>(documentId: string, options: { locale?: string; populate?: TPopulate; localeFallbacks?: boolean } = {}): UseSuspenseQueryResult<<%= model.pascal %>Result<undefined, TPopulate>, Error> {
524
+ const locale = useQueryLocale(options.locale);
480
525
  const localizedOptions = { ...options, locale };
481
- return useSuspenseQuery<<%= model.pascal %>Result<undefined, TPopulate>, Error>({ queryKey: qk.byId(id, localizedOptions), queryFn: () => getById(id, localizedOptions) });
526
+ return useSuspenseQuery<<%= model.pascal %>Result<undefined, TPopulate>, Error>({ queryKey: qk.byDocumentId(documentId, localizedOptions), queryFn: () => getByDocumentId(documentId, localizedOptions) });
482
527
  }
483
528
 
484
529
  export function useSuspenseGetBySlug<TPopulate extends <%= model.pascal %>Populate | undefined = undefined>(slug: string, options: { locale?: string; populate?: TPopulate; localeFallbacks?: boolean } = {}) {
@@ -516,22 +561,22 @@ export function useCreate() {
516
561
 
517
562
  export function useUpdate() {
518
563
  const qc = useQueryClient();
519
- return useMutation<<%= model.pascal %>, Error, { id: string } & Partial<<%= model.pascal %>Input>>({
520
- mutationFn : ({ id, ...patch }) => update(id, patch),
564
+ return useMutation<<%= model.pascal %>, Error, { documentId: string; locale?: string } & Partial<<%= model.pascal %>Input>>({
565
+ mutationFn : ({ documentId, ...patch }) => update(documentId, patch),
521
566
  onSuccess : (_data, vars) => {
522
567
  qc.invalidateQueries({ queryKey: qk.root });
523
- qc.invalidateQueries({ queryKey: qk.byId(vars.id) });
568
+ qc.invalidateQueries({ queryKey: qk.byDocumentId(vars.documentId, { locale: vars.locale ?? defaultLocale() }) });
524
569
  }
525
570
  });
526
571
  }
527
572
 
528
573
  export function useRemove() {
529
574
  const qc = useQueryClient();
530
- return useMutation<void, Error, string>({
531
- mutationFn : remove,
532
- onSuccess : (_data, id) => {
575
+ return useMutation<void, Error, { documentId: string; locale?: string }>({
576
+ mutationFn : ({ documentId, locale }) => remove(documentId, { locale }),
577
+ onSuccess : (_data, { documentId, locale }) => {
533
578
  qc.invalidateQueries({ queryKey: qk.root });
534
- qc.removeQueries({ queryKey: ["<%= model.pascal %>", id], exact: false });
579
+ qc.removeQueries({ queryKey: qk.byDocumentId(documentId, { locale: locale ?? defaultLocale() }), exact: false });
535
580
  }
536
581
  });
537
582
  }
@@ -569,7 +614,7 @@ export function useUpdate() {
569
614
  export default {
570
615
  <% if (!model.isSingleton) { -%>
571
616
  /* async helpers */
572
- getById,
617
+ getByDocumentId,
573
618
  getBySlug,
574
619
  getByField,
575
620
  list,
@@ -579,13 +624,13 @@ export default {
579
624
  remove,
580
625
 
581
626
  /* hooks */
582
- useGetById,
627
+ useGetByDocumentId,
583
628
  useGetBySlug,
584
629
  useGetByField,
585
630
  useList,
586
631
  useInfiniteList,
587
632
  useGet,
588
- useSuspenseGetById,
633
+ useSuspenseGetByDocumentId,
589
634
  useSuspenseGet,
590
635
  useSuspenseGetByField,
591
636
  useSuspenseGetBySlug,
@@ -32,11 +32,15 @@ export interface <%= model.pascal %>FimoMeta {
32
32
  }
33
33
 
34
34
  export interface <%= model.pascal %> extends WithReferences<<%= model.pascal %>References> {
35
- id: string;
35
+ <% if (model.isSingleton) { -%>
36
+ id?: string;
37
+ <% } -%>
36
38
  documentId: string;
37
39
  slug: FimoString;
38
- <% if (model.localized) { -%>
40
+ <% if (!model.isSingleton || model.localized) { -%>
39
41
  locale: string;
42
+ <% } else { -%>
43
+ locale?: string;
40
44
  <% } -%>
41
45
  createdAt: string;
42
46
  updatedAt: string;
@@ -191,10 +191,9 @@ Import from the public schema module, e.g. `import BlogPost from '@/schemas/Blog
191
191
 
192
192
  ```ts
193
193
  interface BlogPost {
194
- id: string;
195
194
  documentId: string;
196
195
  slug: FimoString;
197
- locale: string; // localized schemas only; omitted when i18n.enabled is false
196
+ locale: string;
198
197
  createdAt: string;
199
198
  updatedAt: string;
200
199
  __fimo: {
@@ -219,7 +218,7 @@ When a project has multiple configured locales, create and update each localized
219
218
 
220
219
  For shared content, set `i18n: { "enabled": false }` on the schema and create each entry once. Shared entries:
221
220
 
222
- - have no public `locale` property;
221
+ - expose the effective request locale in collection v2 responses;
223
222
  - are returned in every locale context without duplication;
224
223
  - ignore locale on owner reads and writes;
225
224
  - still use the active/requested locale when a populated reference targets a localized schema.
@@ -247,17 +246,17 @@ Filtering on polymorphic fields is not supported yet.
247
246
 
248
247
  ```ts
249
248
  // low-level fetch helpers
250
- getById<TPopulate extends BlogPostPopulate | undefined>(id: string, options?: { populate?: TPopulate }): Promise<BlogPostResult<undefined, TPopulate>>
249
+ getByDocumentId<TPopulate extends BlogPostPopulate | undefined>(documentId: string, options?: { locale?: string; populate?: TPopulate }): Promise<BlogPostResult<undefined, TPopulate>>
251
250
  getBySlug<TPopulate extends BlogPostPopulate | undefined>(slug: string, options?: { populate?: TPopulate }): Promise<BlogPostResult<undefined, TPopulate> | null>
252
251
  getByField<TPopulate extends BlogPostPopulate | undefined>(fieldName: string, value: string, options?: { populate?: TPopulate }): Promise<BlogPostResult<undefined, TPopulate> | null>
253
252
  list<TFields extends BlogPostFields | undefined, TPopulate extends BlogPostPopulate | undefined>(params?: BlogPostQuery<TFields, TPopulate>): Promise<CollectionPage<BlogPostResult<TFields, TPopulate>>>
254
253
  get<TFields extends BlogPostFields | undefined, TPopulate extends BlogPostPopulate | undefined>(params?: BlogPostQuery<TFields, TPopulate>): Promise<BlogPostResult<TFields, TPopulate>[]>
255
254
  create(payload: BlogPostInput): Promise<BlogPost>
256
- update(id: string, payload: Partial<BlogPostInput>): Promise<BlogPost>
257
- remove(id: string): Promise<void>
255
+ update(documentId: string, payload: Partial<BlogPostInput>): Promise<BlogPost>
256
+ remove(documentId: string, options?: { locale?: string }): Promise<void>
258
257
 
259
258
  // react-query hooks
260
- useGetById(id, { populate? }) // → { data: BlogPostResult<undefined, TPopulate> | undefined, isLoading, ... }
259
+ useGetByDocumentId(documentId, { locale?, populate? }) // → { data: BlogPostResult<undefined, TPopulate> | undefined, isLoading, ... }
261
260
  useGetBySlug(slug, { populate? }) // → { data: BlogPostResult<undefined, TPopulate> | null | undefined, ... } — ideal for /:slug detail routes
262
261
  useGetByField(fieldName, value, { populate? })
263
262
  useList<TFields, TPopulate>(params?) // → { data: CollectionPage<BlogPostResult<TFields, TPopulate>> | undefined, ... }
@@ -266,7 +265,7 @@ useGet<TFields, TPopulate>(params?) // → legacy array-shaped { data:
266
265
  useCreate() / useUpdate() / useRemove()
267
266
 
268
267
  // Suspense variants (same signatures, throw to a <Suspense> boundary instead of returning isLoading)
269
- useSuspenseGetById(id) / useSuspenseGetBySlug(slug) / useSuspenseGetByField(fieldName, value) / useSuspenseGet(params?)
268
+ useSuspenseGetByDocumentId(documentId) / useSuspenseGetBySlug(slug) / useSuspenseGetByField(fieldName, value) / useSuspenseGet(params?)
270
269
  ```
271
270
 
272
271
  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`.
@@ -279,7 +278,7 @@ update(payload: Partial<SettingsInput>): Promise<Settings>
279
278
  useGet(options?) / useSuspenseGet(options?) / useUpdate()
280
279
  ```
281
280
 
282
- 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.
281
+ Singleton clients do not expose `create`, `remove`, `getByDocumentId`, or list queries. Localized singletons have one variant per locale; non-localized singletons have one locale-free row.
283
282
 
284
283
  Choose the collection interface from the UI behavior. Use `useList` when the UI controls a page, and read pagination state from `pageInfo`:
285
284
 
@@ -338,7 +337,7 @@ Operators: scalar equality, `$eq`, `$ne`, `$gt`, `$gte`, `$lt`, `$lte`, `$in`, `
338
337
  Reference field values are reference objects, but reference filters use the target `documentId` string, for example `where: { author: "750e..." }`; for to-many references that means "contains this target document".
339
338
  For to-many references, `$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. Reference fields cannot be sorted.
340
339
 
341
- Use `fields` to keep list payloads small. The API always keeps `id`, and returns only the requested fields beyond that:
340
+ Use `fields` to keep list payloads small. The v2 API always keeps `documentId` and `locale`, and returns only the requested fields beyond that:
342
341
 
343
342
  ```tsx
344
343
  const { data: summaries = [] } = BlogPost.useGet({
@@ -346,7 +345,7 @@ const { data: summaries = [] } = BlogPost.useGet({
346
345
  sort: '-publishedAt',
347
346
  limit: 12,
348
347
  });
349
- // summaries: Array<Pick<BlogPost, 'id' | 'slug' | 'title' | 'excerpt' | 'coverImage'>>
348
+ // summaries: Array<Pick<BlogPost, 'documentId' | 'locale' | 'slug' | 'title' | 'excerpt' | 'coverImage'>>
350
349
  ```
351
350
 
352
351
  Use `populate` to embed reference targets in read results:
@@ -434,7 +433,7 @@ export function BlogList() {
434
433
  return (
435
434
  <ul>
436
435
  {page.items.map((post) => (
437
- <li key={post.id}>
436
+ <li key={`${post.documentId}:${post.locale}`}>
438
437
  <Text value={post.title} as="h2" />
439
438
  <Image value={post.coverImage} width={800} height={400} fit="cover" />
440
439
  <RichText value={post.body} className="prose" />
@@ -451,9 +450,9 @@ For the `<Text>` / `<RichText>` / `<Image>` / `<Date>` components and the rules
451
450
  ## Read path (published app, no auth)
452
451
 
453
452
  ```ts
454
- const res = await fetch(`${import.meta.env.VITE_API_URL}/entries/<typeName>?fields=slug,title&limit=12`);
453
+ const res = await fetch(`${import.meta.env.VITE_API_URL}/v2/entries/<typeName>?locale=en&fields=slug,title&limit=12`);
455
454
  const { data, meta } = await res.json();
456
- // data: Entry[] — fields flattened at top level (id, slug, title, ...)
455
+ // data: Entry[] — fields flattened at top level (documentId, locale, slug, title, ...)
457
456
  // meta: { limit, offset, total, hasMore }
458
457
  ```
459
458
 
@@ -6,7 +6,7 @@ existing RR project.
6
6
 
7
7
  ## Prerequisites
8
8
 
9
- - Node 20+
9
+ - Node 22.22+
10
10
  - A Fimo project created via `fimo init` (gives you `projectId`,
11
11
  `organizationId`, and an `apiUrl`).
12
12
 
@@ -43,9 +43,9 @@ fimo entries create <TypeName> --body '<json>'
43
43
  fimo entries create <TypeName> --locale es --document-id <documentId> --body '<json>'
44
44
  fimo entries list <TypeName> [--limit N] [--offset N] [--search Q] [--where JSON] [--sort F] [--fields FIELDS] [--populate FIELDS]
45
45
  fimo entries list <TypeName> --locale es
46
- fimo entries get <TypeName> <id>
47
- fimo entries update <TypeName> <id> --locale es --body '<json>'
48
- fimo entries delete <TypeName> <id>
46
+ fimo entries get <TypeName> <documentId> --locale es
47
+ fimo entries update <TypeName> <documentId> --locale es --body '<json>'
48
+ fimo entries delete <TypeName> <documentId> --locale es
49
49
  ```
50
50
 
51
51
  `--body` is a **required** option on `create` and `update`. Its JSON shape (where `slug` goes, how `media` fields look, Tiptap richtext format) lives in `fimo/references/content.md`. **Always read that first before constructing an entry payload — slug must be inside `data`, media has a specific shape, richtext must be Tiptap JSONContent.**
@@ -66,7 +66,7 @@ Use the canonical read query flags: `--search`, `--sort`, `--where`, `--limit`,
66
66
  `--where` is a JSON object keyed by schema field name. Simple values mean equality and stay typed inside the JSON (`{"featured":true}`, `{"category":"react"}`, `{"rank":{"$gte":3}}`); operator objects support `$eq`, `$ne`, `$gt`, `$gte`, `$lt`, `$lte`, `$in`, `$nin`, `$contains`, `$startsWith`, and `$null`, subject to the field type. Public Fimo-owned metadata lives under `__fimo`; currently `__fimo.contentType` is filterable.
67
67
 
68
68
  Reference filters take the target `documentId` string even though stored values are objects (`{"author":"750e..."}`). On a to-many field, `$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. Reference fields cannot be sorted; use `--sort field` / `--sort -field` on scalar fields.
69
- `--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`.
69
+ `--fields` is a comma-separated projection for list payloads. The v2 API always keeps `documentId` and `locale`; pass only the other fields your view needs, such as `slug,title,excerpt,coverImage`.
70
70
 
71
71
  `--populate` is a comma-separated list of reference fields to embed. Without it, reference fields return reference objects like `{ "documentId": "..." }`; with `--populate author,contributors`, those fields return the target entry objects for the requested locale. Only reference fields can be populated (scalar fields 400), and populate never selects which target fields come back. A populated target missing in the requested locale resolves to null unless you pass `localeFallbacks=true`, which falls back to the best available locale variant, marked by its `locale` field. Limits: 200 references per to-many write, 500 distinct populated documents per field per request (400 past either).
72
72
 
@@ -95,11 +95,11 @@ Do not add `localized` or field-level `i18n` to reference fields. The reference
95
95
 
96
96
  ## Localized and shared content
97
97
 
98
- 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.
98
+ Schemas are localized by default. Omit `--locale` for default-locale creates and lists. `entries get`, `entries update`, and `entries delete` require `--locale` because collection entry identity is always `documentId + locale`.
99
99
 
100
100
  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.
101
101
 
102
- 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 references that target localized schemas. No other `i18n` options and no field-level localization settings are supported.
102
+ 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. V2 shared entry responses expose the effective request locale even though storage still has one locale-free row. No other `i18n` options and no field-level localization settings are supported.
103
103
 
104
104
  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.
105
105
 
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "bundled": true,
3
3
  "bundler": "esbuild",
4
- "bundledAt": "2026-07-14T13:44:49.449Z",
4
+ "bundledAt": "2026-07-14T21:53:17.245Z",
5
5
  "cliVersion": "0.5.0",
6
6
  "external": [
7
7
  "oxc-parser",
package/dist/cli/index.js CHANGED
@@ -20634,33 +20634,39 @@ var init_api = __esm({
20634
20634
  // ── Tenant API: Entries ──
20635
20635
  async listEntries(projectId, typeName, params) {
20636
20636
  const query = params ? `?${new URLSearchParams(params).toString()}` : "";
20637
- return this.request(this.tenantPath(projectId, `/entries/${typeName}${query}`));
20637
+ return this.request(this.tenantPath(projectId, `/v2/entries/${typeName}${query}`));
20638
20638
  }
20639
- async getEntry(projectId, typeName, id, params) {
20640
- const query = params ? `?${new URLSearchParams(params).toString()}` : "";
20639
+ async getEntry(projectId, typeName, documentId, locale) {
20640
+ const query = new URLSearchParams({ locale }).toString();
20641
20641
  const body = await this.request(
20642
- this.tenantPath(projectId, `/entries/${typeName}/${id}${query}`)
20642
+ this.tenantPath(projectId, `/v2/entries/${typeName}/${documentId}?${query}`)
20643
20643
  );
20644
20644
  return body.data;
20645
20645
  }
20646
20646
  async createEntry(projectId, typeName, data2) {
20647
- const body = await this.request(this.tenantPath(projectId, `/entries/${typeName}`), {
20647
+ const body = await this.request(this.tenantPath(projectId, `/v2/entries/${typeName}`), {
20648
20648
  method: "POST",
20649
20649
  body: JSON.stringify(data2)
20650
20650
  });
20651
20651
  return body.data;
20652
20652
  }
20653
- async updateEntry(projectId, typeName, id, data2) {
20654
- const body = await this.request(this.tenantPath(projectId, `/entries/${typeName}/${id}`), {
20655
- method: "PUT",
20656
- body: JSON.stringify(data2)
20657
- });
20653
+ async updateEntry(projectId, typeName, documentId, locale, data2) {
20654
+ const query = new URLSearchParams({ locale }).toString();
20655
+ const body = await this.request(
20656
+ this.tenantPath(projectId, `/v2/entries/${typeName}/${documentId}?${query}`),
20657
+ {
20658
+ method: "PUT",
20659
+ body: JSON.stringify(data2)
20660
+ }
20661
+ );
20658
20662
  return body.data;
20659
20663
  }
20660
- async deleteEntry(projectId, typeName, id) {
20661
- const body = await this.request(this.tenantPath(projectId, `/entries/${typeName}/${id}`), {
20662
- method: "DELETE"
20663
- });
20664
+ async deleteEntry(projectId, typeName, documentId, locale) {
20665
+ const query = new URLSearchParams({ locale }).toString();
20666
+ const body = await this.request(
20667
+ this.tenantPath(projectId, `/v2/entries/${typeName}/${documentId}?${query}`),
20668
+ { method: "DELETE" }
20669
+ );
20664
20670
  return body.data;
20665
20671
  }
20666
20672
  // ── Tenant API: Assets ──
@@ -44407,7 +44413,7 @@ var init_payment_method_dto = __esm({
44407
44413
  });
44408
44414
 
44409
44415
  // ../../packages/api-contracts/dist/esm/dtos/content.dto.js
44410
- var FieldDefinitionSchema, ContentTypeI18nSchema, ContentTypeDTOSchema, ContentTypeDTO, SchemaDTOSchema, SchemaDTO, EntryDTOSchema, EntryDTO, PaginatedEntriesDTOSchema, PaginatedEntriesDTO, EntryCountDTOSchema, EntryCountDTO, EntryCountsDTOSchema, EntryCountsDTO, ContentTypeResponseSchema, ContentTypeListResponseSchema, SchemaResponseSchema, EntryResponseSchema, EntryListResponseSchema;
44416
+ var FieldDefinitionSchema, ContentTypeI18nSchema, ContentTypeDTOSchema, ContentTypeDTO, SchemaDTOSchema, SchemaDTO, EntryDTOSchema, EntryDTO, EntryV2DTOSchema, EntryV2DTO, PaginatedEntriesDTOSchema, PaginatedEntriesDTO, PaginatedEntriesV2DTOSchema, PaginatedEntriesV2DTO, EntryCountDTOSchema, EntryCountDTO, EntryCountsDTOSchema, EntryCountsDTO, ContentTypeResponseSchema, ContentTypeListResponseSchema, SchemaResponseSchema, EntryResponseSchema, EntryV2ResponseSchema, EntryListResponseSchema, EntryV2ListResponseSchema;
44411
44417
  var init_content_dto = __esm({
44412
44418
  "../../packages/api-contracts/dist/esm/dtos/content.dto.js"() {
44413
44419
  "use strict";
@@ -44449,6 +44455,18 @@ var init_content_dto = __esm({
44449
44455
  })
44450
44456
  }).passthrough();
44451
44457
  EntryDTO = createDTO(EntryDTOSchema);
44458
+ EntryV2DTOSchema = external_exports.object({
44459
+ id: external_exports.never().optional(),
44460
+ documentId: external_exports.string(),
44461
+ slug: external_exports.string(),
44462
+ locale: external_exports.string(),
44463
+ createdAt: external_exports.string(),
44464
+ updatedAt: external_exports.string(),
44465
+ __fimo: external_exports.object({
44466
+ contentType: external_exports.string()
44467
+ })
44468
+ }).passthrough();
44469
+ EntryV2DTO = createDTO(EntryV2DTOSchema);
44452
44470
  PaginatedEntriesDTOSchema = external_exports.object({
44453
44471
  data: external_exports.array(EntryDTOSchema),
44454
44472
  meta: external_exports.object({
@@ -44459,6 +44477,10 @@ var init_content_dto = __esm({
44459
44477
  })
44460
44478
  });
44461
44479
  PaginatedEntriesDTO = createDTO(PaginatedEntriesDTOSchema);
44480
+ PaginatedEntriesV2DTOSchema = PaginatedEntriesDTOSchema.extend({
44481
+ data: external_exports.array(EntryV2DTOSchema)
44482
+ });
44483
+ PaginatedEntriesV2DTO = createDTO(PaginatedEntriesV2DTOSchema);
44462
44484
  EntryCountDTOSchema = external_exports.object({
44463
44485
  contentType: external_exports.string(),
44464
44486
  count: external_exports.number()
@@ -44472,7 +44494,9 @@ var init_content_dto = __esm({
44472
44494
  ContentTypeListResponseSchema = createResponseSchema(external_exports.array(ContentTypeDTOSchema));
44473
44495
  SchemaResponseSchema = createResponseSchema(SchemaDTOSchema);
44474
44496
  EntryResponseSchema = createResponseSchema(EntryDTOSchema);
44497
+ EntryV2ResponseSchema = createResponseSchema(EntryV2DTOSchema);
44475
44498
  EntryListResponseSchema = createResponseSchema(external_exports.array(EntryDTOSchema));
44499
+ EntryV2ListResponseSchema = createResponseSchema(external_exports.array(EntryV2DTOSchema));
44476
44500
  }
44477
44501
  });
44478
44502
 
@@ -48789,13 +48813,15 @@ function isCommandAvailable(command) {
48789
48813
  const result = spawnSync(probe, [command], { stdio: "ignore" });
48790
48814
  return result.status === 0;
48791
48815
  }
48792
- async function resolveOrganization(api, explicit) {
48816
+ async function resolveOrganization(api, explicit, options = {}) {
48793
48817
  if (explicit) {
48794
48818
  return explicit;
48795
48819
  }
48796
48820
  const creds = readCredentials();
48797
- if (creds?.activeOrganization?.id) {
48798
- return creds.activeOrganization.id;
48821
+ const activeOrganizationId = creds?.activeOrganization?.id;
48822
+ const nonInteractive2 = options.nonInteractive ?? (currentMode().format === "json" || !isInteractive());
48823
+ if (nonInteractive2 && activeOrganizationId) {
48824
+ return activeOrganizationId;
48799
48825
  }
48800
48826
  let orgs2;
48801
48827
  try {
@@ -48810,7 +48836,30 @@ async function resolveOrganization(api, explicit) {
48810
48836
  if (orgs2.length === 0) {
48811
48837
  throw new ScaffoldError("NO_ORGANIZATIONS", "No organization found for this account.");
48812
48838
  }
48813
- return orgs2[0].id;
48839
+ if (orgs2.length === 1) {
48840
+ return orgs2[0].id;
48841
+ }
48842
+ if (nonInteractive2) {
48843
+ throw new ScaffoldError(
48844
+ "ORGANIZATION_REQUIRED",
48845
+ "Several organizations are available and no active organization is configured.",
48846
+ "Pass --org <organizationId>, or run `fimo orgs switch --org <id-or-slug>` first."
48847
+ );
48848
+ }
48849
+ const currentId = orgs2.some((organization) => organization.id === activeOrganizationId) ? activeOrganizationId : void 0;
48850
+ const selected = await _t({
48851
+ message: "Select an organization for this project",
48852
+ initialValue: currentId,
48853
+ options: orgs2.map((organization) => ({
48854
+ value: organization.id,
48855
+ label: organization.name,
48856
+ hint: organization.id === currentId ? "current" : organization.slug
48857
+ }))
48858
+ });
48859
+ if (q(selected)) {
48860
+ throw new ScaffoldError("ORGANIZATION_SELECTION_CANCELLED", "Organization selection cancelled.");
48861
+ }
48862
+ return selected;
48814
48863
  }
48815
48864
  var execFileAsync7, MAIN_BRANCH, ScaffoldError;
48816
48865
  var init_scaffold_project = __esm({
@@ -48824,6 +48873,7 @@ var init_scaffold_project = __esm({
48824
48873
  init_auth_errors();
48825
48874
  init_git_credential_setup();
48826
48875
  init_git();
48876
+ init_interactive();
48827
48877
  init_local_project_files();
48828
48878
  init_scaffold();
48829
48879
  init_skills();
@@ -49048,6 +49098,7 @@ async function planInit(args) {
49048
49098
  let projectId;
49049
49099
  let targetDir;
49050
49100
  let displayName;
49101
+ const derived = { ...options };
49051
49102
  if (useExisting) {
49052
49103
  projectId = options.projectId ?? await selectProjectId({
49053
49104
  api,
@@ -49075,8 +49126,27 @@ async function planInit(args) {
49075
49126
  });
49076
49127
  return null;
49077
49128
  }
49129
+ try {
49130
+ derived.org = await resolveOrganization(api, options.org, {
49131
+ nonInteractive: nonInteractive(options)
49132
+ });
49133
+ } catch (error45) {
49134
+ if (error45 instanceof ScaffoldError && error45.code === "ORGANIZATION_SELECTION_CANCELLED") {
49135
+ ui.info({ command: "init", message: "Cancelled." });
49136
+ return null;
49137
+ }
49138
+ if (error45 instanceof ScaffoldError) {
49139
+ ui.error({
49140
+ command: "init",
49141
+ code: error45.code,
49142
+ message: error45.message,
49143
+ hint: error45.hint
49144
+ });
49145
+ return null;
49146
+ }
49147
+ throw error45;
49148
+ }
49078
49149
  }
49079
- const derived = { ...options };
49080
49150
  const gatherAgents = !nonInteractive(options) && (!useExisting || options.skills !== false);
49081
49151
  if (gatherAgents) {
49082
49152
  let agents;
@@ -106306,11 +106376,10 @@ async function entriesListCommand(typeName, options, cmd) {
106306
106376
  ui.result({ command: "entries list", data: result, render: /* @__PURE__ */ __name(() => renderPrettyJson5(result), "render") });
106307
106377
  }
106308
106378
  __name(entriesListCommand, "entriesListCommand");
106309
- async function entriesGetCommand(typeName, id, options, cmd) {
106379
+ async function entriesGetCommand(typeName, documentId, options, cmd) {
106310
106380
  const { ctx, api } = await bootstrapProjectCommand(options, cmd);
106311
106381
  await requireOperation("cms.get", { projectId: ctx.projectId, env: ctx.env });
106312
- const params = options.locale ? { locale: options.locale } : void 0;
106313
- const result = await api.getEntry(ctx.projectId, typeName, id, params);
106382
+ const result = await api.getEntry(ctx.projectId, typeName, documentId, options.locale);
106314
106383
  ui.result({ command: "entries get", data: result, render: /* @__PURE__ */ __name(() => renderPrettyJson5(result), "render") });
106315
106384
  }
106316
106385
  __name(entriesGetCommand, "entriesGetCommand");
@@ -106325,21 +106394,18 @@ async function entriesCreateCommand(typeName, options, cmd) {
106325
106394
  ui.result({ command: "entries create", data: result, render: /* @__PURE__ */ __name(() => renderPrettyJson5(result), "render") });
106326
106395
  }
106327
106396
  __name(entriesCreateCommand, "entriesCreateCommand");
106328
- async function entriesUpdateCommand(typeName, id, options, cmd) {
106397
+ async function entriesUpdateCommand(typeName, documentId, options, cmd) {
106329
106398
  const { ctx, api } = await bootstrapProjectCommand(options, cmd, { refuseReadOnly: true });
106330
106399
  await requireOperation("cms.update", { projectId: ctx.projectId, env: ctx.env });
106331
- const data2 = addDefinedMetadata(parseJsonObject(options.body), {
106332
- locale: options.locale,
106333
- documentId: options.documentId
106334
- });
106335
- const result = await api.updateEntry(ctx.projectId, typeName, id, data2);
106400
+ const data2 = parseJsonObject(options.body);
106401
+ const result = await api.updateEntry(ctx.projectId, typeName, documentId, options.locale, data2);
106336
106402
  ui.result({ command: "entries update", data: result, render: /* @__PURE__ */ __name(() => renderPrettyJson5(result), "render") });
106337
106403
  }
106338
106404
  __name(entriesUpdateCommand, "entriesUpdateCommand");
106339
- async function entriesDeleteCommand(typeName, id, options, cmd) {
106405
+ async function entriesDeleteCommand(typeName, documentId, options, cmd) {
106340
106406
  const { ctx, api } = await bootstrapProjectCommand(options, cmd, { refuseReadOnly: true });
106341
106407
  await requireOperation("cms.delete", { projectId: ctx.projectId, env: ctx.env });
106342
- const result = await api.deleteEntry(ctx.projectId, typeName, id);
106408
+ const result = await api.deleteEntry(ctx.projectId, typeName, documentId, options.locale);
106343
106409
  ui.result({ command: "entries delete", data: result, render: /* @__PURE__ */ __name(() => renderPrettyJson5(result), "render") });
106344
106410
  }
106345
106411
  __name(entriesDeleteCommand, "entriesDeleteCommand");
@@ -110175,7 +110241,7 @@ registerChannelsCommands(program);
110175
110241
  registerSchedulesCommands(program);
110176
110242
  program.command("invite").description("Invite a user to an organization or project").option("--target <target>", "Invite target: organization or project").option("--org <organizationId>", "Organization ID (defaults to active when needed)").option("--project <projectId>", "Project ID (defaults to the current project when available)").option("--email <email>", "Email address of the user to invite").option("--role <role>", "Role or access level to grant").option("-C, --cwd <dir>", "Project directory used to resolve the current project").option("-y, --yes", "Skip the confirmation prompt").option("--resend", "Resend an existing pending organization invite").action(inviteCommand);
110177
110243
  program.command("clone").description("Clone an existing Fimo project locally").argument("[projectId]", "Project ID to clone").argument("[dir]", "Target directory (defaults to the project name)").option("--env <name>", "Env to clone (defaults to main)").action(cloneCommand);
110178
- program.command("init").description("Set up a Fimo project locally: sign in, create or link a project, install deps and skills").argument("[dir]", "Target directory").option("--project-id <id>", "Link an existing project instead of creating a new one").option("--org <organizationId>", "Target organization ID when creating (auto if you belong to one)").option("--env <name>", "Env to check out when linking an existing project (defaults to main)").option("--template-root <path>", "Override template directory when creating").option("--package-manager <pm>", "Install dependencies with: npm | pnpm | yarn | bun").option("--install", "Install dependencies after checkout").option("--no-install", "Skip dependency installation after checkout").option("--skills", "Set up Fimo skills for your AI coding agents").option("--no-skills", "Skip setting up Fimo skills").option(
110244
+ program.command("init").description("Set up a Fimo project locally: sign in, create or link a project, install deps and skills").argument("[dir]", "Target directory").option("--project-id <id>", "Link an existing project instead of creating a new one").option("--org <organizationId>", "Target organization ID when creating (recommended for agents and CI)").option("--env <name>", "Env to check out when linking an existing project (defaults to main)").option("--template-root <path>", "Override template directory when creating").option("--package-manager <pm>", "Install dependencies with: npm | pnpm | yarn | bun").option("--install", "Install dependencies after checkout").option("--no-install", "Skip dependency installation after checkout").option("--skills", "Set up Fimo skills for your AI coding agents").option("--no-skills", "Skip setting up Fimo skills").option(
110179
110245
  "--agents <list>",
110180
110246
  'Comma-separated list of AI agents to set up project rules for (e.g. claude-code,cursor). Pass "none" to skip per-agent variants.'
110181
110247
  ).option(
@@ -110292,10 +110358,10 @@ schemas2.command("push").description("Push schema JSON files to the API (create
110292
110358
  schemas2.command("delete").description("Delete a schema by type name").argument("<typeName>", "Content type name").option("-C, --cwd <dir>", "Project directory").action(schemaDeleteCommand);
110293
110359
  var entries = program.command("entries").description("Manage content entries");
110294
110360
  entries.command("list").description("List entries of a content type").argument("<typeName>", "Content type name").option("-C, --cwd <dir>", "Project directory").option("--limit <n>", "Max entries to return").option("--offset <n>", "Offset for pagination").option("--search <query>", "Search query").option("--where <json>", `JSON filter object, e.g. '{"featured":true}'`).option("--sort <field>", "Sort field, prefix with - for descending").option("--fields <fields>", "Comma-separated fields to return, e.g. slug,title,excerpt").option("--populate <fields>", "Comma-separated reference fields to populate, e.g. author,contributors").option("--locale <locale>", "Locale to list (default en)").option("--sort-by <field>", "Sort field").option("--sort-direction <dir>", "Sort direction (asc|desc)").action(entriesListCommand);
110295
- entries.command("get").description("Get a single entry").argument("<typeName>", "Content type name").argument("<id>", "Entry ID").option("-C, --cwd <dir>", "Project directory").option("--locale <locale>", "Locale to read (default en)").action(entriesGetCommand);
110361
+ entries.command("get").description("Get a single entry").argument("<typeName>", "Content type name").argument("<documentId>", "Document ID").option("-C, --cwd <dir>", "Project directory").requiredOption("--locale <locale>", "Entry locale").action(entriesGetCommand);
110296
110362
  entries.command("create").description("Create a new entry").argument("<typeName>", "Content type name").option("-C, --cwd <dir>", "Project directory").requiredOption("--body <json>", "Entry JSON body (slug + data)").option("--locale <locale>", "Entry locale (default en)").option("--document-id <id>", "Document ID shared by localized variants").action(entriesCreateCommand);
110297
- entries.command("update").description("Update an existing entry").argument("<typeName>", "Content type name").argument("<id>", "Entry ID").option("-C, --cwd <dir>", "Project directory").requiredOption("--body <json>", "Updated entry JSON body").option("--locale <locale>", "Entry locale").option("--document-id <id>", "Document ID shared by localized variants").action(entriesUpdateCommand);
110298
- entries.command("delete").description("Delete an entry").argument("<typeName>", "Content type name").argument("<id>", "Entry ID").option("-C, --cwd <dir>", "Project directory").action(entriesDeleteCommand);
110363
+ entries.command("update").description("Update an existing entry").argument("<typeName>", "Content type name").argument("<documentId>", "Document ID").option("-C, --cwd <dir>", "Project directory").requiredOption("--body <json>", "Updated entry JSON body").requiredOption("--locale <locale>", "Entry locale").action(entriesUpdateCommand);
110364
+ entries.command("delete").description("Delete an entry").argument("<typeName>", "Content type name").argument("<documentId>", "Document ID").option("-C, --cwd <dir>", "Project directory").requiredOption("--locale <locale>", "Entry locale").action(entriesDeleteCommand);
110299
110365
  var assets2 = program.command("assets").description("Manage media assets");
110300
110366
  assets2.command("list").description("List assets").option("-C, --cwd <dir>", "Project directory").option("--parent-id <id>", "Filter by parent folder").option(
110301
110367
  "--search <query>",
@@ -71,7 +71,7 @@ export async function resolveMetaEntries(url) {
71
71
  return;
72
72
  }
73
73
  const { schema, queryField } = paramInfo;
74
- const entryUrl = new URL(`${base}/entries/${schema}/field/${encodeURIComponent(queryField)}/${encodeURIComponent(value)}`);
74
+ const entryUrl = new URL(`${base}/v2/entries/${schema}/field/${encodeURIComponent(queryField)}/${encodeURIComponent(value)}`);
75
75
  entryUrl.searchParams.set('locale', localeResolution.locale);
76
76
  const res = await fetch(entryUrl);
77
77
  if (res.ok) {
@@ -1,6 +1,7 @@
1
1
  type FieldData = Record<string, string>;
2
2
  interface RecordData {
3
3
  id: string;
4
+ locale?: string;
4
5
  data: FieldData;
5
6
  visible: boolean;
6
7
  }
@@ -1 +1 @@
1
- {"version":3,"file":"retrieve-data-sources.d.ts","sourceRoot":"","sources":["../../../../src/runtime/content/utils/retrieve-data-sources.ts"],"names":[],"mappings":"AACA,KAAK,SAAS,GAAG,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;AAExC,UAAU,UAAU;IAClB,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,SAAS,CAAC;IAChB,OAAO,EAAE,OAAO,CAAC;CAClB;AAED,MAAM,MAAM,kBAAkB,GAAG,MAAM,CAAC,MAAM,EAAE,UAAU,EAAE,CAAC,CAAC;AA8B9D;;;;GAIG;AACH,wBAAgB,0BAA0B,IAAI,kBAAkB,CAiD/D"}
1
+ {"version":3,"file":"retrieve-data-sources.d.ts","sourceRoot":"","sources":["../../../../src/runtime/content/utils/retrieve-data-sources.ts"],"names":[],"mappings":"AACA,KAAK,SAAS,GAAG,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;AAExC,UAAU,UAAU;IAClB,EAAE,EAAE,MAAM,CAAC;IACX,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,IAAI,EAAE,SAAS,CAAC;IAChB,OAAO,EAAE,OAAO,CAAC;CAClB;AAED,MAAM,MAAM,kBAAkB,GAAG,MAAM,CAAC,MAAM,EAAE,UAAU,EAAE,CAAC,CAAC;AAgD9D;;;;GAIG;AACH,wBAAgB,0BAA0B,IAAI,kBAAkB,CAwD/D"}
@@ -1,3 +1,19 @@
1
+ function parseRecordIdentity(token) {
2
+ if (!token.startsWith('v2:')) {
3
+ return { id: token };
4
+ }
5
+ const [version, documentId, encodedLocale, ...rest] = token.split(':');
6
+ if (version !== 'v2' || !documentId || !encodedLocale || rest.length > 0) {
7
+ return null;
8
+ }
9
+ try {
10
+ const locale = decodeURIComponent(encodedLocale);
11
+ return locale ? { id: documentId, locale } : null;
12
+ }
13
+ catch {
14
+ return null;
15
+ }
16
+ }
1
17
  /**
2
18
  * Checks if an element is genuinely visible to the user within the viewport.
3
19
  * @param el The element to check.
@@ -38,7 +54,13 @@ export function retrieveVisibleDataSources() {
38
54
  const parts = source.split('.');
39
55
  // Expects the format "SchemaName.id.fieldName"
40
56
  if (parts.length === 3) {
41
- const [schemaName, id, fieldName] = parts;
57
+ const [schemaName, identityToken, fieldName] = parts;
58
+ const identity = parseRecordIdentity(identityToken);
59
+ if (!identity) {
60
+ return;
61
+ }
62
+ const { id, locale } = identity;
63
+ const recordKey = JSON.stringify([id, locale ?? null]);
42
64
  // Skip __translations
43
65
  if (schemaName === '__translations') {
44
66
  return;
@@ -48,16 +70,17 @@ export function retrieveVisibleDataSources() {
48
70
  tempGroupedData[schemaName] = {};
49
71
  }
50
72
  // Ensure the record (by ID) exists for this schema
51
- if (!tempGroupedData[schemaName][id]) {
52
- tempGroupedData[schemaName][id] = {
73
+ if (!tempGroupedData[schemaName][recordKey]) {
74
+ tempGroupedData[schemaName][recordKey] = {
53
75
  id: id,
76
+ ...(locale ? { locale } : {}),
54
77
  data: {},
55
78
  visible: isElementVisible(el),
56
79
  };
57
80
  }
58
81
  // Add the field and its text content to the record
59
82
  if (el.textContent) {
60
- tempGroupedData[schemaName][id].data[fieldName] = el.textContent.trim();
83
+ tempGroupedData[schemaName][recordKey].data[fieldName] = el.textContent.trim();
61
84
  }
62
85
  }
63
86
  });
@@ -35,7 +35,7 @@ function resolveApiBaseUrl(rootDir) {
35
35
  return readEnvValue(rootDir, 'VITE_API_URL') ?? readEnvValue(rootDir, 'VITE_API_URL_DEV');
36
36
  }
37
37
  async function fetchEntries(baseUrl, schema, rootDir, locale) {
38
- const url = new URL(`${baseUrl.replace(/\/$/, '')}/entries/${schema}`);
38
+ const url = new URL(`${baseUrl.replace(/\/$/, '')}/v2/entries/${schema}`);
39
39
  url.searchParams.set('locale', locale);
40
40
  if (isValidationBuild) {
41
41
  url.searchParams.set('limit', '1');
@@ -42,7 +42,6 @@ export type ManyReferenceOps = {
42
42
  };
43
43
  export type FilterFor<T> = [T] extends [FimoString | string] ? string | StringOps : [T] extends [FimoBoolean | boolean] ? boolean | BoolOps : [T] extends [FimoDate | Date] ? string | DateOps : [T] extends [number] ? number | NumberOps : [T] extends [FimoPolymorphicReference | null] ? never : [T] extends [FimoPolymorphicReference[]] ? never : [T] extends [FimoReference | null] ? string | null | ReferenceOps : [T] extends [FimoReference[]] ? string | ManyReferenceOps : [T] extends [FimoMedia | FimoMedia[]] ? NullOnlyOps : [T] extends [FimoRichText] ? string | StringOps : NullOnlyOps;
44
44
  type BaseTopLevelWhere = {
45
- id?: string | StringOps;
46
45
  documentId?: string | StringOps;
47
46
  slug?: string | StringOps;
48
47
  createdAt?: string | DateOps;
@@ -66,9 +65,12 @@ export type Fields<T> = Field<T> | readonly Field<T>[];
66
65
  type SelectedField<TField> = TField extends `__fimo.${string}` ? '__fimo' : TField;
67
66
  type SelectedFields<TFields> = TFields extends readonly (infer K)[] ? SelectedField<K> : SelectedField<TFields>;
68
67
  type ListItem<TValue> = TValue extends readonly (infer K)[] ? K : TValue;
68
+ type EntryIdentityField<T> = Extract<'documentId' | 'locale', keyof T>;
69
69
  export type Projected<T extends {
70
- id: string;
71
- }, TFields extends Fields<T> | undefined> = [TFields] extends [undefined] ? T : Pick<T, Extract<'id' | SelectedFields<TFields>, keyof T>>;
70
+ documentId: string;
71
+ }, TFields extends Fields<T> | undefined> = [TFields] extends [
72
+ undefined
73
+ ] ? T : Pick<T, EntryIdentityField<T> | Extract<SelectedFields<TFields>, keyof T>>;
72
74
  export declare const FIMO_REFERENCES: unique symbol;
73
75
  export interface WithReferences<TReferences> {
74
76
  readonly [FIMO_REFERENCES]?: TReferences;
@@ -78,20 +80,20 @@ export type ReferenceField<TEntry> = Extract<keyof ReferenceMap<TEntry>, string>
78
80
  export type Populate<TEntry> = ReferenceField<TEntry> | readonly ReferenceField<TEntry>[];
79
81
  type PopulatedKeys<TEntry, TPopulate> = Extract<ListItem<TPopulate>, keyof ReferenceMap<TEntry> & keyof TEntry>;
80
82
  export type Populated<TEntry extends {
81
- id: string;
82
- }, TPopulate extends Populate<TEntry>> = Pick<TEntry, 'id'> & Omit<TEntry, PopulatedKeys<TEntry, TPopulate>> & {
83
+ documentId: string;
84
+ }, TPopulate extends Populate<TEntry>> = Pick<TEntry, EntryIdentityField<TEntry>> & Omit<TEntry, PopulatedKeys<TEntry, TPopulate>> & {
83
85
  [K in PopulatedKeys<TEntry, TPopulate>]: ReferenceMap<TEntry>[K];
84
86
  };
85
87
  export type PopulatedResult<TEntry extends {
86
- id: string;
88
+ documentId: string;
87
89
  }, TPopulate extends Populate<TEntry> | undefined> = [
88
90
  TPopulate
89
91
  ] extends [undefined] ? TEntry : Populated<TEntry, NonNullable<TPopulate>>;
90
92
  export type ProjectedWithPopulate<T extends {
91
- id: string;
92
- }, TFields extends string | readonly string[] | undefined, TPopulate extends string | readonly string[] | undefined> = [TFields] extends [undefined] ? T : Pick<T, Extract<'id' | SelectedFields<TFields> | ListItem<NonNullable<TPopulate>>, keyof T>>;
93
+ documentId: string;
94
+ }, TFields extends string | readonly string[] | undefined, TPopulate extends string | readonly string[] | undefined> = [TFields] extends [undefined] ? T : Pick<T, EntryIdentityField<T> | Extract<SelectedFields<TFields> | ListItem<NonNullable<TPopulate>>, keyof T>>;
93
95
  export type Query<T extends {
94
- id: string;
96
+ documentId: string;
95
97
  }, TFields extends Fields<T> | undefined = Fields<T> | undefined, TPopulate extends string | readonly string[] | undefined = string | readonly string[] | undefined> = {
96
98
  where?: Where<T>;
97
99
  locale?: string;
@@ -1 +1 @@
1
- {"version":3,"file":"query.d.ts","sourceRoot":"","sources":["../../../../src/runtime/primitives/lib/query.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"query.d.ts","sourceRoot":"","sources":["../../../../src/runtime/primitives/lib/query.ts"],"names":[],"mappings":"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"}
@@ -12,9 +12,9 @@ describe('fimo/ui public type surface', () => {
12
12
  expectTypeOf().toEqualTypeOf();
13
13
  const localizedWhere = { locale: 'en' };
14
14
  expectTypeOf(localizedWhere).toEqualTypeOf();
15
- // @ts-expect-error locale is not a filter on non-localized generated entry types.
16
- const sharedWhere = { locale: 'en' };
17
- expectTypeOf(sharedWhere).toEqualTypeOf();
15
+ // @ts-expect-error row IDs are not part of the v2 public entry contract.
16
+ const rowIdWhere = { id: '550e8400-e29b-41d4-a716-446655440000' };
17
+ expectTypeOf(rowIdWhere).toEqualTypeOf();
18
18
  });
19
19
  it('supports the reference populate pattern emitted into generated schemas', () => {
20
20
  const query = {
@@ -37,6 +37,8 @@ describe('syncSchemas', () => {
37
37
  const post = readFileSync(join(scratch, 'src/schemas/Post.ts'), 'utf-8');
38
38
  const postTypes = readFileSync(join(scratch, 'src/schemas/Post.types.ts'), 'utf-8');
39
39
  expect(post).toContain('import { wrapWithSource as wrapAuthorWithSource } from "./Author";');
40
+ expect(post).toContain('isPopulatedEntry(value) ? wrapAuthorWithSource(value) : value');
41
+ expect(post).toContain('function isPopulatedEntry(value: unknown): value is PopulatedEntryRecord');
40
42
  expect(post).toContain('import type { Post, PostInput, PostPopulate, PostResult, PostFields, PostQuery } from "./Post.types";');
41
43
  expect(post).toContain('from "./Post.types";');
42
44
  expect(post).not.toContain('import type { Author } from "./Author";');
@@ -47,9 +49,14 @@ describe('syncSchemas', () => {
47
49
  expect(post).toContain('wrapAuthorWithSource(value)');
48
50
  expect(post).toContain('wrapWithSource(value)');
49
51
  expect(post).toContain('export async function create(payload: PostInput): Promise<Post>');
52
+ expect(post).toContain('export async function getByDocumentId');
53
+ expect(post).toContain('/v2/entries/Post/');
50
54
  expect(post).toContain('return wrapWithSource(data.data);');
51
55
  expect(postTypes).toContain('import type { Author } from "./Author.types";');
52
56
  expect(postTypes).toContain('export interface Post extends WithReferences<PostReferences>');
57
+ expect(postTypes).not.toContain(' id: string;');
58
+ expect(postTypes).toContain(' documentId: string;');
59
+ expect(postTypes).toContain(' locale: string;');
53
60
  expect(postTypes).toContain('type FimoReference');
54
61
  expect(postTypes).toContain('author: FimoReference | null;');
55
62
  expect(postTypes).toContain('contributors: FimoReference[];');
@@ -88,15 +95,15 @@ describe('syncSchemas', () => {
88
95
  const settings = readFileSync(join(scratch, 'src/schemas/Settings.ts'), 'utf-8');
89
96
  const settingsTypes = readFileSync(join(scratch, 'src/schemas/Settings.types.ts'), 'utf-8');
90
97
  const homepageTypes = readFileSync(join(scratch, 'src/schemas/Homepage.types.ts'), 'utf-8');
91
- expect(settingsTypes).not.toContain('locale: string;');
98
+ expect(settingsTypes).toContain('locale: string;');
92
99
  expect(settingsTypes).not.toContain('locale?: string;');
93
100
  expect(homepageTypes).toContain('locale: string;');
94
101
  expect(settings).toContain('const LOCALIZED_CONTENT = false;');
95
102
  expect(settings).toContain('const LOCALIZED_REFERENCE_FIELDS = new Set<string>(["homepage"]);');
96
- expect(settings).not.toContain(' locale: entry.locale,');
103
+ expect(settings).toContain(' locale,');
97
104
  expect(settings).toContain('readLocale(params, LOCALIZED_CONTENT, useQueryLocale(params.locale))');
98
105
  expect(settings.match(/readLocale\(params, LOCALIZED_CONTENT, useQueryLocale\(params\.locale\)\)/g)?.length).toBe(4);
99
- expect(settings.match(/readLocale\(options, false, useQueryLocale\(options\.locale\)\)/g)?.length).toBe(2);
106
+ expect(settings.match(/const locale = useQueryLocale\(options\.locale\);/g)?.length).toBe(2);
100
107
  });
101
108
  it('generates singleton document APIs without collection mutations', () => {
102
109
  writeSchema('Settings', {
@@ -110,6 +117,10 @@ describe('syncSchemas', () => {
110
117
  const settings = readFileSync(join(scratch, 'src/schemas/Settings.ts'), 'utf-8');
111
118
  const settingsTypes = readFileSync(join(scratch, 'src/schemas/Settings.types.ts'), 'utf-8');
112
119
  expect(settings).toContain('fetch(`${base}${withReadOptions(`/documents/Settings`, options)}`');
120
+ expect(settings).toContain('const usesV2Identity = typeof id !== "string";');
121
+ expect(settings).toContain('`Settings.v2:${documentId}:${encodeURIComponent(locale)}`');
122
+ expect(settingsTypes).toContain('id?: string;');
123
+ expect(settingsTypes).toContain('locale?: string;');
113
124
  expect(settings).toContain('export async function update(payload: Partial<SettingsInput>)');
114
125
  expect(settings).toContain('export function useSuspenseGet');
115
126
  expect(settings).not.toContain('export async function create(');
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "fimo",
3
- "version": "0.6.0-experimental.1",
3
+ "version": "0.6.0-experimental.2",
4
4
  "description": "Fimo CLI - create, deploy, and manage Fimo projects",
5
5
  "bin": {
6
6
  "fimo": "dist/cli/index.js"
@@ -21,7 +21,7 @@
21
21
  "cmdk": "^1.1.1",
22
22
  "date-fns": "^4.1.0",
23
23
  "embla-carousel-react": "^8.6.0",
24
- "fimo": "0.6.0-experimental.1",
24
+ "fimo": "0.6.0-experimental.2",
25
25
  "input-otp": "^1.4.2",
26
26
  "isbot": "^5",
27
27
  "lucide-react": "^0.577.0",