fimo 0.7.0 → 0.8.0-staging.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,620 +1,63 @@
1
- <%
2
- /**
3
- * INPUT ▸ model = {
4
- * uid: "Post", // matches src/schemas/<uid>.json (letters, numbers, underscores)
5
- * pascal: "Post", // pascalCase(uid), used for generated type names
6
- * fields: {key: {type, required?, multiple?, many?, target?, targetPascal?}},
7
- * relationImports: [{uid, pascal}] // relation targets other than this schema
8
- * }
9
- */
10
- %>
11
- // AUTO-GENERATED ⚠️ – do not edit by hand
12
- import { <% if (!model.isSingleton) { %>useInfiniteQuery, <% } %>useSuspenseQuery, useQuery, useMutation, useQueryClient } from "@tanstack/react-query";
13
- <% if (!model.isSingleton) { -%>
14
- import type { InfiniteData, UseInfiniteQueryResult, UseQueryResult, UseSuspenseQueryResult } from "@tanstack/react-query";
1
+ // AUTO-GENERATED by `fimo` from src/schemas/<%= model.uid %>.json - DO NOT EDIT.
2
+ // Change content fields in the schema, then run `fimo validate`.
3
+ // Runtime implementation lives in the project-local fimo/ui package.
4
+ <% if (model.isSingleton) { -%>
5
+ import { createSingletonClient } from "fimo/ui";
6
+ import type { SingletonContentClient } from "fimo/ui";
7
+ <% } else { -%>
8
+ import { createContentClient } from "fimo/ui";
9
+ import type { CollectionContentClient } from "fimo/ui";
15
10
  <% } -%>
16
- import { FimoString, FimoMedia, FimoBoolean, FimoRichText, useLocale<% if (!model.isSingleton) { %>, parseCollectionResponse<% } %> } from "fimo/ui";
17
- // @ts-ignore - virtual module provided by Fimo's Vite plugin
18
- import fimoConfig from "virtual:fimo-config";
19
- import type { <%= model.pascal %>, <%= model.pascal %>Input, <%= model.pascal %>Populate, <%= model.pascal %>Result<% if (!model.isSingleton) { %>, <%= model.pascal %>Fields, <%= model.pascal %>Query<% } %> } from "./<%= model.pascal %>.types";
11
+
12
+ import schema from "./<%= model.uid %>.json";
20
13
  <%
21
14
  if (model.referenceImports && model.referenceImports.length > 0) {
22
15
  model.referenceImports.forEach((referenceImport) => {
23
16
  -%>
24
- import { wrapWithSource as wrap<%= referenceImport.pascal %>WithSource } from "./<%= referenceImport.pascal %>";
17
+ import * as <%= referenceImport.pascal %> from "./<%= referenceImport.pascal %>";
25
18
  <%
26
19
  });
27
20
  }
21
+ if (model.isSingleton) {
28
22
  -%>
29
- export type { <%= model.pascal %>, <%= model.pascal %>Fields, <%= model.pascal %>FimoMeta, <%= model.pascal %>Input, <%= model.pascal %>Populate, <%= model.pascal %>Query, <%= model.pascal %>References, <%= model.pascal %>Result, <%= model.pascal %>Sort, <%= model.pascal %>Where } from "./<%= model.pascal %>.types";
30
-
31
- /*───────────────────────────────────────────────────────────────*/
32
- /* ──────────── 🎁 Source wrapping ──────────── */
33
- /*───────────────────────────────────────────────────────────────*/
34
-
35
- /**
36
- * Wraps raw API data with Fimo types for source tracking
37
- */
38
- export function wrapWithSource(entry: Record<string, any>): <%= model.pascal %> {
39
- const id = entry.id;
40
- const entryId = entry.entryId;
41
- const documentId = entry.documentId ?? id;
42
- const locale = entry.locale ?? defaultLocale();
43
- const sourcePrefix = `<%= model.pascal %>.id:${id}:${encodeURIComponent(locale)}`;
44
- const entryFimo = entry.__fimo && typeof entry.__fimo === "object" ? entry.__fimo : {};
45
-
46
- return {
47
- id,
48
- entryId,
49
- documentId,
50
- slug: entry.slug != null ? new FimoString(entry.slug, `${sourcePrefix}.slug`) : entry.slug,
51
- <% if (!model.isSingleton || model.localized) { -%>
52
- locale,
53
- <% } else { -%>
54
- ...(entry.locale ? { locale } : {}),
55
- <% } -%>
56
- createdAt: entry.createdAt,
57
- updatedAt: entry.updatedAt,
58
- __fimo: {
59
- contentType: typeof entryFimo.contentType === "string" ? entryFimo.contentType : "<%= model.uid %>",
60
- },
61
- <%
62
- if (model.fields) {
63
- Object.entries(model.fields).forEach(([key, field]) => {
64
- const fieldSource = `\${sourcePrefix}.${key}`;
65
- switch(field.type) {
66
- case 'string':
67
- -%>
68
- <%= key %>: entry.<%= key %> != null ? new FimoString(entry.<%= key %>, `<%= fieldSource %>`) : entry.<%= key %>,
69
- <%
70
- break;
71
- case 'richtext':
72
- -%>
73
- <%= key %>: entry.<%= key %> != null ? new FimoRichText(entry.<%= key %>, `<%= fieldSource %>`) : entry.<%= key %>,
74
- <%
75
- break;
76
- case 'boolean':
77
- -%>
78
- <%= key %>: entry.<%= key %> != null ? new FimoBoolean(entry.<%= key %>, `<%= fieldSource %>`) : entry.<%= key %>,
79
- <%
80
- break;
81
- case 'media':
82
- if (field.multiple) {
83
- -%>
84
- <%= key %>: entry.<%= key %>?.map((m: any, i: number) => new FimoMedia(m, `<%= fieldSource %>[${i}]`)) ?? entry.<%= key %>,
85
- <%
86
- } else {
87
- -%>
88
- <%= key %>: entry.<%= key %> != null ? new FimoMedia(entry.<%= key %>, `<%= fieldSource %>`) : entry.<%= key %>,
89
- <%
90
- }
91
- break;
92
- case 'reference': {
93
- if (Array.isArray(field.targetRefs) && field.targetRefs.length > 0) {
94
- const wrapperFor = (ref) => ref.uid === model.uid ? 'wrapWithSource' : `wrap${ref.pascal}WithSource`;
95
- const wrappers = `{ ${field.targetRefs.map((ref) => `"${ref.uid}": ${wrapperFor(ref)}`).join(', ')} }`;
96
- if (field.many) {
97
- -%>
98
- <%= key %>: Array.isArray(entry.<%= key %>) ? entry.<%= key %>.map((value: any) => wrapPolymorphic(value, <%= wrappers %>)) : entry.<%= key %>,
99
- <%
100
- } else {
101
- -%>
102
- <%= key %>: wrapPolymorphic(entry.<%= key %>, <%= wrappers %>),
103
- <%
104
- }
105
- break;
106
- }
107
- const wrapper = field.target === model.uid ? 'wrapWithSource' : `wrap${field.targetPascal}WithSource`;
108
- if (field.many) {
109
- -%>
110
- <%= key %>: Array.isArray(entry.<%= key %>) ? entry.<%= key %>.map((value: any) => isPopulatedEntry(value) ? <%= wrapper %>(value) : value) : entry.<%= key %>,
111
- <%
112
- } else {
113
- -%>
114
- <%= key %>: isPopulatedEntry(entry.<%= key %>) ? <%= wrapper %>(entry.<%= key %>) : entry.<%= key %>,
115
- <%
116
- }
117
- break;
118
- }
119
- default:
120
- -%>
121
- <%= key %>: entry.<%= key %>,
122
- <%
123
- }
124
- });
125
- }
126
- -%>
127
- } as <%= model.pascal %>;
128
- }
129
-
130
- interface PopulatedEntryRecord extends Record<string, unknown> {
131
- id: string;
132
- __fimo: { contentType: string };
133
- }
134
-
135
- function isPopulatedEntry(value: unknown): value is PopulatedEntryRecord {
136
- if (value == null || typeof value !== "object") {
137
- return false;
138
- }
139
- const entry = value as Record<string, unknown>;
140
- const fimo = entry.__fimo;
141
- return typeof entry.id === "string"
142
- && fimo != null
143
- && typeof fimo === "object"
144
- && typeof (fimo as Record<string, unknown>).contentType === "string";
145
- }
146
-
147
- <%
148
- const hasPolymorphicReferences = model.fields && Object.values(model.fields).some((field) => field.type === 'reference' && Array.isArray(field.targetRefs) && field.targetRefs.length > 0);
149
- if (hasPolymorphicReferences) {
150
- -%>
151
- /**
152
- * Wraps one polymorphic reference value. Populated entries carry their type
153
- * marker; the wrapper picks the right per-type wrapping and stamps a
154
- * top-level $type so the generated union narrows in a switch. Raw
155
- * (unpopulated) values pass through untouched.
156
- */
157
- function wrapPolymorphic(value: any, wrappers: Record<string, (entry: Record<string, any>) => any>): any {
158
- if (!isPopulatedEntry(value)) {
159
- return value;
160
- }
161
- const contentType = value.__fimo && typeof value.__fimo.contentType === "string" ? value.__fimo.contentType : null;
162
- const wrapper = contentType ? wrappers[contentType] : undefined;
163
- if (!wrapper) return value;
164
- const wrapped = wrapper(value) as Record<string, unknown>;
165
- wrapped.$type = contentType;
166
- return wrapped;
167
- }
168
-
169
- <%
170
- }
171
- -%>
172
- /*───────────────────────────────────────────────────────────────*/
173
- /* ──────────── 🔌 Low-level fetch helpers ──────────── */
174
- /*───────────────────────────────────────────────────────────────*/
175
- // @ts-ignore - meta.env is a vite feature
176
- const base = import.meta.env.VITE_API_URL;
177
-
178
- function defaultLocale(): string {
179
- return (fimoConfig as { i18n?: { defaultLocale?: string } }).i18n?.defaultLocale ?? "en";
180
- }
181
-
182
- function useQueryLocale(locale?: string): string {
183
- const activeLocale = useLocale().locale;
184
- return locale ?? activeLocale ?? defaultLocale();
185
- }
186
-
187
- function withMutationLocale<T extends object>(payload: T, activeLocale: string): T & { locale: string } {
188
- const locale = (payload as { locale?: string }).locale;
189
- return { ...payload, locale: locale ?? activeLocale };
190
- }
191
-
192
- const LOCALIZED_CONTENT = <%= model.localized ? 'true' : 'false' %>;
193
- const LOCALIZED_REFERENCE_FIELDS = new Set<string>(<%= JSON.stringify(model.localizedReferenceFields) %>);
194
-
195
- function populateFields(populate: <%= model.pascal %>Populate | undefined): string[] {
196
- if (populate == null) return [];
197
- return typeof populate === "string" ? [populate] : [...populate];
198
- }
199
-
200
- function readLocale(
201
- options: { locale?: string; populate?: <%= model.pascal %>Populate },
202
- ownerUsesLocale: boolean,
203
- fallbackLocale = defaultLocale()
204
- ): string | undefined {
205
- <% if (!model.isSingleton) { -%>
206
- return options.locale ?? fallbackLocale;
23
+ import type { <%= model.pascal %>, <%= model.pascal %>Input } from "./<%= model.pascal %>.types";
207
24
  <% } else { -%>
208
- const populatesLocalizedTarget = populateFields(options.populate).some((field) => LOCALIZED_REFERENCE_FIELDS.has(field));
209
- return ownerUsesLocale || populatesLocalizedTarget ? options.locale ?? fallbackLocale : undefined;
25
+ import type { <%= model.pascal %>, <%= model.pascal %>Fields, <%= model.pascal %>Input, <%= model.pascal %>Populate, <%= model.pascal %>Query } from "./<%= model.pascal %>.types";
210
26
  <% } -%>
211
- }
212
-
213
- function fimoHeaders(extra?: Record<string, string>): Record<string, string> {
214
- const h: Record<string, string> = { ...extra };
215
- // @ts-ignore
216
- const branch = import.meta.env.VITE_FIMO_BRANCH;
217
- if (branch) h["X-Fimo-Branch"] = branch;
218
- return h;
219
- }
220
-
221
- function withReadOptions<TPopulate extends <%= model.pascal %>Populate | undefined>(
222
- url: string,
223
- options: { locale?: string; populate?: TPopulate; localeFallbacks?: boolean } = {},
224
- ownerUsesLocale = LOCALIZED_CONTENT
225
- ): string {
226
- const u = new URL(url, "http://fimo.local");
227
- const locale = readLocale(options, ownerUsesLocale);
228
- if (locale) u.searchParams.set("locale", locale);
229
- if (options.populate != null) {
230
- u.searchParams.set("populate", typeof options.populate === "string" ? options.populate : options.populate.join(","));
231
- }
232
- if (options.localeFallbacks) u.searchParams.set("localeFallbacks", "true");
233
- return `${u.pathname}${u.search}`;
234
- }
235
-
236
- function splitWritePayload(payload: Partial<<%= model.pascal %>Input>): Record<string, unknown> {
237
- const {
238
- id,
239
- entryId,
240
- documentId,
241
- locale,
242
- createdAt,
243
- updatedAt,
244
- ...data
245
- } = payload as Record<string, unknown>;
246
- delete data.__fimo;
247
-
248
- return {
249
- data,
250
- ...(typeof locale === "string" && locale.length > 0 ? { locale } : {}),
251
- ...(typeof documentId === "string" && documentId.length > 0 ? { documentId } : {}),
252
- };
253
- }
254
27
 
28
+ export type { <%= model.pascal %>, <%= model.pascal %>Fields, <%= model.pascal %>FimoMeta, <%= model.pascal %>Input, <%= model.pascal %>Populate, <%= model.pascal %>Query, <%= model.pascal %>References, <%= model.pascal %>Result, <%= model.pascal %>Sort, <%= model.pascal %>Where } from "./<%= model.pascal %>.types";
255
29
  <% if (!model.isSingleton) { -%>
256
- export interface CollectionPageInfo {
257
- readonly limit: number;
258
- readonly offset: number;
259
- readonly total: number;
260
- readonly hasMore: boolean;
261
- readonly nextOffset: number | null;
262
- }
263
-
264
- export interface CollectionPage<TItem> {
265
- readonly items: TItem[];
266
- readonly pageInfo: CollectionPageInfo;
267
- }
268
-
30
+ export type { CollectionPage, CollectionPageInfo, InfiniteListResult } from "fimo/ui";
269
31
  export type InfiniteListQuery<
270
32
  TFields extends <%= model.pascal %>Fields | undefined = undefined,
271
33
  TPopulate extends <%= model.pascal %>Populate | undefined = undefined,
272
34
  > = Omit<<%= model.pascal %>Query<TFields, TPopulate>, "offset">;
273
-
274
- export type InfiniteListResult<TItem> = UseInfiniteQueryResult<
275
- InfiniteData<CollectionPage<TItem>>,
276
- Error
277
- > & {
278
- readonly items: TItem[];
279
- readonly total: number;
280
- readonly hasMore: boolean;
281
- readonly loadMore: () => ReturnType<
282
- UseInfiniteQueryResult<InfiniteData<CollectionPage<TItem>>, Error>["fetchNextPage"]
283
- >;
284
- readonly isLoadingMore: boolean;
285
- };
286
-
287
- export async function getById<TPopulate extends <%= model.pascal %>Populate | undefined = undefined>(
288
- id: string,
289
- options: { locale?: string; populate?: TPopulate; localeFallbacks?: boolean } = {}
290
- ): Promise<<%= model.pascal %>Result<undefined, TPopulate>> {
291
- const res = await fetch(`${base}${withReadOptions(`/entries/<%= model.uid %>/${id}`, options)}`, { headers: fimoHeaders() });
292
- if (!res.ok) throw new Error(await res.text());
293
- const data = await res.json();
294
- return wrapWithSource(data.data) as <%= model.pascal %>Result<undefined, TPopulate>;
295
- }
296
-
297
- export async function getBySlug<TPopulate extends <%= model.pascal %>Populate | undefined = undefined>(
298
- slug: string,
299
- options: { locale?: string; populate?: TPopulate; localeFallbacks?: boolean } = {}
300
- ): Promise<<%= model.pascal %>Result<undefined, TPopulate> | null> {
301
- const res = await fetch(`${base}${withReadOptions(`/entries/<%= model.uid %>/slug/${encodeURIComponent(slug)}`, options)}`, { headers: fimoHeaders() });
302
- if (res.status === 404) return null;
303
- if (!res.ok) throw new Error(await res.text());
304
- const data = await res.json();
305
- return wrapWithSource(data.data) as <%= model.pascal %>Result<undefined, TPopulate>;
306
- }
307
-
308
- export async function getByField<TPopulate extends <%= model.pascal %>Populate | undefined = undefined>(
309
- fieldName: string,
310
- value: string,
311
- options: { locale?: string; populate?: TPopulate; localeFallbacks?: boolean } = {}
312
- ): Promise<<%= model.pascal %>Result<undefined, TPopulate> | null> {
313
- const res = await fetch(`${base}${withReadOptions(`/entries/<%= model.uid %>/field/${encodeURIComponent(fieldName)}/${encodeURIComponent(value)}`, options)}`, { headers: fimoHeaders() });
314
- if (res.status === 404) return null;
315
- if (!res.ok) throw new Error(await res.text());
316
- const data = await res.json();
317
- return wrapWithSource(data.data) as <%= model.pascal %>Result<undefined, TPopulate>;
318
- }
319
-
320
- function toQueryString<
321
- TFields extends <%= model.pascal %>Fields | undefined = undefined,
322
- TPopulate extends <%= model.pascal %>Populate | undefined = undefined,
323
- >(params: <%= model.pascal %>Query<TFields, TPopulate> = {} as <%= model.pascal %>Query<TFields, TPopulate>): string {
324
- const { where, sort, fields, populate, locale: requestedLocale, ...flat } = params;
325
- const query = new URLSearchParams();
326
-
327
- for (const [key, value] of Object.entries(flat)) {
328
- if (value == null) continue;
329
- query.set(key, String(value));
330
- }
331
-
332
- if (sort != null) {
333
- query.set("sort", Array.isArray(sort) ? sort.join(",") : sort);
334
- }
335
-
336
- if (fields != null) {
337
- query.set("fields", typeof fields === "string" ? fields : fields.join(","));
338
- }
339
-
340
- if (populate != null) {
341
- query.set("populate", typeof populate === "string" ? populate : populate.join(","));
342
- }
343
-
344
- if (where && Object.keys(where).length > 0) {
345
- query.set("where", JSON.stringify(where));
346
- }
347
-
348
- const locale = readLocale({ locale: requestedLocale, populate }, LOCALIZED_CONTENT);
349
- if (locale) query.set("locale", locale);
350
-
351
- return query.toString();
352
- }
353
-
354
- export async function list<
355
- TFields extends <%= model.pascal %>Fields | undefined = undefined,
356
- TPopulate extends <%= model.pascal %>Populate | undefined = undefined,
357
- >(params: <%= model.pascal %>Query<TFields, TPopulate> = {} as <%= model.pascal %>Query<TFields, TPopulate>): Promise<CollectionPage<<%= model.pascal %>Result<TFields, TPopulate>>> {
358
- const qs = toQueryString(params);
359
- const res = await fetch(`${base}/entries/<%= model.uid %>${qs ? "?" + qs : ""}`, { headers: fimoHeaders() });
360
- if (!res.ok) throw new Error(await res.text());
361
- const data = parseCollectionResponse(await res.json());
362
- const items = data.data.map(wrapWithSource) as <%= model.pascal %>Result<TFields, TPopulate>[];
363
- const pageInfo: CollectionPageInfo = {
364
- limit: data.meta.limit,
365
- offset: data.meta.offset,
366
- total: data.meta.total,
367
- hasMore: data.meta.hasMore,
368
- nextOffset: data.meta.hasMore ? data.meta.offset + data.meta.limit : null,
369
- };
370
- return { items, pageInfo };
371
- }
372
-
373
- export async function get<
374
- TFields extends <%= model.pascal %>Fields | undefined = undefined,
375
- TPopulate extends <%= model.pascal %>Populate | undefined = undefined,
376
- >(params: <%= model.pascal %>Query<TFields, TPopulate> = {} as <%= model.pascal %>Query<TFields, TPopulate>): Promise<<%= model.pascal %>Result<TFields, TPopulate>[]> {
377
- return (await list(params)).items;
378
- }
379
-
380
- export async function create(payload: <%= model.pascal %>Input): Promise<<%= model.pascal %>> {
381
- const res = await fetch(`${base}/entries/<%= model.uid %>`, {
382
- method: "POST",
383
- headers: fimoHeaders({ "Content-Type": "application/json" }),
384
- body: JSON.stringify(splitWritePayload(payload))
385
- });
386
- if (!res.ok) throw new Error(await res.text());
387
- const data = await res.json();
388
- return wrapWithSource(data.data);
389
- }
390
-
391
- export async function update(id: string, payload: Partial<<%= model.pascal %>Input>): Promise<<%= model.pascal %>> {
392
- const write = splitWritePayload(payload) as { data: Record<string, unknown>; locale?: string };
393
- const url = withReadOptions(`/entries/<%= model.uid %>/${id}`, { locale: write.locale });
394
- const res = await fetch(`${base}${url}`, {
395
- method: "PUT",
396
- headers: fimoHeaders({ "Content-Type": "application/json" }),
397
- body: JSON.stringify({ data: write.data })
398
- });
399
- if (!res.ok) throw new Error(await res.text());
400
- const data = await res.json();
401
- return wrapWithSource(data.data);
402
- }
403
-
404
- export async function remove(id: string, options: { locale?: string } = {}): Promise<void> {
405
- const url = withReadOptions(`/entries/<%= model.uid %>/${id}`, options);
406
- const res = await fetch(`${base}${url}`, { method: "DELETE", headers: fimoHeaders() });
407
- if (!res.ok) throw new Error(await res.text());
408
- }
409
- <% } else { -%>
410
- export async function get<TPopulate extends <%= model.pascal %>Populate | undefined = undefined>(
411
- options: { locale?: string; populate?: TPopulate; localeFallbacks?: boolean } = {}
412
- ): Promise<<%= model.pascal %>Result<undefined, TPopulate>> {
413
- const res = await fetch(`${base}${withReadOptions(`/documents/<%= model.uid %>`, options)}`, { headers: fimoHeaders() });
414
- if (!res.ok) throw new Error(await res.text());
415
- const data = await res.json();
416
- return wrapWithSource(data.data) as <%= model.pascal %>Result<undefined, TPopulate>;
417
- }
418
-
419
- export async function update(payload: Partial<<%= model.pascal %>Input>): Promise<<%= model.pascal %>> {
420
- const write = splitWritePayload(payload) as { data: Record<string, unknown>; locale?: string };
421
- const url = withReadOptions(`/documents/<%= model.uid %>`, { locale: write.locale });
422
- const res = await fetch(`${base}${url}`, {
423
- method: "PUT",
424
- headers: fimoHeaders({ "Content-Type": "application/json" }),
425
- body: JSON.stringify({ data: write.data })
426
- });
427
- if (!res.ok) throw new Error(await res.text());
428
- const data = await res.json();
429
- return wrapWithSource(data.data);
430
- }
431
35
  <% } -%>
432
36
 
433
- /*───────────────────────────────────────────────────────────────*/
434
- /* ──────────── 🍯 React-Query hooks ──────────── */
435
- /*───────────────────────────────────────────────────────────────*/
436
-
437
- <% if (!model.isSingleton) { -%>
438
- const qk = {
439
- root : ["<%= model.pascal %>"] as const,
440
- byId : <TPopulate extends <%= model.pascal %>Populate | undefined = undefined>(id: string, options: { locale?: string; populate?: TPopulate; localeFallbacks?: boolean } = {}) => ["<%= model.pascal %>", "id", id, options.locale, options.populate] as const,
441
- 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,
442
- 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,
443
- list : (params: <%= model.pascal %>Query) => ["<%= model.pascal %>", params] as const,
444
- infiniteList: <
445
- TFields extends <%= model.pascal %>Fields | undefined = undefined,
446
- TPopulate extends <%= model.pascal %>Populate | undefined = undefined,
447
- >(params: InfiniteListQuery<TFields, TPopulate>) => ["<%= model.pascal %>", "infinite", params] as const
448
- };
449
-
450
- export function useGetById<TPopulate extends <%= model.pascal %>Populate | undefined = undefined>(id: string, options: { locale?: string; populate?: TPopulate; localeFallbacks?: boolean } = {}): UseQueryResult<<%= model.pascal %>Result<undefined, TPopulate>, Error> {
451
- const locale = useQueryLocale(options.locale);
452
- const localizedOptions = { ...options, locale };
453
- return useQuery<<%= model.pascal %>Result<undefined, TPopulate>, Error>({ queryKey: qk.byId(id, localizedOptions), queryFn: () => getById(id, localizedOptions) });
454
- }
455
-
456
- export function useGetBySlug<TPopulate extends <%= model.pascal %>Populate | undefined = undefined>(slug: string, options: { locale?: string; populate?: TPopulate; localeFallbacks?: boolean } = {}) {
457
- const locale = readLocale(options, LOCALIZED_CONTENT, useQueryLocale(options.locale));
458
- const localizedOptions = { ...options, locale };
459
- return useQuery<<%= model.pascal %>Result<undefined, TPopulate> | null, Error>({ queryKey: qk.bySlug(slug, localizedOptions), queryFn: () => getBySlug(slug, localizedOptions), enabled: !!slug });
460
- }
461
-
462
- export function useGetByField<TPopulate extends <%= model.pascal %>Populate | undefined = undefined>(fieldName: string, value: string, options: { locale?: string; populate?: TPopulate; localeFallbacks?: boolean } = {}) {
463
- const locale = readLocale(options, LOCALIZED_CONTENT, useQueryLocale(options.locale));
464
- const localizedOptions = { ...options, locale };
465
- return useQuery<<%= model.pascal %>Result<undefined, TPopulate> | null, Error>({ queryKey: qk.byField(fieldName, value, localizedOptions), queryFn: () => getByField(fieldName, value, localizedOptions), enabled: !!value });
466
- }
467
-
468
- export function useGet<
469
- TFields extends <%= model.pascal %>Fields | undefined = undefined,
470
- TPopulate extends <%= model.pascal %>Populate | undefined = undefined,
471
- >(params: <%= model.pascal %>Query<TFields, TPopulate> = {} as <%= model.pascal %>Query<TFields, TPopulate>) {
472
- const locale = readLocale(params, LOCALIZED_CONTENT, useQueryLocale(params.locale));
473
- const localizedParams = { ...params, locale };
474
- return useQuery<CollectionPage<<%= model.pascal %>Result<TFields, TPopulate>>, Error, <%= model.pascal %>Result<TFields, TPopulate>[]>({
475
- queryKey: qk.list(localizedParams),
476
- queryFn: () => list(localizedParams),
477
- select: (page) => page.items,
478
- });
479
- }
480
-
481
- export function useList<
482
- TFields extends <%= model.pascal %>Fields | undefined = undefined,
483
- TPopulate extends <%= model.pascal %>Populate | undefined = undefined,
484
- >(params: <%= model.pascal %>Query<TFields, TPopulate> = {} as <%= model.pascal %>Query<TFields, TPopulate>): UseQueryResult<CollectionPage<<%= model.pascal %>Result<TFields, TPopulate>>, Error> {
485
- const locale = readLocale(params, LOCALIZED_CONTENT, useQueryLocale(params.locale));
486
- const localizedParams = { ...params, locale };
487
- return useQuery<CollectionPage<<%= model.pascal %>Result<TFields, TPopulate>>, Error>({
488
- queryKey: qk.list(localizedParams),
489
- queryFn: () => list(localizedParams),
490
- });
491
- }
492
-
493
- export function useInfiniteList<
494
- TFields extends <%= model.pascal %>Fields | undefined = undefined,
495
- TPopulate extends <%= model.pascal %>Populate | undefined = undefined,
496
- >(params: InfiniteListQuery<TFields, TPopulate> = {} as InfiniteListQuery<TFields, TPopulate>): InfiniteListResult<<%= model.pascal %>Result<TFields, TPopulate>> {
497
- const locale = readLocale(params, LOCALIZED_CONTENT, useQueryLocale(params.locale));
498
- const localizedParams = { ...params, locale };
499
- const query = useInfiniteQuery({
500
- queryKey: qk.infiniteList(localizedParams),
501
- queryFn: ({ pageParam }) => list({ ...localizedParams, offset: pageParam }),
502
- initialPageParam: 0,
503
- getNextPageParam: (lastPage) => lastPage.pageInfo.nextOffset ?? undefined,
504
- });
505
-
506
- return {
507
- ...query,
508
- items: query.data?.pages.flatMap((page) => page.items) ?? [],
509
- total: query.data?.pages[0]?.pageInfo.total ?? 0,
510
- hasMore: query.hasNextPage,
511
- loadMore: () => query.fetchNextPage(),
512
- isLoadingMore: query.isFetchingNextPage,
513
- };
514
- }
515
-
516
- export function useSuspenseGetById<TPopulate extends <%= model.pascal %>Populate | undefined = undefined>(id: string, options: { locale?: string; populate?: TPopulate; localeFallbacks?: boolean } = {}): UseSuspenseQueryResult<<%= model.pascal %>Result<undefined, TPopulate>, Error> {
517
- const locale = useQueryLocale(options.locale);
518
- const localizedOptions = { ...options, locale };
519
- return useSuspenseQuery<<%= model.pascal %>Result<undefined, TPopulate>, Error>({ queryKey: qk.byId(id, localizedOptions), queryFn: () => getById(id, localizedOptions) });
520
- }
521
-
522
- export function useSuspenseGetBySlug<TPopulate extends <%= model.pascal %>Populate | undefined = undefined>(slug: string, options: { locale?: string; populate?: TPopulate; localeFallbacks?: boolean } = {}) {
523
- const locale = readLocale(options, LOCALIZED_CONTENT, useQueryLocale(options.locale));
524
- const localizedOptions = { ...options, locale };
525
- return useSuspenseQuery<<%= model.pascal %>Result<undefined, TPopulate> | null, Error>({ queryKey: qk.bySlug(slug, localizedOptions), queryFn: () => getBySlug(slug, localizedOptions) });
526
- }
527
-
528
- export function useSuspenseGetByField<TPopulate extends <%= model.pascal %>Populate | undefined = undefined>(fieldName: string, value: string, options: { locale?: string; populate?: TPopulate; localeFallbacks?: boolean } = {}) {
529
- const locale = readLocale(options, LOCALIZED_CONTENT, useQueryLocale(options.locale));
530
- const localizedOptions = { ...options, locale };
531
- return useSuspenseQuery<<%= model.pascal %>Result<undefined, TPopulate> | null, Error>({ queryKey: qk.byField(fieldName, value, localizedOptions), queryFn: () => getByField(fieldName, value, localizedOptions) });
532
- }
533
-
534
- export function useSuspenseGet<
535
- TFields extends <%= model.pascal %>Fields | undefined = undefined,
536
- TPopulate extends <%= model.pascal %>Populate | undefined = undefined,
537
- >(params: <%= model.pascal %>Query<TFields, TPopulate> = {} as <%= model.pascal %>Query<TFields, TPopulate>) {
538
- const locale = readLocale(params, LOCALIZED_CONTENT, useQueryLocale(params.locale));
539
- const localizedParams = { ...params, locale };
540
- return useSuspenseQuery<CollectionPage<<%= model.pascal %>Result<TFields, TPopulate>>, Error, <%= model.pascal %>Result<TFields, TPopulate>[]>({
541
- queryKey: qk.list(localizedParams),
542
- queryFn: () => list(localizedParams),
543
- select: (page) => page.items,
544
- });
545
- }
546
-
547
- export function useCreate() {
548
- const qc = useQueryClient();
549
- const activeLocale = useQueryLocale();
550
- return useMutation<<%= model.pascal %>, Error, <%= model.pascal %>Input>({
551
- mutationFn : (payload) => create(withMutationLocale(payload, activeLocale)),
552
- onSuccess : () => qc.invalidateQueries({ queryKey: qk.root })
553
- });
554
- }
555
-
556
- export function useUpdate() {
557
- const qc = useQueryClient();
558
- const activeLocale = useQueryLocale();
559
- return useMutation<<%= model.pascal %>, Error, { id: string; locale?: string } & Partial<<%= model.pascal %>Input>>({
560
- mutationFn : ({ id, ...patch }) => update(id, withMutationLocale(patch, activeLocale)),
561
- onSuccess : (_data, vars) => {
562
- qc.invalidateQueries({ queryKey: qk.root });
563
- qc.invalidateQueries({ queryKey: qk.byId(vars.id, { locale: vars.locale ?? activeLocale }) });
564
- }
565
- });
566
- }
567
-
568
- export function useRemove() {
569
- const qc = useQueryClient();
570
- const activeLocale = useQueryLocale();
571
- return useMutation<void, Error, string | { id: string; locale?: string }>({
572
- mutationFn : (value) => {
573
- const { id, locale } = typeof value === "string" ? { id: value, locale: activeLocale } : { ...value, locale: value.locale ?? activeLocale };
574
- return remove(id, { locale });
575
- },
576
- onSuccess : (_data, value) => {
577
- const { id, locale } = typeof value === "string" ? { id: value, locale: activeLocale } : { ...value, locale: value.locale ?? activeLocale };
578
- qc.invalidateQueries({ queryKey: qk.root });
579
- qc.removeQueries({ queryKey: qk.byId(id, { locale }), exact: false });
580
- }
581
- });
582
- }
37
+ <% if (model.isSingleton) { -%>
38
+ const client: SingletonContentClient<<%= model.pascal %>, <%= model.pascal %>Input> = createSingletonClient<
39
+ <%= model.pascal %>,
40
+ <%= model.pascal %>Input
41
+ >({
42
+ schema,
583
43
  <% } else { -%>
584
- const qk = {
585
- root: ["<%= model.pascal %>"] as const,
586
- document: <TPopulate extends <%= model.pascal %>Populate | undefined = undefined>(options: { locale?: string; populate?: TPopulate; localeFallbacks?: boolean } = {}) => ["<%= model.pascal %>", "document", options.locale, options.populate] as const
587
- };
588
-
589
- export function useGet<TPopulate extends <%= model.pascal %>Populate | undefined = undefined>(options: { locale?: string; populate?: TPopulate; localeFallbacks?: boolean } = {}) {
590
- const locale = readLocale(options, LOCALIZED_CONTENT, useQueryLocale(options.locale));
591
- const localizedOptions = { ...options, locale };
592
- return useQuery<<%= model.pascal %>Result<undefined, TPopulate>, Error>({ queryKey: qk.document(localizedOptions), queryFn: () => get(localizedOptions) });
593
- }
594
-
595
- export function useSuspenseGet<TPopulate extends <%= model.pascal %>Populate | undefined = undefined>(options: { locale?: string; populate?: TPopulate; localeFallbacks?: boolean } = {}) {
596
- const locale = readLocale(options, LOCALIZED_CONTENT, useQueryLocale(options.locale));
597
- const localizedOptions = { ...options, locale };
598
- return useSuspenseQuery<<%= model.pascal %>Result<undefined, TPopulate>, Error>({ queryKey: qk.document(localizedOptions), queryFn: () => get(localizedOptions) });
599
- }
600
-
601
- export function useUpdate() {
602
- const qc = useQueryClient();
603
- const activeLocale = useQueryLocale();
604
- return useMutation<<%= model.pascal %>, Error, Partial<<%= model.pascal %>Input>>({
605
- mutationFn: (payload) => update(withMutationLocale(payload, activeLocale)),
606
- onSuccess: () => qc.invalidateQueries({ queryKey: qk.root })
607
- });
608
- }
44
+ const client: CollectionContentClient<<%= model.pascal %>, <%= model.pascal %>Input> = createContentClient<
45
+ <%= model.pascal %>,
46
+ <%= model.pascal %>Input
47
+ >({
48
+ schema,
609
49
  <% } -%>
50
+ <% if (model.referenceImports && model.referenceImports.length > 0) { -%>
51
+ references: { <%= model.referenceImports.map((referenceImport) => referenceImport.pascal).join(", ") %> },
52
+ <% } -%>
53
+ });
610
54
 
611
- /*───────────────────────────────────────────────────────────────*/
612
- /* ──────────── 🎯 Default export ──────────── */
613
- /*───────────────────────────────────────────────────────────────*/
614
-
615
- export default {
616
- <% if (!model.isSingleton) { -%>
617
- /* async helpers */
55
+ <% if (model.isSingleton) { -%>
56
+ export const { get, update, useGet, useSuspenseGet, useUpdate, wrapWithSource, meta } = client;
57
+ <% } else { -%>
58
+ export const {
59
+ wrapWithSource,
60
+ meta,
618
61
  getById,
619
62
  getBySlug,
620
63
  getByField,
@@ -623,8 +66,6 @@ export default {
623
66
  create,
624
67
  update,
625
68
  remove,
626
-
627
- /* hooks */
628
69
  useGetById,
629
70
  useGetBySlug,
630
71
  useGetByField,
@@ -637,12 +78,8 @@ export default {
637
78
  useSuspenseGetBySlug,
638
79
  useCreate,
639
80
  useUpdate,
640
- useRemove
641
- <% } else { -%>
642
- get,
643
- update,
644
- useGet,
645
- useSuspenseGet,
646
- useUpdate
81
+ useRemove,
82
+ } = client;
647
83
  <% } -%>
648
- };
84
+
85
+ export default client;