fimo 0.4.0-experimental.2 → 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 -121
- package/assets/content-templates/types-template.eta +162 -0
- package/assets/skills/fimo/references/content.md +92 -39
- 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 +188 -64
- 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,79 +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
|
-
}
|
|
26
|
-
|
|
27
|
-
export interface <%= model.pascal %> {
|
|
28
|
-
id: string;
|
|
29
|
-
documentId: string;
|
|
30
|
-
slug: FimoString;
|
|
31
|
-
locale: string;
|
|
32
|
-
createdAt: string;
|
|
33
|
-
updatedAt: string;
|
|
34
|
-
__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";
|
|
35
16
|
<%
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
Object.entries(model.fields).forEach(([key, field]) => {
|
|
39
|
-
const type = getTypeForField(field);
|
|
17
|
+
if (model.relationImports && model.relationImports.length > 0) {
|
|
18
|
+
model.relationImports.forEach((relationImport) => {
|
|
40
19
|
-%>
|
|
41
|
-
|
|
20
|
+
import { wrapWithSource as wrap<%= relationImport.pascal %>WithSource } from "./<%= relationImport.pascal %>";
|
|
42
21
|
<%
|
|
43
22
|
});
|
|
44
23
|
}
|
|
45
|
-
|
|
46
|
-
/**
|
|
47
|
-
* Helper function to convert schema field types to TypeScript types
|
|
48
|
-
* Uses Fimo wrapper types for source tracking
|
|
49
|
-
*/
|
|
50
|
-
function getTypeForField(field) {
|
|
51
|
-
switch(field.type) {
|
|
52
|
-
case 'string':
|
|
53
|
-
return 'FimoString';
|
|
54
|
-
case 'richtext':
|
|
55
|
-
return 'FimoRichText';
|
|
56
|
-
case 'number':
|
|
57
|
-
return 'number';
|
|
58
|
-
case 'boolean':
|
|
59
|
-
return 'FimoBoolean';
|
|
60
|
-
case 'date':
|
|
61
|
-
return 'string';
|
|
62
|
-
case 'json':
|
|
63
|
-
return 'Record<string, any>';
|
|
64
|
-
case 'media':
|
|
65
|
-
return field.multiple ? 'FimoMedia[]' : 'FimoMedia';
|
|
66
|
-
case 'relation':
|
|
67
|
-
return field.many ? 'Array<string | Record<string, any>>' : 'string | Record<string, any> | null';
|
|
68
|
-
default:
|
|
69
|
-
return 'any';
|
|
70
|
-
}
|
|
71
|
-
}
|
|
72
24
|
-%>
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
/*───────────────────────────────────────────────────────────────*/
|
|
76
|
-
/* ──────────── 🔎 Query types ──────────── */
|
|
77
|
-
/*───────────────────────────────────────────────────────────────*/
|
|
78
|
-
|
|
79
|
-
export type <%= model.pascal %>Where = Where<<%= model.pascal %>>;
|
|
80
|
-
export type <%= model.pascal %>Sort = Sort<<%= model.pascal %>>;
|
|
81
|
-
export type <%= model.pascal %>Fields = Fields<<%= model.pascal %>>;
|
|
82
|
-
export type <%= model.pascal %>Query<TFields extends <%= model.pascal %>Fields | undefined = <%= model.pascal %>Fields | undefined> = Query<<%= model.pascal %>, TFields>;
|
|
83
|
-
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";
|
|
84
26
|
|
|
85
27
|
/*───────────────────────────────────────────────────────────────*/
|
|
86
28
|
/* ──────────── 🎁 Source wrapping ──────────── */
|
|
@@ -89,7 +31,7 @@ export type <%= model.pascal %>Result<TFields extends <%= model.pascal %>Fields
|
|
|
89
31
|
/**
|
|
90
32
|
* Wraps raw API data with Fimo types for source tracking
|
|
91
33
|
*/
|
|
92
|
-
function wrapWithSource(entry: Record<string, any>): <%= model.pascal %> {
|
|
34
|
+
export function wrapWithSource(entry: Record<string, any>): <%= model.pascal %> {
|
|
93
35
|
const id = entry.id;
|
|
94
36
|
const sourcePrefix = `<%= model.pascal %>.${id}`;
|
|
95
37
|
const entryFimo = entry.__fimo && typeof entry.__fimo === "object" ? entry.__fimo : {};
|
|
@@ -98,7 +40,9 @@ function wrapWithSource(entry: Record<string, any>): <%= model.pascal %> {
|
|
|
98
40
|
id,
|
|
99
41
|
documentId: entry.documentId,
|
|
100
42
|
slug: entry.slug != null ? new FimoString(entry.slug, `${sourcePrefix}.slug`) : entry.slug,
|
|
43
|
+
<% if (model.localized) { -%>
|
|
101
44
|
locale: entry.locale,
|
|
45
|
+
<% } -%>
|
|
102
46
|
createdAt: entry.createdAt,
|
|
103
47
|
updatedAt: entry.updatedAt,
|
|
104
48
|
__fimo: {
|
|
@@ -135,6 +79,19 @@ if (model.fields) {
|
|
|
135
79
|
<%
|
|
136
80
|
}
|
|
137
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
|
+
}
|
|
138
95
|
default:
|
|
139
96
|
-%>
|
|
140
97
|
<%= key %>: entry.<%= key %>,
|
|
@@ -157,7 +114,25 @@ function defaultLocale(): string {
|
|
|
157
114
|
}
|
|
158
115
|
|
|
159
116
|
function useQueryLocale(locale?: string): string {
|
|
160
|
-
|
|
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;
|
|
161
136
|
}
|
|
162
137
|
|
|
163
138
|
function fimoHeaders(extra?: Record<string, string>): Record<string, string> {
|
|
@@ -168,13 +143,21 @@ function fimoHeaders(extra?: Record<string, string>): Record<string, string> {
|
|
|
168
143
|
return h;
|
|
169
144
|
}
|
|
170
145
|
|
|
171
|
-
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 {
|
|
172
151
|
const u = new URL(url, "http://fimo.local");
|
|
173
|
-
|
|
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
|
+
}
|
|
174
157
|
return `${u.pathname}${u.search}`;
|
|
175
158
|
}
|
|
176
159
|
|
|
177
|
-
function splitWritePayload(payload: Partial<<%= model.pascal
|
|
160
|
+
function splitWritePayload(payload: Partial<<%= model.pascal %>Input>): Record<string, unknown> {
|
|
178
161
|
const {
|
|
179
162
|
id,
|
|
180
163
|
documentId,
|
|
@@ -192,31 +175,45 @@ function splitWritePayload(payload: Partial<<%= model.pascal %>>): Record<string
|
|
|
192
175
|
};
|
|
193
176
|
}
|
|
194
177
|
|
|
195
|
-
|
|
196
|
-
|
|
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() });
|
|
197
184
|
if (!res.ok) throw new Error(await res.text());
|
|
198
185
|
const data = await res.json();
|
|
199
|
-
return wrapWithSource(data.data)
|
|
186
|
+
return wrapWithSource(data.data) as <%= model.pascal %>Result<undefined, TPopulate>;
|
|
200
187
|
}
|
|
201
188
|
|
|
202
|
-
export async function getBySlug
|
|
203
|
-
|
|
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() });
|
|
204
194
|
if (res.status === 404) return null;
|
|
205
195
|
if (!res.ok) throw new Error(await res.text());
|
|
206
196
|
const data = await res.json();
|
|
207
|
-
return wrapWithSource(data.data)
|
|
197
|
+
return wrapWithSource(data.data) as <%= model.pascal %>Result<undefined, TPopulate>;
|
|
208
198
|
}
|
|
209
199
|
|
|
210
|
-
export async function getByField
|
|
211
|
-
|
|
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() });
|
|
212
206
|
if (res.status === 404) return null;
|
|
213
207
|
if (!res.ok) throw new Error(await res.text());
|
|
214
208
|
const data = await res.json();
|
|
215
|
-
return wrapWithSource(data.data)
|
|
209
|
+
return wrapWithSource(data.data) as <%= model.pascal %>Result<undefined, TPopulate>;
|
|
216
210
|
}
|
|
217
211
|
|
|
218
|
-
function toQueryString<
|
|
219
|
-
|
|
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;
|
|
220
217
|
const query = new URLSearchParams();
|
|
221
218
|
|
|
222
219
|
for (const [key, value] of Object.entries(flat)) {
|
|
@@ -240,22 +237,24 @@ function toQueryString<TFields extends <%= model.pascal %>Fields | undefined = u
|
|
|
240
237
|
query.set("where", JSON.stringify(where));
|
|
241
238
|
}
|
|
242
239
|
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
}
|
|
240
|
+
const locale = readLocale({ locale: requestedLocale, populate }, LOCALIZED_CONTENT);
|
|
241
|
+
if (locale) query.set("locale", locale);
|
|
246
242
|
|
|
247
243
|
return query.toString();
|
|
248
244
|
}
|
|
249
245
|
|
|
250
|
-
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>[]> {
|
|
251
250
|
const qs = toQueryString(params);
|
|
252
251
|
const res = await fetch(`${base}/entries/<%= model.uid %>${qs ? "?" + qs : ""}`, { headers: fimoHeaders() });
|
|
253
252
|
if (!res.ok) throw new Error(await res.text());
|
|
254
253
|
const data = await res.json();
|
|
255
|
-
return data.data.map(wrapWithSource) as <%= model.pascal %>Result<TFields>[];
|
|
254
|
+
return data.data.map(wrapWithSource) as <%= model.pascal %>Result<TFields, TPopulate>[];
|
|
256
255
|
}
|
|
257
256
|
|
|
258
|
-
export async function create(payload:
|
|
257
|
+
export async function create(payload: <%= model.pascal %>Input): Promise<<%= model.pascal %>> {
|
|
259
258
|
const res = await fetch(`${base}/entries/<%= model.uid %>`, {
|
|
260
259
|
method: "POST",
|
|
261
260
|
headers: fimoHeaders({ "Content-Type": "application/json" }),
|
|
@@ -263,10 +262,10 @@ export async function create(payload: Partial<<%= model.pascal %>>): Promise<<%=
|
|
|
263
262
|
});
|
|
264
263
|
if (!res.ok) throw new Error(await res.text());
|
|
265
264
|
const data = await res.json();
|
|
266
|
-
return data.data;
|
|
265
|
+
return wrapWithSource(data.data);
|
|
267
266
|
}
|
|
268
267
|
|
|
269
|
-
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 %>> {
|
|
270
269
|
const res = await fetch(`${base}/entries/<%= model.uid %>/${id}`, {
|
|
271
270
|
method: "PUT",
|
|
272
271
|
headers: fimoHeaders({ "Content-Type": "application/json" }),
|
|
@@ -274,71 +273,107 @@ export async function update(id: string, payload: Partial<<%= model.pascal %>>):
|
|
|
274
273
|
});
|
|
275
274
|
if (!res.ok) throw new Error(await res.text());
|
|
276
275
|
const data = await res.json();
|
|
277
|
-
return data.data;
|
|
276
|
+
return wrapWithSource(data.data);
|
|
278
277
|
}
|
|
279
278
|
|
|
280
279
|
export async function remove(id: string): Promise<void> {
|
|
281
280
|
const res = await fetch(`${base}/entries/<%= model.uid %>/${id}`, { method: "DELETE", headers: fimoHeaders() });
|
|
282
281
|
if (!res.ok) throw new Error(await res.text());
|
|
283
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
|
+
<% } -%>
|
|
284
306
|
|
|
285
307
|
/*───────────────────────────────────────────────────────────────*/
|
|
286
308
|
/* ──────────── 🍯 React-Query hooks ──────────── */
|
|
287
309
|
/*───────────────────────────────────────────────────────────────*/
|
|
288
310
|
|
|
311
|
+
<% if (!model.isSingleton) { -%>
|
|
289
312
|
const qk = {
|
|
290
313
|
root : ["<%= model.pascal %>"] as const,
|
|
291
|
-
byId : (id: string, locale?: string) => ["<%= model.pascal %>", id, locale
|
|
292
|
-
bySlug : (slug: string, locale?: string) => ["<%= model.pascal %>", "slug", slug, locale
|
|
293
|
-
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,
|
|
294
317
|
list : (params: <%= model.pascal %>Query) => ["<%= model.pascal %>", params] as const
|
|
295
318
|
};
|
|
296
319
|
|
|
297
|
-
export function useGetById(id: string, options: { locale?: string } = {}) {
|
|
298
|
-
const locale = useQueryLocale(options.locale);
|
|
299
|
-
|
|
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) });
|
|
300
324
|
}
|
|
301
325
|
|
|
302
|
-
export function useGetBySlug(slug: string, options: { locale?: string } = {}) {
|
|
303
|
-
const locale = useQueryLocale(options.locale);
|
|
304
|
-
|
|
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 });
|
|
305
330
|
}
|
|
306
331
|
|
|
307
|
-
export function useGetByField(fieldName: string, value: string, options: { locale?: string } = {}) {
|
|
308
|
-
const locale = useQueryLocale(options.locale);
|
|
309
|
-
|
|
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 });
|
|
310
336
|
}
|
|
311
337
|
|
|
312
|
-
export function useGet<
|
|
313
|
-
|
|
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));
|
|
314
343
|
const localizedParams = { ...params, locale };
|
|
315
|
-
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) });
|
|
316
345
|
}
|
|
317
346
|
|
|
318
|
-
export function useSuspenseGetById(id: string, options: { locale?: string } = {}) {
|
|
319
|
-
const locale = useQueryLocale(options.locale);
|
|
320
|
-
|
|
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) });
|
|
321
351
|
}
|
|
322
352
|
|
|
323
|
-
export function useSuspenseGetBySlug(slug: string, options: { locale?: string } = {}) {
|
|
324
|
-
const locale = useQueryLocale(options.locale);
|
|
325
|
-
|
|
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) });
|
|
326
357
|
}
|
|
327
358
|
|
|
328
|
-
export function useSuspenseGetByField(fieldName: string, value: string, options: { locale?: string } = {}) {
|
|
329
|
-
const locale = useQueryLocale(options.locale);
|
|
330
|
-
|
|
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) });
|
|
331
363
|
}
|
|
332
364
|
|
|
333
|
-
export function useSuspenseGet<
|
|
334
|
-
|
|
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));
|
|
335
370
|
const localizedParams = { ...params, locale };
|
|
336
|
-
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) });
|
|
337
372
|
}
|
|
338
373
|
|
|
339
374
|
export function useCreate() {
|
|
340
375
|
const qc = useQueryClient();
|
|
341
|
-
return useMutation<<%= model.pascal %>, Error,
|
|
376
|
+
return useMutation<<%= model.pascal %>, Error, <%= model.pascal %>Input>({
|
|
342
377
|
mutationFn : create,
|
|
343
378
|
onSuccess : () => qc.invalidateQueries({ queryKey: qk.root })
|
|
344
379
|
});
|
|
@@ -346,9 +381,12 @@ export function useCreate() {
|
|
|
346
381
|
|
|
347
382
|
export function useUpdate() {
|
|
348
383
|
const qc = useQueryClient();
|
|
349
|
-
return useMutation<<%= model.pascal %>, Error, { id: string } & Partial<<%= model.pascal
|
|
384
|
+
return useMutation<<%= model.pascal %>, Error, { id: string } & Partial<<%= model.pascal %>Input>>({
|
|
350
385
|
mutationFn : ({ id, ...patch }) => update(id, patch),
|
|
351
|
-
onSuccess : (_data, vars) =>
|
|
386
|
+
onSuccess : (_data, vars) => {
|
|
387
|
+
qc.invalidateQueries({ queryKey: qk.root });
|
|
388
|
+
qc.invalidateQueries({ queryKey: qk.byId(vars.id) });
|
|
389
|
+
}
|
|
352
390
|
});
|
|
353
391
|
}
|
|
354
392
|
|
|
@@ -358,16 +396,43 @@ export function useRemove() {
|
|
|
358
396
|
mutationFn : remove,
|
|
359
397
|
onSuccess : (_data, id) => {
|
|
360
398
|
qc.invalidateQueries({ queryKey: qk.root });
|
|
361
|
-
qc.removeQueries({ queryKey:
|
|
399
|
+
qc.removeQueries({ queryKey: ["<%= model.pascal %>", id], exact: false });
|
|
362
400
|
}
|
|
363
401
|
});
|
|
364
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
|
+
<% } -%>
|
|
365
429
|
|
|
366
430
|
/*───────────────────────────────────────────────────────────────*/
|
|
367
431
|
/* ──────────── 🎯 Default export ──────────── */
|
|
368
432
|
/*───────────────────────────────────────────────────────────────*/
|
|
369
433
|
|
|
370
434
|
export default {
|
|
435
|
+
<% if (!model.isSingleton) { -%>
|
|
371
436
|
/* async helpers */
|
|
372
437
|
getById,
|
|
373
438
|
getBySlug,
|
|
@@ -389,4 +454,11 @@ export default {
|
|
|
389
454
|
useCreate,
|
|
390
455
|
useUpdate,
|
|
391
456
|
useRemove
|
|
457
|
+
<% } else { -%>
|
|
458
|
+
get,
|
|
459
|
+
update,
|
|
460
|
+
useGet,
|
|
461
|
+
useSuspenseGet,
|
|
462
|
+
useUpdate
|
|
463
|
+
<% } -%>
|
|
392
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>;
|