@carlonicora/nextjs-jsonapi 1.1.1 → 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 (230) hide show
  1. package/dist/{BlockNoteEditor-6NVBI7XL.js → BlockNoteEditor-CNXQ3WL3.js} +7 -7
  2. package/dist/{BlockNoteEditor-6NVBI7XL.js.map → BlockNoteEditor-CNXQ3WL3.js.map} +1 -1
  3. package/dist/{BlockNoteEditor-4VOBTXWC.mjs → BlockNoteEditor-DC33K4IF.mjs} +3 -3
  4. package/dist/{chunk-KPRHIJO6.js → chunk-KDFDGXCX.js} +53 -53
  5. package/dist/{chunk-KPRHIJO6.js.map → chunk-KDFDGXCX.js.map} +1 -1
  6. package/dist/{chunk-FYRFMABS.js → chunk-L6LH7WA4.js} +21 -2
  7. package/dist/chunk-L6LH7WA4.js.map +1 -0
  8. package/dist/{chunk-MA2L2PL2.mjs → chunk-MHVXFWZY.mjs} +20 -1
  9. package/dist/chunk-MHVXFWZY.mjs.map +1 -0
  10. package/dist/{chunk-HP4Q5ZVG.mjs → chunk-Y4XI3AZR.mjs} +3 -3
  11. package/dist/client/index.js +3 -3
  12. package/dist/client/index.mjs +2 -2
  13. package/dist/components/index.js +3 -3
  14. package/dist/components/index.mjs +2 -2
  15. package/dist/contexts/index.js +3 -3
  16. package/dist/contexts/index.mjs +2 -2
  17. package/dist/features/index.d.mts +3 -1
  18. package/dist/features/index.d.ts +3 -1
  19. package/dist/features/index.js +4 -2
  20. package/dist/features/index.js.map +1 -1
  21. package/dist/features/index.mjs +3 -1
  22. package/dist/hooks/index.js +3 -3
  23. package/dist/hooks/index.mjs +2 -2
  24. package/dist/scripts/generate-web-module/generator.d.ts +15 -0
  25. package/dist/scripts/generate-web-module/generator.d.ts.map +1 -0
  26. package/dist/scripts/generate-web-module/generator.js +320 -0
  27. package/dist/scripts/generate-web-module/generator.js.map +1 -0
  28. package/dist/scripts/generate-web-module/index.d.ts +16 -0
  29. package/dist/scripts/generate-web-module/index.d.ts.map +1 -0
  30. package/dist/scripts/generate-web-module/index.js +80 -0
  31. package/dist/scripts/generate-web-module/index.js.map +1 -0
  32. package/dist/scripts/generate-web-module/templates/components/container.template.d.ts +14 -0
  33. package/dist/scripts/generate-web-module/templates/components/container.template.d.ts.map +1 -0
  34. package/dist/scripts/generate-web-module/templates/components/container.template.js +124 -0
  35. package/dist/scripts/generate-web-module/templates/components/container.template.js.map +1 -0
  36. package/dist/scripts/generate-web-module/templates/components/content.template.d.ts +15 -0
  37. package/dist/scripts/generate-web-module/templates/components/content.template.d.ts.map +1 -0
  38. package/dist/scripts/generate-web-module/templates/components/content.template.js +39 -0
  39. package/dist/scripts/generate-web-module/templates/components/content.template.js.map +1 -0
  40. package/dist/scripts/generate-web-module/templates/components/deleter.template.d.ts +14 -0
  41. package/dist/scripts/generate-web-module/templates/components/deleter.template.d.ts.map +1 -0
  42. package/dist/scripts/generate-web-module/templates/components/deleter.template.js +51 -0
  43. package/dist/scripts/generate-web-module/templates/components/deleter.template.js.map +1 -0
  44. package/dist/scripts/generate-web-module/templates/components/details.template.d.ts +14 -0
  45. package/dist/scripts/generate-web-module/templates/components/details.template.d.ts.map +1 -0
  46. package/dist/scripts/generate-web-module/templates/components/details.template.js +109 -0
  47. package/dist/scripts/generate-web-module/templates/components/details.template.js.map +1 -0
  48. package/dist/scripts/generate-web-module/templates/components/editor.template.d.ts +14 -0
  49. package/dist/scripts/generate-web-module/templates/components/editor.template.d.ts.map +1 -0
  50. package/dist/scripts/generate-web-module/templates/components/editor.template.js +459 -0
  51. package/dist/scripts/generate-web-module/templates/components/editor.template.js.map +1 -0
  52. package/dist/scripts/generate-web-module/templates/components/index.d.ts +15 -0
  53. package/dist/scripts/generate-web-module/templates/components/index.d.ts.map +1 -0
  54. package/dist/scripts/generate-web-module/templates/components/index.js +29 -0
  55. package/dist/scripts/generate-web-module/templates/components/index.js.map +1 -0
  56. package/dist/scripts/generate-web-module/templates/components/list-container.template.d.ts +14 -0
  57. package/dist/scripts/generate-web-module/templates/components/list-container.template.d.ts.map +1 -0
  58. package/dist/scripts/generate-web-module/templates/components/list-container.template.js +30 -0
  59. package/dist/scripts/generate-web-module/templates/components/list-container.template.js.map +1 -0
  60. package/dist/scripts/generate-web-module/templates/components/list.template.d.ts +14 -0
  61. package/dist/scripts/generate-web-module/templates/components/list.template.d.ts.map +1 -0
  62. package/dist/scripts/generate-web-module/templates/components/list.template.js +84 -0
  63. package/dist/scripts/generate-web-module/templates/components/list.template.js.map +1 -0
  64. package/dist/scripts/generate-web-module/templates/components/multi-selector.template.d.ts +14 -0
  65. package/dist/scripts/generate-web-module/templates/components/multi-selector.template.d.ts.map +1 -0
  66. package/dist/scripts/generate-web-module/templates/components/multi-selector.template.js +184 -0
  67. package/dist/scripts/generate-web-module/templates/components/multi-selector.template.js.map +1 -0
  68. package/dist/scripts/generate-web-module/templates/components/selector.template.d.ts +14 -0
  69. package/dist/scripts/generate-web-module/templates/components/selector.template.d.ts.map +1 -0
  70. package/dist/scripts/generate-web-module/templates/components/selector.template.js +199 -0
  71. package/dist/scripts/generate-web-module/templates/components/selector.template.js.map +1 -0
  72. package/dist/scripts/generate-web-module/templates/context.template.d.ts +14 -0
  73. package/dist/scripts/generate-web-module/templates/context.template.d.ts.map +1 -0
  74. package/dist/scripts/generate-web-module/templates/context.template.js +121 -0
  75. package/dist/scripts/generate-web-module/templates/context.template.js.map +1 -0
  76. package/dist/scripts/generate-web-module/templates/data/fields.template.d.ts +14 -0
  77. package/dist/scripts/generate-web-module/templates/data/fields.template.d.ts.map +1 -0
  78. package/dist/scripts/generate-web-module/templates/data/fields.template.js +55 -0
  79. package/dist/scripts/generate-web-module/templates/data/fields.template.js.map +1 -0
  80. package/dist/scripts/generate-web-module/templates/data/index.d.ts +10 -0
  81. package/dist/scripts/generate-web-module/templates/data/index.d.ts.map +1 -0
  82. package/dist/scripts/generate-web-module/templates/data/index.js +26 -0
  83. package/dist/scripts/generate-web-module/templates/data/index.js.map +1 -0
  84. package/dist/scripts/generate-web-module/templates/data/interface.template.d.ts +14 -0
  85. package/dist/scripts/generate-web-module/templates/data/interface.template.d.ts.map +1 -0
  86. package/dist/scripts/generate-web-module/templates/data/interface.template.js +116 -0
  87. package/dist/scripts/generate-web-module/templates/data/interface.template.js.map +1 -0
  88. package/dist/scripts/generate-web-module/templates/data/model.template.d.ts +14 -0
  89. package/dist/scripts/generate-web-module/templates/data/model.template.d.ts.map +1 -0
  90. package/dist/scripts/generate-web-module/templates/data/model.template.js +274 -0
  91. package/dist/scripts/generate-web-module/templates/data/model.template.js.map +1 -0
  92. package/dist/scripts/generate-web-module/templates/data/service.template.d.ts +14 -0
  93. package/dist/scripts/generate-web-module/templates/data/service.template.d.ts.map +1 -0
  94. package/dist/scripts/generate-web-module/templates/data/service.template.js +168 -0
  95. package/dist/scripts/generate-web-module/templates/data/service.template.js.map +1 -0
  96. package/dist/scripts/generate-web-module/templates/index.d.ts +12 -0
  97. package/dist/scripts/generate-web-module/templates/index.d.ts.map +1 -0
  98. package/dist/scripts/generate-web-module/templates/index.js +37 -0
  99. package/dist/scripts/generate-web-module/templates/index.js.map +1 -0
  100. package/dist/scripts/generate-web-module/templates/module.template.d.ts +14 -0
  101. package/dist/scripts/generate-web-module/templates/module.template.d.ts.map +1 -0
  102. package/dist/scripts/generate-web-module/templates/module.template.js +64 -0
  103. package/dist/scripts/generate-web-module/templates/module.template.js.map +1 -0
  104. package/dist/scripts/generate-web-module/templates/pages/detail-page.template.d.ts +14 -0
  105. package/dist/scripts/generate-web-module/templates/pages/detail-page.template.d.ts.map +1 -0
  106. package/dist/scripts/generate-web-module/templates/pages/detail-page.template.js +65 -0
  107. package/dist/scripts/generate-web-module/templates/pages/detail-page.template.js.map +1 -0
  108. package/dist/scripts/generate-web-module/templates/pages/index.d.ts +8 -0
  109. package/dist/scripts/generate-web-module/templates/pages/index.d.ts.map +1 -0
  110. package/dist/scripts/generate-web-module/templates/pages/index.js +13 -0
  111. package/dist/scripts/generate-web-module/templates/pages/index.js.map +1 -0
  112. package/dist/scripts/generate-web-module/templates/pages/list-page.template.d.ts +14 -0
  113. package/dist/scripts/generate-web-module/templates/pages/list-page.template.d.ts.map +1 -0
  114. package/dist/scripts/generate-web-module/templates/pages/list-page.template.js +37 -0
  115. package/dist/scripts/generate-web-module/templates/pages/list-page.template.js.map +1 -0
  116. package/dist/scripts/generate-web-module/templates/table-hook.template.d.ts +14 -0
  117. package/dist/scripts/generate-web-module/templates/table-hook.template.d.ts.map +1 -0
  118. package/dist/scripts/generate-web-module/templates/table-hook.template.js +174 -0
  119. package/dist/scripts/generate-web-module/templates/table-hook.template.js.map +1 -0
  120. package/dist/scripts/generate-web-module/transformers/field-mapper.d.ts +55 -0
  121. package/dist/scripts/generate-web-module/transformers/field-mapper.d.ts.map +1 -0
  122. package/dist/scripts/generate-web-module/transformers/field-mapper.js +179 -0
  123. package/dist/scripts/generate-web-module/transformers/field-mapper.js.map +1 -0
  124. package/dist/scripts/generate-web-module/transformers/i18n-generator.d.ts +78 -0
  125. package/dist/scripts/generate-web-module/transformers/i18n-generator.d.ts.map +1 -0
  126. package/dist/scripts/generate-web-module/transformers/i18n-generator.js +182 -0
  127. package/dist/scripts/generate-web-module/transformers/i18n-generator.js.map +1 -0
  128. package/dist/scripts/generate-web-module/transformers/import-resolver.d.ts +106 -0
  129. package/dist/scripts/generate-web-module/transformers/import-resolver.d.ts.map +1 -0
  130. package/dist/scripts/generate-web-module/transformers/import-resolver.js +193 -0
  131. package/dist/scripts/generate-web-module/transformers/import-resolver.js.map +1 -0
  132. package/dist/scripts/generate-web-module/transformers/index.d.ts +12 -0
  133. package/dist/scripts/generate-web-module/transformers/index.d.ts.map +1 -0
  134. package/dist/scripts/generate-web-module/transformers/index.js +28 -0
  135. package/dist/scripts/generate-web-module/transformers/index.js.map +1 -0
  136. package/dist/scripts/generate-web-module/transformers/name-transformer.d.ts +60 -0
  137. package/dist/scripts/generate-web-module/transformers/name-transformer.d.ts.map +1 -0
  138. package/dist/scripts/generate-web-module/transformers/name-transformer.js +115 -0
  139. package/dist/scripts/generate-web-module/transformers/name-transformer.js.map +1 -0
  140. package/dist/scripts/generate-web-module/transformers/parent-detector.d.ts +57 -0
  141. package/dist/scripts/generate-web-module/transformers/parent-detector.d.ts.map +1 -0
  142. package/dist/scripts/generate-web-module/transformers/parent-detector.js +88 -0
  143. package/dist/scripts/generate-web-module/transformers/parent-detector.js.map +1 -0
  144. package/dist/scripts/generate-web-module/transformers/relationship-resolver.d.ts +68 -0
  145. package/dist/scripts/generate-web-module/transformers/relationship-resolver.d.ts.map +1 -0
  146. package/dist/scripts/generate-web-module/transformers/relationship-resolver.js +219 -0
  147. package/dist/scripts/generate-web-module/transformers/relationship-resolver.js.map +1 -0
  148. package/dist/scripts/generate-web-module/types/field-mapping.types.d.ts +68 -0
  149. package/dist/scripts/generate-web-module/types/field-mapping.types.d.ts.map +1 -0
  150. package/dist/scripts/generate-web-module/types/field-mapping.types.js +129 -0
  151. package/dist/scripts/generate-web-module/types/field-mapping.types.js.map +1 -0
  152. package/dist/scripts/generate-web-module/types/index.d.ts +9 -0
  153. package/dist/scripts/generate-web-module/types/index.d.ts.map +1 -0
  154. package/dist/scripts/generate-web-module/types/index.js +25 -0
  155. package/dist/scripts/generate-web-module/types/index.js.map +1 -0
  156. package/dist/scripts/generate-web-module/types/json-schema.interface.d.ts +40 -0
  157. package/dist/scripts/generate-web-module/types/json-schema.interface.d.ts.map +1 -0
  158. package/dist/scripts/generate-web-module/types/json-schema.interface.js +10 -0
  159. package/dist/scripts/generate-web-module/types/json-schema.interface.js.map +1 -0
  160. package/dist/scripts/generate-web-module/types/template-data.interface.d.ts +128 -0
  161. package/dist/scripts/generate-web-module/types/template-data.interface.d.ts.map +1 -0
  162. package/dist/scripts/generate-web-module/types/template-data.interface.js +9 -0
  163. package/dist/scripts/generate-web-module/types/template-data.interface.js.map +1 -0
  164. package/dist/scripts/generate-web-module/utils/bootstrapper-updater.d.ts +29 -0
  165. package/dist/scripts/generate-web-module/utils/bootstrapper-updater.d.ts.map +1 -0
  166. package/dist/scripts/generate-web-module/utils/bootstrapper-updater.js +153 -0
  167. package/dist/scripts/generate-web-module/utils/bootstrapper-updater.js.map +1 -0
  168. package/dist/scripts/generate-web-module/utils/file-writer.d.ts +38 -0
  169. package/dist/scripts/generate-web-module/utils/file-writer.d.ts.map +1 -0
  170. package/dist/scripts/generate-web-module/utils/file-writer.js +126 -0
  171. package/dist/scripts/generate-web-module/utils/file-writer.js.map +1 -0
  172. package/dist/scripts/generate-web-module/utils/i18n-updater.d.ts +28 -0
  173. package/dist/scripts/generate-web-module/utils/i18n-updater.d.ts.map +1 -0
  174. package/dist/scripts/generate-web-module/utils/i18n-updater.js +122 -0
  175. package/dist/scripts/generate-web-module/utils/i18n-updater.js.map +1 -0
  176. package/dist/scripts/generate-web-module/utils/index.d.ts +9 -0
  177. package/dist/scripts/generate-web-module/utils/index.d.ts.map +1 -0
  178. package/dist/scripts/generate-web-module/utils/index.js +20 -0
  179. package/dist/scripts/generate-web-module/utils/index.js.map +1 -0
  180. package/dist/scripts/generate-web-module/validators/json-schema-validator.d.ts +46 -0
  181. package/dist/scripts/generate-web-module/validators/json-schema-validator.d.ts.map +1 -0
  182. package/dist/scripts/generate-web-module/validators/json-schema-validator.js +265 -0
  183. package/dist/scripts/generate-web-module/validators/json-schema-validator.js.map +1 -0
  184. package/package.json +27 -21
  185. package/scripts/generate-web-module/generator.ts +363 -0
  186. package/scripts/generate-web-module/index.ts +49 -0
  187. package/scripts/generate-web-module/templates/components/container.template.ts +129 -0
  188. package/scripts/generate-web-module/templates/components/content.template.ts +40 -0
  189. package/scripts/generate-web-module/templates/components/deleter.template.ts +51 -0
  190. package/scripts/generate-web-module/templates/components/details.template.ts +115 -0
  191. package/scripts/generate-web-module/templates/components/editor.template.ts +495 -0
  192. package/scripts/generate-web-module/templates/components/index.ts +18 -0
  193. package/scripts/generate-web-module/templates/components/list-container.template.ts +30 -0
  194. package/scripts/generate-web-module/templates/components/list.template.ts +91 -0
  195. package/scripts/generate-web-module/templates/components/multi-selector.template.ts +184 -0
  196. package/scripts/generate-web-module/templates/components/selector.template.ts +199 -0
  197. package/scripts/generate-web-module/templates/context.template.ts +121 -0
  198. package/scripts/generate-web-module/templates/data/fields.template.ts +64 -0
  199. package/scripts/generate-web-module/templates/data/index.ts +10 -0
  200. package/scripts/generate-web-module/templates/data/interface.template.ts +136 -0
  201. package/scripts/generate-web-module/templates/data/model.template.ts +327 -0
  202. package/scripts/generate-web-module/templates/data/service.template.ts +185 -0
  203. package/scripts/generate-web-module/templates/index.ts +34 -0
  204. package/scripts/generate-web-module/templates/module.template.ts +69 -0
  205. package/scripts/generate-web-module/templates/pages/detail-page.template.ts +65 -0
  206. package/scripts/generate-web-module/templates/pages/index.ts +8 -0
  207. package/scripts/generate-web-module/templates/pages/list-page.template.ts +37 -0
  208. package/scripts/generate-web-module/templates/table-hook.template.ts +182 -0
  209. package/scripts/generate-web-module/transformers/field-mapper.ts +201 -0
  210. package/scripts/generate-web-module/transformers/i18n-generator.ts +199 -0
  211. package/scripts/generate-web-module/transformers/import-resolver.ts +250 -0
  212. package/scripts/generate-web-module/transformers/index.ts +12 -0
  213. package/scripts/generate-web-module/transformers/name-transformer.ts +115 -0
  214. package/scripts/generate-web-module/transformers/parent-detector.ts +87 -0
  215. package/scripts/generate-web-module/transformers/relationship-resolver.ts +221 -0
  216. package/scripts/generate-web-module/tsconfig.json +24 -0
  217. package/scripts/generate-web-module/types/field-mapping.types.ts +141 -0
  218. package/scripts/generate-web-module/types/index.ts +9 -0
  219. package/scripts/generate-web-module/types/json-schema.interface.ts +42 -0
  220. package/scripts/generate-web-module/types/template-data.interface.ts +164 -0
  221. package/scripts/generate-web-module/utils/bootstrapper-updater.ts +145 -0
  222. package/scripts/generate-web-module/utils/file-writer.ts +115 -0
  223. package/scripts/generate-web-module/utils/i18n-updater.ts +108 -0
  224. package/scripts/generate-web-module/utils/index.ts +9 -0
  225. package/scripts/generate-web-module/validators/json-schema-validator.ts +306 -0
  226. package/src/features/user/index.ts +1 -1
  227. package/dist/chunk-FYRFMABS.js.map +0 -1
  228. package/dist/chunk-MA2L2PL2.mjs.map +0 -1
  229. /package/dist/{BlockNoteEditor-4VOBTXWC.mjs.map → BlockNoteEditor-DC33K4IF.mjs.map} +0 -0
  230. /package/dist/{chunk-HP4Q5ZVG.mjs.map → chunk-Y4XI3AZR.mjs.map} +0 -0
@@ -0,0 +1,69 @@
1
+ /**
2
+ * Module Template
3
+ *
4
+ * Generates {Module}Module.ts for module configuration and registration.
5
+ */
6
+
7
+ import { FrontendTemplateData } from "../types/template-data.interface";
8
+ import { DEFAULT_MODULE_ICON } from "../types/field-mapping.types";
9
+
10
+ /**
11
+ * Generate the module configuration file content
12
+ *
13
+ * @param data - Frontend template data
14
+ * @returns Generated file content
15
+ */
16
+ export function generateModuleTemplate(data: FrontendTemplateData): string {
17
+ const { names, moduleId, endpoint, fields, extendsContent } = data;
18
+
19
+ // Build inclusions for lists
20
+ const listFieldNames = getListFieldNames(data);
21
+ const listInclusionFields = listFieldNames.map((f) => `\`${f}\``).join(", ");
22
+
23
+ return `import { ${names.pascalCase} } from "@/features/${data.targetDir}/${names.kebabCase}/data/${names.pascalCase}";
24
+ import { createJsonApiInclusion } from "@carlonicora/nextjs-jsonapi/core";
25
+ import { ModuleFactory } from "@carlonicora/nextjs-jsonapi/permissions";
26
+ import { ${DEFAULT_MODULE_ICON} } from "lucide-react";
27
+
28
+ export const ${names.pascalCase}Module = (factory: ModuleFactory) =>
29
+ factory({
30
+ pageUrl: "/${names.pluralKebab}",
31
+ name: "${names.pluralCamel}",
32
+ model: ${names.pascalCase},
33
+ moduleId: "${moduleId}",
34
+ icon: ${DEFAULT_MODULE_ICON},
35
+ inclusions: {
36
+ lists: {
37
+ fields: [
38
+ createJsonApiInclusion("${endpoint}", [${listInclusionFields}]),
39
+ createJsonApiInclusion("users", [\`name\`, \`avatar\`]),
40
+ ],
41
+ },
42
+ },
43
+ });
44
+ `;
45
+ }
46
+
47
+ /**
48
+ * Get field names suitable for list display
49
+ */
50
+ function getListFieldNames(data: FrontendTemplateData): string[] {
51
+ const { fields, extendsContent } = data;
52
+ const fieldNames: string[] = [];
53
+
54
+ // Content-extending modules get name, tldr
55
+ if (extendsContent) {
56
+ fieldNames.push("name", "tldr");
57
+ }
58
+
59
+ // Add other displayable fields (not content or abstract)
60
+ fields.forEach((field) => {
61
+ if (!["id", "content", "abstract", "createdAt", "updatedAt"].includes(field.name)) {
62
+ if (!fieldNames.includes(field.name)) {
63
+ fieldNames.push(field.name);
64
+ }
65
+ }
66
+ });
67
+
68
+ return fieldNames;
69
+ }
@@ -0,0 +1,65 @@
1
+ /**
2
+ * Detail Page Template
3
+ *
4
+ * Generates the detail page for the module at [locale]/(main)/(features)/{plural}/[id]/page.tsx
5
+ */
6
+
7
+ import { FrontendTemplateData } from "../../types/template-data.interface";
8
+
9
+ /**
10
+ * Generate the detail page file content
11
+ *
12
+ * @param data - Frontend template data
13
+ * @returns Generated file content
14
+ */
15
+ export function generateDetailPageTemplate(data: FrontendTemplateData): string {
16
+ const { names } = data;
17
+
18
+ return `import ${names.pascalCase}Container from "@/features/${data.targetDir}/${names.kebabCase}/components/containers/${names.pascalCase}Container";
19
+ import { ${names.pascalCase}Provider } from "@/features/${data.targetDir}/${names.kebabCase}/contexts/${names.pascalCase}Context";
20
+ import { ${names.pascalCase}Interface } from "@/features/${data.targetDir}/${names.kebabCase}/data/${names.pascalCase}Interface";
21
+ import { ${names.pascalCase}Service } from "@/features/${data.targetDir}/${names.kebabCase}/data/${names.pascalCase}Service";
22
+ import { generateSpecificMetadata } from "@/utils/metadata";
23
+ import { PageContainer } from "@carlonicora/nextjs-jsonapi/components";
24
+ import { Modules } from "@carlonicora/nextjs-jsonapi/core";
25
+ import { Action } from "@carlonicora/nextjs-jsonapi/permissions";
26
+ import { ServerSession } from "@carlonicora/nextjs-jsonapi/server";
27
+ import { Metadata } from "next";
28
+ import { getTranslations } from "next-intl/server";
29
+ import { cache } from "react";
30
+
31
+ const getCached${names.pascalCase} = cache(async (id: string) => ${names.pascalCase}Service.findOne({ id }));
32
+
33
+ export async function generateMetadata(props: { params: Promise<{ id: string }> }): Promise<Metadata> {
34
+ const params = await props.params;
35
+ const t = await getTranslations();
36
+
37
+ const ${names.camelCase}: ${names.pascalCase}Interface = await getCached${names.pascalCase}(params.id);
38
+
39
+ const title = (await ServerSession.hasPermissionToModule({
40
+ module: Modules.${names.pascalCase},
41
+ action: Action.Read,
42
+ data: ${names.camelCase},
43
+ }))
44
+ ? \`[\${t(\`types.${names.pluralCamel}\`, { count: 1 })}] \${${names.camelCase}.name}\`
45
+ : \`\${t(\`types.${names.pluralCamel}\`, { count: 1 })}\`;
46
+
47
+ return await generateSpecificMetadata({ title: title });
48
+ }
49
+
50
+ export default async function ${names.pascalCase}Page(props: { params: Promise<{ id: string }> }) {
51
+ const params = await props.params;
52
+ const ${names.camelCase}: ${names.pascalCase}Interface = await getCached${names.pascalCase}(params.id);
53
+
54
+ ServerSession.checkPermission({ module: Modules.${names.pascalCase}, action: Action.Read, data: ${names.camelCase} });
55
+
56
+ return (
57
+ <${names.pascalCase}Provider dehydrated${names.pascalCase}={${names.camelCase}.dehydrate()}>
58
+ <PageContainer>
59
+ <${names.pascalCase}Container />
60
+ </PageContainer>
61
+ </${names.pascalCase}Provider>
62
+ );
63
+ }
64
+ `;
65
+ }
@@ -0,0 +1,8 @@
1
+ /**
2
+ * Page Templates Index
3
+ *
4
+ * Exports all page template generators.
5
+ */
6
+
7
+ export { generateListPageTemplate } from "./list-page.template";
8
+ export { generateDetailPageTemplate } from "./detail-page.template";
@@ -0,0 +1,37 @@
1
+ /**
2
+ * List Page Template
3
+ *
4
+ * Generates the list page for the module at [locale]/(main)/(features)/{plural}/page.tsx
5
+ */
6
+
7
+ import { FrontendTemplateData } from "../../types/template-data.interface";
8
+
9
+ /**
10
+ * Generate the list page file content
11
+ *
12
+ * @param data - Frontend template data
13
+ * @returns Generated file content
14
+ */
15
+ export function generateListPageTemplate(data: FrontendTemplateData): string {
16
+ const { names } = data;
17
+
18
+ return `import ${names.pascalCase}ListContainer from "@/features/${data.targetDir}/${names.kebabCase}/components/containers/${names.pascalCase}ListContainer";
19
+ import { ${names.pascalCase}Provider } from "@/features/${data.targetDir}/${names.kebabCase}/contexts/${names.pascalCase}Context";
20
+ import { PageContainer } from "@carlonicora/nextjs-jsonapi/components";
21
+ import { Modules } from "@carlonicora/nextjs-jsonapi/core";
22
+ import { Action } from "@carlonicora/nextjs-jsonapi/permissions";
23
+ import { ServerSession } from "@carlonicora/nextjs-jsonapi/server";
24
+
25
+ export default async function ${names.pluralPascal}ListPage() {
26
+ ServerSession.checkPermission({ module: Modules.${names.pascalCase}, action: Action.Read });
27
+
28
+ return (
29
+ <${names.pascalCase}Provider>
30
+ <PageContainer testId="page-${names.pluralKebab}-container">
31
+ <${names.pascalCase}ListContainer />
32
+ </PageContainer>
33
+ </${names.pascalCase}Provider>
34
+ );
35
+ }
36
+ `;
37
+ }
@@ -0,0 +1,182 @@
1
+ /**
2
+ * Table Hook Template
3
+ *
4
+ * Generates use{Module}TableStructure.tsx for table column definitions.
5
+ */
6
+
7
+ import { FrontendTemplateData, FrontendField } from "../types/template-data.interface";
8
+ import { toCamelCase } from "../transformers/name-transformer";
9
+
10
+ /**
11
+ * Generate the table structure hook file content
12
+ *
13
+ * @param data - Frontend template data
14
+ * @returns Generated file content
15
+ */
16
+ export function generateTableHookTemplate(data: FrontendTemplateData): string {
17
+ const { names, fields, extendsContent, tableFieldNames } = data;
18
+
19
+ const fieldColumnEntries = generateFieldColumnEntries(data);
20
+ const hasAuthors = extendsContent;
21
+ const hasRelevance = fields.some((f) => f.name === "relevance");
22
+
23
+ return `"use client";
24
+
25
+ import { ${names.pascalCase}Fields } from "@/features/${data.targetDir}/${names.kebabCase}/data/${names.pascalCase}Fields";
26
+ import { ${names.pascalCase}Interface } from "@/features/${data.targetDir}/${names.kebabCase}/data/${names.pascalCase}Interface";
27
+ import { cellDate, cellId${hasAuthors ? ", ContributorsList" : ""} } from "@carlonicora/nextjs-jsonapi/components";
28
+ import { Modules } from "@carlonicora/nextjs-jsonapi/core";${extendsContent ? `
29
+ import { ContentInterface } from "@carlonicora/nextjs-jsonapi/features";` : ""}
30
+ import {
31
+ registerTableGenerator,
32
+ TableContent,
33
+ usePageUrlGenerator,
34
+ UseTableStructureHook,
35
+ } from "@carlonicora/nextjs-jsonapi/hooks";
36
+ import { Link, Tooltip, TooltipContent, TooltipTrigger } from "@carlonicora/nextjs-jsonapi/shadcnui";
37
+ import { ColumnDef } from "@tanstack/react-table";
38
+ import { useTranslations } from "next-intl";
39
+ import { useMemo } from "react";
40
+
41
+ export const use${names.pascalCase}TableStructure: UseTableStructureHook<${names.pascalCase}Interface, ${names.pascalCase}Fields> = (params) => {
42
+ const t = useTranslations();
43
+ const generateUrl = usePageUrlGenerator();
44
+
45
+ const tableData = useMemo(() => {
46
+ return params.data.map((${names.camelCase}: ${names.pascalCase}Interface) => {
47
+ const entry: TableContent<${names.pascalCase}Interface> = {
48
+ jsonApiData: ${names.camelCase},
49
+ };
50
+ entry[${names.pascalCase}Fields.${names.camelCase}Id] = ${names.camelCase}.id;
51
+ params.fields.forEach((field) => {
52
+ entry[field] = ${names.camelCase}[field as keyof ${names.pascalCase}Interface];
53
+ });
54
+ return entry;
55
+ });
56
+ }, [params.data, params.fields]);
57
+
58
+ const fieldColumnMap: Partial<Record<${names.pascalCase}Fields, () => any>> = {
59
+ [${names.pascalCase}Fields.${names.camelCase}Id]: () =>
60
+ cellId({
61
+ name: "${names.camelCase}Id",
62
+ checkedIds: params.checkedIds,
63
+ toggleId: params.toggleId,
64
+ }),
65
+ ${fieldColumnEntries}
66
+ [${names.pascalCase}Fields.createdAt]: () =>
67
+ cellDate({
68
+ name: "createdAt",
69
+ title: t(\`generic.date.create\`),
70
+ }),
71
+ [${names.pascalCase}Fields.updatedAt]: () =>
72
+ cellDate({
73
+ name: "updatedAt",
74
+ title: t(\`generic.date.update\`),
75
+ }),
76
+ };
77
+
78
+ const columns = useMemo(() => {
79
+ return params.fields.map((field) => fieldColumnMap[field]?.()).filter((col) => col !== undefined) as ColumnDef<
80
+ TableContent<${names.pascalCase}Interface>
81
+ >[];
82
+ }, [params.fields, fieldColumnMap, t, generateUrl]);
83
+
84
+ return useMemo(() => ({ data: tableData, columns: columns }), [tableData, columns]);
85
+ };
86
+
87
+ registerTableGenerator(Modules.${names.pascalCase}, use${names.pascalCase}TableStructure);
88
+ `;
89
+ }
90
+
91
+ /**
92
+ * Generate field column map entries
93
+ */
94
+ function generateFieldColumnEntries(data: FrontendTemplateData): string {
95
+ const { names, fields, extendsContent } = data;
96
+ const entries: string[] = [];
97
+
98
+ // Name field (with link and tooltip for tldr if Content-extending)
99
+ if (fields.some((f) => f.name === "name") || extendsContent) {
100
+ entries.push(` [${names.pascalCase}Fields.name]: () => ({
101
+ id: "name",
102
+ accessorKey: "name",
103
+ header: t(\`features.${names.camelCase}.fields.name.label\`),
104
+ cell: ({ row }: { row: TableContent<${names.pascalCase}Interface> }) => {
105
+ const ${names.camelCase}: ${names.pascalCase}Interface = row.original.jsonApiData;
106
+ return (
107
+ <Tooltip>
108
+ <TooltipTrigger>
109
+ <Link href={generateUrl({ page: Modules.${names.pascalCase}, id: ${names.camelCase}.id })}>{${names.camelCase}.name}</Link>
110
+ </TooltipTrigger>${extendsContent ? `
111
+ <TooltipContent>{${names.camelCase}.tldr}</TooltipContent>` : `
112
+ <TooltipContent>{${names.camelCase}.name}</TooltipContent>`}
113
+ </Tooltip>
114
+ );
115
+ },
116
+ enableSorting: false,
117
+ enableHiding: false,
118
+ }),`);
119
+ }
120
+
121
+ // Authors field for Content-extending modules
122
+ if (extendsContent) {
123
+ entries.push(` [${names.pascalCase}Fields.authors]: () => ({
124
+ id: "authors",
125
+ accessorKey: "authors",
126
+ header: t(\`generic.relationships.author.label\`),
127
+ cell: ({ row }: { row: TableContent<ContentInterface> }) => {
128
+ const content: ContentInterface = row.original.jsonApiData;
129
+ return <ContributorsList content={content} />;
130
+ },
131
+ enableSorting: false,
132
+ enableHiding: false,
133
+ }),`);
134
+ }
135
+
136
+ // Other displayable fields (excluding id, name, tldr, abstract, content, dates)
137
+ const displayableFields = fields.filter(
138
+ (f) => !["id", "name", "tldr", "abstract", "content", "createdAt", "updatedAt"].includes(f.name)
139
+ );
140
+
141
+ displayableFields.forEach((field) => {
142
+ if (field.type === "string") {
143
+ entries.push(` [${names.pascalCase}Fields.${field.name}]: () => ({
144
+ id: "${field.name}",
145
+ accessorKey: "${field.name}",
146
+ header: t(\`features.${names.camelCase}.fields.${field.name}.label\`),
147
+ cell: ({ row }: { row: TableContent<${names.pascalCase}Interface> }) => {
148
+ const ${names.camelCase}: ${names.pascalCase}Interface = row.original.jsonApiData;
149
+ return <span>{${names.camelCase}.${field.name}}</span>;
150
+ },
151
+ enableSorting: false,
152
+ enableHiding: false,
153
+ }),`);
154
+ } else if (field.type === "boolean") {
155
+ entries.push(` [${names.pascalCase}Fields.${field.name}]: () => ({
156
+ id: "${field.name}",
157
+ accessorKey: "${field.name}",
158
+ header: t(\`features.${names.camelCase}.fields.${field.name}.label\`),
159
+ cell: ({ row }: { row: TableContent<${names.pascalCase}Interface> }) => {
160
+ const ${names.camelCase}: ${names.pascalCase}Interface = row.original.jsonApiData;
161
+ return <span>{${names.camelCase}.${field.name} ? t(\`generic.yes\`) : t(\`generic.no\`)}</span>;
162
+ },
163
+ enableSorting: false,
164
+ enableHiding: false,
165
+ }),`);
166
+ } else if (field.type === "number") {
167
+ entries.push(` [${names.pascalCase}Fields.${field.name}]: () => ({
168
+ id: "${field.name}",
169
+ accessorKey: "${field.name}",
170
+ header: t(\`features.${names.camelCase}.fields.${field.name}.label\`),
171
+ cell: ({ row }: { row: TableContent<${names.pascalCase}Interface> }) => {
172
+ const ${names.camelCase}: ${names.pascalCase}Interface = row.original.jsonApiData;
173
+ return <span>{${names.camelCase}.${field.name}?.toString()}</span>;
174
+ },
175
+ enableSorting: false,
176
+ enableHiding: false,
177
+ }),`);
178
+ }
179
+ });
180
+
181
+ return entries.join("\n");
182
+ }
@@ -0,0 +1,201 @@
1
+ /**
2
+ * Field Mapper
3
+ *
4
+ * Maps JSON schema fields to their TypeScript, Zod, and form component equivalents.
5
+ */
6
+
7
+ import { JsonFieldDefinition } from "../types/json-schema.interface";
8
+ import { FrontendField, FormComponentType } from "../types/template-data.interface";
9
+ import {
10
+ getTsType,
11
+ getZodBase,
12
+ getFormComponent,
13
+ isContentField,
14
+ SPECIAL_FIELD_COMPONENTS,
15
+ } from "../types/field-mapping.types";
16
+
17
+ /**
18
+ * Map a JSON field definition to a frontend field
19
+ *
20
+ * @param field - JSON field definition
21
+ * @param moduleName - Module name for i18n error messages
22
+ * @returns Frontend field representation
23
+ */
24
+ export function mapField(field: JsonFieldDefinition, moduleName: string): FrontendField {
25
+ const formComponent = getFormComponent(field.name, field.type);
26
+ const isContent = isContentField(field.name);
27
+
28
+ return {
29
+ name: field.name,
30
+ type: field.type,
31
+ tsType: getTsType(field.type),
32
+ zodSchema: buildZodSchema(field, moduleName),
33
+ formComponent,
34
+ nullable: field.nullable,
35
+ isContentField: isContent,
36
+ };
37
+ }
38
+
39
+ /**
40
+ * Map all fields from JSON schema
41
+ *
42
+ * @param fields - Array of JSON field definitions
43
+ * @param moduleName - Module name for i18n error messages
44
+ * @returns Array of frontend field representations
45
+ */
46
+ export function mapFields(fields: JsonFieldDefinition[], moduleName: string): FrontendField[] {
47
+ return fields.map((field) => mapField(field, moduleName));
48
+ }
49
+
50
+ /**
51
+ * Build complete Zod schema string for a field
52
+ *
53
+ * @param field - JSON field definition
54
+ * @param moduleName - Module name (lowercase) for i18n keys
55
+ * @returns Zod schema code string
56
+ */
57
+ export function buildZodSchema(field: JsonFieldDefinition, moduleName: string): string {
58
+ // Content fields always use z.any()
59
+ if (isContentField(field.name)) {
60
+ return "z.any()";
61
+ }
62
+
63
+ const base = getZodBase(field.type);
64
+
65
+ // String fields with validation
66
+ if (field.type === "string") {
67
+ if (field.nullable) {
68
+ return `${base}.optional()`;
69
+ }
70
+ // Required string with error message
71
+ return `${base}.min(1, { message: t(\`features.${moduleName.toLowerCase()}.fields.${field.name}.error\`) })`;
72
+ }
73
+
74
+ // Number fields
75
+ if (field.type === "number") {
76
+ if (field.nullable) {
77
+ return `${base}.optional()`;
78
+ }
79
+ return base;
80
+ }
81
+
82
+ // Boolean fields
83
+ if (field.type === "boolean") {
84
+ if (field.nullable) {
85
+ return `${base}.optional()`;
86
+ }
87
+ return base;
88
+ }
89
+
90
+ // Date fields
91
+ if (field.type === "date") {
92
+ if (field.nullable) {
93
+ return `z.coerce.date().optional()`;
94
+ }
95
+ return `z.coerce.date()`;
96
+ }
97
+
98
+ // Default case
99
+ if (field.nullable) {
100
+ return `${base}.optional()`;
101
+ }
102
+ return base;
103
+ }
104
+
105
+ /**
106
+ * Get the form JSX for a field
107
+ *
108
+ * @param field - Frontend field
109
+ * @param moduleName - Module name for i18n keys
110
+ * @returns JSX code for the form field
111
+ */
112
+ export function getFormFieldJsx(field: FrontendField, moduleName: string): string {
113
+ const lowerModuleName = moduleName.toLowerCase();
114
+ const isRequired = !field.nullable;
115
+
116
+ switch (field.formComponent) {
117
+ case "BlockNoteEditor":
118
+ return `<FormContainerGeneric form={form} id="${field.name}" name={t(\`features.${lowerModuleName}.fields.${field.name}.label\`)}>
119
+ <BlockNoteEditorContainer
120
+ id={form.getValues("id")}
121
+ type="${lowerModuleName}"
122
+ initialContent={form.getValues("${field.name}")}
123
+ onChange={(content, isEmpty, hasUnresolvedDiff) => {
124
+ form.setValue("${field.name}", content);
125
+ }}
126
+ placeholder={t(\`features.${lowerModuleName}.fields.${field.name}.placeholder\`)}
127
+ bordered
128
+ />
129
+ </FormContainerGeneric>`;
130
+
131
+ case "FormTextarea":
132
+ return `<FormTextarea
133
+ form={form}
134
+ id="${field.name}"
135
+ name={t(\`features.${lowerModuleName}.fields.${field.name}.label\`)}
136
+ placeholder={t(\`features.${lowerModuleName}.fields.${field.name}.placeholder\`)}
137
+ ${isRequired ? "isRequired" : ""}
138
+ />`;
139
+
140
+ case "Checkbox":
141
+ return `<FormCheckbox
142
+ form={form}
143
+ id="${field.name}"
144
+ name={t(\`features.${lowerModuleName}.fields.${field.name}.label\`)}
145
+ />`;
146
+
147
+ case "DatePicker":
148
+ return `<FormDatePicker
149
+ form={form}
150
+ id="${field.name}"
151
+ name={t(\`features.${lowerModuleName}.fields.${field.name}.label\`)}
152
+ ${isRequired ? "isRequired" : ""}
153
+ />`;
154
+
155
+ case "FormInputNumber":
156
+ return `<FormInput
157
+ form={form}
158
+ id="${field.name}"
159
+ name={t(\`features.${lowerModuleName}.fields.${field.name}.label\`)}
160
+ placeholder={t(\`features.${lowerModuleName}.fields.${field.name}.placeholder\`)}
161
+ type="number"
162
+ ${isRequired ? "isRequired" : ""}
163
+ />`;
164
+
165
+ case "FormInput":
166
+ default:
167
+ return `<FormInput
168
+ form={form}
169
+ id="${field.name}"
170
+ name={t(\`features.${lowerModuleName}.fields.${field.name}.label\`)}
171
+ placeholder={t(\`features.${lowerModuleName}.fields.${field.name}.placeholder\`)}
172
+ ${isRequired ? "isRequired" : ""}
173
+ />`;
174
+ }
175
+ }
176
+
177
+ /**
178
+ * Filter out inherited fields that shouldn't be generated
179
+ *
180
+ * @param fields - Array of frontend fields
181
+ * @param inheritedFields - Array of field names inherited from parent
182
+ * @returns Filtered array of fields
183
+ */
184
+ export function filterInheritedFields(
185
+ fields: FrontendField[],
186
+ inheritedFields: string[]
187
+ ): FrontendField[] {
188
+ return fields.filter((f) => !inheritedFields.includes(f.name));
189
+ }
190
+
191
+ /**
192
+ * Get fields that should be included in the editor form
193
+ *
194
+ * @param fields - Array of frontend fields
195
+ * @returns Fields suitable for form editing
196
+ */
197
+ export function getEditorFields(fields: FrontendField[]): FrontendField[] {
198
+ // Exclude auto-generated fields like createdAt, updatedAt
199
+ const excludeFields = ["createdAt", "updatedAt", "id"];
200
+ return fields.filter((f) => !excludeFields.includes(f.name));
201
+ }