@carlonicora/nextjs-jsonapi 1.2.0 → 1.3.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/scripts/generate-web-module/generator.d.ts +15 -0
- package/dist/scripts/generate-web-module/generator.d.ts.map +1 -0
- package/dist/scripts/generate-web-module/generator.js +320 -0
- package/dist/scripts/generate-web-module/generator.js.map +1 -0
- package/dist/scripts/generate-web-module/index.d.ts +16 -0
- package/dist/scripts/generate-web-module/index.d.ts.map +1 -0
- package/dist/scripts/generate-web-module/index.js +80 -0
- package/dist/scripts/generate-web-module/index.js.map +1 -0
- package/dist/scripts/generate-web-module/templates/components/container.template.d.ts +14 -0
- package/dist/scripts/generate-web-module/templates/components/container.template.d.ts.map +1 -0
- package/dist/scripts/generate-web-module/templates/components/container.template.js +124 -0
- package/dist/scripts/generate-web-module/templates/components/container.template.js.map +1 -0
- package/dist/scripts/generate-web-module/templates/components/content.template.d.ts +15 -0
- package/dist/scripts/generate-web-module/templates/components/content.template.d.ts.map +1 -0
- package/dist/scripts/generate-web-module/templates/components/content.template.js +39 -0
- package/dist/scripts/generate-web-module/templates/components/content.template.js.map +1 -0
- package/dist/scripts/generate-web-module/templates/components/deleter.template.d.ts +14 -0
- package/dist/scripts/generate-web-module/templates/components/deleter.template.d.ts.map +1 -0
- package/dist/scripts/generate-web-module/templates/components/deleter.template.js +51 -0
- package/dist/scripts/generate-web-module/templates/components/deleter.template.js.map +1 -0
- package/dist/scripts/generate-web-module/templates/components/details.template.d.ts +14 -0
- package/dist/scripts/generate-web-module/templates/components/details.template.d.ts.map +1 -0
- package/dist/scripts/generate-web-module/templates/components/details.template.js +109 -0
- package/dist/scripts/generate-web-module/templates/components/details.template.js.map +1 -0
- package/dist/scripts/generate-web-module/templates/components/editor.template.d.ts +14 -0
- package/dist/scripts/generate-web-module/templates/components/editor.template.d.ts.map +1 -0
- package/dist/scripts/generate-web-module/templates/components/editor.template.js +459 -0
- package/dist/scripts/generate-web-module/templates/components/editor.template.js.map +1 -0
- package/dist/scripts/generate-web-module/templates/components/index.d.ts +15 -0
- package/dist/scripts/generate-web-module/templates/components/index.d.ts.map +1 -0
- package/dist/scripts/generate-web-module/templates/components/index.js +29 -0
- package/dist/scripts/generate-web-module/templates/components/index.js.map +1 -0
- package/dist/scripts/generate-web-module/templates/components/list-container.template.d.ts +14 -0
- package/dist/scripts/generate-web-module/templates/components/list-container.template.d.ts.map +1 -0
- package/dist/scripts/generate-web-module/templates/components/list-container.template.js +30 -0
- package/dist/scripts/generate-web-module/templates/components/list-container.template.js.map +1 -0
- package/dist/scripts/generate-web-module/templates/components/list.template.d.ts +14 -0
- package/dist/scripts/generate-web-module/templates/components/list.template.d.ts.map +1 -0
- package/dist/scripts/generate-web-module/templates/components/list.template.js +84 -0
- package/dist/scripts/generate-web-module/templates/components/list.template.js.map +1 -0
- package/dist/scripts/generate-web-module/templates/components/multi-selector.template.d.ts +14 -0
- package/dist/scripts/generate-web-module/templates/components/multi-selector.template.d.ts.map +1 -0
- package/dist/scripts/generate-web-module/templates/components/multi-selector.template.js +184 -0
- package/dist/scripts/generate-web-module/templates/components/multi-selector.template.js.map +1 -0
- package/dist/scripts/generate-web-module/templates/components/selector.template.d.ts +14 -0
- package/dist/scripts/generate-web-module/templates/components/selector.template.d.ts.map +1 -0
- package/dist/scripts/generate-web-module/templates/components/selector.template.js +199 -0
- package/dist/scripts/generate-web-module/templates/components/selector.template.js.map +1 -0
- package/dist/scripts/generate-web-module/templates/context.template.d.ts +14 -0
- package/dist/scripts/generate-web-module/templates/context.template.d.ts.map +1 -0
- package/dist/scripts/generate-web-module/templates/context.template.js +121 -0
- package/dist/scripts/generate-web-module/templates/context.template.js.map +1 -0
- package/dist/scripts/generate-web-module/templates/data/fields.template.d.ts +14 -0
- package/dist/scripts/generate-web-module/templates/data/fields.template.d.ts.map +1 -0
- package/dist/scripts/generate-web-module/templates/data/fields.template.js +55 -0
- package/dist/scripts/generate-web-module/templates/data/fields.template.js.map +1 -0
- package/dist/scripts/generate-web-module/templates/data/index.d.ts +10 -0
- package/dist/scripts/generate-web-module/templates/data/index.d.ts.map +1 -0
- package/dist/scripts/generate-web-module/templates/data/index.js +26 -0
- package/dist/scripts/generate-web-module/templates/data/index.js.map +1 -0
- package/dist/scripts/generate-web-module/templates/data/interface.template.d.ts +14 -0
- package/dist/scripts/generate-web-module/templates/data/interface.template.d.ts.map +1 -0
- package/dist/scripts/generate-web-module/templates/data/interface.template.js +116 -0
- package/dist/scripts/generate-web-module/templates/data/interface.template.js.map +1 -0
- package/dist/scripts/generate-web-module/templates/data/model.template.d.ts +14 -0
- package/dist/scripts/generate-web-module/templates/data/model.template.d.ts.map +1 -0
- package/dist/scripts/generate-web-module/templates/data/model.template.js +274 -0
- package/dist/scripts/generate-web-module/templates/data/model.template.js.map +1 -0
- package/dist/scripts/generate-web-module/templates/data/service.template.d.ts +14 -0
- package/dist/scripts/generate-web-module/templates/data/service.template.d.ts.map +1 -0
- package/dist/scripts/generate-web-module/templates/data/service.template.js +168 -0
- package/dist/scripts/generate-web-module/templates/data/service.template.js.map +1 -0
- package/dist/scripts/generate-web-module/templates/index.d.ts +12 -0
- package/dist/scripts/generate-web-module/templates/index.d.ts.map +1 -0
- package/dist/scripts/generate-web-module/templates/index.js +37 -0
- package/dist/scripts/generate-web-module/templates/index.js.map +1 -0
- package/dist/scripts/generate-web-module/templates/module.template.d.ts +14 -0
- package/dist/scripts/generate-web-module/templates/module.template.d.ts.map +1 -0
- package/dist/scripts/generate-web-module/templates/module.template.js +64 -0
- package/dist/scripts/generate-web-module/templates/module.template.js.map +1 -0
- package/dist/scripts/generate-web-module/templates/pages/detail-page.template.d.ts +14 -0
- package/dist/scripts/generate-web-module/templates/pages/detail-page.template.d.ts.map +1 -0
- package/dist/scripts/generate-web-module/templates/pages/detail-page.template.js +65 -0
- package/dist/scripts/generate-web-module/templates/pages/detail-page.template.js.map +1 -0
- package/dist/scripts/generate-web-module/templates/pages/index.d.ts +8 -0
- package/dist/scripts/generate-web-module/templates/pages/index.d.ts.map +1 -0
- package/dist/scripts/generate-web-module/templates/pages/index.js +13 -0
- package/dist/scripts/generate-web-module/templates/pages/index.js.map +1 -0
- package/dist/scripts/generate-web-module/templates/pages/list-page.template.d.ts +14 -0
- package/dist/scripts/generate-web-module/templates/pages/list-page.template.d.ts.map +1 -0
- package/dist/scripts/generate-web-module/templates/pages/list-page.template.js +37 -0
- package/dist/scripts/generate-web-module/templates/pages/list-page.template.js.map +1 -0
- package/dist/scripts/generate-web-module/templates/table-hook.template.d.ts +14 -0
- package/dist/scripts/generate-web-module/templates/table-hook.template.d.ts.map +1 -0
- package/dist/scripts/generate-web-module/templates/table-hook.template.js +174 -0
- package/dist/scripts/generate-web-module/templates/table-hook.template.js.map +1 -0
- package/dist/scripts/generate-web-module/transformers/field-mapper.d.ts +55 -0
- package/dist/scripts/generate-web-module/transformers/field-mapper.d.ts.map +1 -0
- package/dist/scripts/generate-web-module/transformers/field-mapper.js +179 -0
- package/dist/scripts/generate-web-module/transformers/field-mapper.js.map +1 -0
- package/dist/scripts/generate-web-module/transformers/i18n-generator.d.ts +78 -0
- package/dist/scripts/generate-web-module/transformers/i18n-generator.d.ts.map +1 -0
- package/dist/scripts/generate-web-module/transformers/i18n-generator.js +182 -0
- package/dist/scripts/generate-web-module/transformers/i18n-generator.js.map +1 -0
- package/dist/scripts/generate-web-module/transformers/import-resolver.d.ts +106 -0
- package/dist/scripts/generate-web-module/transformers/import-resolver.d.ts.map +1 -0
- package/dist/scripts/generate-web-module/transformers/import-resolver.js +193 -0
- package/dist/scripts/generate-web-module/transformers/import-resolver.js.map +1 -0
- package/dist/scripts/generate-web-module/transformers/index.d.ts +12 -0
- package/dist/scripts/generate-web-module/transformers/index.d.ts.map +1 -0
- package/dist/scripts/generate-web-module/transformers/index.js +28 -0
- package/dist/scripts/generate-web-module/transformers/index.js.map +1 -0
- package/dist/scripts/generate-web-module/transformers/name-transformer.d.ts +60 -0
- package/dist/scripts/generate-web-module/transformers/name-transformer.d.ts.map +1 -0
- package/dist/scripts/generate-web-module/transformers/name-transformer.js +115 -0
- package/dist/scripts/generate-web-module/transformers/name-transformer.js.map +1 -0
- package/dist/scripts/generate-web-module/transformers/parent-detector.d.ts +57 -0
- package/dist/scripts/generate-web-module/transformers/parent-detector.d.ts.map +1 -0
- package/dist/scripts/generate-web-module/transformers/parent-detector.js +88 -0
- package/dist/scripts/generate-web-module/transformers/parent-detector.js.map +1 -0
- package/dist/scripts/generate-web-module/transformers/relationship-resolver.d.ts +68 -0
- package/dist/scripts/generate-web-module/transformers/relationship-resolver.d.ts.map +1 -0
- package/dist/scripts/generate-web-module/transformers/relationship-resolver.js +219 -0
- package/dist/scripts/generate-web-module/transformers/relationship-resolver.js.map +1 -0
- package/dist/scripts/generate-web-module/types/field-mapping.types.d.ts +68 -0
- package/dist/scripts/generate-web-module/types/field-mapping.types.d.ts.map +1 -0
- package/dist/scripts/generate-web-module/types/field-mapping.types.js +129 -0
- package/dist/scripts/generate-web-module/types/field-mapping.types.js.map +1 -0
- package/dist/scripts/generate-web-module/types/index.d.ts +9 -0
- package/dist/scripts/generate-web-module/types/index.d.ts.map +1 -0
- package/dist/scripts/generate-web-module/types/index.js +25 -0
- package/dist/scripts/generate-web-module/types/index.js.map +1 -0
- package/dist/scripts/generate-web-module/types/json-schema.interface.d.ts +40 -0
- package/dist/scripts/generate-web-module/types/json-schema.interface.d.ts.map +1 -0
- package/dist/scripts/generate-web-module/types/json-schema.interface.js +10 -0
- package/dist/scripts/generate-web-module/types/json-schema.interface.js.map +1 -0
- package/dist/scripts/generate-web-module/types/template-data.interface.d.ts +128 -0
- package/dist/scripts/generate-web-module/types/template-data.interface.d.ts.map +1 -0
- package/dist/scripts/generate-web-module/types/template-data.interface.js +9 -0
- package/dist/scripts/generate-web-module/types/template-data.interface.js.map +1 -0
- package/dist/scripts/generate-web-module/utils/bootstrapper-updater.d.ts +29 -0
- package/dist/scripts/generate-web-module/utils/bootstrapper-updater.d.ts.map +1 -0
- package/dist/scripts/generate-web-module/utils/bootstrapper-updater.js +153 -0
- package/dist/scripts/generate-web-module/utils/bootstrapper-updater.js.map +1 -0
- package/dist/scripts/generate-web-module/utils/file-writer.d.ts +38 -0
- package/dist/scripts/generate-web-module/utils/file-writer.d.ts.map +1 -0
- package/dist/scripts/generate-web-module/utils/file-writer.js +126 -0
- package/dist/scripts/generate-web-module/utils/file-writer.js.map +1 -0
- package/dist/scripts/generate-web-module/utils/i18n-updater.d.ts +28 -0
- package/dist/scripts/generate-web-module/utils/i18n-updater.d.ts.map +1 -0
- package/dist/scripts/generate-web-module/utils/i18n-updater.js +122 -0
- package/dist/scripts/generate-web-module/utils/i18n-updater.js.map +1 -0
- package/dist/scripts/generate-web-module/utils/index.d.ts +9 -0
- package/dist/scripts/generate-web-module/utils/index.d.ts.map +1 -0
- package/dist/scripts/generate-web-module/utils/index.js +20 -0
- package/dist/scripts/generate-web-module/utils/index.js.map +1 -0
- package/dist/scripts/generate-web-module/validators/json-schema-validator.d.ts +46 -0
- package/dist/scripts/generate-web-module/validators/json-schema-validator.d.ts.map +1 -0
- package/dist/scripts/generate-web-module/validators/json-schema-validator.js +265 -0
- package/dist/scripts/generate-web-module/validators/json-schema-validator.js.map +1 -0
- package/package.json +27 -21
- package/scripts/generate-web-module/generator.ts +363 -0
- package/scripts/generate-web-module/index.ts +49 -0
- package/scripts/generate-web-module/templates/components/container.template.ts +129 -0
- package/scripts/generate-web-module/templates/components/content.template.ts +40 -0
- package/scripts/generate-web-module/templates/components/deleter.template.ts +51 -0
- package/scripts/generate-web-module/templates/components/details.template.ts +115 -0
- package/scripts/generate-web-module/templates/components/editor.template.ts +495 -0
- package/scripts/generate-web-module/templates/components/index.ts +18 -0
- package/scripts/generate-web-module/templates/components/list-container.template.ts +30 -0
- package/scripts/generate-web-module/templates/components/list.template.ts +91 -0
- package/scripts/generate-web-module/templates/components/multi-selector.template.ts +184 -0
- package/scripts/generate-web-module/templates/components/selector.template.ts +199 -0
- package/scripts/generate-web-module/templates/context.template.ts +121 -0
- package/scripts/generate-web-module/templates/data/fields.template.ts +64 -0
- package/scripts/generate-web-module/templates/data/index.ts +10 -0
- package/scripts/generate-web-module/templates/data/interface.template.ts +136 -0
- package/scripts/generate-web-module/templates/data/model.template.ts +327 -0
- package/scripts/generate-web-module/templates/data/service.template.ts +185 -0
- package/scripts/generate-web-module/templates/index.ts +34 -0
- package/scripts/generate-web-module/templates/module.template.ts +69 -0
- package/scripts/generate-web-module/templates/pages/detail-page.template.ts +65 -0
- package/scripts/generate-web-module/templates/pages/index.ts +8 -0
- package/scripts/generate-web-module/templates/pages/list-page.template.ts +37 -0
- package/scripts/generate-web-module/templates/table-hook.template.ts +182 -0
- package/scripts/generate-web-module/transformers/field-mapper.ts +201 -0
- package/scripts/generate-web-module/transformers/i18n-generator.ts +199 -0
- package/scripts/generate-web-module/transformers/import-resolver.ts +250 -0
- package/scripts/generate-web-module/transformers/index.ts +12 -0
- package/scripts/generate-web-module/transformers/name-transformer.ts +115 -0
- package/scripts/generate-web-module/transformers/parent-detector.ts +87 -0
- package/scripts/generate-web-module/transformers/relationship-resolver.ts +221 -0
- package/scripts/generate-web-module/tsconfig.json +24 -0
- package/scripts/generate-web-module/types/field-mapping.types.ts +141 -0
- package/scripts/generate-web-module/types/index.ts +9 -0
- package/scripts/generate-web-module/types/json-schema.interface.ts +42 -0
- package/scripts/generate-web-module/types/template-data.interface.ts +164 -0
- package/scripts/generate-web-module/utils/bootstrapper-updater.ts +145 -0
- package/scripts/generate-web-module/utils/file-writer.ts +115 -0
- package/scripts/generate-web-module/utils/i18n-updater.ts +108 -0
- package/scripts/generate-web-module/utils/index.ts +9 -0
- package/scripts/generate-web-module/validators/json-schema-validator.ts +306 -0
|
@@ -0,0 +1,221 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Relationship Resolver
|
|
3
|
+
*
|
|
4
|
+
* Resolves JSON relationship definitions to frontend representations
|
|
5
|
+
* including form fields, imports, and service methods.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { JsonRelationshipDefinition } from "../types/json-schema.interface";
|
|
9
|
+
import { FrontendRelationship, RelationshipServiceMethod } from "../types/template-data.interface";
|
|
10
|
+
import { AUTHOR_VARIANT, AUTHOR_ZOD_SCHEMA, ENTITY_ZOD_SCHEMA } from "../types/field-mapping.types";
|
|
11
|
+
import { toCamelCase, toKebabCase, pluralize, toPascalCase } from "./name-transformer";
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Resolve a JSON relationship to frontend representation
|
|
15
|
+
*
|
|
16
|
+
* @param rel - JSON relationship definition
|
|
17
|
+
* @returns Frontend relationship representation
|
|
18
|
+
*/
|
|
19
|
+
export function resolveRelationship(rel: JsonRelationshipDefinition): FrontendRelationship {
|
|
20
|
+
const isAuthor = rel.variant === AUTHOR_VARIANT;
|
|
21
|
+
const effectiveName = rel.variant || rel.name;
|
|
22
|
+
const effectiveNameLower = toCamelCase(effectiveName);
|
|
23
|
+
const modelKebab = toKebabCase(rel.name);
|
|
24
|
+
|
|
25
|
+
// Form field ID
|
|
26
|
+
const formFieldId = effectiveNameLower;
|
|
27
|
+
const formFieldIdPlural = pluralize(effectiveNameLower);
|
|
28
|
+
|
|
29
|
+
// Payload field ID for API calls
|
|
30
|
+
let payloadFieldId: string;
|
|
31
|
+
if (rel.single) {
|
|
32
|
+
payloadFieldId = `${effectiveNameLower}Id`;
|
|
33
|
+
} else {
|
|
34
|
+
payloadFieldId = `${toCamelCase(rel.name)}Ids`;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
// Selector component name
|
|
38
|
+
let selectorComponent: string;
|
|
39
|
+
if (rel.single) {
|
|
40
|
+
selectorComponent = `${rel.name}Selector`;
|
|
41
|
+
} else {
|
|
42
|
+
selectorComponent = `${rel.name}MultiSelector`;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
// Zod schema
|
|
46
|
+
let zodSchema: string;
|
|
47
|
+
if (isAuthor) {
|
|
48
|
+
zodSchema = rel.nullable
|
|
49
|
+
? `${AUTHOR_ZOD_SCHEMA}.optional()`
|
|
50
|
+
: `${AUTHOR_ZOD_SCHEMA}.refine((data) => data.id && data.id.length > 0, { message: t(\`generic.relationships.author.error\`) })`;
|
|
51
|
+
} else if (rel.single) {
|
|
52
|
+
zodSchema = rel.nullable
|
|
53
|
+
? `${ENTITY_ZOD_SCHEMA}.optional()`
|
|
54
|
+
: `${ENTITY_ZOD_SCHEMA}.refine((data) => data.id && data.id.length > 0, { message: t(\`generic.relationships.${effectiveNameLower}.error\`) })`;
|
|
55
|
+
} else {
|
|
56
|
+
zodSchema = rel.nullable ? `z.array(${ENTITY_ZOD_SCHEMA}).optional()` : `z.array(${ENTITY_ZOD_SCHEMA})`;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
// Import paths - map directory to web path
|
|
60
|
+
const webDirectory = mapDirectoryToWebPath(rel.directory);
|
|
61
|
+
const importPath = `@/features/${webDirectory}/${modelKebab}/components/forms/${selectorComponent}`;
|
|
62
|
+
const interfaceImportPath = `@/features/${webDirectory}/${modelKebab}/data/${rel.name}Interface`;
|
|
63
|
+
const serviceImportPath = `@/features/${webDirectory}/${modelKebab}/data/${rel.name}Service`;
|
|
64
|
+
|
|
65
|
+
return {
|
|
66
|
+
name: rel.name,
|
|
67
|
+
variant: rel.variant,
|
|
68
|
+
directory: rel.directory,
|
|
69
|
+
single: rel.single,
|
|
70
|
+
nullable: rel.nullable,
|
|
71
|
+
formFieldId,
|
|
72
|
+
formFieldIdPlural,
|
|
73
|
+
payloadFieldId,
|
|
74
|
+
selectorComponent,
|
|
75
|
+
zodSchema,
|
|
76
|
+
importPath,
|
|
77
|
+
interfaceImportPath,
|
|
78
|
+
serviceImportPath,
|
|
79
|
+
interfaceName: `${rel.name}Interface`,
|
|
80
|
+
modelKebab,
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* Resolve all relationships from JSON schema
|
|
86
|
+
*
|
|
87
|
+
* @param relationships - Array of JSON relationship definitions
|
|
88
|
+
* @returns Array of frontend relationship representations
|
|
89
|
+
*/
|
|
90
|
+
export function resolveRelationships(relationships: JsonRelationshipDefinition[]): FrontendRelationship[] {
|
|
91
|
+
return relationships.map(resolveRelationship);
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* Map directory value to web path
|
|
96
|
+
* e.g., "features" -> "features", "foundations" -> "foundations"
|
|
97
|
+
*
|
|
98
|
+
* @param directory - Directory value from JSON
|
|
99
|
+
* @returns Web path segment
|
|
100
|
+
*/
|
|
101
|
+
export function mapDirectoryToWebPath(directory: string): string {
|
|
102
|
+
// The directory value maps directly to the web path
|
|
103
|
+
return directory;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* Generate service method definitions for relationships
|
|
108
|
+
*
|
|
109
|
+
* @param relationships - Array of frontend relationships
|
|
110
|
+
* @returns Array of service method definitions
|
|
111
|
+
*/
|
|
112
|
+
export function generateServiceMethods(relationships: FrontendRelationship[]): RelationshipServiceMethod[] {
|
|
113
|
+
return relationships.map((rel) => {
|
|
114
|
+
const effectiveName = rel.variant || rel.name;
|
|
115
|
+
return {
|
|
116
|
+
methodName: `findManyBy${toPascalCase(effectiveName)}`,
|
|
117
|
+
paramName: `${toCamelCase(effectiveName)}Id`,
|
|
118
|
+
relationshipName: rel.name,
|
|
119
|
+
relationshipEndpoint: pluralize(toKebabCase(rel.name)),
|
|
120
|
+
};
|
|
121
|
+
});
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
/**
|
|
125
|
+
* Get unique selector imports needed for the editor
|
|
126
|
+
*
|
|
127
|
+
* @param relationships - Array of frontend relationships
|
|
128
|
+
* @returns Deduplicated array of import statements
|
|
129
|
+
*/
|
|
130
|
+
export function getSelectorImports(relationships: FrontendRelationship[]): string[] {
|
|
131
|
+
const imports = new Set<string>();
|
|
132
|
+
|
|
133
|
+
relationships.forEach((rel) => {
|
|
134
|
+
// Don't import User selector from same module - it's typically a library import
|
|
135
|
+
if (rel.name === "User" && rel.variant === AUTHOR_VARIANT) {
|
|
136
|
+
// User selector is imported from library or user feature
|
|
137
|
+
imports.add(`import { UserSelector } from "@/features/foundations/user/components/forms/UserSelector";`);
|
|
138
|
+
} else {
|
|
139
|
+
imports.add(`import ${rel.selectorComponent} from "${rel.importPath}";`);
|
|
140
|
+
}
|
|
141
|
+
});
|
|
142
|
+
|
|
143
|
+
return Array.from(imports);
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
/**
|
|
147
|
+
* Get form JSX for a relationship field
|
|
148
|
+
*
|
|
149
|
+
* @param rel - Frontend relationship
|
|
150
|
+
* @param moduleName - Module name for i18n keys
|
|
151
|
+
* @returns JSX code for the relationship selector
|
|
152
|
+
*/
|
|
153
|
+
export function getRelationshipFormJsx(rel: FrontendRelationship, moduleName: string): string {
|
|
154
|
+
const isRequired = !rel.nullable;
|
|
155
|
+
const lowerModuleName = moduleName.toLowerCase();
|
|
156
|
+
|
|
157
|
+
if (rel.single) {
|
|
158
|
+
// Single selector
|
|
159
|
+
if (rel.variant === AUTHOR_VARIANT) {
|
|
160
|
+
// Author is auto-set, usually not shown in form or read-only
|
|
161
|
+
return "";
|
|
162
|
+
}
|
|
163
|
+
return `<${rel.name}Selector
|
|
164
|
+
id="${rel.formFieldId}"
|
|
165
|
+
form={form}
|
|
166
|
+
label={t(\`generic.relationships.${rel.formFieldId}.label\`)}
|
|
167
|
+
placeholder={t(\`generic.relationships.${rel.formFieldId}.placeholder\`)}
|
|
168
|
+
${isRequired ? "isRequired" : ""}
|
|
169
|
+
/>`;
|
|
170
|
+
} else {
|
|
171
|
+
// Multi selector
|
|
172
|
+
return `<${rel.name}MultiSelector
|
|
173
|
+
id="${rel.formFieldId}"
|
|
174
|
+
form={form}
|
|
175
|
+
label={t(\`types.${pluralize(toKebabCase(rel.name))}\`, { count: 2 })}
|
|
176
|
+
/>`;
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
/**
|
|
181
|
+
* Get default value expression for a relationship in form
|
|
182
|
+
*
|
|
183
|
+
* @param rel - Frontend relationship
|
|
184
|
+
* @param modelVarName - Variable name for the model (e.g., "article")
|
|
185
|
+
* @returns Expression for default value
|
|
186
|
+
*/
|
|
187
|
+
export function getDefaultValueExpression(rel: FrontendRelationship, modelVarName: string): string {
|
|
188
|
+
const propertyName = rel.variant ? toCamelCase(rel.variant) : toCamelCase(rel.name);
|
|
189
|
+
const pluralPropertyName = pluralize(propertyName);
|
|
190
|
+
|
|
191
|
+
if (rel.single) {
|
|
192
|
+
if (rel.variant === AUTHOR_VARIANT) {
|
|
193
|
+
// Author has special shape with avatar
|
|
194
|
+
return `${modelVarName}?.${propertyName}
|
|
195
|
+
? { id: ${modelVarName}.${propertyName}.id, name: ${modelVarName}.${propertyName}.name, avatar: ${modelVarName}.${propertyName}.avatar }
|
|
196
|
+
: undefined`;
|
|
197
|
+
}
|
|
198
|
+
return `${modelVarName}?.${propertyName}
|
|
199
|
+
? { id: ${modelVarName}.${propertyName}.id, name: ${modelVarName}.${propertyName}.name }
|
|
200
|
+
: undefined`;
|
|
201
|
+
} else {
|
|
202
|
+
// Multi-select
|
|
203
|
+
return `${modelVarName}?.${pluralPropertyName}
|
|
204
|
+
? ${modelVarName}.${pluralPropertyName}.map((item) => ({ id: item.id, name: item.name }))
|
|
205
|
+
: []`;
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
/**
|
|
210
|
+
* Get payload mapping for a relationship
|
|
211
|
+
*
|
|
212
|
+
* @param rel - Frontend relationship
|
|
213
|
+
* @returns Payload field assignment code
|
|
214
|
+
*/
|
|
215
|
+
export function getPayloadMapping(rel: FrontendRelationship): string {
|
|
216
|
+
if (rel.single) {
|
|
217
|
+
return `${rel.payloadFieldId}: values.${rel.formFieldId}?.id`;
|
|
218
|
+
} else {
|
|
219
|
+
return `${rel.payloadFieldId}: values.${rel.formFieldId} ? values.${rel.formFieldId}.map((item) => item.id) : []`;
|
|
220
|
+
}
|
|
221
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
{
|
|
2
|
+
"extends": "../../tsconfig.json",
|
|
3
|
+
"compilerOptions": {
|
|
4
|
+
"module": "NodeNext",
|
|
5
|
+
"moduleResolution": "NodeNext",
|
|
6
|
+
"target": "es2020",
|
|
7
|
+
"outDir": "../../dist/scripts/generate-web-module",
|
|
8
|
+
"rootDir": ".",
|
|
9
|
+
"declaration": true,
|
|
10
|
+
"declarationMap": true,
|
|
11
|
+
"sourceMap": true,
|
|
12
|
+
"esModuleInterop": true,
|
|
13
|
+
"skipLibCheck": true,
|
|
14
|
+
"strict": true,
|
|
15
|
+
"resolveJsonModule": true
|
|
16
|
+
},
|
|
17
|
+
"include": [
|
|
18
|
+
"./**/*.ts"
|
|
19
|
+
],
|
|
20
|
+
"exclude": [
|
|
21
|
+
"node_modules",
|
|
22
|
+
"dist"
|
|
23
|
+
]
|
|
24
|
+
}
|
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Field Mapping Types
|
|
3
|
+
*
|
|
4
|
+
* Defines the mapping rules between JSON schema field types
|
|
5
|
+
* and their TypeScript, Zod, and form component equivalents.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { FormComponentType } from "./template-data.interface";
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Mapping from JSON type to TypeScript type
|
|
12
|
+
*/
|
|
13
|
+
export const JSON_TO_TS_TYPE: Record<string, string> = {
|
|
14
|
+
string: "string",
|
|
15
|
+
number: "number",
|
|
16
|
+
boolean: "boolean",
|
|
17
|
+
date: "Date",
|
|
18
|
+
any: "any",
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Mapping from JSON type to Zod schema (base, without modifiers)
|
|
23
|
+
*/
|
|
24
|
+
export const JSON_TO_ZOD_BASE: Record<string, string> = {
|
|
25
|
+
string: "z.string()",
|
|
26
|
+
number: "z.number()",
|
|
27
|
+
boolean: "z.boolean()",
|
|
28
|
+
date: "z.date()",
|
|
29
|
+
any: "z.any()",
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Special field names that get specific form components
|
|
34
|
+
*/
|
|
35
|
+
export const SPECIAL_FIELD_COMPONENTS: Record<string, FormComponentType> = {
|
|
36
|
+
content: "BlockNoteEditor",
|
|
37
|
+
description: "FormTextarea",
|
|
38
|
+
abstract: "FormTextarea",
|
|
39
|
+
body: "FormTextarea",
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Default form component by type
|
|
44
|
+
*/
|
|
45
|
+
export const TYPE_TO_FORM_COMPONENT: Record<string, FormComponentType> = {
|
|
46
|
+
string: "FormInput",
|
|
47
|
+
number: "FormInputNumber",
|
|
48
|
+
boolean: "Checkbox",
|
|
49
|
+
date: "DatePicker",
|
|
50
|
+
any: "FormInput",
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Fields that indicate the module extends Content
|
|
55
|
+
*/
|
|
56
|
+
export const CONTENT_INDICATOR_FIELDS = ["name", "tldr", "abstract"];
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Standard fields added to all modules
|
|
60
|
+
*/
|
|
61
|
+
export const STANDARD_FIELDS = ["createdAt", "updatedAt"];
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Author variant special handling
|
|
65
|
+
*/
|
|
66
|
+
export const AUTHOR_VARIANT = "Author";
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Zod schema for author relationships
|
|
70
|
+
*/
|
|
71
|
+
export const AUTHOR_ZOD_SCHEMA = "userObjectSchema";
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* Zod schema for entity relationships (non-author)
|
|
75
|
+
*/
|
|
76
|
+
export const ENTITY_ZOD_SCHEMA = "entityObjectSchema";
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* Default icon for new modules
|
|
80
|
+
*/
|
|
81
|
+
export const DEFAULT_MODULE_ICON = "FileTextIcon";
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* Get TypeScript type for a field
|
|
85
|
+
*/
|
|
86
|
+
export function getTsType(jsonType: string): string {
|
|
87
|
+
return JSON_TO_TS_TYPE[jsonType] || "any";
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* Get base Zod schema for a field type
|
|
92
|
+
*/
|
|
93
|
+
export function getZodBase(jsonType: string): string {
|
|
94
|
+
return JSON_TO_ZOD_BASE[jsonType] || "z.any()";
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* Get form component for a field
|
|
99
|
+
*/
|
|
100
|
+
export function getFormComponent(fieldName: string, fieldType: string): FormComponentType {
|
|
101
|
+
// Check for special field names first
|
|
102
|
+
if (SPECIAL_FIELD_COMPONENTS[fieldName]) {
|
|
103
|
+
return SPECIAL_FIELD_COMPONENTS[fieldName];
|
|
104
|
+
}
|
|
105
|
+
// Fall back to type-based component
|
|
106
|
+
return TYPE_TO_FORM_COMPONENT[fieldType] || "FormInput";
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/**
|
|
110
|
+
* Check if a field name indicates BlockNoteEditor should be used
|
|
111
|
+
*/
|
|
112
|
+
export function isContentField(fieldName: string): boolean {
|
|
113
|
+
return fieldName === "content";
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/**
|
|
117
|
+
* Build complete Zod schema for a field
|
|
118
|
+
*/
|
|
119
|
+
export function buildZodSchema(fieldType: string, nullable: boolean, fieldName: string): string {
|
|
120
|
+
const base = getZodBase(fieldType);
|
|
121
|
+
|
|
122
|
+
// Content fields use z.any()
|
|
123
|
+
if (isContentField(fieldName)) {
|
|
124
|
+
return "z.any()";
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
// String fields with min(1) for required
|
|
128
|
+
if (fieldType === "string") {
|
|
129
|
+
if (nullable) {
|
|
130
|
+
return `${base}.optional()`;
|
|
131
|
+
}
|
|
132
|
+
return `${base}.min(1, { message: t(\`features.\${moduleName}.fields.${fieldName}.error\`) })`;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
// Other fields with optional modifier
|
|
136
|
+
if (nullable) {
|
|
137
|
+
return `${base}.optional()`;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
return base;
|
|
141
|
+
}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* JSON Schema Interfaces
|
|
3
|
+
*
|
|
4
|
+
* Type definitions for the JSON module definition files
|
|
5
|
+
* located in the /structure folder.
|
|
6
|
+
* Copied from backend generator - shared schema format.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Field definition in JSON schema
|
|
11
|
+
*/
|
|
12
|
+
export interface JsonFieldDefinition {
|
|
13
|
+
name: string;
|
|
14
|
+
type: string;
|
|
15
|
+
nullable: boolean;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Relationship definition in JSON schema
|
|
20
|
+
*/
|
|
21
|
+
export interface JsonRelationshipDefinition {
|
|
22
|
+
name: string;
|
|
23
|
+
variant?: string;
|
|
24
|
+
directory: string;
|
|
25
|
+
single: boolean;
|
|
26
|
+
relationshipName: string; // Backend-specific, ignored in frontend
|
|
27
|
+
toNode: boolean; // Backend-specific, ignored in frontend
|
|
28
|
+
nullable: boolean;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Complete module definition from JSON
|
|
33
|
+
*/
|
|
34
|
+
export interface JsonModuleDefinition {
|
|
35
|
+
moduleId: string;
|
|
36
|
+
moduleName: string;
|
|
37
|
+
endpointName: string;
|
|
38
|
+
targetDir: string;
|
|
39
|
+
languages: string[];
|
|
40
|
+
fields: JsonFieldDefinition[];
|
|
41
|
+
relationships: JsonRelationshipDefinition[];
|
|
42
|
+
}
|
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Frontend Template Data Interfaces
|
|
3
|
+
*
|
|
4
|
+
* Type definitions for the data passed to templates
|
|
5
|
+
* during frontend module generation.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Name transformations for different naming conventions
|
|
10
|
+
*/
|
|
11
|
+
export interface NameTransforms {
|
|
12
|
+
pascalCase: string; // e.g., "Comment"
|
|
13
|
+
camelCase: string; // e.g., "comment"
|
|
14
|
+
kebabCase: string; // e.g., "comment"
|
|
15
|
+
pluralPascal: string; // e.g., "Comments"
|
|
16
|
+
pluralCamel: string; // e.g., "comments"
|
|
17
|
+
pluralKebab: string; // e.g., "comments"
|
|
18
|
+
titleCase: string; // e.g., "Comment" (for labels)
|
|
19
|
+
pluralTitleCase: string; // e.g., "Comments" (for labels)
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Frontend field representation
|
|
24
|
+
*/
|
|
25
|
+
export interface FrontendField {
|
|
26
|
+
name: string;
|
|
27
|
+
type: string;
|
|
28
|
+
tsType: string; // TypeScript type
|
|
29
|
+
zodSchema: string; // Zod schema code
|
|
30
|
+
formComponent: FormComponentType;
|
|
31
|
+
nullable: boolean;
|
|
32
|
+
isContentField: boolean; // Indicates BlockNoteEditor
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Form component types
|
|
37
|
+
*/
|
|
38
|
+
export type FormComponentType = "FormInput" | "FormTextarea" | "BlockNoteEditor" | "Checkbox" | "DatePicker" | "FormInputNumber";
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Frontend relationship representation
|
|
42
|
+
*/
|
|
43
|
+
export interface FrontendRelationship {
|
|
44
|
+
name: string; // e.g., "User"
|
|
45
|
+
variant?: string; // e.g., "Author"
|
|
46
|
+
directory: string; // e.g., "foundations"
|
|
47
|
+
single: boolean;
|
|
48
|
+
nullable: boolean;
|
|
49
|
+
// Derived properties
|
|
50
|
+
formFieldId: string; // e.g., "author" (lowercase variant) or "user"
|
|
51
|
+
formFieldIdPlural: string; // e.g., "topics" for multi-select
|
|
52
|
+
payloadFieldId: string; // e.g., "authorId" or "topicIds"
|
|
53
|
+
selectorComponent: string; // e.g., "UserSelector" or "TopicMultiSelector"
|
|
54
|
+
zodSchema: string; // e.g., "userObjectSchema" or "z.array(entityObjectSchema)"
|
|
55
|
+
importPath: string; // Full import path for selector
|
|
56
|
+
interfaceImportPath: string; // Full import path for interface
|
|
57
|
+
serviceImportPath: string; // Full import path for service
|
|
58
|
+
interfaceName: string; // e.g., "UserInterface"
|
|
59
|
+
modelKebab: string; // e.g., "user"
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* i18n key structure
|
|
64
|
+
*/
|
|
65
|
+
export interface I18nKeySet {
|
|
66
|
+
moduleName: string; // e.g., "comment"
|
|
67
|
+
fields: Record<
|
|
68
|
+
string,
|
|
69
|
+
{
|
|
70
|
+
label: string;
|
|
71
|
+
placeholder: string;
|
|
72
|
+
error: string;
|
|
73
|
+
}
|
|
74
|
+
>;
|
|
75
|
+
relationships: Record<
|
|
76
|
+
string,
|
|
77
|
+
{
|
|
78
|
+
label: string;
|
|
79
|
+
placeholder: string;
|
|
80
|
+
error: string;
|
|
81
|
+
list: string;
|
|
82
|
+
}
|
|
83
|
+
>;
|
|
84
|
+
type: {
|
|
85
|
+
singular: string;
|
|
86
|
+
plural: string;
|
|
87
|
+
icuPlural: string; // ICU format plural
|
|
88
|
+
};
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* Import statement groups
|
|
93
|
+
*/
|
|
94
|
+
export interface ImportStatements {
|
|
95
|
+
// Related model/interface imports
|
|
96
|
+
models: string[];
|
|
97
|
+
// Selector component imports
|
|
98
|
+
selectors: string[];
|
|
99
|
+
// Library imports (from @carlonicora/nextjs-jsonapi)
|
|
100
|
+
library: string[];
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
* Complete template data passed to all templates
|
|
105
|
+
*/
|
|
106
|
+
export interface FrontendTemplateData {
|
|
107
|
+
// Name variations
|
|
108
|
+
names: NameTransforms;
|
|
109
|
+
|
|
110
|
+
// Module metadata
|
|
111
|
+
moduleId: string;
|
|
112
|
+
endpoint: string;
|
|
113
|
+
targetDir: "features" | "foundations";
|
|
114
|
+
|
|
115
|
+
// Parent class detection
|
|
116
|
+
extendsContent: boolean;
|
|
117
|
+
|
|
118
|
+
// Field mappings
|
|
119
|
+
fields: FrontendField[];
|
|
120
|
+
|
|
121
|
+
// Relationship mappings
|
|
122
|
+
relationships: FrontendRelationship[];
|
|
123
|
+
|
|
124
|
+
// i18n keys
|
|
125
|
+
i18nKeys: I18nKeySet;
|
|
126
|
+
|
|
127
|
+
// Import statements
|
|
128
|
+
imports: ImportStatements;
|
|
129
|
+
|
|
130
|
+
// For table structure
|
|
131
|
+
tableFieldNames: string[];
|
|
132
|
+
|
|
133
|
+
// For service generation
|
|
134
|
+
relationshipServiceMethods: RelationshipServiceMethod[];
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
/**
|
|
138
|
+
* Service method for relationship queries
|
|
139
|
+
*/
|
|
140
|
+
export interface RelationshipServiceMethod {
|
|
141
|
+
methodName: string; // e.g., "findManyByAuthor"
|
|
142
|
+
paramName: string; // e.g., "authorId"
|
|
143
|
+
relationshipName: string; // e.g., "User"
|
|
144
|
+
relationshipEndpoint: string; // e.g., "users"
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
/**
|
|
148
|
+
* Options for the generator
|
|
149
|
+
*/
|
|
150
|
+
export interface GenerateWebModuleOptions {
|
|
151
|
+
jsonPath: string;
|
|
152
|
+
dryRun?: boolean;
|
|
153
|
+
force?: boolean;
|
|
154
|
+
noRegister?: boolean;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
/**
|
|
158
|
+
* Generated file info
|
|
159
|
+
*/
|
|
160
|
+
export interface GeneratedFile {
|
|
161
|
+
path: string;
|
|
162
|
+
content: string;
|
|
163
|
+
type: "data" | "component" | "context" | "hook" | "module" | "page";
|
|
164
|
+
}
|