@carlonicora/nextjs-jsonapi 1.50.0 → 1.52.0
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/dist/{BlockNoteEditor-LENHWRS2.js → BlockNoteEditor-KQPSJCYG.js} +6 -6
- package/dist/{BlockNoteEditor-LENHWRS2.js.map → BlockNoteEditor-KQPSJCYG.js.map} +1 -1
- package/dist/{BlockNoteEditor-WLS36QIF.mjs → BlockNoteEditor-WUVRCTQI.mjs} +2 -2
- package/dist/billing/index.js +299 -299
- package/dist/billing/index.mjs +1 -1
- package/dist/{chunk-VQ35TGD7.mjs → chunk-BTLJZIDS.mjs} +2612 -2269
- package/dist/chunk-BTLJZIDS.mjs.map +1 -0
- package/dist/{chunk-KWAUWJYX.js → chunk-YKPIFJOB.js} +544 -201
- package/dist/chunk-YKPIFJOB.js.map +1 -0
- package/dist/client/index.js +2 -2
- package/dist/client/index.mjs +1 -1
- package/dist/components/index.d.mts +44 -4
- package/dist/components/index.d.ts +44 -4
- package/dist/components/index.js +38 -2
- package/dist/components/index.js.map +1 -1
- package/dist/components/index.mjs +37 -1
- package/dist/contexts/index.js +2 -2
- package/dist/contexts/index.mjs +1 -1
- package/dist/scripts/generate-web-module/generator.d.ts.map +1 -1
- package/dist/scripts/generate-web-module/generator.js +62 -4
- package/dist/scripts/generate-web-module/generator.js.map +1 -1
- package/dist/scripts/generate-web-module/templates/components/container.template.js +12 -43
- package/dist/scripts/generate-web-module/templates/components/container.template.js.map +1 -1
- package/dist/scripts/generate-web-module/templates/components/details.template.js +7 -5
- package/dist/scripts/generate-web-module/templates/components/details.template.js.map +1 -1
- package/dist/scripts/generate-web-module/templates/components/editor.template.d.ts.map +1 -1
- package/dist/scripts/generate-web-module/templates/components/editor.template.js +28 -17
- package/dist/scripts/generate-web-module/templates/components/editor.template.js.map +1 -1
- package/dist/scripts/generate-web-module/templates/components/list.template.js +4 -3
- package/dist/scripts/generate-web-module/templates/components/list.template.js.map +1 -1
- package/dist/scripts/generate-web-module/templates/components/multi-selector.template.d.ts.map +1 -1
- package/dist/scripts/generate-web-module/templates/components/multi-selector.template.js +6 -4
- package/dist/scripts/generate-web-module/templates/components/multi-selector.template.js.map +1 -1
- package/dist/scripts/generate-web-module/templates/components/selector.template.d.ts.map +1 -1
- package/dist/scripts/generate-web-module/templates/components/selector.template.js +8 -5
- package/dist/scripts/generate-web-module/templates/components/selector.template.js.map +1 -1
- package/dist/scripts/generate-web-module/templates/context.template.d.ts.map +1 -1
- package/dist/scripts/generate-web-module/templates/context.template.js +8 -6
- package/dist/scripts/generate-web-module/templates/context.template.js.map +1 -1
- package/dist/scripts/generate-web-module/templates/data/aliases.template.d.ts +15 -0
- package/dist/scripts/generate-web-module/templates/data/aliases.template.d.ts.map +1 -0
- package/dist/scripts/generate-web-module/templates/data/aliases.template.js +31 -0
- package/dist/scripts/generate-web-module/templates/data/aliases.template.js.map +1 -0
- package/dist/scripts/generate-web-module/templates/data/fields.template.js +2 -2
- package/dist/scripts/generate-web-module/templates/data/fields.template.js.map +1 -1
- package/dist/scripts/generate-web-module/templates/data/index.d.ts +1 -0
- package/dist/scripts/generate-web-module/templates/data/index.d.ts.map +1 -1
- package/dist/scripts/generate-web-module/templates/data/index.js +1 -0
- package/dist/scripts/generate-web-module/templates/data/index.js.map +1 -1
- package/dist/scripts/generate-web-module/templates/data/interface.template.js +11 -5
- package/dist/scripts/generate-web-module/templates/data/interface.template.js.map +1 -1
- package/dist/scripts/generate-web-module/templates/data/model.template.js +25 -16
- package/dist/scripts/generate-web-module/templates/data/model.template.js.map +1 -1
- package/dist/scripts/generate-web-module/templates/data/service.template.js +5 -1
- package/dist/scripts/generate-web-module/templates/data/service.template.js.map +1 -1
- package/dist/scripts/generate-web-module/templates/index.d.ts +1 -1
- package/dist/scripts/generate-web-module/templates/index.d.ts.map +1 -1
- package/dist/scripts/generate-web-module/templates/index.js +2 -1
- package/dist/scripts/generate-web-module/templates/index.js.map +1 -1
- package/dist/scripts/generate-web-module/templates/pages/detail-page.template.d.ts.map +1 -1
- package/dist/scripts/generate-web-module/templates/pages/detail-page.template.js +6 -7
- package/dist/scripts/generate-web-module/templates/pages/detail-page.template.js.map +1 -1
- package/dist/scripts/generate-web-module/templates/pages/list-page.template.js +3 -3
- package/dist/scripts/generate-web-module/templates/pages/list-page.template.js.map +1 -1
- package/dist/scripts/generate-web-module/transformers/field-mapper.js +5 -5
- package/dist/scripts/generate-web-module/transformers/field-mapper.js.map +1 -1
- package/dist/scripts/generate-web-module/transformers/i18n-generator.js +2 -2
- package/dist/scripts/generate-web-module/transformers/i18n-generator.js.map +1 -1
- package/dist/scripts/generate-web-module/transformers/import-resolver.d.ts +1 -0
- package/dist/scripts/generate-web-module/transformers/import-resolver.d.ts.map +1 -1
- package/dist/scripts/generate-web-module/transformers/import-resolver.js +1 -0
- package/dist/scripts/generate-web-module/transformers/import-resolver.js.map +1 -1
- package/dist/scripts/generate-web-module/transformers/relationship-resolver.d.ts +3 -3
- package/dist/scripts/generate-web-module/transformers/relationship-resolver.d.ts.map +1 -1
- package/dist/scripts/generate-web-module/transformers/relationship-resolver.js +19 -13
- package/dist/scripts/generate-web-module/transformers/relationship-resolver.js.map +1 -1
- package/dist/scripts/generate-web-module/types/field-mapping.types.d.ts +4 -2
- package/dist/scripts/generate-web-module/types/field-mapping.types.d.ts.map +1 -1
- package/dist/scripts/generate-web-module/types/field-mapping.types.js +23 -7
- package/dist/scripts/generate-web-module/types/field-mapping.types.js.map +1 -1
- package/dist/scripts/generate-web-module/types/template-data.interface.d.ts +2 -0
- package/dist/scripts/generate-web-module/types/template-data.interface.d.ts.map +1 -1
- package/dist/scripts/generate-web-module/utils/bootstrapper-updater.js +2 -2
- package/dist/scripts/generate-web-module/utils/bootstrapper-updater.js.map +1 -1
- package/dist/scripts/generate-web-module/utils/file-writer.d.ts +6 -0
- package/dist/scripts/generate-web-module/utils/file-writer.d.ts.map +1 -1
- package/dist/scripts/generate-web-module/utils/file-writer.js +22 -0
- package/dist/scripts/generate-web-module/utils/file-writer.js.map +1 -1
- package/dist/scripts/generate-web-module/utils/i18n-updater.js +13 -13
- package/dist/scripts/generate-web-module/utils/i18n-updater.js.map +1 -1
- package/dist/scripts/generate-web-module/utils/index.d.ts +1 -1
- package/dist/scripts/generate-web-module/utils/index.d.ts.map +1 -1
- package/dist/scripts/generate-web-module/utils/index.js +2 -1
- package/dist/scripts/generate-web-module/utils/index.js.map +1 -1
- package/package.json +2 -2
- package/scripts/generate-web-module/generator.ts +70 -5
- package/scripts/generate-web-module/templates/components/container.template.ts +12 -43
- package/scripts/generate-web-module/templates/components/details.template.ts +7 -5
- package/scripts/generate-web-module/templates/components/editor.template.ts +27 -17
- package/scripts/generate-web-module/templates/components/list.template.ts +4 -3
- package/scripts/generate-web-module/templates/components/multi-selector.template.ts +6 -4
- package/scripts/generate-web-module/templates/components/selector.template.ts +8 -5
- package/scripts/generate-web-module/templates/context.template.ts +8 -6
- package/scripts/generate-web-module/templates/data/aliases.template.ts +33 -0
- package/scripts/generate-web-module/templates/data/fields.template.ts +2 -2
- package/scripts/generate-web-module/templates/data/index.ts +1 -0
- package/scripts/generate-web-module/templates/data/interface.template.ts +10 -5
- package/scripts/generate-web-module/templates/data/model.template.ts +24 -16
- package/scripts/generate-web-module/templates/data/service.template.ts +6 -1
- package/scripts/generate-web-module/templates/index.ts +1 -0
- package/scripts/generate-web-module/templates/pages/detail-page.template.ts +6 -7
- package/scripts/generate-web-module/templates/pages/list-page.template.ts +3 -3
- package/scripts/generate-web-module/transformers/field-mapper.ts +5 -5
- package/scripts/generate-web-module/transformers/i18n-generator.ts +2 -2
- package/scripts/generate-web-module/transformers/import-resolver.ts +2 -0
- package/scripts/generate-web-module/transformers/relationship-resolver.ts +19 -13
- package/scripts/generate-web-module/types/field-mapping.types.ts +19 -6
- package/scripts/generate-web-module/types/template-data.interface.ts +2 -0
- package/scripts/generate-web-module/utils/bootstrapper-updater.ts +2 -2
- package/scripts/generate-web-module/utils/file-writer.ts +21 -0
- package/scripts/generate-web-module/utils/i18n-updater.ts +13 -13
- package/scripts/generate-web-module/utils/index.ts +1 -1
- package/src/components/containers/RoundPageContainer.tsx +80 -0
- package/src/components/containers/RoundPageContainerTitle.tsx +60 -0
- package/src/components/containers/index.ts +2 -0
- package/src/components/navigations/Header.tsx +3 -2
- package/src/shadcnui/index.ts +1 -0
- package/src/shadcnui/ui/command.tsx +2 -2
- package/dist/chunk-KWAUWJYX.js.map +0 -1
- package/dist/chunk-VQ35TGD7.mjs.map +0 -1
- /package/dist/{BlockNoteEditor-WLS36QIF.mjs.map → BlockNoteEditor-WUVRCTQI.mjs.map} +0 -0
|
@@ -13,7 +13,10 @@ import { FrontendTemplateData } from "../../types/template-data.interface";
|
|
|
13
13
|
* @returns Generated file content
|
|
14
14
|
*/
|
|
15
15
|
export function generateSelectorTemplate(data: FrontendTemplateData): string {
|
|
16
|
-
const { names } = data;
|
|
16
|
+
const { names, fields, extendsContent } = data;
|
|
17
|
+
const hasNameField = extendsContent || fields.some((f) => f.name === "name");
|
|
18
|
+
const i18nKey = names.pluralCamel.toLowerCase();
|
|
19
|
+
const displayProp = hasNameField ? "name" : "id";
|
|
17
20
|
|
|
18
21
|
return `"use client";
|
|
19
22
|
|
|
@@ -103,7 +106,7 @@ export default function ${names.pascalCase}Selector({
|
|
|
103
106
|
return;
|
|
104
107
|
}
|
|
105
108
|
|
|
106
|
-
form.setValue(id, { id: ${names.camelCase}.id, name: ${names.camelCase}
|
|
109
|
+
form.setValue(id, { id: ${names.camelCase}.id, name: ${names.camelCase}.${displayProp} });
|
|
107
110
|
setOpen(false);
|
|
108
111
|
|
|
109
112
|
setTimeout(() => {
|
|
@@ -125,7 +128,7 @@ export default function ${names.pascalCase}Selector({
|
|
|
125
128
|
className="h-auto min-h-10 w-full justify-between px-3 py-2"
|
|
126
129
|
>
|
|
127
130
|
<span className={field.value ? "" : "text-muted-foreground"}>
|
|
128
|
-
{field.value?.name ?? placeholder ?? t(\`generic.search.placeholder\`, { type: t(\`
|
|
131
|
+
{field.value?.name ?? placeholder ?? t(\`generic.search.placeholder\`, { type: t(\`entities.${i18nKey}\`, { count: 1 }) })}
|
|
129
132
|
</span>
|
|
130
133
|
<ChevronsUpDown className="ml-2 h-4 w-4 shrink-0 opacity-50" />
|
|
131
134
|
</Button>
|
|
@@ -140,7 +143,7 @@ export default function ${names.pascalCase}Selector({
|
|
|
140
143
|
<PopoverContent className="w-(--radix-popover-trigger-width) p-0" align="start">
|
|
141
144
|
<Command shouldFilter={false}>
|
|
142
145
|
<CommandInput
|
|
143
|
-
placeholder={t(\`generic.search.placeholder\`, { type: t(\`
|
|
146
|
+
placeholder={t(\`generic.search.placeholder\`, { type: t(\`entities.${i18nKey}\`, { count: 1 }) })}
|
|
144
147
|
value={searchTerm}
|
|
145
148
|
onValueChange={setSearchTerm}
|
|
146
149
|
/>
|
|
@@ -162,7 +165,7 @@ export default function ${names.pascalCase}Selector({
|
|
|
162
165
|
onSelect={() => set${names.pascalCase}(${names.camelCase})}
|
|
163
166
|
className="hover:bg-muted data-selected:hover:bg-muted bg-transparent data-selected:bg-transparent cursor-pointer"
|
|
164
167
|
>
|
|
165
|
-
{${names.camelCase}
|
|
168
|
+
{${names.camelCase}.${displayProp}}
|
|
166
169
|
</CommandItem>
|
|
167
170
|
))}
|
|
168
171
|
</CommandGroup>
|
|
@@ -13,7 +13,9 @@ import { FrontendTemplateData } from "../types/template-data.interface";
|
|
|
13
13
|
* @returns Generated file content
|
|
14
14
|
*/
|
|
15
15
|
export function generateContextTemplate(data: FrontendTemplateData): string {
|
|
16
|
-
const { names, extendsContent } = data;
|
|
16
|
+
const { names, fields, extendsContent } = data;
|
|
17
|
+
const hasNameField = extendsContent || fields.some((f) => f.name === "name");
|
|
18
|
+
const i18nKey = names.pluralCamel.toLowerCase();
|
|
17
19
|
|
|
18
20
|
return `"use client";
|
|
19
21
|
|
|
@@ -61,13 +63,13 @@ export const ${names.pascalCase}Provider = ({ children, dehydrated${names.pascal
|
|
|
61
63
|
const response: BreadcrumbItemData[] = [];
|
|
62
64
|
|
|
63
65
|
response.push({
|
|
64
|
-
name: t(\`
|
|
66
|
+
name: t(\`entities.${i18nKey}\`, { count: 2 }),
|
|
65
67
|
href: generateUrl({ page: Modules.${names.pascalCase} }),
|
|
66
68
|
});
|
|
67
69
|
|
|
68
70
|
if (${names.camelCase})
|
|
69
71
|
response.push({
|
|
70
|
-
name: ${names.camelCase}.name,
|
|
72
|
+
name: ${hasNameField ? `${names.camelCase}.name` : `${names.camelCase}.id`},
|
|
71
73
|
href: generateUrl({ page: Modules.${names.pascalCase}, id: ${names.camelCase}.id }),
|
|
72
74
|
});
|
|
73
75
|
|
|
@@ -76,13 +78,13 @@ export const ${names.pascalCase}Provider = ({ children, dehydrated${names.pascal
|
|
|
76
78
|
|
|
77
79
|
const title = () => {
|
|
78
80
|
const response: any = {
|
|
79
|
-
type: t(\`
|
|
81
|
+
type: t(\`entities.${i18nKey}\`, { count: ${names.camelCase} ? 1 : 2 }),
|
|
80
82
|
};
|
|
81
83
|
|
|
82
84
|
const functions: ReactNode[] = [];
|
|
83
85
|
|
|
84
|
-
if (${names.camelCase}) {
|
|
85
|
-
response.element = ${names.camelCase}.name
|
|
86
|
+
if (${names.camelCase}) {${hasNameField ? `
|
|
87
|
+
response.element = ${names.camelCase}.name;` : ""}
|
|
86
88
|
|
|
87
89
|
functions.push(<${names.pascalCase}Deleter key={\`${names.pascalCase}Deleter\`} ${names.camelCase}={${names.camelCase}} />);
|
|
88
90
|
functions.push(<${names.pascalCase}Editor key={\`${names.pascalCase}Editor\`} ${names.camelCase}={${names.camelCase}} propagateChanges={set${names.pascalCase}} />);
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Aliases Template
|
|
3
|
+
*
|
|
4
|
+
* Generates {Module}Aliases.ts const object for type-safe alias references.
|
|
5
|
+
* Only generated when the module has aliased relationships.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { FrontendTemplateData } from "../../types/template-data.interface";
|
|
9
|
+
import { toCamelCase, toKebabCase, pluralize } from "../../transformers/name-transformer";
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Generate the aliases file content
|
|
13
|
+
*
|
|
14
|
+
* @param data - Frontend template data
|
|
15
|
+
* @returns Generated file content, or null if no aliases exist
|
|
16
|
+
*/
|
|
17
|
+
export function generateAliasesTemplate(data: FrontendTemplateData): string | null {
|
|
18
|
+
const { names, relationships } = data;
|
|
19
|
+
|
|
20
|
+
const aliasedRels = relationships.filter((rel) => rel.alias);
|
|
21
|
+
if (aliasedRels.length === 0) return null;
|
|
22
|
+
|
|
23
|
+
const entries = aliasedRels.map((rel) => {
|
|
24
|
+
const endpoint = rel.single
|
|
25
|
+
? toKebabCase(rel.alias!)
|
|
26
|
+
: pluralize(toKebabCase(rel.alias!));
|
|
27
|
+
const property = toCamelCase(rel.alias!);
|
|
28
|
+
|
|
29
|
+
return ` ${rel.alias}: {\n endpoint: "${endpoint}",\n property: "${property}",\n },`;
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
return `export const ${names.pascalCase}Aliases = {\n${entries.join("\n")}\n} as const;\n`;
|
|
33
|
+
}
|
|
@@ -47,10 +47,10 @@ function generateEnumEntries(data: FrontendTemplateData): string {
|
|
|
47
47
|
|
|
48
48
|
// Add relationship fields
|
|
49
49
|
relationships.forEach((rel) => {
|
|
50
|
-
const effectiveName = rel.variant || rel.name;
|
|
50
|
+
const effectiveName = rel.alias || rel.variant || rel.name;
|
|
51
51
|
const key = rel.single
|
|
52
52
|
? toCamelCase(effectiveName)
|
|
53
|
-
: pluralize(toCamelCase(
|
|
53
|
+
: pluralize(toCamelCase(effectiveName));
|
|
54
54
|
entries.push(` ${key} = "${key}",`);
|
|
55
55
|
});
|
|
56
56
|
|
|
@@ -35,11 +35,14 @@ function generateImports(data: FrontendTemplateData): string {
|
|
|
35
35
|
const { names, extendsContent, relationships } = data;
|
|
36
36
|
const imports: string[] = [];
|
|
37
37
|
|
|
38
|
-
// Relationship interface imports (skip self-referential
|
|
38
|
+
// Relationship interface imports (deduplicated, skip self-referential)
|
|
39
|
+
const seenInterfaces = new Set<string>();
|
|
39
40
|
relationships.forEach((rel) => {
|
|
40
41
|
if (rel.name === names.pascalCase) {
|
|
41
42
|
return; // Skip self-reference - interface defined in this file
|
|
42
43
|
}
|
|
44
|
+
if (seenInterfaces.has(rel.interfaceName)) return;
|
|
45
|
+
seenInterfaces.add(rel.interfaceName);
|
|
43
46
|
imports.push(`import { ${rel.interfaceName} } from "${rel.interfaceImportPath}";`);
|
|
44
47
|
});
|
|
45
48
|
|
|
@@ -73,7 +76,7 @@ function generateInputType(data: FrontendTemplateData): string {
|
|
|
73
76
|
|
|
74
77
|
// Add relationship IDs and relationship property fields
|
|
75
78
|
relationships.forEach((rel) => {
|
|
76
|
-
const effectiveName = rel.variant || rel.name;
|
|
79
|
+
const effectiveName = rel.alias || rel.variant || rel.name;
|
|
77
80
|
if (rel.single) {
|
|
78
81
|
const key = `${toCamelCase(effectiveName)}Id`;
|
|
79
82
|
const optional = rel.nullable ? "?" : "";
|
|
@@ -87,7 +90,8 @@ function generateInputType(data: FrontendTemplateData): string {
|
|
|
87
90
|
});
|
|
88
91
|
}
|
|
89
92
|
} else {
|
|
90
|
-
const
|
|
93
|
+
const effectiveMany = rel.alias || rel.name;
|
|
94
|
+
const key = `${toCamelCase(effectiveMany)}Ids`;
|
|
91
95
|
fieldLines.push(` ${key}?: string[];`);
|
|
92
96
|
}
|
|
93
97
|
});
|
|
@@ -122,7 +126,7 @@ function generateInterface(data: FrontendTemplateData): string {
|
|
|
122
126
|
|
|
123
127
|
// Add relationship getters
|
|
124
128
|
relationships.forEach((rel) => {
|
|
125
|
-
const effectiveName = rel.variant || rel.name;
|
|
129
|
+
const effectiveName = rel.alias || rel.variant || rel.name;
|
|
126
130
|
if (rel.single) {
|
|
127
131
|
const propertyName = toCamelCase(effectiveName);
|
|
128
132
|
|
|
@@ -136,7 +140,8 @@ function generateInterface(data: FrontendTemplateData): string {
|
|
|
136
140
|
const type = rel.nullable ? `(${baseType}) | undefined` : baseType;
|
|
137
141
|
getterLines.push(` get ${propertyName}(): ${type};`);
|
|
138
142
|
} else {
|
|
139
|
-
const
|
|
143
|
+
const effectiveMany = rel.alias || rel.name;
|
|
144
|
+
const propertyName = pluralize(toCamelCase(effectiveMany));
|
|
140
145
|
// Use intersection type if relationship has fields (edge properties)
|
|
141
146
|
if (rel.fields && rel.fields.length > 0) {
|
|
142
147
|
const metaFields = rel.fields.map((f) => `${f.name}?: ${f.tsType}`).join("; ");
|
|
@@ -51,11 +51,14 @@ function generateImports(data: FrontendTemplateData): string {
|
|
|
51
51
|
`import { ${names.pascalCase}Input, ${names.pascalCase}Interface } from "@/features/${data.importTargetDir}/${names.kebabCase}/data/${names.pascalCase}Interface";`,
|
|
52
52
|
);
|
|
53
53
|
|
|
54
|
-
// Relationship interface imports (skip self-referential
|
|
54
|
+
// Relationship interface imports (deduplicated, skip self-referential)
|
|
55
|
+
const seenInterfaces = new Set<string>();
|
|
55
56
|
relationships.forEach((rel) => {
|
|
56
57
|
if (rel.name === names.pascalCase) {
|
|
57
58
|
return; // Skip self-reference - interface already imported
|
|
58
59
|
}
|
|
60
|
+
if (seenInterfaces.has(rel.interfaceName)) return;
|
|
61
|
+
seenInterfaces.add(rel.interfaceName);
|
|
59
62
|
imports.push(`import { ${rel.interfaceName} } from "${rel.interfaceImportPath}";`);
|
|
60
63
|
});
|
|
61
64
|
|
|
@@ -91,7 +94,7 @@ function generatePrivateFields(data: FrontendTemplateData): string {
|
|
|
91
94
|
// Relationship private members
|
|
92
95
|
if (!extendsContent) {
|
|
93
96
|
relationships.forEach((rel) => {
|
|
94
|
-
const effectiveName = rel.variant || rel.name;
|
|
97
|
+
const effectiveName = rel.alias || rel.variant || rel.name;
|
|
95
98
|
if (rel.single) {
|
|
96
99
|
const propName = toCamelCase(effectiveName);
|
|
97
100
|
// Use intersection type if relationship has fields
|
|
@@ -102,7 +105,8 @@ function generatePrivateFields(data: FrontendTemplateData): string {
|
|
|
102
105
|
}
|
|
103
106
|
lines.push(` private _${propName}?: ${typeDecl};`);
|
|
104
107
|
} else {
|
|
105
|
-
const
|
|
108
|
+
const effectiveMany = rel.alias || rel.name;
|
|
109
|
+
const propName = pluralize(toCamelCase(effectiveMany));
|
|
106
110
|
// Use intersection type if relationship has fields (edge properties)
|
|
107
111
|
if (rel.fields && rel.fields.length > 0) {
|
|
108
112
|
const metaFields = rel.fields.map((f) => `${f.name}?: ${f.tsType}`).join("; ");
|
|
@@ -139,7 +143,7 @@ function generateGetters(data: FrontendTemplateData): string {
|
|
|
139
143
|
// Relationship getters (only for non-Content extending)
|
|
140
144
|
if (!extendsContent) {
|
|
141
145
|
relationships.forEach((rel) => {
|
|
142
|
-
const effectiveName = rel.variant || rel.name;
|
|
146
|
+
const effectiveName = rel.alias || rel.variant || rel.name;
|
|
143
147
|
if (rel.single) {
|
|
144
148
|
const propName = toCamelCase(effectiveName);
|
|
145
149
|
|
|
@@ -161,7 +165,8 @@ function generateGetters(data: FrontendTemplateData): string {
|
|
|
161
165
|
}`);
|
|
162
166
|
}
|
|
163
167
|
} else {
|
|
164
|
-
const
|
|
168
|
+
const effectiveMany = rel.alias || rel.name;
|
|
169
|
+
const propName = pluralize(toCamelCase(effectiveMany));
|
|
165
170
|
// Use intersection type if relationship has fields (edge properties)
|
|
166
171
|
if (rel.fields && rel.fields.length > 0) {
|
|
167
172
|
const metaFields = rel.fields.map((f) => `${f.name}?: ${f.tsType}`).join("; ");
|
|
@@ -197,7 +202,7 @@ function generateRehydrateMethod(data: FrontendTemplateData): string {
|
|
|
197
202
|
: fields;
|
|
198
203
|
|
|
199
204
|
fieldsToInclude.forEach((field) => {
|
|
200
|
-
if (field.isContentField
|
|
205
|
+
if (field.isContentField) {
|
|
201
206
|
// Content fields need JSON parsing
|
|
202
207
|
lines.push(
|
|
203
208
|
` this._${field.name} = data.jsonApi.attributes.${field.name} ? JSON.parse(data.jsonApi.attributes.${field.name}) : undefined;`,
|
|
@@ -215,17 +220,19 @@ function generateRehydrateMethod(data: FrontendTemplateData): string {
|
|
|
215
220
|
if (!extendsContent && relationships.length > 0) {
|
|
216
221
|
lines.push(``);
|
|
217
222
|
relationships.forEach((rel) => {
|
|
218
|
-
const effectiveName = rel.variant || rel.name;
|
|
223
|
+
const effectiveName = rel.alias || rel.variant || rel.name;
|
|
219
224
|
if (rel.single) {
|
|
220
225
|
const propName = toCamelCase(effectiveName);
|
|
221
|
-
const relationshipKey = effectiveName
|
|
226
|
+
const relationshipKey = toCamelCase(effectiveName);
|
|
222
227
|
|
|
223
228
|
// Use _readIncludedWithMeta for relationships with fields
|
|
224
229
|
if (rel.fields && rel.fields.length > 0) {
|
|
225
230
|
const metaType = `{ ${rel.fields.map((f) => `${f.name}?: ${f.tsType}`).join("; ")} }`;
|
|
226
|
-
const
|
|
231
|
+
const singleCast = rel.nullable
|
|
232
|
+
? ` as (${rel.interfaceName} & ${metaType}) | undefined`
|
|
233
|
+
: ` as ${rel.interfaceName} & ${metaType}`;
|
|
227
234
|
lines.push(
|
|
228
|
-
` this._${propName} = this._readIncludedWithMeta<${rel.interfaceName}, ${metaType}>(data, "${relationshipKey}", Modules.${rel.name})${
|
|
235
|
+
` this._${propName} = this._readIncludedWithMeta<${rel.interfaceName}, ${metaType}>(data, "${relationshipKey}", Modules.${rel.name})${singleCast};`,
|
|
229
236
|
);
|
|
230
237
|
} else {
|
|
231
238
|
lines.push(
|
|
@@ -233,8 +240,9 @@ function generateRehydrateMethod(data: FrontendTemplateData): string {
|
|
|
233
240
|
);
|
|
234
241
|
}
|
|
235
242
|
} else {
|
|
236
|
-
const
|
|
237
|
-
const
|
|
243
|
+
const effectiveMany = rel.alias || rel.name;
|
|
244
|
+
const propName = pluralize(toCamelCase(effectiveMany));
|
|
245
|
+
const relationshipKey = toCamelCase(effectiveMany);
|
|
238
246
|
|
|
239
247
|
// Use _readIncludedWithMeta for relationships with fields (edge properties)
|
|
240
248
|
if (rel.fields && rel.fields.length > 0) {
|
|
@@ -290,7 +298,7 @@ function generateCreateJsonApiMethod(data: FrontendTemplateData): string {
|
|
|
290
298
|
: fields;
|
|
291
299
|
|
|
292
300
|
fieldsToInclude.forEach((field) => {
|
|
293
|
-
if (field.isContentField
|
|
301
|
+
if (field.isContentField) {
|
|
294
302
|
lines.push(
|
|
295
303
|
` if (data.${field.name} !== undefined) response.data.attributes.${field.name} = JSON.stringify(data.${field.name});`,
|
|
296
304
|
);
|
|
@@ -307,8 +315,8 @@ function generateCreateJsonApiMethod(data: FrontendTemplateData): string {
|
|
|
307
315
|
if (relationshipsToSerialize.length > 0) {
|
|
308
316
|
lines.push(``);
|
|
309
317
|
relationshipsToSerialize.forEach((rel) => {
|
|
310
|
-
const effectiveName = rel.variant || rel.name;
|
|
311
|
-
const payloadKey = rel.single ? `${toCamelCase(effectiveName)}Id` : `${toCamelCase(
|
|
318
|
+
const effectiveName = rel.alias || rel.variant || rel.name;
|
|
319
|
+
const payloadKey = rel.single ? `${toCamelCase(effectiveName)}Id` : `${toCamelCase(effectiveName)}Ids`;
|
|
312
320
|
const relationshipKey = toCamelCase(effectiveName);
|
|
313
321
|
|
|
314
322
|
if (rel.single) {
|
|
@@ -355,7 +363,7 @@ function generateCreateJsonApiMethod(data: FrontendTemplateData): string {
|
|
|
355
363
|
* Get default value for a field type
|
|
356
364
|
*/
|
|
357
365
|
function getDefaultValue(field: FrontendField): string {
|
|
358
|
-
if (field.isContentField
|
|
366
|
+
if (field.isContentField) {
|
|
359
367
|
return "[]";
|
|
360
368
|
}
|
|
361
369
|
|
|
@@ -108,6 +108,11 @@ function generateRelationshipMethods(data: FrontendTemplateData): string {
|
|
|
108
108
|
|
|
109
109
|
return relationshipServiceMethods
|
|
110
110
|
.map((method) => {
|
|
111
|
+
// For aliases, use a raw string endpoint; for standard relationships, use Modules reference
|
|
112
|
+
const endpointValue = method.aliasEndpoint
|
|
113
|
+
? `"${method.aliasEndpoint}"`
|
|
114
|
+
: `Modules.${method.relationshipName}`;
|
|
115
|
+
|
|
111
116
|
return ` static async ${method.methodName}(params: {
|
|
112
117
|
${method.paramName}: string;
|
|
113
118
|
search?: string;
|
|
@@ -116,7 +121,7 @@ function generateRelationshipMethods(data: FrontendTemplateData): string {
|
|
|
116
121
|
prev?: PreviousRef;
|
|
117
122
|
}): Promise<${names.pascalCase}Interface[]> {
|
|
118
123
|
const endpoint = new EndpointCreator({
|
|
119
|
-
endpoint:
|
|
124
|
+
endpoint: ${endpointValue},
|
|
120
125
|
id: params.${method.paramName},
|
|
121
126
|
childEndpoint: Modules.${names.pascalCase},
|
|
122
127
|
});
|
|
@@ -13,14 +13,15 @@ import { FrontendTemplateData } from "../../types/template-data.interface";
|
|
|
13
13
|
* @returns Generated file content
|
|
14
14
|
*/
|
|
15
15
|
export function generateDetailPageTemplate(data: FrontendTemplateData): string {
|
|
16
|
-
const { names } = data;
|
|
16
|
+
const { names, fields, extendsContent } = data;
|
|
17
|
+
const hasNameField = extendsContent || fields.some((f) => f.name === "name");
|
|
18
|
+
const i18nKey = names.pluralCamel.toLowerCase();
|
|
17
19
|
|
|
18
20
|
return `import ${names.pascalCase}Container from "@/features/${data.importTargetDir}/${names.kebabCase}/components/containers/${names.pascalCase}Container";
|
|
19
21
|
import { ${names.pascalCase}Provider } from "@/features/${data.importTargetDir}/${names.kebabCase}/contexts/${names.pascalCase}Context";
|
|
20
22
|
import { ${names.pascalCase}Interface } from "@/features/${data.importTargetDir}/${names.kebabCase}/data/${names.pascalCase}Interface";
|
|
21
23
|
import { ${names.pascalCase}Service } from "@/features/${data.importTargetDir}/${names.kebabCase}/data/${names.pascalCase}Service";
|
|
22
24
|
import { generateSpecificMetadata } from "@/utils/metadata";
|
|
23
|
-
import { PageContainer } from "@carlonicora/nextjs-jsonapi/components";
|
|
24
25
|
import { Modules } from "@carlonicora/nextjs-jsonapi/core";
|
|
25
26
|
import { Action } from "@carlonicora/nextjs-jsonapi/core";
|
|
26
27
|
import { ServerSession } from "@carlonicora/nextjs-jsonapi/server";
|
|
@@ -41,8 +42,8 @@ export async function generateMetadata(props: { params: Promise<{ id: string }>
|
|
|
41
42
|
action: Action.Read,
|
|
42
43
|
data: ${names.camelCase},
|
|
43
44
|
}))
|
|
44
|
-
? \`[\${t(\`
|
|
45
|
-
: \`\${t(\`
|
|
45
|
+
? \`[\${t(\`entities.${i18nKey}\`, { count: 1 })}]${hasNameField ? ` \${${names.camelCase}.name}` : ""}\`
|
|
46
|
+
: \`\${t(\`entities.${i18nKey}\`, { count: 1 })}\`;
|
|
46
47
|
|
|
47
48
|
return await generateSpecificMetadata({ title: title });
|
|
48
49
|
}
|
|
@@ -55,9 +56,7 @@ export default async function ${names.pascalCase}Page(props: { params: Promise<{
|
|
|
55
56
|
|
|
56
57
|
return (
|
|
57
58
|
<${names.pascalCase}Provider dehydrated${names.pascalCase}={${names.camelCase}.dehydrate()}>
|
|
58
|
-
|
|
59
|
-
<${names.pascalCase}Container />
|
|
60
|
-
</PageContainer>
|
|
59
|
+
<${names.pascalCase}Container />
|
|
61
60
|
</${names.pascalCase}Provider>
|
|
62
61
|
);
|
|
63
62
|
}
|
|
@@ -17,7 +17,7 @@ export function generateListPageTemplate(data: FrontendTemplateData): string {
|
|
|
17
17
|
|
|
18
18
|
return `import ${names.pascalCase}ListContainer from "@/features/${data.importTargetDir}/${names.kebabCase}/components/containers/${names.pascalCase}ListContainer";
|
|
19
19
|
import { ${names.pascalCase}Provider } from "@/features/${data.importTargetDir}/${names.kebabCase}/contexts/${names.pascalCase}Context";
|
|
20
|
-
import {
|
|
20
|
+
import { RoundPageContainer } from "@carlonicora/nextjs-jsonapi/components";
|
|
21
21
|
import { Modules } from "@carlonicora/nextjs-jsonapi/core";
|
|
22
22
|
import { Action } from "@carlonicora/nextjs-jsonapi/core";
|
|
23
23
|
import { ServerSession } from "@carlonicora/nextjs-jsonapi/server";
|
|
@@ -27,9 +27,9 @@ export default async function ${names.pluralPascal}ListPage() {
|
|
|
27
27
|
|
|
28
28
|
return (
|
|
29
29
|
<${names.pascalCase}Provider>
|
|
30
|
-
<
|
|
30
|
+
<RoundPageContainer module={Modules.${names.pascalCase}}>
|
|
31
31
|
<${names.pascalCase}ListContainer />
|
|
32
|
-
</
|
|
32
|
+
</RoundPageContainer>
|
|
33
33
|
</${names.pascalCase}Provider>
|
|
34
34
|
);
|
|
35
35
|
}
|
|
@@ -23,7 +23,7 @@ import {
|
|
|
23
23
|
*/
|
|
24
24
|
export function mapField(field: JsonFieldDefinition, moduleName: string): FrontendField {
|
|
25
25
|
const formComponent = getFormComponent(field.name, field.type);
|
|
26
|
-
const isContent = isContentField(field.name);
|
|
26
|
+
const isContent = isContentField(field.name, field.type);
|
|
27
27
|
|
|
28
28
|
return {
|
|
29
29
|
name: field.name,
|
|
@@ -56,7 +56,7 @@ export function mapFields(fields: JsonFieldDefinition[], moduleName: string): Fr
|
|
|
56
56
|
*/
|
|
57
57
|
export function buildZodSchema(field: JsonFieldDefinition, moduleName: string): string {
|
|
58
58
|
// Content fields always use z.any()
|
|
59
|
-
if (isContentField(field.name)) {
|
|
59
|
+
if (isContentField(field.name, field.type)) {
|
|
60
60
|
return "z.any()";
|
|
61
61
|
}
|
|
62
62
|
|
|
@@ -87,12 +87,12 @@ export function buildZodSchema(field: JsonFieldDefinition, moduleName: string):
|
|
|
87
87
|
return base;
|
|
88
88
|
}
|
|
89
89
|
|
|
90
|
-
// Date fields
|
|
90
|
+
// Date fields — use z.date() not z.coerce.date() to avoid react-hook-form type inference issues
|
|
91
91
|
if (field.type === "date") {
|
|
92
92
|
if (field.nullable) {
|
|
93
|
-
return `z.
|
|
93
|
+
return `z.date().optional()`;
|
|
94
94
|
}
|
|
95
|
-
return `z.
|
|
95
|
+
return `z.date()`;
|
|
96
96
|
}
|
|
97
97
|
|
|
98
98
|
// Default case
|
|
@@ -90,7 +90,7 @@ export function buildI18nMessages(i18nKeys: I18nKeySet): Record<string, any> {
|
|
|
90
90
|
relationships: i18nKeys.relationships,
|
|
91
91
|
},
|
|
92
92
|
},
|
|
93
|
-
|
|
93
|
+
entities: {
|
|
94
94
|
[pluralLowercaseKey]: i18nKeys.type.icuPlural,
|
|
95
95
|
},
|
|
96
96
|
};
|
|
@@ -136,7 +136,7 @@ export function getFieldErrorKey(moduleName: string, fieldName: string): string
|
|
|
136
136
|
* @returns Translation key path
|
|
137
137
|
*/
|
|
138
138
|
export function getTypeKey(pluralKebab: string): string {
|
|
139
|
-
return `
|
|
139
|
+
return `entities.${pluralKebab}`;
|
|
140
140
|
}
|
|
141
141
|
|
|
142
142
|
/**
|
|
@@ -171,6 +171,7 @@ export interface ModuleFilePaths {
|
|
|
171
171
|
model: string;
|
|
172
172
|
service: string;
|
|
173
173
|
fields: string;
|
|
174
|
+
aliases: string;
|
|
174
175
|
editor: string;
|
|
175
176
|
deleter: string;
|
|
176
177
|
selector: string;
|
|
@@ -211,6 +212,7 @@ export function buildFilePaths(
|
|
|
211
212
|
model: `${basePath}/data/${names.pascalCase}.ts`,
|
|
212
213
|
service: `${basePath}/data/${names.pascalCase}Service.ts`,
|
|
213
214
|
fields: `${basePath}/data/${names.pascalCase}Fields.ts`,
|
|
215
|
+
aliases: `${basePath}/data/${names.pascalCase}Aliases.ts`,
|
|
214
216
|
|
|
215
217
|
// Components
|
|
216
218
|
editor: `${basePath}/components/forms/${names.pascalCase}Editor.tsx`,
|
|
@@ -35,7 +35,7 @@ export function isFoundationImport(directory: string): boolean {
|
|
|
35
35
|
* @param rel - JSON relationship definition
|
|
36
36
|
* @returns Frontend relationship representation
|
|
37
37
|
*/
|
|
38
|
-
export function resolveRelationship(rel: JsonRelationshipDefinition): FrontendRelationship {
|
|
38
|
+
export function resolveRelationship(rel: JsonRelationshipDefinition, targetHasNameMap?: Map<string, boolean>): FrontendRelationship {
|
|
39
39
|
const isAuthor = rel.variant === AUTHOR_VARIANT;
|
|
40
40
|
const effectiveName = rel.alias || rel.variant || rel.name;
|
|
41
41
|
const effectiveNameLower = toCamelCase(effectiveName);
|
|
@@ -50,19 +50,18 @@ export function resolveRelationship(rel: JsonRelationshipDefinition): FrontendRe
|
|
|
50
50
|
if (rel.single) {
|
|
51
51
|
payloadFieldId = `${effectiveNameLower}Id`;
|
|
52
52
|
} else {
|
|
53
|
-
payloadFieldId = `${
|
|
53
|
+
payloadFieldId = `${effectiveNameLower}Ids`;
|
|
54
54
|
}
|
|
55
55
|
|
|
56
|
-
// Selector component name
|
|
56
|
+
// Selector component name - always based on target entity name, not alias
|
|
57
57
|
// Foundation components use MultiSelect, generated modules use MultiSelector
|
|
58
58
|
let selectorComponent: string;
|
|
59
|
-
const selectorBaseName = rel.alias || rel.name;
|
|
60
59
|
if (rel.single) {
|
|
61
|
-
selectorComponent = `${
|
|
60
|
+
selectorComponent = `${rel.name}Selector`;
|
|
62
61
|
} else {
|
|
63
62
|
selectorComponent = isFoundationImport(rel.directory)
|
|
64
|
-
? `${
|
|
65
|
-
: `${
|
|
63
|
+
? `${rel.name}MultiSelect`
|
|
64
|
+
: `${rel.name}MultiSelector`;
|
|
66
65
|
}
|
|
67
66
|
|
|
68
67
|
// Zod schema
|
|
@@ -122,6 +121,7 @@ export function resolveRelationship(rel: JsonRelationshipDefinition): FrontendRe
|
|
|
122
121
|
interfaceName: `${rel.name}Interface`,
|
|
123
122
|
modelKebab,
|
|
124
123
|
fields,
|
|
124
|
+
targetHasName: targetHasNameMap?.get(rel.name) ?? true, // Foundation entities default to true
|
|
125
125
|
};
|
|
126
126
|
}
|
|
127
127
|
|
|
@@ -131,8 +131,8 @@ export function resolveRelationship(rel: JsonRelationshipDefinition): FrontendRe
|
|
|
131
131
|
* @param relationships - Array of JSON relationship definitions
|
|
132
132
|
* @returns Array of frontend relationship representations
|
|
133
133
|
*/
|
|
134
|
-
export function resolveRelationships(relationships: JsonRelationshipDefinition[]): FrontendRelationship[] {
|
|
135
|
-
return relationships.map(resolveRelationship);
|
|
134
|
+
export function resolveRelationships(relationships: JsonRelationshipDefinition[], targetHasNameMap?: Map<string, boolean>): FrontendRelationship[] {
|
|
135
|
+
return relationships.map((rel) => resolveRelationship(rel, targetHasNameMap));
|
|
136
136
|
}
|
|
137
137
|
|
|
138
138
|
/**
|
|
@@ -157,14 +157,18 @@ export function mapDirectoryToWebPath(directory: string): string {
|
|
|
157
157
|
* @param relationships - Array of frontend relationships
|
|
158
158
|
* @returns Array of service method definitions
|
|
159
159
|
*/
|
|
160
|
-
export function generateServiceMethods(relationships: FrontendRelationship[]): RelationshipServiceMethod[] {
|
|
160
|
+
export function generateServiceMethods(relationships: FrontendRelationship[], conflictingAliases?: Set<string>): RelationshipServiceMethod[] {
|
|
161
161
|
return relationships.map((rel) => {
|
|
162
162
|
const effectiveName = rel.alias || rel.variant || rel.name;
|
|
163
|
+
// Only use alias endpoint when the alias conflicts with another alias targeting the same entity
|
|
164
|
+
const needsAliasEndpoint = rel.alias && (conflictingAliases?.has(rel.alias) ?? false);
|
|
163
165
|
return {
|
|
164
166
|
methodName: `findManyBy${toPascalCase(effectiveName)}`,
|
|
165
167
|
paramName: `${toCamelCase(effectiveName)}Id`,
|
|
166
168
|
relationshipName: rel.name,
|
|
167
169
|
relationshipEndpoint: pluralize(toKebabCase(rel.name)),
|
|
170
|
+
// For conflicting aliases, use a raw string endpoint (e.g., "created-by") instead of Modules reference
|
|
171
|
+
aliasEndpoint: needsAliasEndpoint ? toKebabCase(rel.alias!) : undefined,
|
|
168
172
|
};
|
|
169
173
|
});
|
|
170
174
|
}
|
|
@@ -219,7 +223,7 @@ export function getRelationshipFormJsx(rel: FrontendRelationship, moduleName: st
|
|
|
219
223
|
return `<${rel.name}MultiSelector
|
|
220
224
|
id="${rel.formFieldId}"
|
|
221
225
|
form={form}
|
|
222
|
-
label={t(\`
|
|
226
|
+
label={t(\`entities.${pluralize(rel.name.toLowerCase())}\`, { count: 2 })}
|
|
223
227
|
/>`;
|
|
224
228
|
}
|
|
225
229
|
}
|
|
@@ -242,13 +246,15 @@ export function getDefaultValueExpression(rel: FrontendRelationship, modelVarNam
|
|
|
242
246
|
? { id: ${modelVarName}.${propertyName}.id, name: ${modelVarName}.${propertyName}.name, avatar: ${modelVarName}.${propertyName}.avatar }
|
|
243
247
|
: undefined`;
|
|
244
248
|
}
|
|
249
|
+
const displayProp = rel.targetHasName ? "name" : "id";
|
|
245
250
|
return `${modelVarName}?.${propertyName}
|
|
246
|
-
? { id: ${modelVarName}.${propertyName}.id, name: ${modelVarName}.${propertyName}
|
|
251
|
+
? { id: ${modelVarName}.${propertyName}.id, name: ${modelVarName}.${propertyName}.${displayProp} }
|
|
247
252
|
: undefined`;
|
|
248
253
|
} else {
|
|
249
254
|
// Multi-select
|
|
255
|
+
const displayProp = rel.targetHasName ? "name" : "id";
|
|
250
256
|
return `${modelVarName}?.${pluralPropertyName}
|
|
251
|
-
? ${modelVarName}.${pluralPropertyName}.map((item) => ({ id: item.id, name: item
|
|
257
|
+
? ${modelVarName}.${pluralPropertyName}.map((item) => ({ id: item.id, name: item.${displayProp} }))
|
|
252
258
|
: []`;
|
|
253
259
|
}
|
|
254
260
|
}
|
|
@@ -98,19 +98,32 @@ export function getZodBase(jsonType: string): string {
|
|
|
98
98
|
* Get form component for a field
|
|
99
99
|
*/
|
|
100
100
|
export function getFormComponent(fieldName: string, fieldType: string): FormComponentType {
|
|
101
|
-
// Check for special field names first
|
|
101
|
+
// Check for special field names first, but only if the type is compatible
|
|
102
|
+
// (e.g., "content" as a string field should NOT use BlockNoteEditor)
|
|
102
103
|
if (SPECIAL_FIELD_COMPONENTS[fieldName]) {
|
|
103
|
-
|
|
104
|
+
const specialComponent = SPECIAL_FIELD_COMPONENTS[fieldName];
|
|
105
|
+
if (specialComponent === "BlockNoteEditor" && isContentField(fieldName, fieldType)) {
|
|
106
|
+
return specialComponent;
|
|
107
|
+
} else if (specialComponent !== "BlockNoteEditor") {
|
|
108
|
+
return specialComponent;
|
|
109
|
+
}
|
|
104
110
|
}
|
|
105
111
|
// Fall back to type-based component
|
|
106
112
|
return TYPE_TO_FORM_COMPONENT[fieldType] || "FormInput";
|
|
107
113
|
}
|
|
108
114
|
|
|
109
115
|
/**
|
|
110
|
-
* Check if a field
|
|
116
|
+
* Check if a field indicates BlockNoteEditor should be used.
|
|
117
|
+
* Only fields named "content" with non-primitive types (not string/number/boolean/date)
|
|
118
|
+
* are treated as rich-content (JSON/BlockNote) fields.
|
|
111
119
|
*/
|
|
112
|
-
export function isContentField(fieldName: string): boolean {
|
|
113
|
-
|
|
120
|
+
export function isContentField(fieldName: string, fieldType?: string): boolean {
|
|
121
|
+
if (fieldName !== "content") return false;
|
|
122
|
+
// If no type provided, assume it's a content field (backwards compat)
|
|
123
|
+
if (!fieldType) return true;
|
|
124
|
+
// Primitive types are NOT content fields even when named "content"
|
|
125
|
+
const primitiveTypes = ["string", "number", "boolean", "date", "datetime"];
|
|
126
|
+
return !primitiveTypes.includes(fieldType);
|
|
114
127
|
}
|
|
115
128
|
|
|
116
129
|
/**
|
|
@@ -120,7 +133,7 @@ export function buildZodSchema(fieldType: string, nullable: boolean, fieldName:
|
|
|
120
133
|
const base = getZodBase(fieldType);
|
|
121
134
|
|
|
122
135
|
// Content fields use z.any()
|
|
123
|
-
if (isContentField(fieldName)) {
|
|
136
|
+
if (isContentField(fieldName, fieldType)) {
|
|
124
137
|
return "z.any()";
|
|
125
138
|
}
|
|
126
139
|
|
|
@@ -67,6 +67,7 @@ export interface FrontendRelationship {
|
|
|
67
67
|
interfaceName: string; // e.g., "UserInterface"
|
|
68
68
|
modelKebab: string; // e.g., "user"
|
|
69
69
|
fields?: FrontendField[]; // Relationship property fields (stored on edges)
|
|
70
|
+
targetHasName: boolean; // Whether the target entity has a "name" field (or extends Content)
|
|
70
71
|
}
|
|
71
72
|
|
|
72
73
|
/**
|
|
@@ -162,6 +163,7 @@ export interface RelationshipServiceMethod {
|
|
|
162
163
|
paramName: string; // e.g., "authorId"
|
|
163
164
|
relationshipName: string; // e.g., "User"
|
|
164
165
|
relationshipEndpoint: string; // e.g., "users"
|
|
166
|
+
aliasEndpoint?: string; // e.g., "created-by" — when set, uses raw string instead of Modules reference
|
|
165
167
|
}
|
|
166
168
|
|
|
167
169
|
/**
|