@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.
Files changed (202) hide show
  1. package/dist/scripts/generate-web-module/generator.d.ts +15 -0
  2. package/dist/scripts/generate-web-module/generator.d.ts.map +1 -0
  3. package/dist/scripts/generate-web-module/generator.js +320 -0
  4. package/dist/scripts/generate-web-module/generator.js.map +1 -0
  5. package/dist/scripts/generate-web-module/index.d.ts +16 -0
  6. package/dist/scripts/generate-web-module/index.d.ts.map +1 -0
  7. package/dist/scripts/generate-web-module/index.js +80 -0
  8. package/dist/scripts/generate-web-module/index.js.map +1 -0
  9. package/dist/scripts/generate-web-module/templates/components/container.template.d.ts +14 -0
  10. package/dist/scripts/generate-web-module/templates/components/container.template.d.ts.map +1 -0
  11. package/dist/scripts/generate-web-module/templates/components/container.template.js +124 -0
  12. package/dist/scripts/generate-web-module/templates/components/container.template.js.map +1 -0
  13. package/dist/scripts/generate-web-module/templates/components/content.template.d.ts +15 -0
  14. package/dist/scripts/generate-web-module/templates/components/content.template.d.ts.map +1 -0
  15. package/dist/scripts/generate-web-module/templates/components/content.template.js +39 -0
  16. package/dist/scripts/generate-web-module/templates/components/content.template.js.map +1 -0
  17. package/dist/scripts/generate-web-module/templates/components/deleter.template.d.ts +14 -0
  18. package/dist/scripts/generate-web-module/templates/components/deleter.template.d.ts.map +1 -0
  19. package/dist/scripts/generate-web-module/templates/components/deleter.template.js +51 -0
  20. package/dist/scripts/generate-web-module/templates/components/deleter.template.js.map +1 -0
  21. package/dist/scripts/generate-web-module/templates/components/details.template.d.ts +14 -0
  22. package/dist/scripts/generate-web-module/templates/components/details.template.d.ts.map +1 -0
  23. package/dist/scripts/generate-web-module/templates/components/details.template.js +109 -0
  24. package/dist/scripts/generate-web-module/templates/components/details.template.js.map +1 -0
  25. package/dist/scripts/generate-web-module/templates/components/editor.template.d.ts +14 -0
  26. package/dist/scripts/generate-web-module/templates/components/editor.template.d.ts.map +1 -0
  27. package/dist/scripts/generate-web-module/templates/components/editor.template.js +459 -0
  28. package/dist/scripts/generate-web-module/templates/components/editor.template.js.map +1 -0
  29. package/dist/scripts/generate-web-module/templates/components/index.d.ts +15 -0
  30. package/dist/scripts/generate-web-module/templates/components/index.d.ts.map +1 -0
  31. package/dist/scripts/generate-web-module/templates/components/index.js +29 -0
  32. package/dist/scripts/generate-web-module/templates/components/index.js.map +1 -0
  33. package/dist/scripts/generate-web-module/templates/components/list-container.template.d.ts +14 -0
  34. package/dist/scripts/generate-web-module/templates/components/list-container.template.d.ts.map +1 -0
  35. package/dist/scripts/generate-web-module/templates/components/list-container.template.js +30 -0
  36. package/dist/scripts/generate-web-module/templates/components/list-container.template.js.map +1 -0
  37. package/dist/scripts/generate-web-module/templates/components/list.template.d.ts +14 -0
  38. package/dist/scripts/generate-web-module/templates/components/list.template.d.ts.map +1 -0
  39. package/dist/scripts/generate-web-module/templates/components/list.template.js +84 -0
  40. package/dist/scripts/generate-web-module/templates/components/list.template.js.map +1 -0
  41. package/dist/scripts/generate-web-module/templates/components/multi-selector.template.d.ts +14 -0
  42. package/dist/scripts/generate-web-module/templates/components/multi-selector.template.d.ts.map +1 -0
  43. package/dist/scripts/generate-web-module/templates/components/multi-selector.template.js +184 -0
  44. package/dist/scripts/generate-web-module/templates/components/multi-selector.template.js.map +1 -0
  45. package/dist/scripts/generate-web-module/templates/components/selector.template.d.ts +14 -0
  46. package/dist/scripts/generate-web-module/templates/components/selector.template.d.ts.map +1 -0
  47. package/dist/scripts/generate-web-module/templates/components/selector.template.js +199 -0
  48. package/dist/scripts/generate-web-module/templates/components/selector.template.js.map +1 -0
  49. package/dist/scripts/generate-web-module/templates/context.template.d.ts +14 -0
  50. package/dist/scripts/generate-web-module/templates/context.template.d.ts.map +1 -0
  51. package/dist/scripts/generate-web-module/templates/context.template.js +121 -0
  52. package/dist/scripts/generate-web-module/templates/context.template.js.map +1 -0
  53. package/dist/scripts/generate-web-module/templates/data/fields.template.d.ts +14 -0
  54. package/dist/scripts/generate-web-module/templates/data/fields.template.d.ts.map +1 -0
  55. package/dist/scripts/generate-web-module/templates/data/fields.template.js +55 -0
  56. package/dist/scripts/generate-web-module/templates/data/fields.template.js.map +1 -0
  57. package/dist/scripts/generate-web-module/templates/data/index.d.ts +10 -0
  58. package/dist/scripts/generate-web-module/templates/data/index.d.ts.map +1 -0
  59. package/dist/scripts/generate-web-module/templates/data/index.js +26 -0
  60. package/dist/scripts/generate-web-module/templates/data/index.js.map +1 -0
  61. package/dist/scripts/generate-web-module/templates/data/interface.template.d.ts +14 -0
  62. package/dist/scripts/generate-web-module/templates/data/interface.template.d.ts.map +1 -0
  63. package/dist/scripts/generate-web-module/templates/data/interface.template.js +116 -0
  64. package/dist/scripts/generate-web-module/templates/data/interface.template.js.map +1 -0
  65. package/dist/scripts/generate-web-module/templates/data/model.template.d.ts +14 -0
  66. package/dist/scripts/generate-web-module/templates/data/model.template.d.ts.map +1 -0
  67. package/dist/scripts/generate-web-module/templates/data/model.template.js +274 -0
  68. package/dist/scripts/generate-web-module/templates/data/model.template.js.map +1 -0
  69. package/dist/scripts/generate-web-module/templates/data/service.template.d.ts +14 -0
  70. package/dist/scripts/generate-web-module/templates/data/service.template.d.ts.map +1 -0
  71. package/dist/scripts/generate-web-module/templates/data/service.template.js +168 -0
  72. package/dist/scripts/generate-web-module/templates/data/service.template.js.map +1 -0
  73. package/dist/scripts/generate-web-module/templates/index.d.ts +12 -0
  74. package/dist/scripts/generate-web-module/templates/index.d.ts.map +1 -0
  75. package/dist/scripts/generate-web-module/templates/index.js +37 -0
  76. package/dist/scripts/generate-web-module/templates/index.js.map +1 -0
  77. package/dist/scripts/generate-web-module/templates/module.template.d.ts +14 -0
  78. package/dist/scripts/generate-web-module/templates/module.template.d.ts.map +1 -0
  79. package/dist/scripts/generate-web-module/templates/module.template.js +64 -0
  80. package/dist/scripts/generate-web-module/templates/module.template.js.map +1 -0
  81. package/dist/scripts/generate-web-module/templates/pages/detail-page.template.d.ts +14 -0
  82. package/dist/scripts/generate-web-module/templates/pages/detail-page.template.d.ts.map +1 -0
  83. package/dist/scripts/generate-web-module/templates/pages/detail-page.template.js +65 -0
  84. package/dist/scripts/generate-web-module/templates/pages/detail-page.template.js.map +1 -0
  85. package/dist/scripts/generate-web-module/templates/pages/index.d.ts +8 -0
  86. package/dist/scripts/generate-web-module/templates/pages/index.d.ts.map +1 -0
  87. package/dist/scripts/generate-web-module/templates/pages/index.js +13 -0
  88. package/dist/scripts/generate-web-module/templates/pages/index.js.map +1 -0
  89. package/dist/scripts/generate-web-module/templates/pages/list-page.template.d.ts +14 -0
  90. package/dist/scripts/generate-web-module/templates/pages/list-page.template.d.ts.map +1 -0
  91. package/dist/scripts/generate-web-module/templates/pages/list-page.template.js +37 -0
  92. package/dist/scripts/generate-web-module/templates/pages/list-page.template.js.map +1 -0
  93. package/dist/scripts/generate-web-module/templates/table-hook.template.d.ts +14 -0
  94. package/dist/scripts/generate-web-module/templates/table-hook.template.d.ts.map +1 -0
  95. package/dist/scripts/generate-web-module/templates/table-hook.template.js +174 -0
  96. package/dist/scripts/generate-web-module/templates/table-hook.template.js.map +1 -0
  97. package/dist/scripts/generate-web-module/transformers/field-mapper.d.ts +55 -0
  98. package/dist/scripts/generate-web-module/transformers/field-mapper.d.ts.map +1 -0
  99. package/dist/scripts/generate-web-module/transformers/field-mapper.js +179 -0
  100. package/dist/scripts/generate-web-module/transformers/field-mapper.js.map +1 -0
  101. package/dist/scripts/generate-web-module/transformers/i18n-generator.d.ts +78 -0
  102. package/dist/scripts/generate-web-module/transformers/i18n-generator.d.ts.map +1 -0
  103. package/dist/scripts/generate-web-module/transformers/i18n-generator.js +182 -0
  104. package/dist/scripts/generate-web-module/transformers/i18n-generator.js.map +1 -0
  105. package/dist/scripts/generate-web-module/transformers/import-resolver.d.ts +106 -0
  106. package/dist/scripts/generate-web-module/transformers/import-resolver.d.ts.map +1 -0
  107. package/dist/scripts/generate-web-module/transformers/import-resolver.js +193 -0
  108. package/dist/scripts/generate-web-module/transformers/import-resolver.js.map +1 -0
  109. package/dist/scripts/generate-web-module/transformers/index.d.ts +12 -0
  110. package/dist/scripts/generate-web-module/transformers/index.d.ts.map +1 -0
  111. package/dist/scripts/generate-web-module/transformers/index.js +28 -0
  112. package/dist/scripts/generate-web-module/transformers/index.js.map +1 -0
  113. package/dist/scripts/generate-web-module/transformers/name-transformer.d.ts +60 -0
  114. package/dist/scripts/generate-web-module/transformers/name-transformer.d.ts.map +1 -0
  115. package/dist/scripts/generate-web-module/transformers/name-transformer.js +115 -0
  116. package/dist/scripts/generate-web-module/transformers/name-transformer.js.map +1 -0
  117. package/dist/scripts/generate-web-module/transformers/parent-detector.d.ts +57 -0
  118. package/dist/scripts/generate-web-module/transformers/parent-detector.d.ts.map +1 -0
  119. package/dist/scripts/generate-web-module/transformers/parent-detector.js +88 -0
  120. package/dist/scripts/generate-web-module/transformers/parent-detector.js.map +1 -0
  121. package/dist/scripts/generate-web-module/transformers/relationship-resolver.d.ts +68 -0
  122. package/dist/scripts/generate-web-module/transformers/relationship-resolver.d.ts.map +1 -0
  123. package/dist/scripts/generate-web-module/transformers/relationship-resolver.js +219 -0
  124. package/dist/scripts/generate-web-module/transformers/relationship-resolver.js.map +1 -0
  125. package/dist/scripts/generate-web-module/types/field-mapping.types.d.ts +68 -0
  126. package/dist/scripts/generate-web-module/types/field-mapping.types.d.ts.map +1 -0
  127. package/dist/scripts/generate-web-module/types/field-mapping.types.js +129 -0
  128. package/dist/scripts/generate-web-module/types/field-mapping.types.js.map +1 -0
  129. package/dist/scripts/generate-web-module/types/index.d.ts +9 -0
  130. package/dist/scripts/generate-web-module/types/index.d.ts.map +1 -0
  131. package/dist/scripts/generate-web-module/types/index.js +25 -0
  132. package/dist/scripts/generate-web-module/types/index.js.map +1 -0
  133. package/dist/scripts/generate-web-module/types/json-schema.interface.d.ts +40 -0
  134. package/dist/scripts/generate-web-module/types/json-schema.interface.d.ts.map +1 -0
  135. package/dist/scripts/generate-web-module/types/json-schema.interface.js +10 -0
  136. package/dist/scripts/generate-web-module/types/json-schema.interface.js.map +1 -0
  137. package/dist/scripts/generate-web-module/types/template-data.interface.d.ts +128 -0
  138. package/dist/scripts/generate-web-module/types/template-data.interface.d.ts.map +1 -0
  139. package/dist/scripts/generate-web-module/types/template-data.interface.js +9 -0
  140. package/dist/scripts/generate-web-module/types/template-data.interface.js.map +1 -0
  141. package/dist/scripts/generate-web-module/utils/bootstrapper-updater.d.ts +29 -0
  142. package/dist/scripts/generate-web-module/utils/bootstrapper-updater.d.ts.map +1 -0
  143. package/dist/scripts/generate-web-module/utils/bootstrapper-updater.js +153 -0
  144. package/dist/scripts/generate-web-module/utils/bootstrapper-updater.js.map +1 -0
  145. package/dist/scripts/generate-web-module/utils/file-writer.d.ts +38 -0
  146. package/dist/scripts/generate-web-module/utils/file-writer.d.ts.map +1 -0
  147. package/dist/scripts/generate-web-module/utils/file-writer.js +126 -0
  148. package/dist/scripts/generate-web-module/utils/file-writer.js.map +1 -0
  149. package/dist/scripts/generate-web-module/utils/i18n-updater.d.ts +28 -0
  150. package/dist/scripts/generate-web-module/utils/i18n-updater.d.ts.map +1 -0
  151. package/dist/scripts/generate-web-module/utils/i18n-updater.js +122 -0
  152. package/dist/scripts/generate-web-module/utils/i18n-updater.js.map +1 -0
  153. package/dist/scripts/generate-web-module/utils/index.d.ts +9 -0
  154. package/dist/scripts/generate-web-module/utils/index.d.ts.map +1 -0
  155. package/dist/scripts/generate-web-module/utils/index.js +20 -0
  156. package/dist/scripts/generate-web-module/utils/index.js.map +1 -0
  157. package/dist/scripts/generate-web-module/validators/json-schema-validator.d.ts +46 -0
  158. package/dist/scripts/generate-web-module/validators/json-schema-validator.d.ts.map +1 -0
  159. package/dist/scripts/generate-web-module/validators/json-schema-validator.js +265 -0
  160. package/dist/scripts/generate-web-module/validators/json-schema-validator.js.map +1 -0
  161. package/package.json +27 -21
  162. package/scripts/generate-web-module/generator.ts +363 -0
  163. package/scripts/generate-web-module/index.ts +49 -0
  164. package/scripts/generate-web-module/templates/components/container.template.ts +129 -0
  165. package/scripts/generate-web-module/templates/components/content.template.ts +40 -0
  166. package/scripts/generate-web-module/templates/components/deleter.template.ts +51 -0
  167. package/scripts/generate-web-module/templates/components/details.template.ts +115 -0
  168. package/scripts/generate-web-module/templates/components/editor.template.ts +495 -0
  169. package/scripts/generate-web-module/templates/components/index.ts +18 -0
  170. package/scripts/generate-web-module/templates/components/list-container.template.ts +30 -0
  171. package/scripts/generate-web-module/templates/components/list.template.ts +91 -0
  172. package/scripts/generate-web-module/templates/components/multi-selector.template.ts +184 -0
  173. package/scripts/generate-web-module/templates/components/selector.template.ts +199 -0
  174. package/scripts/generate-web-module/templates/context.template.ts +121 -0
  175. package/scripts/generate-web-module/templates/data/fields.template.ts +64 -0
  176. package/scripts/generate-web-module/templates/data/index.ts +10 -0
  177. package/scripts/generate-web-module/templates/data/interface.template.ts +136 -0
  178. package/scripts/generate-web-module/templates/data/model.template.ts +327 -0
  179. package/scripts/generate-web-module/templates/data/service.template.ts +185 -0
  180. package/scripts/generate-web-module/templates/index.ts +34 -0
  181. package/scripts/generate-web-module/templates/module.template.ts +69 -0
  182. package/scripts/generate-web-module/templates/pages/detail-page.template.ts +65 -0
  183. package/scripts/generate-web-module/templates/pages/index.ts +8 -0
  184. package/scripts/generate-web-module/templates/pages/list-page.template.ts +37 -0
  185. package/scripts/generate-web-module/templates/table-hook.template.ts +182 -0
  186. package/scripts/generate-web-module/transformers/field-mapper.ts +201 -0
  187. package/scripts/generate-web-module/transformers/i18n-generator.ts +199 -0
  188. package/scripts/generate-web-module/transformers/import-resolver.ts +250 -0
  189. package/scripts/generate-web-module/transformers/index.ts +12 -0
  190. package/scripts/generate-web-module/transformers/name-transformer.ts +115 -0
  191. package/scripts/generate-web-module/transformers/parent-detector.ts +87 -0
  192. package/scripts/generate-web-module/transformers/relationship-resolver.ts +221 -0
  193. package/scripts/generate-web-module/tsconfig.json +24 -0
  194. package/scripts/generate-web-module/types/field-mapping.types.ts +141 -0
  195. package/scripts/generate-web-module/types/index.ts +9 -0
  196. package/scripts/generate-web-module/types/json-schema.interface.ts +42 -0
  197. package/scripts/generate-web-module/types/template-data.interface.ts +164 -0
  198. package/scripts/generate-web-module/utils/bootstrapper-updater.ts +145 -0
  199. package/scripts/generate-web-module/utils/file-writer.ts +115 -0
  200. package/scripts/generate-web-module/utils/i18n-updater.ts +108 -0
  201. package/scripts/generate-web-module/utils/index.ts +9 -0
  202. package/scripts/generate-web-module/validators/json-schema-validator.ts +306 -0
@@ -0,0 +1,10 @@
1
+ /**
2
+ * Data Layer Templates Index
3
+ *
4
+ * Re-exports all data layer template generators.
5
+ */
6
+
7
+ export * from "./interface.template";
8
+ export * from "./model.template";
9
+ export * from "./service.template";
10
+ export * from "./fields.template";
@@ -0,0 +1,136 @@
1
+ /**
2
+ * Interface Template
3
+ *
4
+ * Generates {Module}Interface.ts file with Input type and Interface definition.
5
+ */
6
+
7
+ import { FrontendTemplateData, FrontendField, FrontendRelationship } from "../../types/template-data.interface";
8
+ import { toCamelCase, pluralize } from "../../transformers/name-transformer";
9
+
10
+ /**
11
+ * Generate the interface file content
12
+ *
13
+ * @param data - Frontend template data
14
+ * @returns Generated file content
15
+ */
16
+ export function generateInterfaceTemplate(data: FrontendTemplateData): string {
17
+ const { names, extendsContent, fields, relationships } = data;
18
+
19
+ const imports = generateImports(data);
20
+ const inputType = generateInputType(data);
21
+ const interfaceDefinition = generateInterface(data);
22
+
23
+ return `${imports}
24
+
25
+ ${inputType}
26
+
27
+ ${interfaceDefinition}
28
+ `;
29
+ }
30
+
31
+ /**
32
+ * Generate import statements
33
+ */
34
+ function generateImports(data: FrontendTemplateData): string {
35
+ const { extendsContent, relationships } = data;
36
+ const imports: string[] = [];
37
+
38
+ // Relationship interface imports
39
+ relationships.forEach((rel) => {
40
+ imports.push(
41
+ `import { ${rel.interfaceName} } from "${rel.interfaceImportPath}";`
42
+ );
43
+ });
44
+
45
+ // Base interface import
46
+ if (extendsContent) {
47
+ imports.push(
48
+ `import { ContentInput, ContentInterface } from "@/features/features/content/data/ContentInterface";`
49
+ );
50
+ } else {
51
+ imports.push(
52
+ `import { ApiDataInterface } from "@carlonicora/nextjs-jsonapi/core";`
53
+ );
54
+ }
55
+
56
+ return imports.join("\n");
57
+ }
58
+
59
+ /**
60
+ * Generate Input type definition
61
+ */
62
+ function generateInputType(data: FrontendTemplateData): string {
63
+ const { names, extendsContent, fields, relationships } = data;
64
+
65
+ const fieldLines: string[] = [" id: string;"];
66
+
67
+ // Add fields (excluding inherited ones if extending Content)
68
+ const fieldsToInclude = extendsContent
69
+ ? fields.filter((f) => !["name", "tldr", "abstract"].includes(f.name))
70
+ : fields;
71
+
72
+ fieldsToInclude.forEach((field) => {
73
+ const optional = field.nullable ? "?" : "";
74
+ fieldLines.push(` ${field.name}${optional}: ${field.tsType};`);
75
+ });
76
+
77
+ // Add relationship IDs
78
+ relationships.forEach((rel) => {
79
+ const effectiveName = rel.variant || rel.name;
80
+ if (rel.single) {
81
+ const key = `${toCamelCase(effectiveName)}Id`;
82
+ const optional = rel.nullable ? "?" : "";
83
+ fieldLines.push(` ${key}${optional}: string;`);
84
+ } else {
85
+ const key = `${toCamelCase(rel.name)}Ids`;
86
+ fieldLines.push(` ${key}?: string[];`);
87
+ }
88
+ });
89
+
90
+ if (extendsContent) {
91
+ return `export type ${names.pascalCase}Input = ContentInput & {
92
+ ${fieldLines.join("\n")}
93
+ };`;
94
+ } else {
95
+ return `export type ${names.pascalCase}Input = {
96
+ ${fieldLines.join("\n")}
97
+ };`;
98
+ }
99
+ }
100
+
101
+ /**
102
+ * Generate interface definition
103
+ */
104
+ function generateInterface(data: FrontendTemplateData): string {
105
+ const { names, extendsContent, fields, relationships } = data;
106
+
107
+ const getterLines: string[] = [];
108
+
109
+ // Add field getters (excluding inherited ones if extending Content)
110
+ const fieldsToInclude = extendsContent
111
+ ? fields.filter((f) => !["name", "tldr", "abstract"].includes(f.name))
112
+ : fields;
113
+
114
+ fieldsToInclude.forEach((field) => {
115
+ getterLines.push(` get ${field.name}(): ${field.tsType};`);
116
+ });
117
+
118
+ // Add relationship getters
119
+ relationships.forEach((rel) => {
120
+ const effectiveName = rel.variant || rel.name;
121
+ if (rel.single) {
122
+ const propertyName = toCamelCase(effectiveName);
123
+ const type = rel.nullable ? `${rel.interfaceName} | undefined` : rel.interfaceName;
124
+ getterLines.push(` get ${propertyName}(): ${type};`);
125
+ } else {
126
+ const propertyName = pluralize(toCamelCase(rel.name));
127
+ getterLines.push(` get ${propertyName}(): ${rel.interfaceName}[];`);
128
+ }
129
+ });
130
+
131
+ const baseInterface = extendsContent ? "ContentInterface" : "ApiDataInterface";
132
+
133
+ return `export interface ${names.pascalCase}Interface extends ${baseInterface} {
134
+ ${getterLines.join("\n")}
135
+ }`;
136
+ }
@@ -0,0 +1,327 @@
1
+ /**
2
+ * Model Template
3
+ *
4
+ * Generates {Module}.ts class file with rehydrate and createJsonApi methods.
5
+ */
6
+
7
+ import { FrontendTemplateData, FrontendField, FrontendRelationship } from "../../types/template-data.interface";
8
+ import { toCamelCase, toPascalCase, pluralize } from "../../transformers/name-transformer";
9
+ import { AUTHOR_VARIANT } from "../../types/field-mapping.types";
10
+
11
+ /**
12
+ * Generate the model file content
13
+ *
14
+ * @param data - Frontend template data
15
+ * @returns Generated file content
16
+ */
17
+ export function generateModelTemplate(data: FrontendTemplateData): string {
18
+ const { names, extendsContent, fields, relationships } = data;
19
+
20
+ const imports = generateImports(data);
21
+ const privateFields = generatePrivateFields(data);
22
+ const getters = generateGetters(data);
23
+ const rehydrateMethod = generateRehydrateMethod(data);
24
+ const createJsonApiMethod = generateCreateJsonApiMethod(data);
25
+
26
+ const baseClass = extendsContent ? "Content" : "AbstractApiData";
27
+
28
+ return `${imports}
29
+
30
+ export class ${names.pascalCase} extends ${baseClass} implements ${names.pascalCase}Interface {
31
+ ${privateFields}
32
+
33
+ ${getters}
34
+
35
+ ${rehydrateMethod}
36
+
37
+ ${createJsonApiMethod}
38
+ }
39
+ `;
40
+ }
41
+
42
+ /**
43
+ * Generate import statements
44
+ */
45
+ function generateImports(data: FrontendTemplateData): string {
46
+ const { names, extendsContent, relationships } = data;
47
+ const imports: string[] = [];
48
+
49
+ // Own interface import
50
+ imports.push(
51
+ `import { ${names.pascalCase}Input, ${names.pascalCase}Interface } from "@/features/${data.targetDir}/${names.kebabCase}/data/${names.pascalCase}Interface";`
52
+ );
53
+
54
+ // Relationship interface imports
55
+ relationships.forEach((rel) => {
56
+ imports.push(
57
+ `import { ${rel.interfaceName} } from "${rel.interfaceImportPath}";`
58
+ );
59
+ });
60
+
61
+ // Base class and core imports
62
+ if (extendsContent) {
63
+ imports.push(
64
+ `import { Content } from "@/features/features/content/data/Content";`
65
+ );
66
+ imports.push(
67
+ `import { JsonApiHydratedDataInterface, Modules } from "@carlonicora/nextjs-jsonapi/core";`
68
+ );
69
+ } else {
70
+ imports.push(
71
+ `import { AbstractApiData, JsonApiHydratedDataInterface, Modules } from "@carlonicora/nextjs-jsonapi/core";`
72
+ );
73
+ }
74
+
75
+ return imports.join("\n");
76
+ }
77
+
78
+ /**
79
+ * Generate private field declarations
80
+ */
81
+ function generatePrivateFields(data: FrontendTemplateData): string {
82
+ const { fields, relationships, extendsContent } = data;
83
+ const lines: string[] = [];
84
+
85
+ // Field private members (excluding inherited)
86
+ const fieldsToInclude = extendsContent
87
+ ? fields.filter((f) => !["name", "tldr", "abstract"].includes(f.name))
88
+ : fields;
89
+
90
+ fieldsToInclude.forEach((field) => {
91
+ lines.push(` private _${field.name}?: ${field.tsType};`);
92
+ });
93
+
94
+ // Relationship private members
95
+ if (!extendsContent) {
96
+ relationships.forEach((rel) => {
97
+ const effectiveName = rel.variant || rel.name;
98
+ if (rel.single) {
99
+ const propName = toCamelCase(effectiveName);
100
+ lines.push(` private _${propName}?: ${rel.interfaceName};`);
101
+ } else {
102
+ const propName = pluralize(toCamelCase(rel.name));
103
+ lines.push(` private _${propName}?: ${rel.interfaceName}[];`);
104
+ }
105
+ });
106
+ }
107
+
108
+ return lines.join("\n");
109
+ }
110
+
111
+ /**
112
+ * Generate getter methods
113
+ */
114
+ function generateGetters(data: FrontendTemplateData): string {
115
+ const { fields, relationships, extendsContent } = data;
116
+ const lines: string[] = [];
117
+
118
+ // Field getters (excluding inherited)
119
+ const fieldsToInclude = extendsContent
120
+ ? fields.filter((f) => !["name", "tldr", "abstract"].includes(f.name))
121
+ : fields;
122
+
123
+ fieldsToInclude.forEach((field) => {
124
+ const defaultValue = getDefaultValue(field);
125
+ lines.push(` get ${field.name}(): ${field.tsType} {
126
+ return this._${field.name} ?? ${defaultValue};
127
+ }`);
128
+ });
129
+
130
+ // Relationship getters (only for non-Content extending)
131
+ if (!extendsContent) {
132
+ relationships.forEach((rel) => {
133
+ const effectiveName = rel.variant || rel.name;
134
+ if (rel.single) {
135
+ const propName = toCamelCase(effectiveName);
136
+ if (rel.nullable) {
137
+ lines.push(` get ${propName}(): ${rel.interfaceName} | undefined {
138
+ return this._${propName};
139
+ }`);
140
+ } else {
141
+ lines.push(` get ${propName}(): ${rel.interfaceName} {
142
+ if (this._${propName} === undefined) throw new Error("JsonApi error: ${data.names.camelCase} ${propName} is missing");
143
+ return this._${propName};
144
+ }`);
145
+ }
146
+ } else {
147
+ const propName = pluralize(toCamelCase(rel.name));
148
+ lines.push(` get ${propName}(): ${rel.interfaceName}[] {
149
+ return this._${propName} ?? [];
150
+ }`);
151
+ }
152
+ });
153
+ }
154
+
155
+ return lines.join("\n\n");
156
+ }
157
+
158
+ /**
159
+ * Generate rehydrate method
160
+ */
161
+ function generateRehydrateMethod(data: FrontendTemplateData): string {
162
+ const { names, fields, relationships, extendsContent } = data;
163
+ const lines: string[] = [];
164
+
165
+ lines.push(` rehydrate(data: JsonApiHydratedDataInterface): this {`);
166
+ lines.push(` super.rehydrate(data);`);
167
+ lines.push(``);
168
+
169
+ // Field rehydration (excluding inherited)
170
+ const fieldsToInclude = extendsContent
171
+ ? fields.filter((f) => !["name", "tldr", "abstract"].includes(f.name))
172
+ : fields;
173
+
174
+ fieldsToInclude.forEach((field) => {
175
+ if (field.isContentField || field.name === "content") {
176
+ // Content fields need JSON parsing
177
+ lines.push(
178
+ ` this._${field.name} = data.jsonApi.attributes.${field.name} ? JSON.parse(data.jsonApi.attributes.${field.name}) : undefined;`
179
+ );
180
+ } else if (field.type === "date") {
181
+ lines.push(
182
+ ` this._${field.name} = data.jsonApi.attributes.${field.name} ? new Date(data.jsonApi.attributes.${field.name}) : undefined;`
183
+ );
184
+ } else {
185
+ lines.push(
186
+ ` this._${field.name} = data.jsonApi.attributes.${field.name};`
187
+ );
188
+ }
189
+ });
190
+
191
+ // Relationship rehydration (only for non-Content)
192
+ if (!extendsContent && relationships.length > 0) {
193
+ lines.push(``);
194
+ relationships.forEach((rel) => {
195
+ const effectiveName = rel.variant || rel.name;
196
+ if (rel.single) {
197
+ const propName = toCamelCase(effectiveName);
198
+ const relationshipKey = effectiveName.toLowerCase();
199
+ lines.push(
200
+ ` this._${propName} = this._readIncluded(data, "${relationshipKey}", Modules.${rel.name}) as ${rel.interfaceName}${rel.nullable ? " | undefined" : ""};`
201
+ );
202
+ } else {
203
+ const propName = pluralize(toCamelCase(rel.name));
204
+ const relationshipKey = pluralize(rel.name.toLowerCase());
205
+ lines.push(
206
+ ` this._${propName} = this._readIncludedList(data, "${relationshipKey}", Modules.${rel.name}) as ${rel.interfaceName}[];`
207
+ );
208
+ }
209
+ });
210
+ }
211
+
212
+ lines.push(``);
213
+ lines.push(` return this;`);
214
+ lines.push(` }`);
215
+
216
+ return lines.join("\n");
217
+ }
218
+
219
+ /**
220
+ * Generate createJsonApi method
221
+ */
222
+ function generateCreateJsonApiMethod(data: FrontendTemplateData): string {
223
+ const { names, fields, relationships, extendsContent } = data;
224
+ const lines: string[] = [];
225
+
226
+ lines.push(` createJsonApi(data: ${names.pascalCase}Input) {`);
227
+ lines.push(` const response: any = {`);
228
+ lines.push(` data: {`);
229
+ lines.push(` type: Modules.${names.pascalCase}.name,`);
230
+ lines.push(` id: data.id,`);
231
+ lines.push(` attributes: {},`);
232
+ lines.push(` meta: {},`);
233
+ lines.push(` relationships: {},`);
234
+ lines.push(` },`);
235
+ lines.push(` included: [],`);
236
+ lines.push(` };`);
237
+ lines.push(``);
238
+
239
+ // Call super.addContentInput if extending Content
240
+ if (extendsContent) {
241
+ lines.push(` super.addContentInput(response, data);`);
242
+ lines.push(``);
243
+ }
244
+
245
+ // Field serialization (excluding inherited)
246
+ const fieldsToInclude = extendsContent
247
+ ? fields.filter((f) => !["name", "tldr", "abstract"].includes(f.name))
248
+ : fields;
249
+
250
+ fieldsToInclude.forEach((field) => {
251
+ if (field.isContentField || field.name === "content") {
252
+ lines.push(
253
+ ` if (data.${field.name} !== undefined) response.data.attributes.${field.name} = JSON.stringify(data.${field.name});`
254
+ );
255
+ } else {
256
+ lines.push(
257
+ ` if (data.${field.name} !== undefined) response.data.attributes.${field.name} = data.${field.name};`
258
+ );
259
+ }
260
+ });
261
+
262
+ // Relationship serialization (skip author for Content, handle others)
263
+ const relationshipsToSerialize = relationships.filter(
264
+ (rel) => !(extendsContent && rel.variant === AUTHOR_VARIANT)
265
+ );
266
+
267
+ if (relationshipsToSerialize.length > 0) {
268
+ lines.push(``);
269
+ relationshipsToSerialize.forEach((rel) => {
270
+ const effectiveName = rel.variant || rel.name;
271
+ const payloadKey = rel.single
272
+ ? `${toCamelCase(effectiveName)}Id`
273
+ : `${toCamelCase(rel.name)}Ids`;
274
+ const relationshipKey = toCamelCase(effectiveName);
275
+
276
+ if (rel.single) {
277
+ lines.push(` if (data.${payloadKey}) {`);
278
+ lines.push(` response.data.relationships.${relationshipKey} = {`);
279
+ lines.push(` data: {`);
280
+ lines.push(` type: Modules.${rel.name}.name,`);
281
+ lines.push(` id: data.${payloadKey},`);
282
+ lines.push(` },`);
283
+ lines.push(` };`);
284
+ lines.push(` }`);
285
+ } else {
286
+ lines.push(` if (data.${payloadKey} && data.${payloadKey}.length > 0) {`);
287
+ lines.push(` response.data.relationships.${relationshipKey} = {`);
288
+ lines.push(` data: data.${payloadKey}.map((id) => ({`);
289
+ lines.push(` type: Modules.${rel.name}.name,`);
290
+ lines.push(` id,`);
291
+ lines.push(` })),`);
292
+ lines.push(` };`);
293
+ lines.push(` }`);
294
+ }
295
+ });
296
+ }
297
+
298
+ lines.push(``);
299
+ lines.push(` return response;`);
300
+ lines.push(` }`);
301
+
302
+ return lines.join("\n");
303
+ }
304
+
305
+ /**
306
+ * Get default value for a field type
307
+ */
308
+ function getDefaultValue(field: FrontendField): string {
309
+ if (field.isContentField || field.name === "content") {
310
+ return "[]";
311
+ }
312
+
313
+ switch (field.type) {
314
+ case "string":
315
+ return '""';
316
+ case "number":
317
+ return "0";
318
+ case "boolean":
319
+ return "false";
320
+ case "date":
321
+ return "new Date()";
322
+ case "any":
323
+ return "undefined";
324
+ default:
325
+ return "undefined";
326
+ }
327
+ }
@@ -0,0 +1,185 @@
1
+ /**
2
+ * Service Template
3
+ *
4
+ * Generates {Module}Service.ts class file with CRUD and relationship methods.
5
+ */
6
+
7
+ import { FrontendTemplateData, FrontendRelationship } from "../../types/template-data.interface";
8
+ import { toCamelCase, toPascalCase } from "../../transformers/name-transformer";
9
+
10
+ /**
11
+ * Generate the service file content
12
+ *
13
+ * @param data - Frontend template data
14
+ * @returns Generated file content
15
+ */
16
+ export function generateServiceTemplate(data: FrontendTemplateData): string {
17
+ const { names, relationships, relationshipServiceMethods } = data;
18
+
19
+ const imports = generateImports(data);
20
+ const findOneMethod = generateFindOneMethod(data);
21
+ const findManyMethod = generateFindManyMethod(data);
22
+ const relationshipMethods = generateRelationshipMethods(data);
23
+ const createMethod = generateCreateMethod(data);
24
+ const updateMethod = generateUpdateMethod(data);
25
+ const deleteMethod = generateDeleteMethod(data);
26
+
27
+ return `${imports}
28
+
29
+ export class ${names.pascalCase}Service extends AbstractService {
30
+ ${findOneMethod}
31
+
32
+ ${findManyMethod}
33
+
34
+ ${relationshipMethods}
35
+ ${createMethod}
36
+
37
+ ${updateMethod}
38
+
39
+ ${deleteMethod}
40
+ }
41
+ `;
42
+ }
43
+
44
+ /**
45
+ * Generate import statements
46
+ */
47
+ function generateImports(data: FrontendTemplateData): string {
48
+ const { names } = data;
49
+
50
+ return `import { AbstractService, HttpMethod, NextRef, PreviousRef } from "@carlonicora/nextjs-jsonapi/core";
51
+ import { EndpointCreator } from "@carlonicora/nextjs-jsonapi/core";
52
+ import { ${names.pascalCase}Input, ${names.pascalCase}Interface } from "@/features/${data.targetDir}/${names.kebabCase}/data/${names.pascalCase}Interface";
53
+ import { Modules } from "@carlonicora/nextjs-jsonapi/core";`;
54
+ }
55
+
56
+ /**
57
+ * Generate findOne method
58
+ */
59
+ function generateFindOneMethod(data: FrontendTemplateData): string {
60
+ const { names } = data;
61
+
62
+ return ` static async findOne(params: { id: string }): Promise<${names.pascalCase}Interface> {
63
+ return this.callApi<${names.pascalCase}Interface>({
64
+ type: Modules.${names.pascalCase},
65
+ method: HttpMethod.GET,
66
+ endpoint: new EndpointCreator({ endpoint: Modules.${names.pascalCase}, id: params.id }).generate(),
67
+ });
68
+ }`;
69
+ }
70
+
71
+ /**
72
+ * Generate findMany method
73
+ */
74
+ function generateFindManyMethod(data: FrontendTemplateData): string {
75
+ const { names } = data;
76
+
77
+ return ` static async findMany(params: {
78
+ search?: string;
79
+ fetchAll?: boolean;
80
+ next?: NextRef;
81
+ prev?: PreviousRef;
82
+ }): Promise<${names.pascalCase}Interface[]> {
83
+ const endpoint = new EndpointCreator({ endpoint: Modules.${names.pascalCase} });
84
+
85
+ if (params.fetchAll) endpoint.addAdditionalParam("fetchAll", "true");
86
+ if (params.search) endpoint.addAdditionalParam("search", params.search);
87
+ if (Modules.${names.pascalCase}.inclusions?.lists?.fields) endpoint.limitToFields(Modules.${names.pascalCase}.inclusions.lists.fields);
88
+ if (Modules.${names.pascalCase}.inclusions?.lists?.types) endpoint.limitToType(Modules.${names.pascalCase}.inclusions.lists.types);
89
+
90
+ return this.callApi({
91
+ type: Modules.${names.pascalCase},
92
+ method: HttpMethod.GET,
93
+ endpoint: endpoint.generate(),
94
+ next: params.next,
95
+ });
96
+ }`;
97
+ }
98
+
99
+ /**
100
+ * Generate relationship query methods
101
+ */
102
+ function generateRelationshipMethods(data: FrontendTemplateData): string {
103
+ const { names, relationshipServiceMethods } = data;
104
+
105
+ if (relationshipServiceMethods.length === 0) {
106
+ return "";
107
+ }
108
+
109
+ return relationshipServiceMethods
110
+ .map((method) => {
111
+ return ` static async ${method.methodName}(params: {
112
+ ${method.paramName}: string;
113
+ search?: string;
114
+ fetchAll?: boolean;
115
+ next?: NextRef;
116
+ prev?: PreviousRef;
117
+ }): Promise<${names.pascalCase}Interface[]> {
118
+ const endpoint = new EndpointCreator({
119
+ endpoint: Modules.${method.relationshipName},
120
+ id: params.${method.paramName},
121
+ childEndpoint: Modules.${names.pascalCase},
122
+ });
123
+
124
+ if (params.fetchAll) endpoint.addAdditionalParam("fetchAll", "true");
125
+ if (params.search) endpoint.addAdditionalParam("search", params.search);
126
+ if (Modules.${names.pascalCase}.inclusions?.lists?.fields) endpoint.limitToFields(Modules.${names.pascalCase}.inclusions.lists.fields);
127
+ if (Modules.${names.pascalCase}.inclusions?.lists?.types) endpoint.limitToType(Modules.${names.pascalCase}.inclusions.lists.types);
128
+
129
+ return this.callApi({
130
+ type: Modules.${names.pascalCase},
131
+ method: HttpMethod.GET,
132
+ endpoint: endpoint.generate(),
133
+ next: params.next,
134
+ });
135
+ }`;
136
+ })
137
+ .join("\n\n");
138
+ }
139
+
140
+ /**
141
+ * Generate create method
142
+ */
143
+ function generateCreateMethod(data: FrontendTemplateData): string {
144
+ const { names } = data;
145
+
146
+ return ` static async create(params: ${names.pascalCase}Input): Promise<${names.pascalCase}Interface> {
147
+ return this.callApi({
148
+ type: Modules.${names.pascalCase},
149
+ method: HttpMethod.POST,
150
+ endpoint: new EndpointCreator({ endpoint: Modules.${names.pascalCase} }).generate(),
151
+ input: params,
152
+ });
153
+ }`;
154
+ }
155
+
156
+ /**
157
+ * Generate update method
158
+ */
159
+ function generateUpdateMethod(data: FrontendTemplateData): string {
160
+ const { names } = data;
161
+
162
+ return ` static async update(params: ${names.pascalCase}Input): Promise<${names.pascalCase}Interface> {
163
+ return this.callApi({
164
+ type: Modules.${names.pascalCase},
165
+ method: HttpMethod.PUT,
166
+ endpoint: new EndpointCreator({ endpoint: Modules.${names.pascalCase}, id: params.id }).generate(),
167
+ input: params,
168
+ });
169
+ }`;
170
+ }
171
+
172
+ /**
173
+ * Generate delete method
174
+ */
175
+ function generateDeleteMethod(data: FrontendTemplateData): string {
176
+ const { names } = data;
177
+
178
+ return ` static async delete(params: { ${names.camelCase}Id: string }): Promise<void> {
179
+ await this.callApi({
180
+ type: Modules.${names.pascalCase},
181
+ method: HttpMethod.DELETE,
182
+ endpoint: new EndpointCreator({ endpoint: Modules.${names.pascalCase}, id: params.${names.camelCase}Id }).generate(),
183
+ });
184
+ }`;
185
+ }
@@ -0,0 +1,34 @@
1
+ /**
2
+ * Templates Index
3
+ *
4
+ * Exports all template generators.
5
+ */
6
+
7
+ // Data layer templates
8
+ export {
9
+ generateInterfaceTemplate,
10
+ generateModelTemplate,
11
+ generateServiceTemplate,
12
+ generateFieldsTemplate,
13
+ } from "./data";
14
+
15
+ // Component templates
16
+ export {
17
+ generateEditorTemplate,
18
+ generateDeleterTemplate,
19
+ generateSelectorTemplate,
20
+ generateMultiSelectorTemplate,
21
+ generateListTemplate,
22
+ generateDetailsTemplate,
23
+ generateContentTemplate,
24
+ generateContainerTemplate,
25
+ generateListContainerTemplate,
26
+ } from "./components";
27
+
28
+ // Context, hooks, module templates
29
+ export { generateContextTemplate } from "./context.template";
30
+ export { generateTableHookTemplate } from "./table-hook.template";
31
+ export { generateModuleTemplate } from "./module.template";
32
+
33
+ // Page templates
34
+ export { generateListPageTemplate, generateDetailPageTemplate } from "./pages";