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.
- package/assets/content-templates/hooks-template.eta +83 -38
- package/assets/content-templates/types-template.eta +6 -2
- package/assets/skills/fimo/references/content.md +13 -14
- package/assets/skills/fimo/references/setup-react-router.md +1 -1
- package/assets/skills/fimo-cli/references/content.md +6 -6
- package/dist/cli/bundle.json +1 -1
- package/dist/cli/index.js +101 -35
- package/dist/runtime/app/prefetch.js +1 -1
- package/dist/runtime/content/utils/retrieve-data-sources.d.ts +1 -0
- package/dist/runtime/content/utils/retrieve-data-sources.d.ts.map +1 -1
- package/dist/runtime/content/utils/retrieve-data-sources.js +27 -4
- package/dist/runtime/paths/get-fimo-paths.js +1 -1
- package/dist/runtime/primitives/lib/query.d.ts +11 -9
- package/dist/runtime/primitives/lib/query.d.ts.map +1 -1
- package/dist/runtime/primitives/public-surface.test.js +3 -3
- package/dist/scripts/sync-schemas.test.js +14 -3
- package/package.json +1 -1
- package/templates/react-router/package.json +1 -1
|
@@ -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
|
|
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
|
-
|
|
45
|
-
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.
|
|
48
|
-
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
|
|
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 %>
|
|
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
|
|
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
|
|
253
|
-
|
|
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 %>/${
|
|
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(
|
|
357
|
-
const
|
|
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(
|
|
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(
|
|
368
|
-
const
|
|
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
|
-
|
|
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
|
|
413
|
-
const 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.
|
|
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
|
|
479
|
-
const 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.
|
|
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, {
|
|
520
|
-
mutationFn : ({
|
|
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.
|
|
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,
|
|
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:
|
|
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
|
-
|
|
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
|
-
|
|
627
|
+
useGetByDocumentId,
|
|
583
628
|
useGetBySlug,
|
|
584
629
|
useGetByField,
|
|
585
630
|
useList,
|
|
586
631
|
useInfiniteList,
|
|
587
632
|
useGet,
|
|
588
|
-
|
|
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
|
-
|
|
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;
|
|
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
|
-
-
|
|
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
|
-
|
|
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(
|
|
257
|
-
remove(
|
|
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
|
-
|
|
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
|
-
|
|
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`, `
|
|
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 `
|
|
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, '
|
|
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.
|
|
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 (
|
|
455
|
+
// data: Entry[] — fields flattened at top level (documentId, locale, slug, title, ...)
|
|
457
456
|
// meta: { limit, offset, total, hasMore }
|
|
458
457
|
```
|
|
459
458
|
|
|
@@ -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> <
|
|
47
|
-
fimo entries update <TypeName> <
|
|
48
|
-
fimo entries delete <TypeName> <
|
|
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 `
|
|
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
|
|
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.
|
|
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
|
|
package/dist/cli/bundle.json
CHANGED
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,
|
|
20640
|
-
const query =
|
|
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}/${
|
|
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,
|
|
20654
|
-
const
|
|
20655
|
-
|
|
20656
|
-
|
|
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,
|
|
20661
|
-
const
|
|
20662
|
-
|
|
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
|
-
|
|
48798
|
-
|
|
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
|
-
|
|
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,
|
|
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
|
|
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,
|
|
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 =
|
|
106332
|
-
|
|
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,
|
|
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,
|
|
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 (
|
|
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("<
|
|
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("<
|
|
110298
|
-
entries.command("delete").description("Delete an entry").argument("<typeName>", "Content type name").argument("<
|
|
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 +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;
|
|
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,
|
|
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][
|
|
52
|
-
tempGroupedData[schemaName][
|
|
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][
|
|
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
|
-
|
|
71
|
-
}, TFields extends Fields<T> | undefined> = [TFields] extends [
|
|
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
|
-
|
|
82
|
-
}, TPopulate extends Populate<TEntry>> = Pick<TEntry,
|
|
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
|
-
|
|
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
|
-
|
|
92
|
-
}, TFields extends string | readonly string[] | undefined, TPopulate extends string | readonly string[] | undefined> = [TFields] extends [undefined] ? T : Pick<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
|
-
|
|
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
|
|
16
|
-
const
|
|
17
|
-
expectTypeOf(
|
|
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).
|
|
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).
|
|
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(/
|
|
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