fimo 0.4.0-experimental.1 → 0.4.0-experimental.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/assets/content-templates/hooks-template.eta +193 -123
- package/assets/content-templates/types-template.eta +162 -0
- package/assets/skills/fimo/references/content.md +93 -41
- package/assets/skills/fimo/references/locales.md +1 -1
- package/assets/skills/fimo-cli/references/content.md +17 -9
- package/assets/skills/fimo-cli/references/locales.md +2 -2
- package/dist/cli/bundle.json +2 -2
- package/dist/cli/index.js +189 -68
- package/dist/runtime/primitives/index.d.ts +2 -2
- package/dist/runtime/primitives/index.d.ts.map +1 -1
- package/dist/runtime/primitives/lib/index.d.ts +2 -2
- package/dist/runtime/primitives/lib/index.d.ts.map +1 -1
- package/dist/runtime/primitives/lib/primitives.d.ts +3 -0
- package/dist/runtime/primitives/lib/primitives.d.ts.map +1 -1
- package/dist/runtime/primitives/lib/query.d.ts +46 -8
- package/dist/runtime/primitives/lib/query.d.ts.map +1 -1
- package/dist/runtime/primitives/public-surface.test.js +27 -1
- package/dist/scripts/sync-schemas.js +91 -27
- package/dist/scripts/sync-schemas.test.d.ts +1 -0
- package/dist/scripts/sync-schemas.test.js +121 -0
- package/dist/scripts/validate-schemas.js +31 -4
- package/dist/scripts/validate-schemas.test.js +49 -0
- package/package.json +1 -1
- package/templates/react-router/package.json +1 -1
|
@@ -8,80 +8,21 @@
|
|
|
8
8
|
*/
|
|
9
9
|
%>
|
|
10
10
|
// AUTO-GENERATED ⚠️ – do not edit by hand
|
|
11
|
-
import { useSuspenseQuery, useQuery, useMutation, useQueryClient
|
|
12
|
-
import { FimoString,
|
|
11
|
+
import { useSuspenseQuery, useQuery, useMutation, useQueryClient } from "@tanstack/react-query";
|
|
12
|
+
import { FimoString, FimoMedia, FimoBoolean, FimoRichText, useLocale } from "fimo/ui";
|
|
13
13
|
// @ts-ignore - virtual module provided by Fimo's Vite plugin
|
|
14
14
|
import fimoConfig from "virtual:fimo-config";
|
|
15
|
-
|
|
16
|
-
/*───────────────────────────────────────────────────────────────*/
|
|
17
|
-
/* ──────────── 📄 Type definitions ──────────── */
|
|
18
|
-
/*───────────────────────────────────────────────────────────────*/
|
|
19
|
-
|
|
20
|
-
/**
|
|
21
|
-
* <%= model.pascal %> entity interface
|
|
22
|
-
*/
|
|
23
|
-
export interface <%= model.pascal %>FimoMeta {
|
|
24
|
-
contentType: string;
|
|
25
|
-
translationProgress: { state: "queued" | "translating"; fields?: string[] } | null;
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
export interface <%= model.pascal %> {
|
|
29
|
-
id: string;
|
|
30
|
-
documentId: string;
|
|
31
|
-
slug: FimoString;
|
|
32
|
-
locale: string;
|
|
33
|
-
createdAt: string;
|
|
34
|
-
updatedAt: string;
|
|
35
|
-
__fimo: <%= model.pascal %>FimoMeta;
|
|
15
|
+
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";
|
|
36
16
|
<%
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
Object.entries(model.fields).forEach(([key, field]) => {
|
|
40
|
-
const type = getTypeForField(field);
|
|
17
|
+
if (model.relationImports && model.relationImports.length > 0) {
|
|
18
|
+
model.relationImports.forEach((relationImport) => {
|
|
41
19
|
-%>
|
|
42
|
-
|
|
20
|
+
import { wrapWithSource as wrap<%= relationImport.pascal %>WithSource } from "./<%= relationImport.pascal %>";
|
|
43
21
|
<%
|
|
44
22
|
});
|
|
45
23
|
}
|
|
46
|
-
|
|
47
|
-
/**
|
|
48
|
-
* Helper function to convert schema field types to TypeScript types
|
|
49
|
-
* Uses Fimo wrapper types for source tracking
|
|
50
|
-
*/
|
|
51
|
-
function getTypeForField(field) {
|
|
52
|
-
switch(field.type) {
|
|
53
|
-
case 'string':
|
|
54
|
-
return 'FimoString';
|
|
55
|
-
case 'richtext':
|
|
56
|
-
return 'FimoRichText';
|
|
57
|
-
case 'number':
|
|
58
|
-
return 'number';
|
|
59
|
-
case 'boolean':
|
|
60
|
-
return 'FimoBoolean';
|
|
61
|
-
case 'date':
|
|
62
|
-
return 'string';
|
|
63
|
-
case 'json':
|
|
64
|
-
return 'Record<string, any>';
|
|
65
|
-
case 'media':
|
|
66
|
-
return field.multiple ? 'FimoMedia[]' : 'FimoMedia';
|
|
67
|
-
case 'relation':
|
|
68
|
-
return field.many ? 'Array<string | Record<string, any>>' : 'string | Record<string, any> | null';
|
|
69
|
-
default:
|
|
70
|
-
return 'any';
|
|
71
|
-
}
|
|
72
|
-
}
|
|
73
24
|
-%>
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
/*───────────────────────────────────────────────────────────────*/
|
|
77
|
-
/* ──────────── 🔎 Query types ──────────── */
|
|
78
|
-
/*───────────────────────────────────────────────────────────────*/
|
|
79
|
-
|
|
80
|
-
export type <%= model.pascal %>Where = Where<<%= model.pascal %>>;
|
|
81
|
-
export type <%= model.pascal %>Sort = Sort<<%= model.pascal %>>;
|
|
82
|
-
export type <%= model.pascal %>Fields = Fields<<%= model.pascal %>>;
|
|
83
|
-
export type <%= model.pascal %>Query<TFields extends <%= model.pascal %>Fields | undefined = <%= model.pascal %>Fields | undefined> = Query<<%= model.pascal %>, TFields>;
|
|
84
|
-
export type <%= model.pascal %>Result<TFields extends <%= model.pascal %>Fields | undefined = undefined> = Projected<<%= model.pascal %>, TFields>;
|
|
25
|
+
export type { <%= model.pascal %>, <%= model.pascal %>Fields, <%= model.pascal %>FimoMeta, <%= model.pascal %>Input, <%= model.pascal %>Populate, <%= model.pascal %>Query, <%= model.pascal %>Relations, <%= model.pascal %>Result, <%= model.pascal %>Sort, <%= model.pascal %>Where } from "./<%= model.pascal %>.types";
|
|
85
26
|
|
|
86
27
|
/*───────────────────────────────────────────────────────────────*/
|
|
87
28
|
/* ──────────── 🎁 Source wrapping ──────────── */
|
|
@@ -90,7 +31,7 @@ export type <%= model.pascal %>Result<TFields extends <%= model.pascal %>Fields
|
|
|
90
31
|
/**
|
|
91
32
|
* Wraps raw API data with Fimo types for source tracking
|
|
92
33
|
*/
|
|
93
|
-
function wrapWithSource(entry: Record<string, any>): <%= model.pascal %> {
|
|
34
|
+
export function wrapWithSource(entry: Record<string, any>): <%= model.pascal %> {
|
|
94
35
|
const id = entry.id;
|
|
95
36
|
const sourcePrefix = `<%= model.pascal %>.${id}`;
|
|
96
37
|
const entryFimo = entry.__fimo && typeof entry.__fimo === "object" ? entry.__fimo : {};
|
|
@@ -99,12 +40,13 @@ function wrapWithSource(entry: Record<string, any>): <%= model.pascal %> {
|
|
|
99
40
|
id,
|
|
100
41
|
documentId: entry.documentId,
|
|
101
42
|
slug: entry.slug != null ? new FimoString(entry.slug, `${sourcePrefix}.slug`) : entry.slug,
|
|
43
|
+
<% if (model.localized) { -%>
|
|
102
44
|
locale: entry.locale,
|
|
45
|
+
<% } -%>
|
|
103
46
|
createdAt: entry.createdAt,
|
|
104
47
|
updatedAt: entry.updatedAt,
|
|
105
48
|
__fimo: {
|
|
106
49
|
contentType: typeof entryFimo.contentType === "string" ? entryFimo.contentType : "<%= model.uid %>",
|
|
107
|
-
translationProgress: entryFimo.translationProgress && typeof entryFimo.translationProgress === "object" ? entryFimo.translationProgress as <%= model.pascal %>FimoMeta["translationProgress"] : null,
|
|
108
50
|
},
|
|
109
51
|
<%
|
|
110
52
|
if (model.fields) {
|
|
@@ -137,6 +79,19 @@ if (model.fields) {
|
|
|
137
79
|
<%
|
|
138
80
|
}
|
|
139
81
|
break;
|
|
82
|
+
case 'relation': {
|
|
83
|
+
const wrapper = field.target === model.uid ? 'wrapWithSource' : `wrap${field.targetPascal}WithSource`;
|
|
84
|
+
if (field.many) {
|
|
85
|
+
-%>
|
|
86
|
+
<%= key %>: Array.isArray(entry.<%= key %>) ? entry.<%= key %>.map((value: any) => value != null && typeof value === "object" && typeof value.id === "string" ? <%= wrapper %>(value) : value) : entry.<%= key %>,
|
|
87
|
+
<%
|
|
88
|
+
} else {
|
|
89
|
+
-%>
|
|
90
|
+
<%= key %>: entry.<%= key %> != null && typeof entry.<%= key %> === "object" && typeof entry.<%= key %>.id === "string" ? <%= wrapper %>(entry.<%= key %>) : entry.<%= key %>,
|
|
91
|
+
<%
|
|
92
|
+
}
|
|
93
|
+
break;
|
|
94
|
+
}
|
|
140
95
|
default:
|
|
141
96
|
-%>
|
|
142
97
|
<%= key %>: entry.<%= key %>,
|
|
@@ -159,7 +114,25 @@ function defaultLocale(): string {
|
|
|
159
114
|
}
|
|
160
115
|
|
|
161
116
|
function useQueryLocale(locale?: string): string {
|
|
162
|
-
|
|
117
|
+
const activeLocale = useLocale().locale;
|
|
118
|
+
return locale ?? activeLocale ?? defaultLocale();
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
const LOCALIZED_CONTENT = <%= model.localized ? 'true' : 'false' %>;
|
|
122
|
+
const LOCALIZED_RELATION_FIELDS = new Set<string>(<%= JSON.stringify(model.localizedRelationFields) %>);
|
|
123
|
+
|
|
124
|
+
function populateFields(populate: <%= model.pascal %>Populate | undefined): string[] {
|
|
125
|
+
if (populate == null) return [];
|
|
126
|
+
return typeof populate === "string" ? [populate] : [...populate];
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
function readLocale(
|
|
130
|
+
options: { locale?: string; populate?: <%= model.pascal %>Populate },
|
|
131
|
+
ownerUsesLocale: boolean,
|
|
132
|
+
fallbackLocale = defaultLocale()
|
|
133
|
+
): string | undefined {
|
|
134
|
+
const populatesLocalizedTarget = populateFields(options.populate).some((field) => LOCALIZED_RELATION_FIELDS.has(field));
|
|
135
|
+
return ownerUsesLocale || populatesLocalizedTarget ? options.locale ?? fallbackLocale : undefined;
|
|
163
136
|
}
|
|
164
137
|
|
|
165
138
|
function fimoHeaders(extra?: Record<string, string>): Record<string, string> {
|
|
@@ -170,13 +143,21 @@ function fimoHeaders(extra?: Record<string, string>): Record<string, string> {
|
|
|
170
143
|
return h;
|
|
171
144
|
}
|
|
172
145
|
|
|
173
|
-
function
|
|
146
|
+
function withReadOptions<TPopulate extends <%= model.pascal %>Populate | undefined>(
|
|
147
|
+
url: string,
|
|
148
|
+
options: { locale?: string; populate?: TPopulate } = {},
|
|
149
|
+
ownerUsesLocale = LOCALIZED_CONTENT
|
|
150
|
+
): string {
|
|
174
151
|
const u = new URL(url, "http://fimo.local");
|
|
175
|
-
|
|
152
|
+
const locale = readLocale(options, ownerUsesLocale);
|
|
153
|
+
if (locale) u.searchParams.set("locale", locale);
|
|
154
|
+
if (options.populate != null) {
|
|
155
|
+
u.searchParams.set("populate", typeof options.populate === "string" ? options.populate : options.populate.join(","));
|
|
156
|
+
}
|
|
176
157
|
return `${u.pathname}${u.search}`;
|
|
177
158
|
}
|
|
178
159
|
|
|
179
|
-
function splitWritePayload(payload: Partial<<%= model.pascal
|
|
160
|
+
function splitWritePayload(payload: Partial<<%= model.pascal %>Input>): Record<string, unknown> {
|
|
180
161
|
const {
|
|
181
162
|
id,
|
|
182
163
|
documentId,
|
|
@@ -194,31 +175,45 @@ function splitWritePayload(payload: Partial<<%= model.pascal %>>): Record<string
|
|
|
194
175
|
};
|
|
195
176
|
}
|
|
196
177
|
|
|
197
|
-
|
|
198
|
-
|
|
178
|
+
<% if (!model.isSingleton) { -%>
|
|
179
|
+
export async function getById<TPopulate extends <%= model.pascal %>Populate | undefined = undefined>(
|
|
180
|
+
id: string,
|
|
181
|
+
options: { locale?: string; populate?: TPopulate } = {}
|
|
182
|
+
): Promise<<%= model.pascal %>Result<undefined, TPopulate>> {
|
|
183
|
+
const res = await fetch(`${base}${withReadOptions(`/entries/<%= model.uid %>/${id}`, options, false)}`, { headers: fimoHeaders() });
|
|
199
184
|
if (!res.ok) throw new Error(await res.text());
|
|
200
185
|
const data = await res.json();
|
|
201
|
-
return wrapWithSource(data.data)
|
|
186
|
+
return wrapWithSource(data.data) as <%= model.pascal %>Result<undefined, TPopulate>;
|
|
202
187
|
}
|
|
203
188
|
|
|
204
|
-
export async function getBySlug
|
|
205
|
-
|
|
189
|
+
export async function getBySlug<TPopulate extends <%= model.pascal %>Populate | undefined = undefined>(
|
|
190
|
+
slug: string,
|
|
191
|
+
options: { locale?: string; populate?: TPopulate } = {}
|
|
192
|
+
): Promise<<%= model.pascal %>Result<undefined, TPopulate> | null> {
|
|
193
|
+
const res = await fetch(`${base}${withReadOptions(`/entries/<%= model.uid %>/slug/${encodeURIComponent(slug)}`, options)}`, { headers: fimoHeaders() });
|
|
206
194
|
if (res.status === 404) return null;
|
|
207
195
|
if (!res.ok) throw new Error(await res.text());
|
|
208
196
|
const data = await res.json();
|
|
209
|
-
return wrapWithSource(data.data)
|
|
197
|
+
return wrapWithSource(data.data) as <%= model.pascal %>Result<undefined, TPopulate>;
|
|
210
198
|
}
|
|
211
199
|
|
|
212
|
-
export async function getByField
|
|
213
|
-
|
|
200
|
+
export async function getByField<TPopulate extends <%= model.pascal %>Populate | undefined = undefined>(
|
|
201
|
+
fieldName: string,
|
|
202
|
+
value: string,
|
|
203
|
+
options: { locale?: string; populate?: TPopulate } = {}
|
|
204
|
+
): Promise<<%= model.pascal %>Result<undefined, TPopulate> | null> {
|
|
205
|
+
const res = await fetch(`${base}${withReadOptions(`/entries/<%= model.uid %>/field/${encodeURIComponent(fieldName)}/${encodeURIComponent(value)}`, options)}`, { headers: fimoHeaders() });
|
|
214
206
|
if (res.status === 404) return null;
|
|
215
207
|
if (!res.ok) throw new Error(await res.text());
|
|
216
208
|
const data = await res.json();
|
|
217
|
-
return wrapWithSource(data.data)
|
|
209
|
+
return wrapWithSource(data.data) as <%= model.pascal %>Result<undefined, TPopulate>;
|
|
218
210
|
}
|
|
219
211
|
|
|
220
|
-
function toQueryString<
|
|
221
|
-
|
|
212
|
+
function toQueryString<
|
|
213
|
+
TFields extends <%= model.pascal %>Fields | undefined = undefined,
|
|
214
|
+
TPopulate extends <%= model.pascal %>Populate | undefined = undefined,
|
|
215
|
+
>(params: <%= model.pascal %>Query<TFields, TPopulate> = {} as <%= model.pascal %>Query<TFields, TPopulate>): string {
|
|
216
|
+
const { where, sort, fields, populate, locale: requestedLocale, ...flat } = params;
|
|
222
217
|
const query = new URLSearchParams();
|
|
223
218
|
|
|
224
219
|
for (const [key, value] of Object.entries(flat)) {
|
|
@@ -242,22 +237,24 @@ function toQueryString<TFields extends <%= model.pascal %>Fields | undefined = u
|
|
|
242
237
|
query.set("where", JSON.stringify(where));
|
|
243
238
|
}
|
|
244
239
|
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
}
|
|
240
|
+
const locale = readLocale({ locale: requestedLocale, populate }, LOCALIZED_CONTENT);
|
|
241
|
+
if (locale) query.set("locale", locale);
|
|
248
242
|
|
|
249
243
|
return query.toString();
|
|
250
244
|
}
|
|
251
245
|
|
|
252
|
-
export async function get<
|
|
246
|
+
export async function get<
|
|
247
|
+
TFields extends <%= model.pascal %>Fields | undefined = undefined,
|
|
248
|
+
TPopulate extends <%= model.pascal %>Populate | undefined = undefined,
|
|
249
|
+
>(params: <%= model.pascal %>Query<TFields, TPopulate> = {} as <%= model.pascal %>Query<TFields, TPopulate>): Promise<<%= model.pascal %>Result<TFields, TPopulate>[]> {
|
|
253
250
|
const qs = toQueryString(params);
|
|
254
251
|
const res = await fetch(`${base}/entries/<%= model.uid %>${qs ? "?" + qs : ""}`, { headers: fimoHeaders() });
|
|
255
252
|
if (!res.ok) throw new Error(await res.text());
|
|
256
253
|
const data = await res.json();
|
|
257
|
-
return data.data.map(wrapWithSource) as <%= model.pascal %>Result<TFields>[];
|
|
254
|
+
return data.data.map(wrapWithSource) as <%= model.pascal %>Result<TFields, TPopulate>[];
|
|
258
255
|
}
|
|
259
256
|
|
|
260
|
-
export async function create(payload:
|
|
257
|
+
export async function create(payload: <%= model.pascal %>Input): Promise<<%= model.pascal %>> {
|
|
261
258
|
const res = await fetch(`${base}/entries/<%= model.uid %>`, {
|
|
262
259
|
method: "POST",
|
|
263
260
|
headers: fimoHeaders({ "Content-Type": "application/json" }),
|
|
@@ -265,10 +262,10 @@ export async function create(payload: Partial<<%= model.pascal %>>): Promise<<%=
|
|
|
265
262
|
});
|
|
266
263
|
if (!res.ok) throw new Error(await res.text());
|
|
267
264
|
const data = await res.json();
|
|
268
|
-
return data.data;
|
|
265
|
+
return wrapWithSource(data.data);
|
|
269
266
|
}
|
|
270
267
|
|
|
271
|
-
export async function update(id: string, payload: Partial<<%= model.pascal
|
|
268
|
+
export async function update(id: string, payload: Partial<<%= model.pascal %>Input>): Promise<<%= model.pascal %>> {
|
|
272
269
|
const res = await fetch(`${base}/entries/<%= model.uid %>/${id}`, {
|
|
273
270
|
method: "PUT",
|
|
274
271
|
headers: fimoHeaders({ "Content-Type": "application/json" }),
|
|
@@ -276,71 +273,107 @@ export async function update(id: string, payload: Partial<<%= model.pascal %>>):
|
|
|
276
273
|
});
|
|
277
274
|
if (!res.ok) throw new Error(await res.text());
|
|
278
275
|
const data = await res.json();
|
|
279
|
-
return data.data;
|
|
276
|
+
return wrapWithSource(data.data);
|
|
280
277
|
}
|
|
281
278
|
|
|
282
279
|
export async function remove(id: string): Promise<void> {
|
|
283
280
|
const res = await fetch(`${base}/entries/<%= model.uid %>/${id}`, { method: "DELETE", headers: fimoHeaders() });
|
|
284
281
|
if (!res.ok) throw new Error(await res.text());
|
|
285
282
|
}
|
|
283
|
+
<% } else { -%>
|
|
284
|
+
export async function get<TPopulate extends <%= model.pascal %>Populate | undefined = undefined>(
|
|
285
|
+
options: { locale?: string; populate?: TPopulate } = {}
|
|
286
|
+
): Promise<<%= model.pascal %>Result<undefined, TPopulate>> {
|
|
287
|
+
const res = await fetch(`${base}${withReadOptions(`/documents/<%= model.uid %>`, options)}`, { headers: fimoHeaders() });
|
|
288
|
+
if (!res.ok) throw new Error(await res.text());
|
|
289
|
+
const data = await res.json();
|
|
290
|
+
return wrapWithSource(data.data) as <%= model.pascal %>Result<undefined, TPopulate>;
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
export async function update(payload: Partial<<%= model.pascal %>Input>): Promise<<%= model.pascal %>> {
|
|
294
|
+
const write = splitWritePayload(payload) as { data: Record<string, unknown>; locale?: string };
|
|
295
|
+
const url = withReadOptions(`/documents/<%= model.uid %>`, { locale: write.locale });
|
|
296
|
+
const res = await fetch(`${base}${url}`, {
|
|
297
|
+
method: "PUT",
|
|
298
|
+
headers: fimoHeaders({ "Content-Type": "application/json" }),
|
|
299
|
+
body: JSON.stringify({ data: write.data })
|
|
300
|
+
});
|
|
301
|
+
if (!res.ok) throw new Error(await res.text());
|
|
302
|
+
const data = await res.json();
|
|
303
|
+
return wrapWithSource(data.data);
|
|
304
|
+
}
|
|
305
|
+
<% } -%>
|
|
286
306
|
|
|
287
307
|
/*───────────────────────────────────────────────────────────────*/
|
|
288
308
|
/* ──────────── 🍯 React-Query hooks ──────────── */
|
|
289
309
|
/*───────────────────────────────────────────────────────────────*/
|
|
290
310
|
|
|
311
|
+
<% if (!model.isSingleton) { -%>
|
|
291
312
|
const qk = {
|
|
292
313
|
root : ["<%= model.pascal %>"] as const,
|
|
293
|
-
byId : (id: string, locale?: string) => ["<%= model.pascal %>", id, locale
|
|
294
|
-
bySlug : (slug: string, locale?: string) => ["<%= model.pascal %>", "slug", slug, locale
|
|
295
|
-
byField : (field: string, value: string, locale?: string) => ["<%= model.pascal %>", field, value, locale
|
|
314
|
+
byId : <TPopulate extends <%= model.pascal %>Populate | undefined = undefined>(id: string, options: { locale?: string; populate?: TPopulate } = {}) => ["<%= model.pascal %>", id, options.locale, options.populate] as const,
|
|
315
|
+
bySlug : <TPopulate extends <%= model.pascal %>Populate | undefined = undefined>(slug: string, options: { locale?: string; populate?: TPopulate } = {}) => ["<%= model.pascal %>", "slug", slug, options.locale, options.populate] as const,
|
|
316
|
+
byField : <TPopulate extends <%= model.pascal %>Populate | undefined = undefined>(field: string, value: string, options: { locale?: string; populate?: TPopulate } = {}) => ["<%= model.pascal %>", field, value, options.locale, options.populate] as const,
|
|
296
317
|
list : (params: <%= model.pascal %>Query) => ["<%= model.pascal %>", params] as const
|
|
297
318
|
};
|
|
298
319
|
|
|
299
|
-
export function useGetById(id: string, options: { locale?: string } = {}) {
|
|
300
|
-
const locale = useQueryLocale(options.locale);
|
|
301
|
-
|
|
320
|
+
export function useGetById<TPopulate extends <%= model.pascal %>Populate | undefined = undefined>(id: string, options: { locale?: string; populate?: TPopulate } = {}) {
|
|
321
|
+
const locale = readLocale(options, false, useQueryLocale(options.locale));
|
|
322
|
+
const localizedOptions = { ...options, locale };
|
|
323
|
+
return useQuery<<%= model.pascal %>Result<undefined, TPopulate>, Error>({ queryKey: qk.byId(id, localizedOptions), queryFn: () => getById(id, localizedOptions) });
|
|
302
324
|
}
|
|
303
325
|
|
|
304
|
-
export function useGetBySlug(slug: string, options: { locale?: string } = {}) {
|
|
305
|
-
const locale = useQueryLocale(options.locale);
|
|
306
|
-
|
|
326
|
+
export function useGetBySlug<TPopulate extends <%= model.pascal %>Populate | undefined = undefined>(slug: string, options: { locale?: string; populate?: TPopulate } = {}) {
|
|
327
|
+
const locale = readLocale(options, LOCALIZED_CONTENT, useQueryLocale(options.locale));
|
|
328
|
+
const localizedOptions = { ...options, locale };
|
|
329
|
+
return useQuery<<%= model.pascal %>Result<undefined, TPopulate> | null, Error>({ queryKey: qk.bySlug(slug, localizedOptions), queryFn: () => getBySlug(slug, localizedOptions), enabled: !!slug });
|
|
307
330
|
}
|
|
308
331
|
|
|
309
|
-
export function useGetByField(fieldName: string, value: string, options: { locale?: string } = {}) {
|
|
310
|
-
const locale = useQueryLocale(options.locale);
|
|
311
|
-
|
|
332
|
+
export function useGetByField<TPopulate extends <%= model.pascal %>Populate | undefined = undefined>(fieldName: string, value: string, options: { locale?: string; populate?: TPopulate } = {}) {
|
|
333
|
+
const locale = readLocale(options, LOCALIZED_CONTENT, useQueryLocale(options.locale));
|
|
334
|
+
const localizedOptions = { ...options, locale };
|
|
335
|
+
return useQuery<<%= model.pascal %>Result<undefined, TPopulate> | null, Error>({ queryKey: qk.byField(fieldName, value, localizedOptions), queryFn: () => getByField(fieldName, value, localizedOptions), enabled: !!value });
|
|
312
336
|
}
|
|
313
337
|
|
|
314
|
-
export function useGet<
|
|
315
|
-
|
|
338
|
+
export function useGet<
|
|
339
|
+
TFields extends <%= model.pascal %>Fields | undefined = undefined,
|
|
340
|
+
TPopulate extends <%= model.pascal %>Populate | undefined = undefined,
|
|
341
|
+
>(params: <%= model.pascal %>Query<TFields, TPopulate> = {} as <%= model.pascal %>Query<TFields, TPopulate>) {
|
|
342
|
+
const locale = readLocale(params, LOCALIZED_CONTENT, useQueryLocale(params.locale));
|
|
316
343
|
const localizedParams = { ...params, locale };
|
|
317
|
-
return useQuery<<%= model.pascal %>Result<TFields>[], Error>({ queryKey: qk.list(localizedParams), queryFn: () => get(localizedParams) });
|
|
344
|
+
return useQuery<<%= model.pascal %>Result<TFields, TPopulate>[], Error>({ queryKey: qk.list(localizedParams), queryFn: () => get(localizedParams) });
|
|
318
345
|
}
|
|
319
346
|
|
|
320
|
-
export function useSuspenseGetById(id: string, options: { locale?: string } = {}) {
|
|
321
|
-
const locale = useQueryLocale(options.locale);
|
|
322
|
-
|
|
347
|
+
export function useSuspenseGetById<TPopulate extends <%= model.pascal %>Populate | undefined = undefined>(id: string, options: { locale?: string; populate?: TPopulate } = {}) {
|
|
348
|
+
const locale = readLocale(options, false, useQueryLocale(options.locale));
|
|
349
|
+
const localizedOptions = { ...options, locale };
|
|
350
|
+
return useSuspenseQuery<<%= model.pascal %>Result<undefined, TPopulate>, Error>({ queryKey: qk.byId(id, localizedOptions), queryFn: () => getById(id, localizedOptions) });
|
|
323
351
|
}
|
|
324
352
|
|
|
325
|
-
export function useSuspenseGetBySlug(slug: string, options: { locale?: string } = {}) {
|
|
326
|
-
const locale = useQueryLocale(options.locale);
|
|
327
|
-
|
|
353
|
+
export function useSuspenseGetBySlug<TPopulate extends <%= model.pascal %>Populate | undefined = undefined>(slug: string, options: { locale?: string; populate?: TPopulate } = {}) {
|
|
354
|
+
const locale = readLocale(options, LOCALIZED_CONTENT, useQueryLocale(options.locale));
|
|
355
|
+
const localizedOptions = { ...options, locale };
|
|
356
|
+
return useSuspenseQuery<<%= model.pascal %>Result<undefined, TPopulate> | null, Error>({ queryKey: qk.bySlug(slug, localizedOptions), queryFn: () => getBySlug(slug, localizedOptions) });
|
|
328
357
|
}
|
|
329
358
|
|
|
330
|
-
export function useSuspenseGetByField(fieldName: string, value: string, options: { locale?: string } = {}) {
|
|
331
|
-
const locale = useQueryLocale(options.locale);
|
|
332
|
-
|
|
359
|
+
export function useSuspenseGetByField<TPopulate extends <%= model.pascal %>Populate | undefined = undefined>(fieldName: string, value: string, options: { locale?: string; populate?: TPopulate } = {}) {
|
|
360
|
+
const locale = readLocale(options, LOCALIZED_CONTENT, useQueryLocale(options.locale));
|
|
361
|
+
const localizedOptions = { ...options, locale };
|
|
362
|
+
return useSuspenseQuery<<%= model.pascal %>Result<undefined, TPopulate> | null, Error>({ queryKey: qk.byField(fieldName, value, localizedOptions), queryFn: () => getByField(fieldName, value, localizedOptions) });
|
|
333
363
|
}
|
|
334
364
|
|
|
335
|
-
export function useSuspenseGet<
|
|
336
|
-
|
|
365
|
+
export function useSuspenseGet<
|
|
366
|
+
TFields extends <%= model.pascal %>Fields | undefined = undefined,
|
|
367
|
+
TPopulate extends <%= model.pascal %>Populate | undefined = undefined,
|
|
368
|
+
>(params: <%= model.pascal %>Query<TFields, TPopulate> = {} as <%= model.pascal %>Query<TFields, TPopulate>) {
|
|
369
|
+
const locale = readLocale(params, LOCALIZED_CONTENT, useQueryLocale(params.locale));
|
|
337
370
|
const localizedParams = { ...params, locale };
|
|
338
|
-
return useSuspenseQuery<<%= model.pascal %>Result<TFields>[], Error>({ queryKey: qk.list(localizedParams), queryFn: () => get(localizedParams) });
|
|
371
|
+
return useSuspenseQuery<<%= model.pascal %>Result<TFields, TPopulate>[], Error>({ queryKey: qk.list(localizedParams), queryFn: () => get(localizedParams) });
|
|
339
372
|
}
|
|
340
373
|
|
|
341
374
|
export function useCreate() {
|
|
342
375
|
const qc = useQueryClient();
|
|
343
|
-
return useMutation<<%= model.pascal %>, Error,
|
|
376
|
+
return useMutation<<%= model.pascal %>, Error, <%= model.pascal %>Input>({
|
|
344
377
|
mutationFn : create,
|
|
345
378
|
onSuccess : () => qc.invalidateQueries({ queryKey: qk.root })
|
|
346
379
|
});
|
|
@@ -348,9 +381,12 @@ export function useCreate() {
|
|
|
348
381
|
|
|
349
382
|
export function useUpdate() {
|
|
350
383
|
const qc = useQueryClient();
|
|
351
|
-
return useMutation<<%= model.pascal %>, Error, { id: string } & Partial<<%= model.pascal
|
|
384
|
+
return useMutation<<%= model.pascal %>, Error, { id: string } & Partial<<%= model.pascal %>Input>>({
|
|
352
385
|
mutationFn : ({ id, ...patch }) => update(id, patch),
|
|
353
|
-
onSuccess : (_data, vars) =>
|
|
386
|
+
onSuccess : (_data, vars) => {
|
|
387
|
+
qc.invalidateQueries({ queryKey: qk.root });
|
|
388
|
+
qc.invalidateQueries({ queryKey: qk.byId(vars.id) });
|
|
389
|
+
}
|
|
354
390
|
});
|
|
355
391
|
}
|
|
356
392
|
|
|
@@ -360,16 +396,43 @@ export function useRemove() {
|
|
|
360
396
|
mutationFn : remove,
|
|
361
397
|
onSuccess : (_data, id) => {
|
|
362
398
|
qc.invalidateQueries({ queryKey: qk.root });
|
|
363
|
-
qc.removeQueries({ queryKey:
|
|
399
|
+
qc.removeQueries({ queryKey: ["<%= model.pascal %>", id], exact: false });
|
|
364
400
|
}
|
|
365
401
|
});
|
|
366
402
|
}
|
|
403
|
+
<% } else { -%>
|
|
404
|
+
const qk = {
|
|
405
|
+
root: ["<%= model.pascal %>"] as const,
|
|
406
|
+
document: <TPopulate extends <%= model.pascal %>Populate | undefined = undefined>(options: { locale?: string; populate?: TPopulate } = {}) => ["<%= model.pascal %>", "document", options.locale, options.populate] as const
|
|
407
|
+
};
|
|
408
|
+
|
|
409
|
+
export function useGet<TPopulate extends <%= model.pascal %>Populate | undefined = undefined>(options: { locale?: string; populate?: TPopulate } = {}) {
|
|
410
|
+
const locale = readLocale(options, LOCALIZED_CONTENT, useQueryLocale(options.locale));
|
|
411
|
+
const localizedOptions = { ...options, locale };
|
|
412
|
+
return useQuery<<%= model.pascal %>Result<undefined, TPopulate>, Error>({ queryKey: qk.document(localizedOptions), queryFn: () => get(localizedOptions) });
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
export function useSuspenseGet<TPopulate extends <%= model.pascal %>Populate | undefined = undefined>(options: { locale?: string; populate?: TPopulate } = {}) {
|
|
416
|
+
const locale = readLocale(options, LOCALIZED_CONTENT, useQueryLocale(options.locale));
|
|
417
|
+
const localizedOptions = { ...options, locale };
|
|
418
|
+
return useSuspenseQuery<<%= model.pascal %>Result<undefined, TPopulate>, Error>({ queryKey: qk.document(localizedOptions), queryFn: () => get(localizedOptions) });
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
export function useUpdate() {
|
|
422
|
+
const qc = useQueryClient();
|
|
423
|
+
return useMutation<<%= model.pascal %>, Error, Partial<<%= model.pascal %>Input>>({
|
|
424
|
+
mutationFn: update,
|
|
425
|
+
onSuccess: () => qc.invalidateQueries({ queryKey: qk.root })
|
|
426
|
+
});
|
|
427
|
+
}
|
|
428
|
+
<% } -%>
|
|
367
429
|
|
|
368
430
|
/*───────────────────────────────────────────────────────────────*/
|
|
369
431
|
/* ──────────── 🎯 Default export ──────────── */
|
|
370
432
|
/*───────────────────────────────────────────────────────────────*/
|
|
371
433
|
|
|
372
434
|
export default {
|
|
435
|
+
<% if (!model.isSingleton) { -%>
|
|
373
436
|
/* async helpers */
|
|
374
437
|
getById,
|
|
375
438
|
getBySlug,
|
|
@@ -391,4 +454,11 @@ export default {
|
|
|
391
454
|
useCreate,
|
|
392
455
|
useUpdate,
|
|
393
456
|
useRemove
|
|
457
|
+
<% } else { -%>
|
|
458
|
+
get,
|
|
459
|
+
update,
|
|
460
|
+
useGet,
|
|
461
|
+
useSuspenseGet,
|
|
462
|
+
useUpdate
|
|
463
|
+
<% } -%>
|
|
394
464
|
};
|
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
<%
|
|
2
|
+
/**
|
|
3
|
+
* INPUT ▸ model = {
|
|
4
|
+
* uid: "api::blog.blog", // UID in format "api::name.name"
|
|
5
|
+
* pascal: "Blog" // PascalCase for type names
|
|
6
|
+
* fields: {key: {type, required?, multiple?}} // Schema fields
|
|
7
|
+
* }
|
|
8
|
+
*/
|
|
9
|
+
%>
|
|
10
|
+
// AUTO-GENERATED ⚠️ – do not edit by hand
|
|
11
|
+
import { type Fields, type FimoBoolean, type FimoMedia, type FimoReference, type FimoRichText, type FimoRichTextContent, type FimoString, type Populate, type PopulatedResult, type ProjectedWithPopulate, type Query, type Sort, type Where, type WithRelations } from "fimo/ui";
|
|
12
|
+
<%
|
|
13
|
+
if (model.relationImports && model.relationImports.length > 0) {
|
|
14
|
+
model.relationImports.forEach((relationImport) => {
|
|
15
|
+
-%>
|
|
16
|
+
import type { <%= relationImport.pascal %> } from "./<%= relationImport.pascal %>.types";
|
|
17
|
+
<%
|
|
18
|
+
});
|
|
19
|
+
}
|
|
20
|
+
-%>
|
|
21
|
+
|
|
22
|
+
/*───────────────────────────────────────────────────────────────*/
|
|
23
|
+
/* ──────────── 📄 Type definitions ──────────── */
|
|
24
|
+
/*───────────────────────────────────────────────────────────────*/
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* <%= model.pascal %> entity interface
|
|
28
|
+
*/
|
|
29
|
+
export interface <%= model.pascal %>FimoMeta {
|
|
30
|
+
contentType: string;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export interface <%= model.pascal %> extends WithRelations<<%= model.pascal %>Relations> {
|
|
34
|
+
id: string;
|
|
35
|
+
documentId: string;
|
|
36
|
+
slug: FimoString;
|
|
37
|
+
<% if (model.localized) { -%>
|
|
38
|
+
locale: string;
|
|
39
|
+
<% } -%>
|
|
40
|
+
createdAt: string;
|
|
41
|
+
updatedAt: string;
|
|
42
|
+
__fimo: <%= model.pascal %>FimoMeta;
|
|
43
|
+
<%
|
|
44
|
+
// Generate interface properties based on fields
|
|
45
|
+
if (model.fields) {
|
|
46
|
+
Object.entries(model.fields).forEach(([key, field]) => {
|
|
47
|
+
const type = getTypeForField(field);
|
|
48
|
+
-%>
|
|
49
|
+
<%= key %>: <%= type %>;
|
|
50
|
+
<%
|
|
51
|
+
});
|
|
52
|
+
}
|
|
53
|
+
-%>
|
|
54
|
+
|
|
55
|
+
export interface <%= model.pascal %>Input {
|
|
56
|
+
slug?: string;
|
|
57
|
+
<% if (model.localized) { -%>
|
|
58
|
+
locale?: string;
|
|
59
|
+
<% } -%>
|
|
60
|
+
<% if (!model.isSingleton) { -%>
|
|
61
|
+
documentId?: string;
|
|
62
|
+
<% } -%>
|
|
63
|
+
<%
|
|
64
|
+
if (model.fields) {
|
|
65
|
+
Object.entries(model.fields).forEach(([key, field]) => {
|
|
66
|
+
const type = getInputTypeForField(field);
|
|
67
|
+
-%>
|
|
68
|
+
<%= key %><%= field.required ? '' : '?' %>: <%= type %>;
|
|
69
|
+
<%
|
|
70
|
+
});
|
|
71
|
+
}
|
|
72
|
+
-%>
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
<%
|
|
76
|
+
/**
|
|
77
|
+
* Helper function to convert schema field types to TypeScript types
|
|
78
|
+
* Uses Fimo wrapper types for source tracking
|
|
79
|
+
*/
|
|
80
|
+
function getTypeForField(field) {
|
|
81
|
+
switch(field.type) {
|
|
82
|
+
case 'string':
|
|
83
|
+
return 'FimoString';
|
|
84
|
+
case 'richtext':
|
|
85
|
+
return 'FimoRichText';
|
|
86
|
+
case 'number':
|
|
87
|
+
return 'number';
|
|
88
|
+
case 'boolean':
|
|
89
|
+
return 'FimoBoolean';
|
|
90
|
+
case 'date':
|
|
91
|
+
return 'string';
|
|
92
|
+
case 'json':
|
|
93
|
+
return 'Record<string, any>';
|
|
94
|
+
case 'media':
|
|
95
|
+
return field.multiple ? 'FimoMedia[]' : 'FimoMedia';
|
|
96
|
+
case 'relation':
|
|
97
|
+
return field.many ? 'FimoReference[]' : 'FimoReference | null';
|
|
98
|
+
default:
|
|
99
|
+
return 'any';
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
function getInputTypeForField(field) {
|
|
104
|
+
switch(field.type) {
|
|
105
|
+
case 'string':
|
|
106
|
+
return 'string';
|
|
107
|
+
case 'richtext':
|
|
108
|
+
return 'FimoRichTextContent';
|
|
109
|
+
case 'number':
|
|
110
|
+
return 'number';
|
|
111
|
+
case 'boolean':
|
|
112
|
+
return 'boolean';
|
|
113
|
+
case 'date':
|
|
114
|
+
return 'string';
|
|
115
|
+
case 'json':
|
|
116
|
+
return 'Record<string, unknown>';
|
|
117
|
+
case 'media': {
|
|
118
|
+
const media = 'Pick<FimoMedia, "url" | "alt" | "name" | "mime" | "width" | "height">';
|
|
119
|
+
return field.multiple ? `${media}[]` : media;
|
|
120
|
+
}
|
|
121
|
+
case 'relation':
|
|
122
|
+
return field.many ? 'FimoReference[]' : 'FimoReference | null';
|
|
123
|
+
default:
|
|
124
|
+
return 'unknown';
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
-%>
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
export interface <%= model.pascal %>Relations {
|
|
131
|
+
<%
|
|
132
|
+
if (model.fields) {
|
|
133
|
+
Object.entries(model.fields).forEach(([key, field]) => {
|
|
134
|
+
if (field.type !== 'relation' || typeof field.targetPascal !== 'string') {
|
|
135
|
+
return;
|
|
136
|
+
}
|
|
137
|
+
const type = field.many ? `${field.targetPascal}[]` : `${field.targetPascal} | null`;
|
|
138
|
+
-%>
|
|
139
|
+
<%= key %>: <%= type %>;
|
|
140
|
+
<%
|
|
141
|
+
});
|
|
142
|
+
}
|
|
143
|
+
-%>
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
export type <%= model.pascal %>Populate = Populate<<%= model.pascal %>>;
|
|
147
|
+
|
|
148
|
+
/*───────────────────────────────────────────────────────────────*/
|
|
149
|
+
/* ──────────── 🔎 Query types ──────────── */
|
|
150
|
+
/*───────────────────────────────────────────────────────────────*/
|
|
151
|
+
|
|
152
|
+
export type <%= model.pascal %>Where = Where<<%= model.pascal %>>;
|
|
153
|
+
export type <%= model.pascal %>Sort = Sort<<%= model.pascal %>>;
|
|
154
|
+
export type <%= model.pascal %>Fields = Fields<<%= model.pascal %>>;
|
|
155
|
+
export type <%= model.pascal %>Query<
|
|
156
|
+
TFields extends <%= model.pascal %>Fields | undefined = <%= model.pascal %>Fields | undefined,
|
|
157
|
+
TPopulate extends <%= model.pascal %>Populate | undefined = <%= model.pascal %>Populate | undefined,
|
|
158
|
+
> = Query<<%= model.pascal %>, TFields, TPopulate>;
|
|
159
|
+
export type <%= model.pascal %>Result<
|
|
160
|
+
TFields extends <%= model.pascal %>Fields | undefined = undefined,
|
|
161
|
+
TPopulate extends <%= model.pascal %>Populate | undefined = undefined,
|
|
162
|
+
> = ProjectedWithPopulate<PopulatedResult<<%= model.pascal %>, TPopulate>, TFields, TPopulate>;
|