@cdmbase/wiki-browser 12.0.18-alpha.5

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 (367) hide show
  1. package/LICENSE +21 -0
  2. package/lib/components/Logo.d.ts +4 -0
  3. package/lib/components/Logo.d.ts.map +1 -0
  4. package/lib/components/Logo.js +16 -0
  5. package/lib/components/Logo.js.map +1 -0
  6. package/lib/components/help/SidebarSearch.d.ts +8 -0
  7. package/lib/components/help/SidebarSearch.d.ts.map +1 -0
  8. package/lib/components/help/SidebarSearch.js +111 -0
  9. package/lib/components/help/SidebarSearch.js.map +1 -0
  10. package/lib/components/help/index.d.ts +2 -0
  11. package/lib/components/help/index.d.ts.map +1 -0
  12. package/lib/components/landing/FeatureCard.d.ts +13 -0
  13. package/lib/components/landing/FeatureCard.d.ts.map +1 -0
  14. package/lib/components/landing/FeatureCard.js +85 -0
  15. package/lib/components/landing/FeatureCard.js.map +1 -0
  16. package/lib/components/landing/QuickLinkCard.d.ts +8 -0
  17. package/lib/components/landing/QuickLinkCard.d.ts.map +1 -0
  18. package/lib/components/landing/QuickLinkCard.js +26 -0
  19. package/lib/components/landing/QuickLinkCard.js.map +1 -0
  20. package/lib/components/landing/SearchInput.d.ts +10 -0
  21. package/lib/components/landing/SearchInput.d.ts.map +1 -0
  22. package/lib/components/landing/SearchInput.js +223 -0
  23. package/lib/components/landing/SearchInput.js.map +1 -0
  24. package/lib/components/landing/index.d.ts +4 -0
  25. package/lib/components/landing/index.d.ts.map +1 -0
  26. package/lib/components/welcome.d.ts +3 -0
  27. package/lib/components/welcome.d.ts.map +1 -0
  28. package/lib/compute.d.ts +4 -0
  29. package/lib/compute.d.ts.map +1 -0
  30. package/lib/compute.js +96 -0
  31. package/lib/compute.js.map +1 -0
  32. package/lib/config/env-config.d.ts +4 -0
  33. package/lib/config/env-config.d.ts.map +1 -0
  34. package/lib/config/env-config.js +7 -0
  35. package/lib/config/env-config.js.map +1 -0
  36. package/lib/docs.config.d.ts +48 -0
  37. package/lib/docs.config.d.ts.map +1 -0
  38. package/lib/index.d.ts +4 -0
  39. package/lib/index.d.ts.map +1 -0
  40. package/lib/index.js +2 -0
  41. package/lib/index.js.map +1 -0
  42. package/lib/loaders/search.d.ts +1 -0
  43. package/lib/loaders/search.d.ts.map +1 -0
  44. package/lib/module.d.ts +4 -0
  45. package/lib/module.d.ts.map +1 -0
  46. package/lib/module.js +11 -0
  47. package/lib/module.js.map +1 -0
  48. package/lib/pages/ArticlePage/ArticlePage.d.ts +4 -0
  49. package/lib/pages/ArticlePage/ArticlePage.d.ts.map +1 -0
  50. package/lib/pages/ArticlePage/ArticlePage.js +222 -0
  51. package/lib/pages/ArticlePage/ArticlePage.js.map +1 -0
  52. package/lib/pages/ArticlePage/index.d.ts +3 -0
  53. package/lib/pages/ArticlePage/index.d.ts.map +1 -0
  54. package/lib/pages/ArticlePage/index.js +3 -0
  55. package/lib/pages/ArticlePage/index.js.map +1 -0
  56. package/lib/pages/CategoryCollection/CategoryCollection.d.ts +4 -0
  57. package/lib/pages/CategoryCollection/CategoryCollection.d.ts.map +1 -0
  58. package/lib/pages/CategoryCollection/CategoryCollection.js +103 -0
  59. package/lib/pages/CategoryCollection/CategoryCollection.js.map +1 -0
  60. package/lib/pages/CategoryCollection/index.d.ts +3 -0
  61. package/lib/pages/CategoryCollection/index.d.ts.map +1 -0
  62. package/lib/pages/CategoryCollection/index.js +3 -0
  63. package/lib/pages/CategoryCollection/index.js.map +1 -0
  64. package/lib/pages/Help/HelpIndex.d.ts +4 -0
  65. package/lib/pages/Help/HelpIndex.d.ts.map +1 -0
  66. package/lib/pages/Help/HelpIndex.js +44 -0
  67. package/lib/pages/Help/HelpIndex.js.map +1 -0
  68. package/lib/pages/Help/index.d.ts +4 -0
  69. package/lib/pages/Help/index.d.ts.map +1 -0
  70. package/lib/pages/Help/index.js +226 -0
  71. package/lib/pages/Help/index.js.map +1 -0
  72. package/lib/pages/Landing/index.d.ts +3 -0
  73. package/lib/pages/Landing/index.d.ts.map +1 -0
  74. package/lib/pages/Landing/index.js +281 -0
  75. package/lib/pages/Landing/index.js.map +1 -0
  76. package/lib/routes.json +2533 -0
  77. package/lib/seo.d.ts +22 -0
  78. package/lib/seo.d.ts.map +1 -0
  79. package/lib/slot-fill/FooterFill.d.ts +3 -0
  80. package/lib/slot-fill/FooterFill.d.ts.map +1 -0
  81. package/lib/slot-fill/FooterFill.js +18 -0
  82. package/lib/slot-fill/FooterFill.js.map +1 -0
  83. package/lib/slot-fill/LogoFill.d.ts +5 -0
  84. package/lib/slot-fill/LogoFill.d.ts.map +1 -0
  85. package/lib/slot-fill/LogoFill.js +74 -0
  86. package/lib/slot-fill/LogoFill.js.map +1 -0
  87. package/lib/slot-fill/consts.d.ts +5 -0
  88. package/lib/slot-fill/consts.d.ts.map +1 -0
  89. package/lib/slot-fill/consts.js +1 -0
  90. package/lib/slot-fill/consts.js.map +1 -0
  91. package/lib/slot-fill/index.d.ts +4 -0
  92. package/lib/slot-fill/index.d.ts.map +1 -0
  93. package/lib/templates/assets/images/add-link-frontend.png +0 -0
  94. package/lib/templates/assets/images/add-package-backend.png +0 -0
  95. package/lib/templates/assets/images/add-to-backend-module.png +0 -0
  96. package/lib/templates/assets/images/add-upload-client-frontend.png +0 -0
  97. package/lib/templates/assets/images/additional-parameters.png +0 -0
  98. package/lib/templates/assets/images/aeh-implementation.png +0 -0
  99. package/lib/templates/assets/images/aeh-usage.png +0 -0
  100. package/lib/templates/assets/images/apollo-client/recommendation_cache_mgmt.png +0 -0
  101. package/lib/templates/assets/images/app-deploy-new-version/jenkins1.PNG +0 -0
  102. package/lib/templates/assets/images/app-deploy-new-version/jenkins2.PNG +0 -0
  103. package/lib/templates/assets/images/auth-wrapper-code.png +0 -0
  104. package/lib/templates/assets/images/cdebase.png +0 -0
  105. package/lib/templates/assets/images/cdm-locales-directory.png +0 -0
  106. package/lib/templates/assets/images/client-settings.png +0 -0
  107. package/lib/templates/assets/images/codegen_file_update.png +0 -0
  108. package/lib/templates/assets/images/configuration.png +0 -0
  109. package/lib/templates/assets/images/copy-plugin.png +0 -0
  110. package/lib/templates/assets/images/docusaurus.png +0 -0
  111. package/lib/templates/assets/images/error-link.png +0 -0
  112. package/lib/templates/assets/images/error-sample.png +0 -0
  113. package/lib/templates/assets/images/extension copy.png +0 -0
  114. package/lib/templates/assets/images/extension.png +0 -0
  115. package/lib/templates/assets/images/graphql/graphql-folder-backend.png +0 -0
  116. package/lib/templates/assets/images/graphql/graphql-folder-with-gql.png +0 -0
  117. package/lib/templates/assets/images/i18n-config.png +0 -0
  118. package/lib/templates/assets/images/image.png +0 -0
  119. package/lib/templates/assets/images/logo.svg +10 -0
  120. package/lib/templates/assets/images/logo1.svg +1 -0
  121. package/lib/templates/assets/images/modify-upload-false-server.png +0 -0
  122. package/lib/templates/assets/images/navigation-auth-enabled.png +0 -0
  123. package/lib/templates/assets/images/org-dashboard-navigation.png +0 -0
  124. package/lib/templates/assets/images/org-navigation.png +0 -0
  125. package/lib/templates/assets/images/preferences_graphql_type.png +0 -0
  126. package/lib/templates/assets/images/provider.png +0 -0
  127. package/lib/templates/assets/images/route-config.png +0 -0
  128. package/lib/templates/assets/images/service-accounts.png +0 -0
  129. package/lib/templates/assets/images/source-code/source-code-environments.png +0 -0
  130. package/lib/templates/assets/images/source-code/source-code-organization.png +0 -0
  131. package/lib/templates/assets/images/spin-clone-develop-deployment/jenkins-changes.png +0 -0
  132. package/lib/templates/assets/images/spin-clone-develop-deployment/lerna-changes.png +0 -0
  133. package/lib/templates/assets/images/spin-clone-develop-deployment/root-package-json-changes.png +0 -0
  134. package/lib/templates/assets/images/spin-clone-develop-deployment/values-dev-changes.png +0 -0
  135. package/lib/templates/assets/images/sso-mappers.png +0 -0
  136. package/lib/templates/assets/images/sso-picture-mapper.png +0 -0
  137. package/lib/templates/assets/images/sso-settings.png +0 -0
  138. package/lib/templates/assets/images/timesheet_apollo_cache.png +0 -0
  139. package/lib/templates/assets/images/timesheet_query.png +0 -0
  140. package/lib/templates/assets/images/tutorial/docsVersionDropdown.png +0 -0
  141. package/lib/templates/assets/images/tutorial/localeDropdown.png +0 -0
  142. package/lib/templates/assets/images/unauthenticated.png +0 -0
  143. package/lib/templates/assets/images/undraw_docusaurus_mountain.svg +170 -0
  144. package/lib/templates/assets/images/undraw_docusaurus_react.svg +169 -0
  145. package/lib/templates/assets/images/undraw_docusaurus_tree.svg +1 -0
  146. package/lib/templates/assets/images/vite-plugin-config.png +0 -0
  147. package/lib/templates/content/docs/Generators/Project/generate-fullproject.md +12 -0
  148. package/lib/templates/content/docs/LLM/Logger.llm.md +194 -0
  149. package/lib/templates/content/docs/LLM/backend-proxies-services-llm.md +2687 -0
  150. package/lib/templates/content/docs/LLM/backend-service-llm.md +3384 -0
  151. package/lib/templates/content/docs/LLM/db_migration_llm.md +954 -0
  152. package/lib/templates/content/docs/LLM/frontend/REMIX-15.3-upgrade-llm.md +1245 -0
  153. package/lib/templates/content/docs/LLM/inngest/INNGEST_FUNCTION_DEVELOPMENT_GUIDE_LLM.md +1241 -0
  154. package/lib/templates/content/docs/LLM/inngest/INNGEST_NAMESPACE_LLM.md +384 -0
  155. package/lib/templates/content/docs/LLM/llm_workflow_namespace.md +384 -0
  156. package/lib/templates/content/docs/LLM/organization-components-form-llm.md +1395 -0
  157. package/lib/templates/content/docs/LLM/page-component-llm.md +173 -0
  158. package/lib/templates/content/docs/LLM/preferences-settings-llm.md +2781 -0
  159. package/lib/templates/content/docs/LLM/tailwind-css-llm.md +502 -0
  160. package/lib/templates/content/docs/UI/SchemaBasedUI.md +334 -0
  161. package/lib/templates/content/docs/UI/SlotFillComponent.md +334 -0
  162. package/lib/templates/content/docs/adminide-modules/account/auth0-login.md +31 -0
  163. package/lib/templates/content/docs/adminide-modules/account/index.md +14 -0
  164. package/lib/templates/content/docs/adminide-modules/account/keycloak-remix-setup.md +86 -0
  165. package/lib/templates/content/docs/adminide-modules/account/remix-auth-setup.md +79 -0
  166. package/lib/templates/content/docs/adminide-modules/account/various-auth-qatest.md +157 -0
  167. package/lib/templates/content/docs/adminide-modules/api-builders/graphql.md +906 -0
  168. package/lib/templates/content/docs/adminide-modules/billing/payments/index.md +14 -0
  169. package/lib/templates/content/docs/adminide-modules/billing/payments/stripe/index.md +14 -0
  170. package/lib/templates/content/docs/adminide-modules/billing/payments/stripe/settingup-stripe-locally.md +25 -0
  171. package/lib/templates/content/docs/adminide-modules/billing/tier-config.md +293 -0
  172. package/lib/templates/content/docs/adminide-modules/connectors/Connector.md +207 -0
  173. package/lib/templates/content/docs/adminide-modules/file-upload/index.md +16 -0
  174. package/lib/templates/content/docs/adminide-modules/file-upload/setup.md +435 -0
  175. package/lib/templates/content/docs/adminide-modules/file-upload/upload-file-using-signed-url.md +161 -0
  176. package/lib/templates/content/docs/adminide-modules/preferences/AddAdditionalPermissions.md +151 -0
  177. package/lib/templates/content/docs/adminide-modules/preferences/Configuration.md +241 -0
  178. package/lib/templates/content/docs/adminide-modules/preferences/Policy-Configuration.md +61 -0
  179. package/lib/templates/content/docs/adminide-modules/preferences/UI-components/ResourceSettingsLoader.md +319 -0
  180. package/lib/templates/content/docs/adminide-modules/preferences/contribute_scope_target.md +280 -0
  181. package/lib/templates/content/docs/adminide-modules/preferences/generate-urii.md +94 -0
  182. package/lib/templates/content/docs/adminide-modules/preferences/index.md +28 -0
  183. package/lib/templates/content/docs/adminide-modules/preferences/machine-configuration.md +157 -0
  184. package/lib/templates/content/docs/adminide-modules/preferences/pageSettings/generateCdecodeUri.md +1289 -0
  185. package/lib/templates/content/docs/adminide-modules/preferences/pageSettings/migratingFromUseSettings.md +215 -0
  186. package/lib/templates/content/docs/adminide-modules/preferences/permissions/Roles-Permissions.md +72 -0
  187. package/lib/templates/content/docs/adminide-modules/preferences/permissions/settingUserPermissions.md +139 -0
  188. package/lib/templates/content/docs/adminide-modules/preferences/preference-dependency.md +138 -0
  189. package/lib/templates/content/docs/adminide-modules/preferences/route-based-configuration.md +41 -0
  190. package/lib/templates/content/docs/adminide-modules/preferences/schema-configuration.md +71 -0
  191. package/lib/templates/content/docs/adminide-modules/preferences/supported.md +24 -0
  192. package/lib/templates/content/docs/adminide-modules/preferences/useSettingsLoader.md +248 -0
  193. package/lib/templates/content/docs/adminide-modules/project-tools/auth-providers.md +1317 -0
  194. package/lib/templates/content/docs/adminide-modules/project-tools/keycloak-guide.md +543 -0
  195. package/lib/templates/content/docs/adminide-modules/project-tools/tenant-management/tenant-based-authentication.md +846 -0
  196. package/lib/templates/content/docs/adminide-modules/project-tools/tenant-management/tenant-management.md +708 -0
  197. package/lib/templates/content/docs/adminide-modules/project-tools/tenant-management/tenants.md +1117 -0
  198. package/lib/templates/content/docs/chrome-extension/index.md +14 -0
  199. package/lib/templates/content/docs/chrome-extension/setup.md +30 -0
  200. package/lib/templates/content/docs/contributing/adding-package.md +23 -0
  201. package/lib/templates/content/docs/contributing/adding_new_modules.md +99 -0
  202. package/lib/templates/content/docs/contributing/architecture-updates.md +19 -0
  203. package/lib/templates/content/docs/contributing/avoid-using-promises-ui.md +116 -0
  204. package/lib/templates/content/docs/contributing/coding-guidelines.md +111 -0
  205. package/lib/templates/content/docs/contributing/do-and-dont.md +42 -0
  206. package/lib/templates/content/docs/contributing/faq.md +22 -0
  207. package/lib/templates/content/docs/contributing/folder-setup/browser.md +12 -0
  208. package/lib/templates/content/docs/contributing/folder-setup/config.md +12 -0
  209. package/lib/templates/content/docs/contributing/folder-setup/containers-server.md +12 -0
  210. package/lib/templates/content/docs/contributing/folder-setup/core.md +12 -0
  211. package/lib/templates/content/docs/contributing/folder-setup/graphql.md +12 -0
  212. package/lib/templates/content/docs/contributing/folder-setup/index.md +30 -0
  213. package/lib/templates/content/docs/contributing/folder-setup/module.md +12 -0
  214. package/lib/templates/content/docs/contributing/folder-setup/server.md +12 -0
  215. package/lib/templates/content/docs/contributing/folder-setup/services.md +12 -0
  216. package/lib/templates/content/docs/contributing/folder-setup/store.md +12 -0
  217. package/lib/templates/content/docs/contributing/frontend-coding.md +30 -0
  218. package/lib/templates/content/docs/contributing/git-subtree-sharing.md +73 -0
  219. package/lib/templates/content/docs/contributing/graphql-subscriptions.md +69 -0
  220. package/lib/templates/content/docs/contributing/how-to-contribute.md +30 -0
  221. package/lib/templates/content/docs/contributing/how_to_check_pure_esm.md +29 -0
  222. package/lib/templates/content/docs/contributing/index.md +60 -0
  223. package/lib/templates/content/docs/contributing/installation-issues.md +23 -0
  224. package/lib/templates/content/docs/contributing/keyboard-shortcut.md +131 -0
  225. package/lib/templates/content/docs/contributing/language/locale-support.md +12 -0
  226. package/lib/templates/content/docs/contributing/lerna-build-tools.md +516 -0
  227. package/lib/templates/content/docs/contributing/lerna-yarn-workspaces.md +95 -0
  228. package/lib/templates/content/docs/contributing/lint-and-formatter.md +20 -0
  229. package/lib/templates/content/docs/contributing/mobile-setup.md +16 -0
  230. package/lib/templates/content/docs/contributing/project-setup.md +233 -0
  231. package/lib/templates/content/docs/contributing/react/index.md +14 -0
  232. package/lib/templates/content/docs/contributing/react/lazy-component.md +70 -0
  233. package/lib/templates/content/docs/contributing/run-various-options.md +124 -0
  234. package/lib/templates/content/docs/contributing/schema-first-graphql-types.md +37 -0
  235. package/lib/templates/content/docs/contributing/source-code-organization.md +57 -0
  236. package/lib/templates/content/docs/contributing/staging-docker.md +88 -0
  237. package/lib/templates/content/docs/contributing/third-party/apollo-client-v3-tutorials.md +28 -0
  238. package/lib/templates/content/docs/contributing/third-party/index.md +18 -0
  239. package/lib/templates/content/docs/contributing/typescript-contribution.md +16 -0
  240. package/lib/templates/content/docs/devops/app-deploy-new-version.md +30 -0
  241. package/lib/templates/content/docs/devops/index.md +14 -0
  242. package/lib/templates/content/docs/devops/mobile-jenkins-build.md +40 -0
  243. package/lib/templates/content/docs/devops/versioning-the-project.md +128 -0
  244. package/lib/templates/content/docs/error-handler/application-error-handler.md +40 -0
  245. package/lib/templates/content/docs/error-handler/error-handling.md +26 -0
  246. package/lib/templates/content/docs/error-handler/index.md +16 -0
  247. package/lib/templates/content/docs/error-handler/logging-errors.md +14 -0
  248. package/lib/templates/content/docs/feature-api/copy-operation.md +427 -0
  249. package/lib/templates/content/docs/feature-api/feature-browser/assets.md +46 -0
  250. package/lib/templates/content/docs/feature-api/feature-browser/auth-permissions.md +12 -0
  251. package/lib/templates/content/docs/feature-api/feature-browser/feature.md +131 -0
  252. package/lib/templates/content/docs/feature-api/feature-browser/index.md +22 -0
  253. package/lib/templates/content/docs/feature-api/feature-browser/routes-menu.md +110 -0
  254. package/lib/templates/content/docs/feature-api/feature-browser/routing-convention.md +124 -0
  255. package/lib/templates/content/docs/feature-api/feature-browser/routing.md +338 -0
  256. package/lib/templates/content/docs/feature-api/feature-mobile/auth-permissions.md +20 -0
  257. package/lib/templates/content/docs/feature-api/feature-mobile/feature.md +130 -0
  258. package/lib/templates/content/docs/feature-api/feature-mobile/index.md +18 -0
  259. package/lib/templates/content/docs/feature-api/feature-mobile/navigation.md +187 -0
  260. package/lib/templates/content/docs/feature-api/feature-server/Scheduling.md +44 -0
  261. package/lib/templates/content/docs/feature-api/feature-server/dataloader.md +320 -0
  262. package/lib/templates/content/docs/feature-api/feature-server/dependency-injection.md +81 -0
  263. package/lib/templates/content/docs/feature-api/feature-server/feature.md +65 -0
  264. package/lib/templates/content/docs/feature-api/feature-server/generic-dataloader.md +135 -0
  265. package/lib/templates/content/docs/feature-api/feature-server/index.md +40 -0
  266. package/lib/templates/content/docs/feature-api/feature-server/migration.md +127 -0
  267. package/lib/templates/content/docs/feature-api/feature-server/mongo-model.md +72 -0
  268. package/lib/templates/content/docs/feature-api/feature-server/permissions.md +12 -0
  269. package/lib/templates/content/docs/feature-api/feature-server/policies.md +57 -0
  270. package/lib/templates/content/docs/feature-api/feature-server/preferences.md +57 -0
  271. package/lib/templates/content/docs/feature-api/feature-server/repositories.md +114 -0
  272. package/lib/templates/content/docs/feature-api/feature-server/resolvers.md +126 -0
  273. package/lib/templates/content/docs/feature-api/feature-server/rules.md +132 -0
  274. package/lib/templates/content/docs/feature-api/feature-server/schema.md +12 -0
  275. package/lib/templates/content/docs/feature-api/feature-server/services.md +102 -0
  276. package/lib/templates/content/docs/feature-api/feature-server/setup-resource-crud.md +359 -0
  277. package/lib/templates/content/docs/feature-api/index.md +18 -0
  278. package/lib/templates/content/docs/graphql/apolloClient-mutation.md +94 -0
  279. package/lib/templates/content/docs/graphql/index.md +14 -0
  280. package/lib/templates/content/docs/graphql/scalars.md +15 -0
  281. package/lib/templates/content/docs/help/index.md +14 -0
  282. package/lib/templates/content/docs/help/intro.md +16 -0
  283. package/lib/templates/content/docs/intl/ant-design-menu-translation.md +74 -0
  284. package/lib/templates/content/docs/intl/intl-namespace.md +129 -0
  285. package/lib/templates/content/docs/intl/vite-plugin-intl.md +87 -0
  286. package/lib/templates/content/docs/intl/webpack-plugin-intl.md +12 -0
  287. package/lib/templates/content/docs/intro.md +18 -0
  288. package/lib/templates/content/docs/knowledge/basic-fullstack.md +238 -0
  289. package/lib/templates/content/docs/mailing/index.md +14 -0
  290. package/lib/templates/content/docs/mailing/mailing-template.md +148 -0
  291. package/lib/templates/content/docs/mobile/App-navigation-generator.md +410 -0
  292. package/lib/templates/content/docs/mobile/MobileTestCases.md +264 -0
  293. package/lib/templates/content/docs/mobile/eas-profile-build.md +107 -0
  294. package/lib/templates/content/docs/mobile/expo-push-notification-setup.md +216 -0
  295. package/lib/templates/content/docs/mobile/index.md +14 -0
  296. package/lib/templates/content/docs/mobile/routes.md +83 -0
  297. package/lib/templates/content/docs/organization/adding-account-context.md +116 -0
  298. package/lib/templates/content/docs/organization/adding-org-mobile-navigation.md +22 -0
  299. package/lib/templates/content/docs/organization/adding-org-web-navigation.md +12 -0
  300. package/lib/templates/content/docs/organization/index.md +20 -0
  301. package/lib/templates/content/docs/organization/initialization.md +20 -0
  302. package/lib/templates/content/docs/organization/organization-resource-vs-resource.md +112 -0
  303. package/lib/templates/content/docs/remix/configuration/component-structure-best-practices.md +152 -0
  304. package/lib/templates/content/docs/remix/configuration/configurations.md +218 -0
  305. package/lib/templates/content/docs/remix/configuration/css-import-and-stylesheets.md +142 -0
  306. package/lib/templates/content/docs/remix/configuration/dont-subcomponent-network.md +166 -0
  307. package/lib/templates/content/docs/remix/configuration/generated-data-loaders.md +122 -0
  308. package/lib/templates/content/docs/remix/configuration/generated-resource-loaders.md +257 -0
  309. package/lib/templates/content/docs/remix/configuration/query-params-generator.md +216 -0
  310. package/lib/templates/content/docs/remix/configuration/routes-extra-icons.md +103 -0
  311. package/lib/templates/content/docs/remix/configuration/routes-json-advanced.md +86 -0
  312. package/lib/templates/content/docs/remix/configuration/routes-json-auth.md +113 -0
  313. package/lib/templates/content/docs/remix/configuration/routes-json-best-practices.md +55 -0
  314. package/lib/templates/content/docs/remix/configuration/routes-json-fields.md +79 -0
  315. package/lib/templates/content/docs/remix/configuration/routes-json-graphql.md +79 -0
  316. package/lib/templates/content/docs/remix/configuration/routes-json-index.md +112 -0
  317. package/lib/templates/content/docs/remix/configuration/routes-json-loaders.md +165 -0
  318. package/lib/templates/content/docs/remix/configuration/routes-json-middleware.md +196 -0
  319. package/lib/templates/content/docs/remix/configuration/routes-json-overview.md +53 -0
  320. package/lib/templates/content/docs/remix/data-loaders.md +43 -0
  321. package/lib/templates/content/docs/remix/devtools/remix-devtools.md +58 -0
  322. package/lib/templates/content/docs/remix/examples/changes-using-servercode.md +79 -0
  323. package/lib/templates/content/docs/remix/extra-icons.md +62 -0
  324. package/lib/templates/content/docs/remix/extra-links.md +65 -0
  325. package/lib/templates/content/docs/remix/generated-data-loaders.md +114 -0
  326. package/lib/templates/content/docs/remix/queryParamsGenerator.md +89 -0
  327. package/lib/templates/content/docs/remix/resources.md +16 -0
  328. package/lib/templates/content/docs/remix/styles.md +132 -0
  329. package/lib/templates/content/docs/remix/wiki.md +12 -0
  330. package/lib/templates/content/docs/security/auth-wrapper/auth-wrapper.md +24 -0
  331. package/lib/templates/content/docs/security/index.md +18 -0
  332. package/lib/templates/content/docs/security/secure-button-mobilenative.md +88 -0
  333. package/lib/templates/content/docs/security/secure-button-web.md +89 -0
  334. package/lib/templates/content/docs/server-side/account-customization.md +82 -0
  335. package/lib/templates/content/docs/server-side/apollo/caching.md +164 -0
  336. package/lib/templates/content/docs/server-side/backend-architecture/FINAL-DECISION.md +209 -0
  337. package/lib/templates/content/docs/server-side/backend-architecture/TRUE-FINAL-ARCHITECTURE.md +603 -0
  338. package/lib/templates/content/docs/server-side/backend-architecture/index1.md +0 -0
  339. package/lib/templates/content/docs/server-side/backend-coding.md +839 -0
  340. package/lib/templates/content/docs/server-side/e2b/manageing-template.md +197 -0
  341. package/lib/templates/content/docs/server-side/index.md +14 -0
  342. package/lib/templates/content/docs/server-side/inngest-functions-module.md +309 -0
  343. package/lib/templates/content/docs/server-side/listen-stripe-events.md +43 -0
  344. package/lib/templates/content/docs/server-side/slug-service.md +323 -0
  345. package/lib/templates/content/docs/tests/index.md +18 -0
  346. package/lib/templates/content/docs/tests/jest-test-debug-vscode.md +40 -0
  347. package/lib/templates/content/docs/tests/known-errors.md +116 -0
  348. package/lib/templates/content/docs/tests/service-test-template.md +118 -0
  349. package/lib/templates/content/docs/tests/test-setup.md +93 -0
  350. package/lib/templates/content/docs/xstate.md +23 -0
  351. package/lib/types.d.ts +37 -0
  352. package/lib/types.d.ts.map +1 -0
  353. package/lib/utils/docsNavigation.d.ts +9 -0
  354. package/lib/utils/docsNavigation.d.ts.map +1 -0
  355. package/lib/utils/docsNavigation.js +37 -0
  356. package/lib/utils/docsNavigation.js.map +1 -0
  357. package/lib/utils/helpCenterUtils.d.ts +26 -0
  358. package/lib/utils/helpCenterUtils.d.ts.map +1 -0
  359. package/lib/utils/index.d.ts +3 -0
  360. package/lib/utils/index.d.ts.map +1 -0
  361. package/lib/utils/index.js +3 -0
  362. package/lib/utils/index.js.map +1 -0
  363. package/lib/utils/markdownLoader.d.ts +36 -0
  364. package/lib/utils/markdownLoader.d.ts.map +1 -0
  365. package/lib/utils/markdownLoader.js +2242 -0
  366. package/lib/utils/markdownLoader.js.map +1 -0
  367. package/package.json +71 -0
@@ -0,0 +1,3384 @@
1
+ # LLM Template: Complete Backend Service Creation Guide
2
+
3
+ ## Overview
4
+
5
+ This is a comprehensive template for creating backend services in the adminIde-stack architecture. Follow this exact sequence to create new services with GraphQL, Mongoose, templates, and proper module integration.
6
+
7
+ ## ⚠️ CRITICAL: Naming Conventions and Conflict Prevention
8
+
9
+ ### **Module-Prefixed Naming for Large Projects**
10
+
11
+ **In large monorepo projects, name conflicts are common and destructive. ALWAYS use module prefixes for ALL GraphQL types, enums, queries, and mutations.**
12
+
13
+ #### **GraphQL Naming Pattern:**
14
+
15
+ ```graphql
16
+ # ✅ CORRECT - Module prefixed (prevents conflicts)
17
+ type MarketplacePublisher @entity {
18
+ id: ID! @id
19
+ # ... fields
20
+ }
21
+
22
+ type MarketplacePublisherStats @entity(embedded: true) {
23
+ totalDownloads: Int!
24
+ # ... fields
25
+ }
26
+
27
+ enum MarketplacePublisherStatus {
28
+ ACTIVE
29
+ INACTIVE
30
+ VERIFIED
31
+ }
32
+
33
+ input CreateMarketplacePublisherInput {
34
+ # ... fields
35
+ }
36
+
37
+ extend type Query {
38
+ getMarketplacePublisher(id: ID!): MarketplacePublisher @auth
39
+ listMarketplacePublishers(filter: MarketplacePublisherFilter): [MarketplacePublisher!]! @auth
40
+ }
41
+
42
+ extend type Mutation {
43
+ createMarketplacePublisher(input: CreateMarketplacePublisherInput!): MarketplacePublisher! @auth
44
+ updateMarketplacePublisher(id: ID!, input: UpdateMarketplacePublisherInput!): MarketplacePublisher! @auth
45
+ }
46
+
47
+ # ❌ WRONG - No prefix (will conflict with other modules)
48
+ type Publisher @entity {
49
+ id: ID! @id
50
+ }
51
+
52
+ enum PublisherStatus {
53
+ ACTIVE
54
+ }
55
+
56
+ extend type Query {
57
+ getPublisher(id: ID!): Publisher @auth # Conflicts with other Publisher types
58
+ }
59
+ ```
60
+
61
+ #### **Why Module Prefixes Matter:**
62
+
63
+ 1. **Conflict Prevention**: Multiple modules may have `Publisher`, `User`, `Project` types
64
+ 2. **GraphQL Schema Merging**: All modules merge into single schema - conflicts break builds
65
+ 3. **Generated Type Safety**: TypeScript generation creates clean, non-conflicting interfaces
66
+ 4. **API Clarity**: Clear which module/domain the type belongs to
67
+ 5. **Maintenance**: Easy to identify type ownership across large teams
68
+
69
+ #### **Prefix Examples by Module:**
70
+
71
+ - **Marketplace**: `MarketplacePublisher`, `MarketplaceExtension`, `MarketplaceStats`
72
+ - **Account**: `AccountUser`, `AccountOrganization`, `AccountTeam`
73
+ - **Project**: `ProjectTask`, `ProjectTemplate`, `ProjectStatus`
74
+ - **Extension**: `ExtensionRegistry`, `ExtensionInstalled`, `ExtensionManifest`
75
+ - **Billing**: `BillingPlan`, `BillingInvoice`, `BillingSubscription`
76
+
77
+ ### **Complete Service Creation Workflow**
78
+
79
+ After defining GraphQL schemas with proper prefixes, follow this exact sequence:
80
+
81
+ 1. **Create GraphQL Schemas** (with module prefixes)
82
+ 2. **Create Templates** (Service/Repository interfaces)
83
+ 3. **Run Template Generation** (copies to common package)
84
+ 4. **Implement Services & Repositories** (using generated interfaces)
85
+ 5. **Container Binding** (dependency injection setup)
86
+ 6. **Module Integration** (createServiceFunc setup)
87
+ 7. **Resolver Implementation** (GraphQL endpoint logic)
88
+ 8. **Build & Test** (verify integration)
89
+
90
+ **This workflow ensures:**
91
+
92
+ - ✅ Type safety across the entire stack
93
+ - ✅ Proper dependency injection
94
+ - ✅ No naming conflicts
95
+ - ✅ Consistent architecture patterns
96
+ - ✅ Template-driven development (not manual interfaces)
97
+
98
+ ## 📋 Complete Implementation Checklist
99
+
100
+ ### Phase 1: GraphQL Schema Design
101
+
102
+ **File**: `packages-modules/{module}/server/src/graphql/schemas/{entity}.graphql`
103
+
104
+ ```graphql
105
+ # Main Entity with Database Mapping - ALWAYS USE MODULE PREFIX
106
+ type {ModulePrefix}{EntityName} implements Node @entity {
107
+ id: ID! @id
108
+ tenantId: String! @column
109
+
110
+ # Object References - USE ObjectId pattern for relationships
111
+ {relationField}: {ModulePrefix}{RelatedType} @column(overrideType: "ObjectId")
112
+
113
+ # String/Primitive Fields
114
+ {fieldName}: String! @column
115
+ {optionalField}: String @column
116
+
117
+ # Embedded Documents
118
+ {embeddedField}: {ModulePrefix}{EmbeddedType}! @embedded
119
+
120
+ # Status/Enum Fields - MODULE PREFIXED
121
+ status: {ModulePrefix}{EntityName}Status! @column
122
+
123
+ # Audit Fields
124
+ createdAt: DateTime! @column(overrideType: "Date")
125
+ updatedAt: DateTime @column(overrideType: "Date")
126
+ createdBy: UserAccount! @column(overrideType: "ObjectId")
127
+ updatedBy: UserAccount @column(overrideType: "ObjectId")
128
+ }
129
+
130
+ # Embedded Types - NO @entity directive, use @entity(embedded: true) - MODULE PREFIXED
131
+ type {ModulePrefix}{EmbeddedTypeName} @entity(embedded: true) {
132
+ {field}: String! @column
133
+ {nestedField}: Int @column
134
+ {nestedObject}: {ModulePrefix}{NestedType} @embedded
135
+ }
136
+
137
+ # Status Enums - MODULE PREFIXED
138
+ enum {ModulePrefix}{EntityName}Status {
139
+ ACTIVE
140
+ INACTIVE
141
+ PENDING
142
+ ARCHIVED
143
+ }
144
+
145
+ # Input Types for GraphQL Operations - MODULE PREFIXED
146
+ input Create{ModulePrefix}{EntityName}Input {
147
+ {fieldName}: String!
148
+ {optionalField}: String
149
+ {embeddedInput}: {ModulePrefix}{EmbeddedTypeInput}
150
+ }
151
+
152
+ input Update{ModulePrefix}{EntityName}Input {
153
+ {fieldName}: String
154
+ {optionalField}: String
155
+ {embeddedInput}: {ModulePrefix}{EmbeddedTypeInput}
156
+ status: {ModulePrefix}{EntityName}Status
157
+ }
158
+
159
+ input {ModulePrefix}{EmbeddedTypeInput} {
160
+ {field}: String
161
+ {nestedField}: Int
162
+ }
163
+
164
+ # Query Extensions - MODULE PREFIXED
165
+ extend type Query {
166
+ get{ModulePrefix}{EntityName}(id: ID!): {ModulePrefix}{EntityName} @auth
167
+ list{ModulePrefix}{EntityName}s(tenantId: String, filter: {ModulePrefix}{EntityName}Filter): [{ModulePrefix}{EntityName}!]! @auth
168
+ }
169
+
170
+ # Mutation Extensions - MODULE PREFIXED
171
+ extend type Mutation {
172
+ create{ModulePrefix}{EntityName}(input: Create{ModulePrefix}{EntityName}Input!): {ModulePrefix}{EntityName}! @auth
173
+ update{ModulePrefix}{EntityName}(id: ID!, input: Update{ModulePrefix}{EntityName}Input!): {ModulePrefix}{EntityName}! @auth
174
+ delete{ModulePrefix}{EntityName}(id: ID!): Boolean! @auth
175
+ }
176
+ ```
177
+
178
+ **Key Points:**
179
+
180
+ 1. **Object References**: Use `@column(overrideType: "ObjectId")` for relationships
181
+ 2. **Embedded Types**: Use `@entity(embedded: true)` for nested objects
182
+ 3. **Field Mapping**: API uses human-readable IDs, database stores ObjectId references
183
+ 4. **Audit Fields**: Always include createdAt, updatedAt, createdBy, updatedBy
184
+
185
+ ### Phase 2: Event System in service.graphql
186
+
187
+ **File**: `packages-modules/{module}/server/src/graphql/schemas/service.graphql`
188
+
189
+ ```graphql
190
+ # Extension-specific event types (NO @entity directives for events!)
191
+ type {EntityName}CreatedEvent {
192
+ {entity}Id: String!
193
+ tenantId: String!
194
+ createdBy: String!
195
+ createdAt: String!
196
+ {fieldName}: String!
197
+ status: String!
198
+ }
199
+
200
+ type {EntityName}UpdatedEvent {
201
+ {entity}Id: String!
202
+ tenantId: String!
203
+ updatedBy: String!
204
+ updatedAt: String!
205
+ changes: JSON
206
+ previousStatus: String
207
+ newStatus: String
208
+ }
209
+
210
+ type {EntityName}DeletedEvent {
211
+ {entity}Id: String!
212
+ tenantId: String!
213
+ deletedBy: String!
214
+ deletedAt: String!
215
+ reason: String
216
+ }
217
+
218
+ type {EntityName}StatusChangedEvent {
219
+ {entity}Id: String!
220
+ tenantId: String!
221
+ changedBy: String!
222
+ changedAt: String!
223
+ fromStatus: String!
224
+ toStatus: String!
225
+ reason: String
226
+ }
227
+ ```
228
+
229
+ ### Phase 3: Service Template Creation
230
+
231
+ **File**: `packages-modules/{module}/server/src/templates/services/{EntityName}Service.ts.template`
232
+
233
+ ````typescript
234
+ // from package: {module}-server
235
+ import {
236
+ I{EntityName}Model,
237
+ ICreate{EntityName}Input,
238
+ IUpdate{EntityName}Input,
239
+ I{EntityName}Filter,
240
+ I{EntityName}CreatedEvent,
241
+ I{EntityName}UpdatedEvent,
242
+ I{EntityName}DeletedEvent,
243
+ } from 'common/server';
244
+
245
+ /**
246
+ * Extended input type for creating {entity} with server-side fields
247
+ */
248
+ export interface ICreate{EntityName}ServerInput extends ICreate{EntityName}Input {
249
+ tenantId: string;
250
+ createdBy: string; // User ObjectId as string
251
+ }
252
+
253
+ /**
254
+ * Extended input type for updating {entity} with server-side fields
255
+ */
256
+ export interface IUpdate{EntityName}ServerInput extends IUpdate{EntityName}Input {
257
+ updatedBy: string; // User ObjectId as string
258
+ }
259
+
260
+ /**
261
+ * Service interface for managing {entity} entities
262
+ * IMPORTANT: All service methods that return domain entities MUST use AsDomainType<IEntityModel>
263
+ * This ensures proper type compatibility between MongoDB models and GraphQL domain types
264
+ */
265
+ export interface I{EntityName}Service {
266
+ /**
267
+ * Create a new {entity}
268
+ */
269
+ create{EntityName}(input: ICreate{EntityName}ServerInput): Promise<AsDomainType<I{EntityName}Model>>;
270
+
271
+ /**
272
+ * Update an existing {entity}
273
+ */
274
+ update{EntityName}(id: string, input: IUpdate{EntityName}ServerInput): Promise<AsDomainType<I{EntityName}Model>>;
275
+
276
+ /**
277
+ * Get a {entity} by ID
278
+ */
279
+ get{EntityName}(id: string, tenantId: string): Promise<AsDomainType<I{EntityName}Model> | null>;
280
+
281
+ /**
282
+ * List {entity} entities with filtering
283
+ */
284
+ list{EntityName}s(filter: I{EntityName}Filter): Promise<AsDomainType<I{EntityName}Model>[]>;
285
+
286
+ /**
287
+ * Delete a {entity}
288
+ */
289
+ delete{EntityName}(id: string, tenantId: string, deletedBy: string): Promise<boolean>;
290
+
291
+ /**
292
+ * Update {entity} status
293
+ */
294
+ update{EntityName}Status(
295
+ id: string,
296
+ tenantId: string,
297
+ status: string,
298
+ changedBy: string,
299
+ ): Promise<AsDomainType<I{EntityName}Model>>;
300
+
301
+ /**
302
+ * Check if {entity} exists
303
+ */
304
+ {entity}Exists(id: string, tenantId: string): Promise<boolean>;
305
+ }
306
+
307
+ ## 🔄 Critical Service Return Type Pattern
308
+
309
+ **MANDATORY**: All service methods returning domain entities MUST use `AsDomainType<IEntityModel>`
310
+
311
+ ### Why AsDomainType is Required
312
+
313
+ ```typescript
314
+ // ❌ WRONG - Direct model return
315
+ Promise<IEntityModel>
316
+
317
+ // ✅ CORRECT - AsDomainType wrapper
318
+ Promise<AsDomainType<IEntityModel>>
319
+ Promise<AsDomainType<IEntityModel> | null>
320
+ Promise<AsDomainType<IEntityModel>[]>
321
+ ````
322
+
323
+ ### Service Implementation Pattern
324
+
325
+ ```typescript
326
+ // In service implementation - ALWAYS cast the return
327
+ public async create{EntityName}(input: ICreate{EntityName}Input): Promise<AsDomainType<I{EntityName}Model>> {
328
+ const result = await this.repository.create(input);
329
+
330
+ // CRITICAL: Cast repository result via unknown for type compatibility
331
+ return result as unknown as AsDomainType<I{EntityName}Model>;
332
+ }
333
+
334
+ // For nullable returns
335
+ public async get{EntityName}(id: string): Promise<AsDomainType<I{EntityName}Model> | null> {
336
+ const result = await this.repository.findById(id);
337
+
338
+ // Handle null case explicitly
339
+ return result ? (result as unknown as AsDomainType<I{EntityName}Model>) : null;
340
+ }
341
+
342
+ // For arrays
343
+ public async list{EntityName}s(filter: IFilter): Promise<AsDomainType<I{EntityName}Model>[]> {
344
+ const results = await this.repository.findMany(filter);
345
+
346
+ // Cast entire array
347
+ return results as unknown as AsDomainType<I{EntityName}Model>[];
348
+ }
349
+ ```
350
+
351
+ ### Import Requirements
352
+
353
+ ```typescript
354
+ import { AsDomainType } from 'common/server';
355
+ ```
356
+
357
+ **Note**: AsDomainType bridges the gap between MongoDB models (with `_id`) and GraphQL domain types (with `id`). The type casting via `unknown` is necessary for TypeScript compatibility.
358
+
359
+ /\*\*
360
+
361
+ - This file augments the ServerContext interface from the central apollo-context.
362
+ - Through declaration merging, the {entity}Service will be added to the ServerContext.
363
+ \*/
364
+ declare module '../apollo-context' {
365
+ export interface ServerContext {
366
+ {entity}Service: I{EntityName}Service;
367
+ }
368
+ }
369
+
370
+ ````
371
+
372
+ ### Phase 4: Repository Template Creation
373
+
374
+ **File**: `packages-modules/{module}/server/src/templates/repositories/{EntityName}Repository.ts.template`
375
+
376
+ ```typescript
377
+ // from package: {module}-server
378
+ import { I{EntityName}Model, I{EntityName}Filter } from 'common/server';
379
+ import { ICreate{EntityName}ServerInput, IUpdate{EntityName}ServerInput } from '../services/{EntityName}Service';
380
+
381
+ /**
382
+ * Repository interface for {EntityName} data access
383
+ */
384
+ export interface I{EntityName}Repository {
385
+ /**
386
+ * Create a new {entity}
387
+ */
388
+ create(input: ICreate{EntityName}ServerInput): Promise<I{EntityName}Model>;
389
+
390
+ /**
391
+ * Update an existing {entity}
392
+ */
393
+ update(id: string, input: IUpdate{EntityName}ServerInput): Promise<I{EntityName}Model>;
394
+
395
+ /**
396
+ * Find a {entity} by ID and tenant
397
+ */
398
+ findById(id: string, tenantId: string): Promise<I{EntityName}Model | null>;
399
+
400
+ /**
401
+ * Find multiple {entity} entities with filtering
402
+ */
403
+ find(filter: I{EntityName}Filter): Promise<I{EntityName}Model[]>;
404
+
405
+ /**
406
+ * Delete a {entity}
407
+ */
408
+ delete(id: string, tenantId: string): Promise<boolean>;
409
+
410
+ /**
411
+ * Check if {entity} exists
412
+ */
413
+ exists(id: string, tenantId: string): Promise<boolean>;
414
+
415
+ /**
416
+ * Update {entity} status
417
+ */
418
+ updateStatus(id: string, tenantId: string, status: string, updatedBy: string): Promise<I{EntityName}Model>;
419
+
420
+ /**
421
+ * Get {entity} count with optional filtering
422
+ */
423
+ count(filter?: Partial<I{EntityName}Filter>): Promise<number>;
424
+ }
425
+ ````
426
+
427
+ ### Phase 5: Constants Template Update
428
+
429
+ **File**: `packages-modules/{module}/server/src/templates/constants/DB_COLL_NAMES.ts.template`
430
+
431
+ ```typescript
432
+ export const DB_COLL_NAMES = {
433
+ // ... existing collections
434
+ {EntityName}: '{entityName}s',
435
+ // Add other related collections if needed
436
+ } as const;
437
+
438
+ export const SERVER_TYPES = {
439
+ // ... existing types
440
+ I{EntityName}Service: Symbol.for('I{EntityName}Service'),
441
+ I{EntityName}Repository: Symbol.for('I{EntityName}Repository'),
442
+ } as const;
443
+ ```
444
+
445
+ ### Phase 6: Run Template Generation
446
+
447
+ ```bash
448
+ # CRITICAL: Navigate to ROOT of the project (not module directory)
449
+ cd /path/to/your/project-root
450
+
451
+ # CRITICAL: Always run these commands in sequence from PROJECT ROOT
452
+ # 1. Regenerate templates (copies to common package but may remove codegen files)
453
+ yarn regenerateGraphql
454
+
455
+ # 2. IMMEDIATELY generate GraphQL types (restores codegen files)
456
+ yarn generateGraphql
457
+ ```
458
+
459
+ **⚠️ CRITICAL**:
460
+
461
+ - Both commands MUST be run from the PROJECT ROOT, not the module directory
462
+ - Always run `yarn regenerateGraphql && yarn generateGraphql` in sequence
463
+ - The regenerateGraphql command copies templates to the common package but can remove codegen files
464
+ - The generateGraphql command must be run immediately after to regenerate TypeScript types
465
+
466
+ ### Phase 7: Mongoose Model Creation
467
+
468
+ **File**: `packages-modules/{module}/server/src/store/models/{entity-name}-model.ts`
469
+
470
+ ```typescript
471
+ import { Schema, Types } from 'mongoose';
472
+ import type { I{EntityName}Model, I{EmbeddedType}Model, {EntityName}Status } from 'common/server';
473
+ import { DB_COLL_NAMES } from 'common/server';
474
+
475
+ // Embedded Schema (if applicable)
476
+ const {EmbeddedType}Schema = new Schema<I{EmbeddedType}Model>({
477
+ {field}: {
478
+ type: String,
479
+ required: true,
480
+ },
481
+ {nestedField}: {
482
+ type: Number,
483
+ default: 0,
484
+ },
485
+ }, { _id: false }); // No _id for embedded documents
486
+
487
+ // Main Entity Schema
488
+ const {EntityName}Schema = new Schema<I{EntityName}Model>({
489
+ tenantId: {
490
+ type: String,
491
+ required: true,
492
+ index: true,
493
+ },
494
+ {relationField}: {
495
+ type: Schema.Types.ObjectId,
496
+ ref: DB_COLL_NAMES.{RelatedEntity}, // Reference to related collection
497
+ required: true,
498
+ index: true,
499
+ },
500
+ {fieldName}: {
501
+ type: String,
502
+ required: true,
503
+ index: true,
504
+ },
505
+ {optionalField}: {
506
+ type: String,
507
+ index: true,
508
+ },
509
+ {embeddedField}: {
510
+ type: {EmbeddedType}Schema,
511
+ required: true,
512
+ },
513
+ status: {
514
+ type: String,
515
+ enum: Object.values({EntityName}Status), // Use enum from codegen
516
+ default: {EntityName}Status.Active,
517
+ index: true,
518
+ },
519
+ createdAt: {
520
+ type: Date,
521
+ default: Date.now,
522
+ index: true,
523
+ },
524
+ updatedAt: {
525
+ type: Date,
526
+ index: true,
527
+ },
528
+ createdBy: {
529
+ type: Schema.Types.ObjectId,
530
+ ref: DB_COLL_NAMES.UserAccount,
531
+ required: true,
532
+ index: true,
533
+ },
534
+ updatedBy: {
535
+ type: Schema.Types.ObjectId,
536
+ ref: DB_COLL_NAMES.UserAccount,
537
+ index: true,
538
+ },
539
+ }, {
540
+ timestamps: true, // Automatically manage createdAt/updatedAt
541
+ collection: DB_COLL_NAMES.{EntityName},
542
+ });
543
+
544
+ // Virtual for id field
545
+ {EntityName}Schema.virtual('id').get(function() {
546
+ return this._id?.toHexString();
547
+ });
548
+
549
+ // Ensure virtual fields are serialized
550
+ {EntityName}Schema.set('toJSON', {
551
+ virtuals: true,
552
+ transform: (doc, ret) => {
553
+ delete ret._id;
554
+ delete ret.__v;
555
+ return ret;
556
+ },
557
+ });
558
+
559
+ {EntityName}Schema.set('toObject', { virtuals: true });
560
+
561
+ // Indexes for common queries
562
+ {EntityName}Schema.index({ tenantId: 1, status: 1 });
563
+ {EntityName}Schema.index({ tenantId: 1, {relationField}: 1 });
564
+ {EntityName}Schema.index({ createdAt: -1 });
565
+
566
+ // Export model function
567
+ export const {EntityName}ModelFunc = (conn: any) =>
568
+ conn.model(DB_COLL_NAMES.{EntityName}, {EntityName}Schema);
569
+ ```
570
+
571
+ ### Phase 8: Repository Implementation
572
+
573
+ **File**: `packages-modules/{module}/server/src/store/repositories/{entity-name}-repository.ts`
574
+
575
+ ```typescript
576
+ import { injectable } from 'inversify';
577
+ import { Connection } from 'mongoose';
578
+ import { BaseRepository } from '@cdm-logger/server';
579
+ import type { I{EntityName}Model, I{EntityName}Filter } from 'common/server';
580
+ import type { I{EntityName}Repository, ICreate{EntityName}ServerInput, IUpdate{EntityName}ServerInput } from 'common/server';
581
+ import { {EntityName}ModelFunc } from '../models';
582
+
583
+ @injectable()
584
+ export class {EntityName}Repository extends BaseRepository<I{EntityName}Model> implements I{EntityName}Repository {
585
+ constructor(connection: Connection) {
586
+ const {EntityName}Model = {EntityName}ModelFunc(connection);
587
+ super({EntityName}Model);
588
+ }
589
+
590
+ async create(input: ICreate{EntityName}ServerInput): Promise<I{EntityName}Model> {
591
+ const {entity}Data = {
592
+ ...input,
593
+ createdAt: new Date(),
594
+ };
595
+ return await this.create(entityData);
596
+ }
597
+
598
+ async update(id: string, input: IUpdate{EntityName}ServerInput): Promise<I{EntityName}Model> {
599
+ const updateData = {
600
+ ...input,
601
+ updatedAt: new Date(),
602
+ };
603
+
604
+ const updated = await this.update(
605
+ { _id: id, tenantId: input.tenantId },
606
+ updateData,
607
+ { new: true }
608
+ );
609
+
610
+ if (!updated) {
611
+ throw new Error(`{EntityName} not found: ${id}`);
612
+ }
613
+
614
+ return updated;
615
+ }
616
+
617
+ async findById(id: string, tenantId: string): Promise<I{EntityName}Model | null> {
618
+ return await this.get({ _id: id, tenantId });
619
+ }
620
+
621
+ async find(filter: I{EntityName}Filter): Promise<I{EntityName}Model[]> {
622
+ const conditions: any = {};
623
+
624
+ if (filter.tenantId) {
625
+ conditions.tenantId = filter.tenantId;
626
+ }
627
+
628
+ if (filter.status?.length) {
629
+ conditions.status = { $in: filter.status };
630
+ }
631
+
632
+ if (filter.{fieldName}) {
633
+ conditions.{fieldName} = new RegExp(filter.{fieldName}, 'i');
634
+ }
635
+
636
+ if (filter.createdBy) {
637
+ conditions.createdBy = filter.createdBy;
638
+ }
639
+
640
+ return await this.getAll({
641
+ conditions,
642
+ sort: { createdAt: -1 },
643
+ });
644
+ }
645
+
646
+ async delete(id: string, tenantId: string): Promise<boolean> {
647
+ const result = await this.delete({ _id: id, tenantId });
648
+ return result.deletedCount > 0;
649
+ }
650
+
651
+ async exists(id: string, tenantId: string): Promise<boolean> {
652
+ const count = await this.count({ _id: id, tenantId });
653
+ return count > 0;
654
+ }
655
+
656
+ async updateStatus(
657
+ id: string,
658
+ tenantId: string,
659
+ status: string,
660
+ updatedBy: string
661
+ ): Promise<I{EntityName}Model> {
662
+ return await this.update(id, {
663
+ status,
664
+ updatedBy,
665
+ tenantId,
666
+ });
667
+ }
668
+
669
+ async count(filter?: Partial<I{EntityName}Filter>): Promise<number> {
670
+ const conditions: any = {};
671
+
672
+ if (filter?.tenantId) {
673
+ conditions.tenantId = filter.tenantId;
674
+ }
675
+
676
+ if (filter?.status?.length) {
677
+ conditions.status = { $in: filter.status };
678
+ }
679
+
680
+ return await this.count(conditions);
681
+ }
682
+ }
683
+ ```
684
+
685
+ ### Phase 9: Service Implementation
686
+
687
+ **File**: `packages-modules/{module}/server/src/services/{entity-name}-service.ts`
688
+
689
+ ```typescript
690
+ import { injectable, inject } from 'inversify';
691
+ import { Emitter } from '@common-stack/utils';
692
+ import { BaseService } from '@cdm-logger/server';
693
+ import { SERVER_TYPES } from 'common/server';
694
+ import type {
695
+ I{EntityName}Model,
696
+ I{EntityName}Service,
697
+ I{EntityName}Repository,
698
+ I{EntityName}Filter,
699
+ I{EntityName}CreatedEvent,
700
+ I{EntityName}UpdatedEvent,
701
+ I{EntityName}DeletedEvent,
702
+ AsDomainType,
703
+ } from 'common/server';
704
+ import type { ICreate{EntityName}ServerInput, IUpdate{EntityName}ServerInput } from 'common/server';
705
+
706
+ @injectable()
707
+ export class {EntityName}Service extends BaseService<I{EntityName}Model> implements I{EntityName}Service {
708
+ // Event emitters for broadcasting operations
709
+ protected readonly on{EntityName}Created = new Emitter<I{EntityName}CreatedEvent>();
710
+ protected readonly on{EntityName}Updated = new Emitter<I{EntityName}UpdatedEvent>();
711
+ protected readonly on{EntityName}Deleted = new Emitter<I{EntityName}DeletedEvent>();
712
+ protected readonly on{EntityName}StatusChanged = new Emitter<I{EntityName}StatusChangedEvent>();
713
+
714
+ constructor(
715
+ @inject(SERVER_TYPES.I{EntityName}Repository)
716
+ private {entity}Repository: I{EntityName}Repository,
717
+ ) {
718
+ super();
719
+ }
720
+
721
+ // Event getters for external access
722
+ public get {entity}Created() {
723
+ return this.on{EntityName}Created.event;
724
+ }
725
+
726
+ public get {entity}Updated() {
727
+ return this.on{EntityName}Updated.event;
728
+ }
729
+
730
+ public get {entity}Deleted() {
731
+ return this.on{EntityName}Deleted.event;
732
+ }
733
+
734
+ public get {entity}StatusChanged() {
735
+ return this.on{EntityName}StatusChanged.event;
736
+ }
737
+
738
+ async create{EntityName}(input: ICreate{EntityName}ServerInput): Promise<I{EntityName}Model> {
739
+ // Business logic validation
740
+ const existing = await this.{entity}Repository.exists(
741
+ input.{fieldName}, // or relevant unique field
742
+ input.tenantId
743
+ );
744
+
745
+ if (existing) {
746
+ throw new Error(`{EntityName} with {fieldName} '${input.{fieldName}}' already exists`);
747
+ }
748
+
749
+ // Create the entity
750
+ const {entity} = await this.{entity}Repository.create(input);
751
+
752
+ // Fire event
753
+ const event: I{EntityName}CreatedEvent = {
754
+ {entity}Id: {entity}.id!,
755
+ tenantId: {entity}.tenantId,
756
+ createdBy: input.createdBy,
757
+ createdAt: {entity}.createdAt.toISOString(),
758
+ {fieldName}: {entity}.{fieldName},
759
+ status: {entity}.status,
760
+ };
761
+ this.on{EntityName}Created.fire(event);
762
+
763
+ return {entity};
764
+ }
765
+
766
+ async update{EntityName}(id: string, input: IUpdate{EntityName}ServerInput): Promise<I{EntityName}Model> {
767
+ // Get current state for change detection
768
+ const current = await this.{entity}Repository.findById(id, input.tenantId);
769
+ if (!current) {
770
+ throw new Error(`{EntityName} not found: ${id}`);
771
+ }
772
+
773
+ // Update the entity
774
+ const updated = await this.{entity}Repository.update(id, input);
775
+
776
+ // Fire event
777
+ const event: I{EntityName}UpdatedEvent = {
778
+ {entity}Id: updated.id!,
779
+ tenantId: updated.tenantId,
780
+ updatedBy: input.updatedBy,
781
+ updatedAt: updated.updatedAt!.toISOString(),
782
+ changes: this.getChanges(current, updated),
783
+ previousStatus: current.status,
784
+ newStatus: updated.status,
785
+ };
786
+ this.on{EntityName}Updated.fire(event);
787
+
788
+ return updated;
789
+ }
790
+
791
+ async get{EntityName}(id: string, tenantId: string): Promise<I{EntityName}Model | null> {
792
+ return await this.{entity}Repository.findById(id, tenantId);
793
+ }
794
+
795
+ async list{EntityName}s(filter: I{EntityName}Filter): Promise<I{EntityName}Model[]> {
796
+ return await this.{entity}Repository.find(filter);
797
+ }
798
+
799
+ async delete{EntityName}(id: string, tenantId: string, deletedBy: string): Promise<boolean> {
800
+ // Get entity before deletion for event
801
+ const {entity} = await this.{entity}Repository.findById(id, tenantId);
802
+ if (!{entity}) {
803
+ throw new Error(`{EntityName} not found: ${id}`);
804
+ }
805
+
806
+ // Delete the entity
807
+ const deleted = await this.{entity}Repository.delete(id, tenantId);
808
+
809
+ if (deleted) {
810
+ // Fire event
811
+ const event: I{EntityName}DeletedEvent = {
812
+ {entity}Id: id,
813
+ tenantId,
814
+ deletedBy,
815
+ deletedAt: new Date().toISOString(),
816
+ reason: 'Manual deletion',
817
+ };
818
+ this.on{EntityName}Deleted.fire(event);
819
+ }
820
+
821
+ return deleted;
822
+ }
823
+
824
+ async update{EntityName}Status(
825
+ id: string,
826
+ tenantId: string,
827
+ status: string,
828
+ changedBy: string,
829
+ ): Promise<I{EntityName}Model> {
830
+ const current = await this.{entity}Repository.findById(id, tenantId);
831
+ if (!current) {
832
+ throw new Error(`{EntityName} not found: ${id}`);
833
+ }
834
+
835
+ const previousStatus = current.status;
836
+ const updated = await this.{entity}Repository.updateStatus(id, tenantId, status, changedBy);
837
+
838
+ // Fire status change event
839
+ const event: I{EntityName}StatusChangedEvent = {
840
+ {entity}Id: id,
841
+ tenantId,
842
+ changedBy,
843
+ changedAt: new Date().toISOString(),
844
+ fromStatus: previousStatus,
845
+ toStatus: status,
846
+ reason: 'Status update',
847
+ };
848
+ this.on{EntityName}StatusChanged.fire(event);
849
+
850
+ return updated;
851
+ }
852
+
853
+ async {entity}Exists(id: string, tenantId: string): Promise<boolean> {
854
+ return await this.{entity}Repository.exists(id, tenantId);
855
+ }
856
+
857
+ // Helper method to detect changes
858
+ private getChanges(before: I{EntityName}Model, after: I{EntityName}Model): any {
859
+ const changes: any = {};
860
+
861
+ if (before.{fieldName} !== after.{fieldName}) {
862
+ changes.{fieldName} = { from: before.{fieldName}, to: after.{fieldName} };
863
+ }
864
+
865
+ if (before.status !== after.status) {
866
+ changes.status = { from: before.status, to: after.status };
867
+ }
868
+
869
+ return changes;
870
+ }
871
+
872
+ // Cleanup method
873
+ dispose(): void {
874
+ this.on{EntityName}Created.dispose();
875
+ this.on{EntityName}Updated.dispose();
876
+ this.on{EntityName}Deleted.dispose();
877
+ this.on{EntityName}StatusChanged.dispose();
878
+ }
879
+ }
880
+ ```
881
+
882
+ ### Phase 10: Moleculer Service Extension (Optional)
883
+
884
+ **File**: `packages-modules/{module}/server/src/services/{entity-name}-service-ext.ts`
885
+
886
+ ```typescript
887
+ import { injectable, inject } from 'inversify';
888
+ import { Moleculer } from '@common-stack/server-core';
889
+ import { {EntityName}Service } from './{entity-name}-service';
890
+ import { MoleculerServiceName, {EntityName}ServiceAction } from '../constants';
891
+ import type { I{EntityName}CreatedEvent, I{EntityName}UpdatedEvent, I{EntityName}DeletedEvent } from 'common/server';
892
+
893
+ @injectable()
894
+ export class {EntityName}ServiceExt extends {EntityName}Service {
895
+ private broker: any;
896
+
897
+ constructor(
898
+ @inject('Environment') environment: any,
899
+ ...args: any[]
900
+ ) {
901
+ super(...args);
902
+ this.broker = environment.broker;
903
+ this.setupEventListeners();
904
+ }
905
+
906
+ private setupEventListeners(): void {
907
+ // Broadcast created event
908
+ this.{entity}Created((event: I{EntityName}CreatedEvent) => {
909
+ this.broker.broadcast({EntityName}ServiceAction.On{EntityName}Created, { event }, [
910
+ MoleculerServiceName.NotificationService,
911
+ MoleculerServiceName.AuditService,
912
+ ]);
913
+ });
914
+
915
+ // Broadcast updated event
916
+ this.{entity}Updated((event: I{EntityName}UpdatedEvent) => {
917
+ this.broker.broadcast({EntityName}ServiceAction.On{EntityName}Updated, { event }, [
918
+ MoleculerServiceName.NotificationService,
919
+ MoleculerServiceName.AuditService,
920
+ ]);
921
+ });
922
+
923
+ // Broadcast deleted event
924
+ this.{entity}Deleted((event: I{EntityName}DeletedEvent) => {
925
+ this.broker.broadcast({EntityName}ServiceAction.On{EntityName}Deleted, { event }, [
926
+ MoleculerServiceName.NotificationService,
927
+ MoleculerServiceName.AuditService,
928
+ ]);
929
+ });
930
+ }
931
+ }
932
+ ```
933
+
934
+ ### Phase 11: DataLoader Implementation (for Object Resolution)
935
+
936
+ **Files**:
937
+
938
+ - `packages-modules/{module}/server/src/dataloaders/{entity}-data-loader.ts`
939
+ - `packages-modules/{module}/server/src/dataloaders/index.ts`
940
+ - `packages-modules/{module}/server/src/templates/services/{EntityName}DataLoader.ts.template`
941
+
942
+ ```typescript
943
+ // DataLoader Implementation
944
+ import { I{EntityName}, I{EntityName}Service, SERVER_TYPES, IBaseService, AsDomainType } from 'common/server';
945
+ import { injectable, inject } from 'inversify';
946
+ import { BulkDataLoader2 } from '@common-stack/store-mongo';
947
+
948
+ @injectable()
949
+ export class {EntityName}DataLoader extends BulkDataLoader2<AsDomainType<I{EntityName}>> {
950
+ constructor(
951
+ @inject(SERVER_TYPES.I{EntityName}Service)
952
+ {entity}Service: I{EntityName}Service,
953
+ ) {
954
+ super({entity}Service as unknown as IBaseService<AsDomainType<I{EntityName}>>);
955
+ }
956
+ }
957
+ ```
958
+
959
+ **DataLoader Index**:
960
+
961
+ ```typescript
962
+ // File: packages-modules/{module}/server/src/dataloaders/index.ts
963
+ export { {EntityName}DataLoader } from './{entity}-data-loader';
964
+ ```
965
+
966
+ **Template File**:
967
+
968
+ ```typescript
969
+ // File: packages-modules/{module}/server/src/templates/services/{EntityName}DataLoader.ts.template
970
+ import { I{EntityName}, IDataLoader } from 'common/server';
971
+ export type I{EntityName}DataLoader = IDataLoader<I{EntityName}>;
972
+ ```
973
+
974
+ ### Phase 12: GraphQL Resolver Implementation
975
+
976
+ **File**: `packages-modules/{module}/server/src/graphql/resolvers/{entity-name}-resolver.ts`
977
+
978
+ ````typescript
979
+ import { IResolvers } from '@graphql-tools/utils';
980
+ import { IResolverOptions } from '@common-stack/server-core';
981
+ import type {
982
+ I{EntityName}Model,
983
+ ICreate{EntityName}Input,
984
+ IUpdate{EntityName}Input,
985
+ ServerContext
986
+ } from 'common/server';
987
+
988
+ // Define context interface for this resolver
989
+ interface I{EntityName}Context extends ServerContext {
990
+ {entity}Service: any; // Will be injected via createServiceFunc
991
+ {entity}DataLoader: any; // DataLoader for resolving object references
992
+ userContext: {
993
+ tenantId: string;
994
+ userId: string;
995
+ accountId: string;
996
+ emailId: string;
997
+ organization?: {
998
+ id: string;
999
+ name: string;
1000
+ };
1001
+ };
1002
+ }
1003
+
1004
+ export const resolvers: (options: IResolverOptions) => IResolvers = (options) => ({
1005
+ // Field resolvers for nested data relationships
1006
+ {EntityName}: {
1007
+ // Resolve object references using DataLoaders
1008
+ {relationField}: async (
1009
+ parent: I{EntityName}Model,
1010
+ _: any,
1011
+ { {relatedEntity}DataLoader }: I{EntityName}Context,
1012
+ ) => {
1013
+ // Check if the relation field exists and is not null before calling dataloader
1014
+ if (!parent.{relationField} || parent.{relationField} === null || parent.{relationField} === undefined) {
1015
+ return null;
1016
+ }
1017
+ const relationId = String(parent.{relationField});
1018
+ return {relatedEntity}DataLoader.load(relationId);
1019
+ },
1020
+
1021
+ // Resolve computed fields or complex transformations
1022
+ displayName: (parent: I{EntityName}Model) => {
1023
+ return `${parent.{fieldName}} (${parent.status})`;
1024
+ },
1025
+
1026
+ // Resolve configuration or settings URIs (following team-resolver pattern)
1027
+ settingsUri: (params: I{EntityName}Model, args: any, { userContext }: I{EntityName}Context) => {
1028
+ // Create a resource URI for configuration management
1029
+ return createStandardResourceUri(
1030
+ ConfigCollectionName.{EntityName}s, // Collection name
1031
+ params.id,
1032
+ params.{relationField} as unknown as string, // Parent relation
1033
+ {
1034
+ tenantId: userContext.tenantId,
1035
+ fragment: ConfigFragmentName.Settings,
1036
+ },
1037
+ );
1038
+ },
1039
+ },
1040
+
1041
+ Query: {
1042
+ get{EntityName}: async (
1043
+ _: any,
1044
+ { id }: { id: string },
1045
+ { {entity}Service, userContext }: I{EntityName}Context,
1046
+ ): Promise<I{EntityName} | null> => {
1047
+ options.logger.trace('(Query.get{EntityName}) id [%j]', id);
1048
+
1049
+ if (!userContext?.tenantId) {
1050
+ throw new Error('Authentication required');
1051
+ }
1052
+
1053
+ // Cast service result to GraphQL interface type
1054
+ // Field resolvers will handle ObjectId → object transformations via DataLoaders
1055
+ const result = await {entity}Service.get{EntityName}(id, userContext.tenantId);
1056
+ return result as unknown as I{EntityName} | null;
1057
+ },
1058
+
1059
+ list{EntityName}s: async (
1060
+ _: any,
1061
+ { filter }: { filter?: any },
1062
+ { {entity}Service, userContext }: I{EntityName}Context,
1063
+ ): Promise<I{EntityName}[]> => {
1064
+ options.logger.trace('(Query.list{EntityName}s) filter [%j]', filter);
1065
+
1066
+ if (!userContext?.tenantId) {
1067
+ throw new Error('Authentication required');
1068
+ }
1069
+
1070
+ const searchFilter = {
1071
+ tenantId: userContext.tenantId,
1072
+ ...filter,
1073
+ };
1074
+
1075
+ // Cast service result to GraphQL interface type
1076
+ // Field resolvers will handle ObjectId → object transformations via DataLoaders
1077
+ const result = await {entity}Service.list{EntityName}s(searchFilter);
1078
+ return result as unknown as I{EntityName}[];
1079
+ },
1080
+
1081
+ // User-specific queries
1082
+ getUser{EntityName}s: async (
1083
+ _: any,
1084
+ args: any,
1085
+ { {entity}Service, userContext }: I{EntityName}Context,
1086
+ ): Promise<I{EntityName}Model[]> => {
1087
+ options.logger.trace('(Query.getUser{EntityName}s) args [%j]', args);
1088
+
1089
+ if (!userContext) {
1090
+ return [];
1091
+ }
1092
+
1093
+ return await {entity}Service.list{EntityName}s({
1094
+ tenantId: userContext.tenantId,
1095
+ createdBy: userContext.accountId,
1096
+ });
1097
+ },
1098
+
1099
+ // Organization/tenant-wide queries with permissions
1100
+ getOrganization{EntityName}s: async (
1101
+ _: any,
1102
+ { orgName }: { orgName?: string },
1103
+ { {entity}Service, userContext }: I{EntityName}Context,
1104
+ ): Promise<I{EntityName}Model[]> => {
1105
+ options.logger.trace('(Query.getOrganization{EntityName}s) orgName [%j]', orgName);
1106
+
1107
+ if (!userContext) {
1108
+ return [];
1109
+ }
1110
+
1111
+ const { permissions, accountId } = userContext;
1112
+ let fullAccess = false;
1113
+
1114
+ // Check permissions for viewing organization-wide data
1115
+ if (permissions?.organization?.{entity}s?.viewOthers === PermissionType.Allow) {
1116
+ fullAccess = true;
1117
+ }
1118
+
1119
+ return await {entity}Service.getOrganization{EntityName}s(
1120
+ orgName || userContext?.organization?.name,
1121
+ accountId,
1122
+ fullAccess,
1123
+ );
1124
+ },
1125
+ },
1126
+
1127
+ Mutation: {
1128
+ create{EntityName}: async (
1129
+ _: any,
1130
+ { input }: { input: ICreate{EntityName}Input },
1131
+ { {entity}Service, userContext }: I{EntityName}Context,
1132
+ ): Promise<I{EntityName}> => {
1133
+ options.logger.trace('(Mutation.create{EntityName}) input [%j]', input);
1134
+
1135
+ if (!userContext?.tenantId || !userContext?.userId) {
1136
+ throw new Error('Authentication required');
1137
+ }
1138
+
1139
+ const serverInput = {
1140
+ ...input,
1141
+ tenantId: userContext.tenantId,
1142
+ createdBy: userContext.userId,
1143
+ };
1144
+
1145
+ try {
1146
+ const result = await {entity}Service.create{EntityName}(serverInput);
1147
+ options.logger.trace('(Mutation.create{EntityName}) result [%j]', result);
1148
+ // Cast service result to GraphQL interface type
1149
+ return result as unknown as I{EntityName};
1150
+ } catch (error) {
1151
+ options.logger.error('Error creating {entity}:', error);
1152
+ throw error;
1153
+ }
1154
+ },
1155
+
1156
+ update{EntityName}: async (
1157
+ _: any,
1158
+ { id, input }: { id: string; input: IUpdate{EntityName}Input },
1159
+ { {entity}Service, userContext }: I{EntityName}Context,
1160
+ ): Promise<I{EntityName}> => {
1161
+ options.logger.trace('(Mutation.update{EntityName}) args [%j, %j]', id, input);
1162
+
1163
+ if (!userContext?.tenantId || !userContext?.userId) {
1164
+ throw new Error('Authentication required');
1165
+ }
1166
+
1167
+ const serverInput = {
1168
+ ...input,
1169
+ tenantId: userContext.tenantId,
1170
+ updatedBy: userContext.userId,
1171
+ };
1172
+
1173
+ try {
1174
+ const result = await {entity}Service.update{EntityName}(id, serverInput);
1175
+ options.logger.trace('(Mutation.update{EntityName}) result [%j]', result);
1176
+ // Cast service result to GraphQL interface type
1177
+ return result as unknown as I{EntityName};
1178
+ } catch (error) {
1179
+ options.logger.error('Error updating {entity}:', error);
1180
+ throw error;
1181
+ }
1182
+ },
1183
+
1184
+ delete{EntityName}: async (
1185
+ _: any,
1186
+ { id }: { id: string },
1187
+ { {entity}Service, userContext }: I{EntityName}Context,
1188
+ ): Promise<boolean> => {
1189
+ options.logger.trace('(Mutation.delete{EntityName}) id [%j]', id);
1190
+
1191
+ if (!userContext?.tenantId || !userContext?.userId) {
1192
+ throw new Error('Authentication required');
1193
+ }
1194
+
1195
+ try {
1196
+ const result = await {entity}Service.delete{EntityName}(
1197
+ id,
1198
+ userContext.tenantId,
1199
+ userContext.userId
1200
+ );
1201
+ options.logger.trace('(Mutation.delete{EntityName}) result [%j]', result);
1202
+ return result;
1203
+ } catch (error) {
1204
+ options.logger.error('Error deleting {entity}:', error);
1205
+ throw error;
1206
+ }
1207
+ },
1208
+
1209
+ // Status update mutations
1210
+ update{EntityName}Status: async (
1211
+ _: any,
1212
+ { id, status }: { id: string; status: string },
1213
+ { {entity}Service, userContext }: I{EntityName}Context,
1214
+ ): Promise<I{EntityName}Model> => {
1215
+ options.logger.trace('(Mutation.update{EntityName}Status) args [%j, %j]', id, status);
1216
+
1217
+ if (!userContext?.tenantId || !userContext?.userId) {
1218
+ throw new Error('Authentication required');
1219
+ }
1220
+
1221
+ try {
1222
+ const result = await {entity}Service.update{EntityName}Status(
1223
+ id,
1224
+ userContext.tenantId,
1225
+ status,
1226
+ userContext.userId,
1227
+ );
1228
+ return result;
1229
+ } catch (error) {
1230
+ options.logger.error('Error updating {entity} status:', error);
1231
+ throw error;
1232
+ }
1233
+ },
1234
+
1235
+ // Complex business logic operations (like extension installation)
1236
+ install{EntityName}: async (
1237
+ _: any,
1238
+ { {entity}Id }: { {entity}Id: string },
1239
+ { {entity}Service, registryService, userContext }: I{EntityName}Context,
1240
+ ): Promise<I{EntityName}Model> => {
1241
+ options.logger.trace('(Mutation.install{EntityName}) {entity}Id [%j]', {entity}Id);
1242
+
1243
+ if (!userContext?.tenantId || !userContext?.userId) {
1244
+ throw new Error('Authentication required');
1245
+ }
1246
+
1247
+ try {
1248
+ // First check if the entity exists in registry
1249
+ const registryEntity = await registryService.find{EntityName}({entity}Id);
1250
+ if (!registryEntity) {
1251
+ throw new Error(`{EntityName} ${{entity}Id} not found in registry`);
1252
+ }
1253
+
1254
+ // Check if already installed
1255
+ const existingInstallation = await {entity}Service.get{EntityName}(
1256
+ userContext.tenantId,
1257
+ {entity}Id
1258
+ );
1259
+
1260
+ if (existingInstallation) {
1261
+ throw new Error(`{EntityName} ${{entity}Id} is already installed`);
1262
+ }
1263
+
1264
+ // Perform installation with proper input mapping
1265
+ const installInput = {
1266
+ tenantId: userContext.tenantId,
1267
+ registryRef: registryEntity._id,
1268
+ {entity}Id,
1269
+ installedVersion: registryEntity.version,
1270
+ installedBy: userContext.userId,
1271
+ };
1272
+
1273
+ const result = await {entity}Service.install{EntityName}(installInput);
1274
+ options.logger.trace('(Mutation.install{EntityName}) result [%j]', result);
1275
+ return result;
1276
+ } catch (error) {
1277
+ options.logger.error('Error installing {entity}:', error);
1278
+ throw error;
1279
+ }
1280
+ },
1281
+
1282
+ uninstall{EntityName}: async (
1283
+ _: any,
1284
+ { {entity}Id }: { {entity}Id: string },
1285
+ { {entity}Service, userContext }: I{EntityName}Context,
1286
+ ): Promise<boolean> => {
1287
+ options.logger.trace('(Mutation.uninstall{EntityName}) {entity}Id [%j]', {entity}Id);
1288
+
1289
+ if (!userContext?.tenantId || !userContext?.userId) {
1290
+ throw new Error('Authentication required');
1291
+ }
1292
+
1293
+ try {
1294
+ const result = await {entity}Service.uninstall{EntityName}(
1295
+ userContext.tenantId,
1296
+ {entity}Id,
1297
+ userContext.userId
1298
+ );
1299
+
1300
+ if (!result) {
1301
+ throw new Error(`{EntityName} ${{entity}Id} was not installed`);
1302
+ }
1303
+
1304
+ options.logger.trace('(Mutation.uninstall{EntityName}) result [%j]', result);
1305
+ return result;
1306
+ } catch (error) {
1307
+ options.logger.error('Error uninstalling {entity}:', error);
1308
+ throw error;
1309
+ }
1310
+ },
1311
+ },
1312
+ });
1313
+ # Key Points for GraphQL Resolvers:**
1314
+
1315
+ 1. **Use IResolverOptions Pattern**: Follow the `(options: IResolverOptions) => IResolvers` pattern
1316
+ 2. **Logging**: Use `options.logger.trace()` for request/response logging
1317
+ 3. **Context Typing**: Define specific context interfaces for type safety
1318
+ 4. **Error Handling**: Wrap mutations in try-catch with proper error logging
1319
+ 5. **Authentication**: Always check `userContext` for authentication
1320
+ 6. **DataLoader Integration**: Use DataLoaders for nested field resolution to prevent N+1 queries
1321
+ 7. **Permission Checks**: Implement proper authorization logic
1322
+ 8. **Batch Operations**: Support batch operations where appropriate
1323
+
1324
+ ### Phase 13: Resolver Index and Integration
1325
+
1326
+ **File**: `packages-modules/{module}/server/src/graphql/resolvers/index.ts`
1327
+
1328
+ The resolver index file combines all individual resolver files into a single export array. Each resolver file exports a `resolver` function that takes `pubsub` and `logger` parameters.
1329
+
1330
+ #### **Individual Resolver File Pattern:**
1331
+ ```typescript
1332
+ // File: {entity-name}-resolver.ts
1333
+ import { PubSub } from 'graphql-subscriptions';
1334
+ import { CdmLogger } from '@cdm-logger/core';
1335
+ import { IResolvers } from 'common/server';
1336
+
1337
+ export const resolver = (pubsub: PubSub, logger?: CdmLogger.ILogger): IResolvers => ({
1338
+ // Field resolvers with DataLoaders
1339
+ {EntityName}: {
1340
+ {relationField}: (root, args, { {entity}DataLoader }) => {
1341
+ if (!root.{relationField}) return null;
1342
+ return {entity}DataLoader.load(String(root.{relationField}));
1343
+ },
1344
+ },
1345
+
1346
+ Query: {
1347
+ get{EntityName}: async (_, { id }, { {entity}Service, userContext }) => {
1348
+ const result = await {entity}Service.get{EntityName}(id, userContext.tenantId);
1349
+ return result as unknown as I{EntityName}; // Type casting for DataLoader compatibility
1350
+ },
1351
+ },
1352
+
1353
+ Mutation: {
1354
+ create{EntityName}: async (_, { input }, { {entity}Service, userContext }) => {
1355
+ const result = await {entity}Service.create{EntityName}({
1356
+ ...input,
1357
+ tenantId: userContext.tenantId,
1358
+ createdBy: userContext.accountId,
1359
+ });
1360
+ return result as unknown as I{EntityName}; // Type casting for DataLoader compatibility
1361
+ },
1362
+ },
1363
+ });
1364
+ ````
1365
+
1366
+ #### **Index File Combination Pattern:**
1367
+
1368
+ ```typescript
1369
+ // File: packages-modules/{module}/server/src/graphql/resolvers/index.ts
1370
+ import { resolver as registryResolver } from './registry-extension-resolver';
1371
+ import { resolver as installedExtensionResolver } from './installed-extension-resolver';
1372
+ import { resolver as {entity}Resolver } from './{entity-name}-resolver';
1373
+
1374
+ // Export array of resolver functions
1375
+ export const resolvers = [
1376
+ registryResolver,
1377
+ installedExtensionResolver,
1378
+ {entity}Resolver,
1379
+ ];
1380
+ ```
1381
+
1382
+ #### **Alternative IResolverOptions Pattern:**
1383
+
1384
+ For resolvers following the newer IResolverOptions pattern (like team-resolver), use this approach:
1385
+
1386
+ ```typescript
1387
+ // Individual resolver file
1388
+ export const resolvers: (options: IResolverOptions) => IResolvers = (options) => ({
1389
+ // ... resolver implementation with options.logger
1390
+ });
1391
+
1392
+ // Index file
1393
+ import { merge } from 'lodash-es';
1394
+ import { resolvers as {entity}Resolvers } from './{entity-name}-resolver';
1395
+ import { IResolverOptions } from '@common-stack/server-core';
1396
+
1397
+ export const resolvers = (options: IResolverOptions) =>
1398
+ merge(
1399
+ {entity}Resolvers(options),
1400
+ // Add other resolver functions here
1401
+ );
1402
+ ```
1403
+
1404
+ #### **Key Integration Points:**
1405
+
1406
+ 1. **Function Signature**: Each resolver exports a `resolver` function taking `(pubsub, logger?)` parameters
1407
+ 2. **Array Export**: Index combines resolvers into an array for GraphQL schema execution
1408
+ 3. **Naming Convention**: Use `{entity-name}-resolver.ts` for individual files (singular)
1409
+ 4. **Import Aliasing**: Use `resolver as {entity}Resolver` for clarity in index
1410
+ 5. **Type Casting**: Always cast service results with `as unknown as I{EntityName}` for DataLoader compatibility
1411
+ 6. **Context Usage**: Use proper destructuring for `{ userContext, {entity}Service, {entity}DataLoader }`
1412
+
1413
+ **File**: `packages-modules/{module}/server/src/graphql/schemas/index.ts`
1414
+
1415
+ ```typescript
1416
+ import { loadSchemaSync } from '@graphql-tools/load';
1417
+ import { GraphQLFileLoader } from '@graphql-tools/graphql-file-loader';
1418
+ import { join } from 'path';
1419
+
1420
+ const schema = loadSchemaSync(join(__dirname, './*.graphql'), {
1421
+ loaders: [new GraphQLFileLoader()],
1422
+ });
1423
+
1424
+ export { schema };
1425
+ ```
1426
+
1427
+ **File**: `packages-modules/{module}/server/src/graphql/index.ts`
1428
+
1429
+ ```typescript
1430
+ export { resolvers } from './resolvers';
1431
+ export { schema } from './schemas';
1432
+ ```
1433
+
1434
+ **File**: `packages-modules/{module}/server/src/config/env-config.ts`
1435
+
1436
+ ```typescript
1437
+ import { str, cleanEnv, num, bool } from 'envalid';
1438
+
1439
+ export const config = cleanEnv(
1440
+ process.env,
1441
+ {
1442
+ NODE_ENV: str({ choices: ['production', 'test', 'staging', 'development'], default: 'production' }),
1443
+
1444
+ // Database Configuration
1445
+ MONGO_URL: str({ desc: 'MongoDB connection string' }),
1446
+
1447
+ // Service-specific Configuration
1448
+ {ENTITY_NAME}_AUTO_CLEANUP_ENABLED: bool({ default: false }),
1449
+ {ENTITY_NAME}_CLEANUP_INTERVAL_HOURS: num({ default: 24 }),
1450
+ {ENTITY_NAME}_MAX_RETENTION_DAYS: num({ default: 90 }),
1451
+
1452
+ // Feature Flags
1453
+ {ENTITY_NAME}_NOTIFICATIONS_ENABLED: bool({ default: true }),
1454
+ {ENTITY_NAME}_AUDIT_ENABLED: bool({ default: true }),
1455
+
1456
+ // External Service URLs
1457
+ NOTIFICATION_SERVICE_URL: str({ default: '' }),
1458
+ AUDIT_SERVICE_URL: str({ default: '' }),
1459
+
1460
+ // Security
1461
+ {ENTITY_NAME}_RATE_LIMIT_PER_HOUR: num({ default: 100 }),
1462
+
1463
+ // GraphQL
1464
+ GRAPHQL_URL: str({ desc: 'GraphQL endpoint URL' }),
1465
+
1466
+ // Common settings
1467
+ APP_NAME: str({ default: 'CDEBASE.IO' }),
1468
+ CLIENT_URL: str({ desc: 'Client application URL' }),
1469
+ },
1470
+ );
1471
+ ```
1472
+
1473
+ ### Phase 14: Container Module with createServiceFunc
1474
+
1475
+ **File**: `packages-modules/{module}/server/src/containers/module.ts`
1476
+
1477
+ ```typescript
1478
+ import { ContainerModule, interfaces } from 'inversify';
1479
+ import { SERVER_TYPES } from 'common/server';
1480
+ import type {
1481
+ I{EntityName}Service,
1482
+ I{EntityName}Repository,
1483
+ ServerContext
1484
+ } from 'common/server';
1485
+ import { {EntityName}ServiceExt } from '../services/{entity-name}-service-ext';
1486
+ import { {EntityName}Repository } from '../store/repositories/{entity-name}-repository';
1487
+
1488
+ export const {entity}Module: (settings: any) => interfaces.ContainerModule = (settings: any) =>
1489
+ new ContainerModule((bind: interfaces.Bind) => {
1490
+ // MongoDB Connection
1491
+ bind(SERVER_TYPES.I{Entity}MongoConnection).toConstantValue(settings.mongoConnection);
1492
+
1493
+ // Repository
1494
+ bind<I{EntityName}Repository>(SERVER_TYPES.I{EntityName}Repository)
1495
+ .to({EntityName}Repository as any)
1496
+ .inSingletonScope()
1497
+ .whenTargetIsDefault();
1498
+
1499
+ // Service (use ServiceExt for Moleculer integration)
1500
+ bind<I{EntityName}Service>(SERVER_TYPES.I{EntityName}Service)
1501
+ .to({EntityName}ServiceExt)
1502
+ .inSingletonScope()
1503
+ .whenTargetIsDefault();
1504
+ });
1505
+
1506
+ // Service creation function for ServerContext
1507
+ const createServiceFunc = (container: interfaces.Container): Partial<ServerContext> => ({
1508
+ {entity}Service: container.get<I{EntityName}Service>(SERVER_TYPES.I{EntityName}Service),
1509
+ });
1510
+
1511
+ export { createServiceFunc };
1512
+ ```
1513
+
1514
+ ### Phase 14: Main Module File
1515
+
1516
+ **File**: `packages-modules/{module}/server/src/module.ts`
1517
+
1518
+ ```typescript
1519
+ import { interfaces } from 'inversify';
1520
+ import { Feature } from '@common-stack/server-core';
1521
+ import type { ServerContext } from 'common/server';
1522
+ import { SERVER_TYPES, I{EntityName}Service } from 'common/server';
1523
+ import { {entity}Module, createServiceFunc } from './containers/module';
1524
+ import { resolvers, schema } from './graphql';
1525
+ import { rules } from './permissions';
1526
+ import { {EntityName}MoleculerService } from './plugins';
1527
+
1528
+ // Service generation function - REQUIRED TYPE: (container: interfaces.Container) => Partial<ServerContext>
1529
+ const {entity}ServiceGen = (container: interfaces.Container): Partial<ServerContext> => {
1530
+ const environment = container.get('Environment');
1531
+ return {
1532
+ {entity}Service: container.get<I{EntityName}Service>(SERVER_TYPES.I{EntityName}Service),
1533
+ };
1534
+ };
1535
+
1536
+ export default new Feature({
1537
+ schema,
1538
+ createResolversFunc: resolvers,
1539
+ createContainerFunc: [{entity}Module],
1540
+ createServiceFunc: {entity}ServiceGen, // This must return Partial<ServerContext>
1541
+ rules,
1542
+ addBrokerMainServiceClass: [{EntityName}MoleculerService],
1543
+ middleware: [
1544
+ // Add custom middleware here
1545
+ (app) => {
1546
+ app.get('/api/{entity}/health', (req, res) => {
1547
+ res.json({ status: 'OK', service: '{entity}Service', timestamp: new Date().toISOString() });
1548
+ });
1549
+ },
1550
+ ],
1551
+ });
1552
+ ```
1553
+
1554
+ ### Phase 15: Main Module File
1555
+
1556
+ **File**: `packages-modules/{module}/server/src/module.ts`
1557
+
1558
+ ```typescript
1559
+ import { interfaces } from 'inversify';
1560
+ import { Feature } from '@common-stack/server-core';
1561
+ import type { ServerContext } from 'common/server';
1562
+ import { SERVER_TYPES, I{EntityName}Service } from 'common/server';
1563
+ import { {entity}Module, createServiceFunc } from './containers/module';
1564
+ import { resolvers, schema } from './graphql';
1565
+ import { rules } from './permissions';
1566
+ import { {EntityName}MoleculerService } from './plugins';
1567
+
1568
+ // Service generation function - REQUIRED TYPE: (container: interfaces.Container) => Partial<ServerContext>
1569
+ const {entity}ServiceGen = (container: interfaces.Container): Partial<ServerContext> => {
1570
+ const environment = container.get('Environment');
1571
+ return {
1572
+ {entity}Service: container.get<I{EntityName}Service>(SERVER_TYPES.I{EntityName}Service),
1573
+ {entity}DataLoader: container.get(SERVER_TYPES.{EntityName}DataLoader), // Include DataLoader
1574
+ };
1575
+ };
1576
+
1577
+ export default new Feature({
1578
+ schema,
1579
+ createResolversFunc: resolvers,
1580
+ createContainerFunc: [{entity}Module],
1581
+ createServiceFunc: {entity}ServiceGen, // This must return Partial<ServerContext>
1582
+ rules,
1583
+ addBrokerMainServiceClass: [{EntityName}MoleculerService],
1584
+ middleware: [
1585
+ // Add custom middleware here
1586
+ (app) => {
1587
+ app.get('/api/{entity}/health', (req, res) => {
1588
+ res.json({ status: 'OK', service: '{entity}Service', timestamp: new Date().toISOString() });
1589
+ });
1590
+ },
1591
+ ],
1592
+ });
1593
+ ```
1594
+
1595
+ ### Phase 16: Index Files and Exports
1596
+
1597
+ **File**: `packages-modules/{module}/server/src/store/models/index.ts`
1598
+
1599
+ ```typescript
1600
+ export { {EntityName}ModelFunc } from './{entity-name}-model';
1601
+ ```
1602
+
1603
+ **File**: `packages-modules/{module}/server/src/store/repositories/index.ts`
1604
+
1605
+ ```typescript
1606
+ export { {EntityName}Repository } from './{entity-name}-repository';
1607
+ ```
1608
+
1609
+ **File**: `packages-modules/{module}/server/src/services/index.ts`
1610
+
1611
+ ```typescript
1612
+ export { {EntityName}Service } from './{entity-name}-service';
1613
+ export { {EntityName}ServiceExt } from './{entity-name}-service-ext';
1614
+ ```
1615
+
1616
+ **File**: `packages-modules/{module}/server/src/dataloaders/index.ts`
1617
+
1618
+ ```typescript
1619
+ export { {EntityName}DataLoader } from './{entity-name}-data-loader';
1620
+ ```
1621
+
1622
+ **File**: `packages-modules/{module}/server/src/graphql/resolvers/index.ts`
1623
+
1624
+ ```typescript
1625
+ import { merge } from 'lodash-es';
1626
+ import { {entity}Resolvers } from './{entity-name}-resolver';
1627
+
1628
+ export const resolvers = () => merge({entity}Resolvers);
1629
+ ```
1630
+
1631
+ **File**: `packages-modules/{module}/server/src/graphql/schemas/index.ts`
1632
+
1633
+ ```typescript
1634
+ import { loadSchemaSync } from '@graphql-tools/load';
1635
+ import { GraphQLFileLoader } from '@graphql-tools/graphql-file-loader';
1636
+ import { join } from 'path';
1637
+
1638
+ const schema = loadSchemaSync(join(__dirname, './*.graphql'), {
1639
+ loaders: [new GraphQLFileLoader()],
1640
+ });
1641
+
1642
+ export { schema };
1643
+ ```
1644
+
1645
+ ### Phase 17: Constants and Actions
1646
+
1647
+ **File**: `packages-modules/{module}/server/src/constants/moleculer-actions.ts`
1648
+
1649
+ ```typescript
1650
+ export const {EntityName}ServiceAction = {
1651
+ On{EntityName}Created: '{entity}.created',
1652
+ On{EntityName}Updated: '{entity}.updated',
1653
+ On{EntityName}Deleted: '{entity}.deleted',
1654
+ On{EntityName}StatusChanged: '{entity}.status.changed',
1655
+ } as const;
1656
+
1657
+ export const MoleculerServiceName = {
1658
+ NotificationService: 'notification',
1659
+ AuditService: 'audit',
1660
+ ConfigurationService: 'configuration',
1661
+ } as const;
1662
+ ```
1663
+
1664
+ ### Phase 18: Final Build and Verification
1665
+
1666
+ ```bash
1667
+ # 1. CRITICAL: Navigate to PROJECT ROOT (not module directory)
1668
+ cd /path/to/your/project-root
1669
+
1670
+ # 2. CRITICAL: Regenerate templates and immediately generate types from ROOT
1671
+ # (regenerateGraphql can remove codegen files, so generateGraphql must follow immediately)
1672
+ yarn regenerateGraphql && yarn generateGraphql
1673
+
1674
+ # 3. Build specific module using Lerna
1675
+ lerna exec --scope=@adminide-stack/{module}-module-server yarn build
1676
+
1677
+ # Alternative builds:
1678
+ # For marketplace module specifically:
1679
+ lerna exec --scope=@adminide-stack/marketplace-module-server yarn build
1680
+
1681
+ # For extension module:
1682
+ lerna exec --scope=@adminide-stack/extension-module-server yarn build
1683
+
1684
+ # For general module build:
1685
+ yarn build
1686
+
1687
+ # 5. Run tests
1688
+ yarn test
1689
+
1690
+ # 6. Full monorepo build (if needed)
1691
+ yarn build:all
1692
+ ```
1693
+
1694
+ ### Phase 19: Verification Checklist
1695
+
1696
+ ✅ **Files Created:**
1697
+
1698
+ - [ ] GraphQL schema: `{entity}.graphql`
1699
+ - [ ] Event types: `service.graphql`
1700
+ - [ ] Templates: Service and Repository templates
1701
+ - [ ] Constants: DB_COLL_NAMES and SERVER_TYPES
1702
+ - [ ] Model: Mongoose schema with proper typing
1703
+ - [ ] Repository: Data access with BaseRepository
1704
+ - [ ] Service: Business logic with event emitters
1705
+ - [ ] ServiceExt: Moleculer broadcasting
1706
+ - [ ] Resolver: GraphQL operations
1707
+ - [ ] Container: Dependency injection bindings
1708
+ - [ ] Module: Feature integration with createServiceFunc
1709
+ - [ ] Config: Environment configuration
1710
+ - [ ] Constants: Moleculer actions and service names
1711
+
1712
+ ✅ **Generated Files:**
1713
+
1714
+ - [ ] `packages/common/src/services/{EntityName}Service.ts`
1715
+ - [ ] `packages/common/src/repositories/{EntityName}Repository.ts`
1716
+ - [ ] `packages/common/src/constants/DB_COLL_NAMES.ts` (updated)
1717
+ - [ ] `packages/common/src/generated/generated-models.ts` (updated)
1718
+
1719
+ ✅ **Type Safety:**
1720
+
1721
+ - [ ] All imports from `common/server` resolve
1722
+ - [ ] No TypeScript compilation errors
1723
+ - [ ] Proper ServerContext augmentation
1724
+ - [ ] Event types generated without @entity
1725
+
1726
+ ✅ **Architecture:**
1727
+
1728
+ - [ ] Repository extends BaseRepository
1729
+ - [ ] Service extends BaseService
1730
+ - [ ] ServiceExt for Moleculer integration
1731
+ - [ ] Proper dependency injection
1732
+ - [ ] Event-driven architecture
1733
+ - [ ] Tenant isolation implemented
1734
+ - [ ] Audit fields included
1735
+
1736
+ ## 🎯 Key Patterns Summary
1737
+
1738
+ ### **1. Field Mapping Architecture**
1739
+
1740
+ ```typescript
1741
+ // GraphQL API Layer (human-readable)
1742
+ input InstallExtensionInput {
1743
+ extensionID: String! // "publisher/extension-name"
1744
+ }
1745
+
1746
+ // Database Layer (ObjectId references)
1747
+ type InstalledExtension @entity {
1748
+ extension: RegistryExtension @column(overrideType: "ObjectId")
1749
+ }
1750
+
1751
+ // Resolver handles the mapping
1752
+ const installInput = {
1753
+ tenantId: userContext.tenantId,
1754
+ registryRef: registryEntity._id, // ObjectId reference
1755
+ extensionID, // Human-readable ID for API
1756
+ installedVersion: registryEntity.version,
1757
+ installedBy: userContext.userId,
1758
+ };
1759
+ ```
1760
+
1761
+ ### **2. Service Creation Function Pattern**
1762
+
1763
+ ```typescript
1764
+ // REQUIRED signature for createServiceFunc
1765
+ const createServiceFunc = (container: interfaces.Container): Partial<ServerContext> => ({
1766
+ {entity}Service: container.get<I{EntityName}Service>(SERVER_TYPES.I{EntityName}Service),
1767
+ });
1768
+ ```
1769
+
1770
+ ### **3. Event System Pattern**
1771
+
1772
+ ```typescript
1773
+ // Events in service.graphql (NO @entity)
1774
+ type {EntityName}CreatedEvent {
1775
+ {entity}Id: String!
1776
+ tenantId: String!
1777
+ // ... other fields
1778
+ }
1779
+
1780
+ // Service emits events
1781
+ protected readonly on{EntityName}Created = new Emitter<I{EntityName}CreatedEvent>();
1782
+ ```
1783
+
1784
+ ### **4. Repository Pattern**
1785
+
1786
+ ```typescript
1787
+ // Repository extends BaseRepository
1788
+ @injectable()
1789
+ export class {EntityName}Repository extends BaseRepository<I{EntityName}Model>
1790
+ implements I{EntityName}Repository {
1791
+
1792
+ constructor(connection: Connection) {
1793
+ const {EntityName}Model = {EntityName}ModelFunc(connection);
1794
+ super({EntityName}Model);
1795
+ }
1796
+ }
1797
+ ```
1798
+
1799
+ ### **5. Container Binding Pattern**
1800
+
1801
+ ```typescript
1802
+ // Container module with proper typing
1803
+ bind<I{EntityName}Service>(SERVER_TYPES.I{EntityName}Service)
1804
+ .to({EntityName}ServiceExt) // Use ServiceExt for Moleculer
1805
+ .inSingletonScope()
1806
+ .whenTargetIsDefault();
1807
+ ```
1808
+
1809
+ ### **6. DataLoader Template Pattern**
1810
+
1811
+ ```typescript
1812
+ // File: packages-modules/{module}/server/src/templates/services/{EntityName}DataLoader.ts.template
1813
+ import { I{EntityName}, IDataLoader } from 'common/server';
1814
+ export type I{EntityName}DataLoader = IDataLoader<I{EntityName}>;
1815
+ ```
1816
+
1817
+ ### **7. SERVER_TYPES for DataLoaders**
1818
+
1819
+ ```typescript
1820
+ // Add to SERVER_TYPES.ts (automatically generated from templates)
1821
+ export const SERVER_TYPES = {
1822
+ // ... existing types
1823
+ {EntityName}DataLoader: Symbol('{EntityName}DataLoader'),
1824
+ } as const;
1825
+ ```
1826
+
1827
+ ## 🔄 **DataLoader Implementation for Object Resolution**
1828
+
1829
+ ### **Problem**:
1830
+
1831
+ When GraphQL schema defines object relationships but database stores ObjectId references, you need DataLoaders to resolve these efficiently and prevent N+1 query problems.
1832
+
1833
+ ### **Solution Pattern**:
1834
+
1835
+ #### **1. DataLoader Implementation**
1836
+
1837
+ ```typescript
1838
+ // File: packages-modules/{module}/server/src/dataloaders/{entity}-data-loader.ts
1839
+ import { I{EntityName}, I{EntityName}Service, SERVER_TYPES, IBaseService, AsDomainType } from 'common/server';
1840
+ import { injectable, inject } from 'inversify';
1841
+ import { BulkDataLoader2 } from '@common-stack/store-mongo';
1842
+
1843
+ @injectable()
1844
+ export class {EntityName}DataLoader extends BulkDataLoader2<AsDomainType<I{EntityName}>> {
1845
+ constructor(
1846
+ @inject(SERVER_TYPES.I{EntityName}Service)
1847
+ {entity}Service: I{EntityName}Service,
1848
+ ) {
1849
+ super({entity}Service as unknown as IBaseService<AsDomainType<I{EntityName}>>);
1850
+ }
1851
+ }
1852
+ ```
1853
+
1854
+ #### **2. Field Resolver with DataLoader**
1855
+
1856
+ ```typescript
1857
+ // In your GraphQL resolver
1858
+ {ParentEntity}: {
1859
+ // Field resolver for object references using DataLoaders
1860
+ {relationField}: (root, args, { {entity}DataLoader }) => {
1861
+ // CRITICAL: Check if relation field exists and is not null before calling dataloader
1862
+ if (!root.{relationField} || root.{relationField} === null || root.{relationField} === undefined) {
1863
+ return null;
1864
+ }
1865
+ const relationId = String(root.{relationField}); // Convert ObjectId to string
1866
+ return {entity}DataLoader.load(relationId);
1867
+ },
1868
+
1869
+ // Example: Resolving organization from ObjectId reference
1870
+ organization: (root, args, { organizationDataLoader }) => {
1871
+ // Check if organization exists and is not null/undefined before calling dataloader
1872
+ if (!root.organization || root.organization === null || root.organization === undefined) {
1873
+ return null;
1874
+ }
1875
+ const orgId = String(root.organization);
1876
+ return organizationDataLoader.load(orgId);
1877
+ },
1878
+
1879
+ // Example: Resolving user from ObjectId reference
1880
+ user: (root, args, { accountUserDataLoader }) => {
1881
+ // Check if user exists and is not null/undefined before calling dataloader
1882
+ if (!root.user || root.user === null || root.user === undefined) {
1883
+ return null;
1884
+ }
1885
+ const userId = String(root.user);
1886
+ return accountUserDataLoader.load(userId);
1887
+ },
1888
+
1889
+ // Example: Resolving extension from ObjectId reference in installed extension
1890
+ extension: (root, args, { registryExtensionDataLoader }) => {
1891
+ if (!root.extension || root.extension === null || root.extension === undefined) {
1892
+ return null;
1893
+ }
1894
+ const extensionID = String(root.extension);
1895
+ return registryExtensionDataLoader.load(extensionID);
1896
+ },
1897
+ },
1898
+ ```
1899
+
1900
+ #### **3. Context Integration**
1901
+
1902
+ ```typescript
1903
+ // Context interface must include DataLoaders
1904
+ interface I{EntityName}Context extends ServerContext {
1905
+ // Service dependencies
1906
+ {entity}Service: I{EntityName}Service;
1907
+ organizationService: IOrganizationService;
1908
+
1909
+ // DataLoader dependencies for resolving ObjectId references
1910
+ {entity}DataLoader: {EntityName}DataLoader;
1911
+ organizationDataLoader: OrganizationDataLoader;
1912
+ accountUserDataLoader: AccountUserDataLoader;
1913
+ registryExtensionDataLoader: RegistryExtensionDataLoader;
1914
+
1915
+ // User context
1916
+ userContext: {
1917
+ tenantId: string;
1918
+ userId: string;
1919
+ accountId: string;
1920
+ emailId: string;
1921
+ organization?: {
1922
+ id: string;
1923
+ name: string;
1924
+ };
1925
+ };
1926
+ }
1927
+ ```
1928
+
1929
+ #### **4. Container Module DataLoader Registration**
1930
+
1931
+ ```typescript
1932
+ // File: packages-modules/{module}/server/src/containers/module.ts
1933
+ import { {EntityName}DataLoader } from '../dataloaders/{entity}-data-loader';
1934
+
1935
+ export const {entity}Module: (settings: any) => interfaces.ContainerModule = (settings: any) =>
1936
+ new ContainerModule((bind: interfaces.Bind) => {
1937
+ // ... existing bindings
1938
+
1939
+ // DataLoader - CRITICAL: Use request scope for caching
1940
+ bind<{EntityName}DataLoader>(SERVER_TYPES.{EntityName}DataLoader)
1941
+ .to({EntityName}DataLoader)
1942
+ .inRequestScope(); // Important: Request scope for DataLoader caching
1943
+ });
1944
+
1945
+ // Update createServiceFunc to include DataLoader
1946
+ const createServiceFunc = (container: interfaces.Container): Partial<ServerContext> => ({
1947
+ {entity}Service: container.get<I{EntityName}Service>(SERVER_TYPES.I{EntityName}Service),
1948
+ {entity}DataLoader: container.get<{EntityName}DataLoader>(SERVER_TYPES.{EntityName}DataLoader),
1949
+ });
1950
+ ```
1951
+
1952
+ ### **🔧 CRITICAL: Template Configuration for DataLoaders**
1953
+
1954
+ #### **1. Add DataLoader Templates to package.json**
1955
+
1956
+ ```json
1957
+ {
1958
+ "cdecode": {
1959
+ "common": {
1960
+ "constants": [
1961
+ "./${libDir}/templates/constants/SERVER_TYPES.ts.template",
1962
+ "./${libDir}/templates/constants/DB_COLL_NAMES.ts.template"
1963
+ ],
1964
+ "services": [
1965
+ "./${libDir}/templates/services/{EntityName}Service.ts.template",
1966
+ "./${libDir}/templates/services/{EntityName}DataLoader.ts.template"
1967
+ ],
1968
+ "repositories": ["./${libDir}/templates/repositories/{EntityName}Repository.ts.template"]
1969
+ }
1970
+ }
1971
+ }
1972
+ ```
1973
+
1974
+ #### **2. Create DataLoader Template File**
1975
+
1976
+ ```typescript
1977
+ // File: packages-modules/{module}/server/src/templates/services/{EntityName}DataLoader.ts.template
1978
+ import { I{EntityName}, IDataLoader } from 'common/server';
1979
+ export type I{EntityName}DataLoader = IDataLoader<I{EntityName}>;
1980
+ ```
1981
+
1982
+ #### **3. Add SERVER_TYPES Template**
1983
+
1984
+ ```typescript
1985
+ // File: packages-modules/{module}/server/src/templates/constants/SERVER_TYPES.ts.template
1986
+ export const SERVER_TYPES = {
1987
+ // ... existing types from other modules
1988
+
1989
+ // Add DataLoader types for this module
1990
+ {EntityName}DataLoader: Symbol('{EntityName}DataLoader'),
1991
+ };
1992
+ ```
1993
+
1994
+ #### **4. Regenerate Types**
1995
+
1996
+ ```bash
1997
+ # Run these commands to regenerate templates
1998
+ yarn regenerateGraphql
1999
+ yarn generateGraphql
2000
+ ```
2001
+
2002
+ This will automatically:
2003
+
2004
+ - Generate `I{EntityName}DataLoader` type in `common/server`
2005
+ - Add `{EntityName}DataLoader` to SERVER_TYPES constants
2006
+ - Make DataLoader available in GraphQL context for resolvers
2007
+
2008
+ #### **5. GraphQL Schema Definition**
2009
+
2010
+ ```graphql
2011
+ # Schema showing ObjectId stored as references but resolved as objects
2012
+ type InstalledExtension @entity {
2013
+ id: ID!
2014
+ tenantId: String! @column
2015
+
2016
+ # Database stores ObjectId reference to RegistryExtension
2017
+ extension: RegistryExtension @column(overrideType: "ObjectId")
2018
+
2019
+ # Database stores string but resolves to object via DataLoader
2020
+ extensionID: String! @column
2021
+
2022
+ installedVersion: String! @column
2023
+ installedBy: String! @column
2024
+
2025
+ createdAt: Date! @column(overrideType: "Date")
2026
+ updatedAt: Date! @column(overrideType: "Date")
2027
+ }
2028
+
2029
+ type RegistryExtension @entity {
2030
+ id: ID!
2031
+ extensionID: String! @column
2032
+ version: String! @column
2033
+ name: String! @column
2034
+ # ... other fields
2035
+ }
2036
+ ```
2037
+
2038
+ #### **6. Complete Real-World Example: Installed Extension**
2039
+
2040
+ ```typescript
2041
+ export const resolvers: (options: IResolverOptions) => IResolvers = (options) => ({
2042
+ // Field resolvers for InstalledExtension
2043
+ InstalledExtension: {
2044
+ // Resolve extension ObjectId reference to actual RegistryExtension object
2045
+ extension: (root, args, { registryExtensionDataLoader }) => {
2046
+ if (!root.extension || root.extension === null || root.extension === undefined) {
2047
+ return null;
2048
+ }
2049
+ const extensionID = String(root.extension); // Convert ObjectId to string
2050
+ return registryExtensionDataLoader.load(extensionID);
2051
+ },
2052
+
2053
+ // Resolve installedBy ObjectId reference to User object
2054
+ installedByUser: (root, args, { accountUserDataLoader }) => {
2055
+ if (!root.installedBy || root.installedBy === null || root.installedBy === undefined) {
2056
+ return null;
2057
+ }
2058
+ const userId = String(root.installedBy);
2059
+ return accountUserDataLoader.load(userId);
2060
+ },
2061
+
2062
+ // Computed field combining data from both sources
2063
+ displayName: async (root, args, { registryExtensionDataLoader }) => {
2064
+ if (!root.extension) return root.extensionID;
2065
+
2066
+ const extension = await registryExtensionDataLoader.load(String(root.extension));
2067
+ return extension ? extension.name : root.extensionID;
2068
+ },
2069
+ },
2070
+
2071
+ // Query resolvers
2072
+ Query: {
2073
+ getInstalledExtensions: async (
2074
+ _: any,
2075
+ { filter }: { filter?: any },
2076
+ { installedExtensionService, userContext }: InstalledExtensionContext,
2077
+ ): Promise<IInstalledExtensionModel[]> => {
2078
+ options.logger.trace('(Query.getInstalledExtensions) filter [%j]', filter);
2079
+
2080
+ if (!userContext?.tenantId) {
2081
+ throw new Error('Authentication required');
2082
+ }
2083
+
2084
+ const searchFilter = {
2085
+ tenantId: userContext.tenantId,
2086
+ ...filter,
2087
+ };
2088
+
2089
+ // Service returns models with ObjectId references
2090
+ // Field resolvers will use DataLoaders to resolve them to actual objects
2091
+ return await installedExtensionService.getInstalledExtensions(searchFilter);
2092
+ },
2093
+ },
2094
+
2095
+ // Mutation resolvers
2096
+ Mutation: {
2097
+ installExtension: async (
2098
+ _: any,
2099
+ { extensionID }: { extensionID: string },
2100
+ { installedExtensionService, registryExtensionService, userContext }: InstalledExtensionContext,
2101
+ ) => {
2102
+ options.logger.trace('(Mutation.installExtension) extensionID [%j]', extensionID);
2103
+
2104
+ if (!userContext?.tenantId || !userContext?.userId) {
2105
+ throw new Error('Authentication required');
2106
+ }
2107
+
2108
+ try {
2109
+ // First get the registry extension (this will be stored as ObjectId reference)
2110
+ const extension = await registryExtensionService.findExtension(extensionID);
2111
+ if (!extension) {
2112
+ throw new Error(`Extension ${extensionID} not found`);
2113
+ }
2114
+
2115
+ // Install extension - stores extension._id as ObjectId reference
2116
+ const installInput = {
2117
+ tenantId: userContext.tenantId,
2118
+ extension: extension._id, // ObjectId reference
2119
+ extensionID, // Human-readable ID
2120
+ installedVersion: extension.version,
2121
+ installedBy: userContext.userId, // Also ObjectId reference
2122
+ };
2123
+
2124
+ const result = await installedExtensionService.installExtension(installInput);
2125
+
2126
+ // When returned, field resolvers will use DataLoaders to resolve:
2127
+ // - result.extension (ObjectId) → RegistryExtension object
2128
+ // - result.installedBy (ObjectId) → User object
2129
+
2130
+ return result;
2131
+ } catch (error) {
2132
+ options.logger.error('Error installing extension:', error);
2133
+ throw error;
2134
+ }
2135
+ },
2136
+ },
2137
+ });
2138
+ ```
2139
+
2140
+ ### **Key Benefits of DataLoader Pattern:**
2141
+
2142
+ 1. **Performance**: Prevents N+1 query problems by batching database requests
2143
+ 2. **Caching**: DataLoaders cache results within a single request
2144
+ 3. **Type Safety**: Resolves ObjectId references to properly typed objects
2145
+ 4. **Clean Separation**: Database stores efficient ObjectId references, GraphQL returns rich objects
2146
+ 5. **Consistency**: All object references resolved through same pattern
2147
+
2148
+ ### **Critical Implementation Notes:**
2149
+
2150
+ 1. **Null Checks**: Always check if reference field exists before calling DataLoader
2151
+ 2. **String Conversion**: Convert ObjectId to string before passing to DataLoader
2152
+ 3. **Request Scope**: DataLoaders should be bound in request scope for proper caching
2153
+ 4. **Field Mapping**: Database stores ObjectId, GraphQL schema exposes as object type
2154
+ 5. **Error Handling**: DataLoaders handle missing references gracefully by returning null
2155
+ 6. **Type Casting**: When field resolvers use DataLoaders to resolve ObjectId references, you must cast return types in queries/mutations
2156
+
2157
+ ### **Type Casting Pattern for DataLoader Resolvers:**
2158
+
2159
+ When you have field resolvers that use DataLoaders to resolve ObjectId references to objects, the service layer returns `Model` types but GraphQL expects the interface types. You need to cast the return types:
2160
+
2161
+ ```typescript
2162
+ // Query resolvers need type casting
2163
+ Query: {
2164
+ // Single object query
2165
+ get{EntityName}: async (_, { id }, { {entity}Service, userContext }) => {
2166
+ return await {entity}Service.get{EntityName}(id, userContext.tenantId) as unknown as I{EntityName};
2167
+ },
2168
+
2169
+ // Array query
2170
+ list{EntityName}s: async (_, { filter }, { {entity}Service, userContext }) => {
2171
+ const result = await {entity}Service.list{EntityName}s({
2172
+ tenantId: userContext.tenantId,
2173
+ ...filter,
2174
+ });
2175
+ return result as unknown as I{EntityName}[];
2176
+ },
2177
+ },
2178
+
2179
+ // Mutation resolvers also need type casting
2180
+ Mutation: {
2181
+ create{EntityName}: async (_, { input }, { {entity}Service, userContext }) => {
2182
+ const result = await {entity}Service.create{EntityName}({
2183
+ ...input,
2184
+ tenantId: userContext.tenantId,
2185
+ createdBy: userContext.accountId,
2186
+ });
2187
+ return result as unknown as I{EntityName};
2188
+ },
2189
+
2190
+ // When returning objects in mutation responses
2191
+ installExtension: async (_, { input }, { installedExtensionService, userContext }) => {
2192
+ const installedExtension = await installedExtensionService.installExtension({
2193
+ ...input,
2194
+ tenantId: userContext.tenantId,
2195
+ installedBy: userContext.accountId,
2196
+ });
2197
+
2198
+ return {
2199
+ success: true,
2200
+ extension: installedExtension as unknown as IInstalledExtension,
2201
+ message: `Extension installed successfully`,
2202
+ };
2203
+ },
2204
+ },
2205
+ ```
2206
+
2207
+ ### **Why Type Casting is Required:**
2208
+
2209
+ 1. **Service Layer**: Returns `I{EntityName}Model` (Mongoose document with ObjectId fields)
2210
+ 2. **GraphQL Schema**: Expects `I{EntityName}` (interface with resolved object references)
2211
+ 3. **Field Resolvers**: Use DataLoaders to transform ObjectId → actual objects during GraphQL execution
2212
+ 4. **Type System**: TypeScript can't infer that field resolvers will handle the transformation
2213
+
2214
+ ### **Example with InstalledExtension:**
2215
+
2216
+ ```typescript
2217
+ // Database stores this
2218
+ type IInstalledExtensionModel = {
2219
+ _id: ObjectId;
2220
+ extension: ObjectId; // Reference to RegistryExtension
2221
+ extensionID: string;
2222
+ tenantId: string;
2223
+ // ...
2224
+ }
2225
+
2226
+ // GraphQL expects this after field resolution
2227
+ type IInstalledExtension = {
2228
+ id: string;
2229
+ extension: IRegistryExtension; // Resolved object via DataLoader
2230
+ extensionID: string;
2231
+ tenantId: string;
2232
+ // ...
2233
+ }
2234
+
2235
+ // Field resolver handles the transformation
2236
+ InstalledExtension: {
2237
+ extension: (root, args, { registryExtensionDataLoader }) => {
2238
+ if (!root.extension) return null;
2239
+ return registryExtensionDataLoader.load(String(root.extension)); // ObjectId → IRegistryExtension
2240
+ },
2241
+ }
2242
+
2243
+ // Query must cast the type
2244
+ Query: {
2245
+ installedExtensions: async (_, args, { installedExtensionService, userContext }) => {
2246
+ // Service returns IInstalledExtensionModel[]
2247
+ const result = await installedExtensionService.getInstalledExtensions({
2248
+ tenantId: userContext.tenantId
2249
+ });
2250
+ // Cast to what GraphQL expects - field resolver will handle extension transformation
2251
+ return result as unknown as IInstalledExtension[];
2252
+ }
2253
+ }
2254
+ ```
2255
+
2256
+ This pattern ensures efficient object resolution while maintaining clean GraphQL schemas and preventing performance issues.
2257
+
2258
+ ## 🎯 **Complete DataLoader Implementation Checklist**
2259
+
2260
+ When implementing DataLoaders for object resolution, follow this complete checklist:
2261
+
2262
+ ### ✅ **1. Create DataLoader Class**
2263
+
2264
+ ```typescript
2265
+ // File: src/dataloaders/{entity}-data-loader.ts
2266
+ @injectable()
2267
+ export class {EntityName}DataLoader extends BulkDataLoader2<AsDomainType<I{EntityName}>> {
2268
+ constructor(@inject(SERVER_TYPES.I{EntityName}Service) {entity}Service: I{EntityName}Service) {
2269
+ super({entity}Service as unknown as IBaseService<AsDomainType<I{EntityName}>>);
2270
+ }
2271
+ }
2272
+ ```
2273
+
2274
+ ### ✅ **2. Add to SERVER_TYPES**
2275
+
2276
+ ```typescript
2277
+ // packages/common/src/constants/SERVER_TYPES.ts
2278
+ I{EntityName}DataLoader: Symbol.for('I{EntityName}DataLoader'),
2279
+ ```
2280
+
2281
+ ### ✅ **3. Register in Container**
2282
+
2283
+ ```typescript
2284
+ // src/containers/module.ts
2285
+ bind<{EntityName}DataLoader>(SERVER_TYPES.I{EntityName}DataLoader)
2286
+ .to({EntityName}DataLoader)
2287
+ .inRequestScope();
2288
+ ```
2289
+
2290
+ ### ✅ **4. Add to Service Context**
2291
+
2292
+ ```typescript
2293
+ // src/containers/module.ts - createServiceFunc
2294
+ const createServiceFunc = (container: interfaces.Container) => ({
2295
+ {entity}DataLoader: container.get<{EntityName}DataLoader>(SERVER_TYPES.I{EntityName}DataLoader),
2296
+ });
2297
+ ```
2298
+
2299
+ ### ✅ **5. Create Field Resolvers**
2300
+
2301
+ ```typescript
2302
+ // src/graphql/resolvers/{entity}-resolver.ts
2303
+ {ParentEntity}: {
2304
+ {relationField}: (root, args, { {entity}DataLoader }) => {
2305
+ if (!root.{relationField}) return null;
2306
+ return {entity}DataLoader.load(String(root.{relationField}));
2307
+ },
2308
+ }
2309
+ ```
2310
+
2311
+ ### ✅ **6. Add Type Casting in Queries/Mutations**
2312
+
2313
+ ```typescript
2314
+ Query: {
2315
+ get{EntityName}: async (_, { id }, { {entity}Service }) => {
2316
+ const result = await {entity}Service.get{EntityName}(id);
2317
+ return result as unknown as I{EntityName};
2318
+ },
2319
+ }
2320
+ ```
2321
+
2322
+ ### ✅ **7. Run Template Regeneration**
2323
+
2324
+ ```bash
2325
+ yarn regenerateGraphql && yarn generateGraphql
2326
+ ```
2327
+
2328
+ This complete implementation ensures proper DataLoader integration with the adminIde-stack architecture! 🚀
2329
+
2330
+ ## 📋 **Complete GraphQL Resolver Architecture Pattern**
2331
+
2332
+ ### **File Structure:**
2333
+
2334
+ ```
2335
+ src/graphql/resolvers/
2336
+ ├── index.ts # Resolver combination and export
2337
+ ├── {entity-name}-resolver.ts # Individual entity resolvers
2338
+ ├── registry-extension-resolver.ts
2339
+ ├── installed-extension-resolver.ts
2340
+ └── ...
2341
+ ```
2342
+
2343
+ ### **Individual Resolver Structure:**
2344
+
2345
+ ```typescript
2346
+ // File: {entity-name}-resolver.ts
2347
+ import { PubSub } from 'graphql-subscriptions';
2348
+ import { CdmLogger } from '@cdm-logger/core';
2349
+ import { I{EntityName}, IResolvers } from 'common/server';
2350
+
2351
+ export const resolver = (pubsub: PubSub, logger?: CdmLogger.ILogger): IResolvers => ({
2352
+ // 1. Field Resolvers - Handle ObjectId → Object transformations
2353
+ {EntityName}: {
2354
+ {relationField}: (root, args, { {relation}DataLoader }) => {
2355
+ if (!root.{relationField}) return null;
2356
+ return {relation}DataLoader.load(String(root.{relationField}));
2357
+ },
2358
+ },
2359
+
2360
+ // 2. Query Resolvers - Always cast return types
2361
+ Query: {
2362
+ get{EntityName}: async (_, { id }, { {entity}Service, userContext }) => {
2363
+ if (!userContext?.tenantId) throw new Error('Authentication required');
2364
+ const result = await {entity}Service.get{EntityName}(id, userContext.tenantId);
2365
+ return result as unknown as I{EntityName};
2366
+ },
2367
+ },
2368
+
2369
+ // 3. Mutation Resolvers - Always cast return types
2370
+ Mutation: {
2371
+ create{EntityName}: async (_, { input }, { {entity}Service, userContext }) => {
2372
+ if (!userContext?.tenantId) throw new Error('Authentication required');
2373
+ const result = await {entity}Service.create{EntityName}({
2374
+ ...input,
2375
+ tenantId: userContext.tenantId,
2376
+ createdBy: userContext.accountId,
2377
+ });
2378
+ return result as unknown as I{EntityName};
2379
+ },
2380
+ },
2381
+ });
2382
+ ```
2383
+
2384
+ ### **Index Resolver Combination:**
2385
+
2386
+ ```typescript
2387
+ // File: index.ts
2388
+ import { resolver as entityResolver } from './{entity-name}-resolver';
2389
+ import { resolver as registryResolver } from './registry-extension-resolver';
2390
+
2391
+ export const resolvers = [entityResolver, registryResolver];
2392
+ ```
2393
+
2394
+ ### **Context Integration Pattern:**
2395
+
2396
+ The resolvers receive context with these properties:
2397
+
2398
+ - `userContext`: Authentication and tenant information
2399
+ - `{entity}Service`: Business logic services
2400
+ - `{entity}DataLoader`: Efficient object resolution
2401
+ - `pubsub`: GraphQL subscriptions (if needed)
2402
+ - `logger`: Logging functionality
2403
+
2404
+ ### **Critical Rules:**
2405
+
2406
+ 1. **🔑 Authentication**: Always check `userContext?.tenantId` in resolvers
2407
+ 2. **🔄 Type Casting**: Always use `as unknown as I{EntityName}` for service returns
2408
+ 3. **🚀 DataLoaders**: Use for all ObjectId → Object field resolutions
2409
+ 4. **🛡️ Null Checks**: Check field existence before calling DataLoaders
2410
+ 5. **📝 Logging**: Log important operations for debugging
2411
+ 6. **🎯 Context**: Use proper destructuring for clean code
2412
+
2413
+ ## 🎯 **CRITICAL: DataLoader Requirements for LLM Service Creation**
2414
+
2415
+ ### **Mandatory Steps When Creating Any New Service:**
2416
+
2417
+ 1. **Create DataLoader Template**:
2418
+
2419
+ ```typescript
2420
+ // File: src/templates/services/{EntityName}DataLoader.ts.template
2421
+ import { I{EntityName}, IDataLoader } from 'common/server';
2422
+ export type I{EntityName}DataLoader = IDataLoader<I{EntityName}>;
2423
+ ```
2424
+
2425
+ 2. **Add to package.json cdecode**:
2426
+
2427
+ ```json
2428
+ "services": [
2429
+ "./${libDir}/templates/services/{EntityName}Service.ts.template",
2430
+ "./${libDir}/templates/services/{EntityName}DataLoader.ts.template"
2431
+ ]
2432
+ ```
2433
+
2434
+ 3. **Add SERVER_TYPES symbols**:
2435
+
2436
+ ```typescript
2437
+ // In templates/constants/SERVER_TYPES.ts.template
2438
+ {EntityName}DataLoader: Symbol('{EntityName}DataLoader'),
2439
+ ```
2440
+
2441
+ 4. **Container Binding (Request Scope)**:
2442
+
2443
+ ```typescript
2444
+ bind<{EntityName}DataLoader>(SERVER_TYPES.{EntityName}DataLoader)
2445
+ .to({EntityName}DataLoader)
2446
+ .inRequestScope(); // CRITICAL for caching
2447
+ ```
2448
+
2449
+ 5. **Include in createServiceFunc**:
2450
+
2451
+ ```typescript
2452
+ const createServiceFunc = (container: interfaces.Container): Partial<ServerContext> => ({
2453
+ {entity}Service: container.get<I{EntityName}Service>(SERVER_TYPES.I{EntityName}Service),
2454
+ {entity}DataLoader: container.get<{EntityName}DataLoader>(SERVER_TYPES.{EntityName}DataLoader),
2455
+ });
2456
+ ```
2457
+
2458
+ 6. **Field Resolver Pattern**:
2459
+ ```typescript
2460
+ {relationField}: (root, args, { {entity}DataLoader }) => {
2461
+ if (!root.{relationField} || root.{relationField} === null || root.{relationField} === undefined) {
2462
+ return null;
2463
+ }
2464
+ const relationId = String(root.{relationField});
2465
+ return {entity}DataLoader.load(relationId);
2466
+ },
2467
+ ```
2468
+
2469
+ ### **Why This Matters:**
2470
+
2471
+ - **Performance**: Prevents N+1 query problems
2472
+ - **Type Safety**: Generated types ensure consistent context
2473
+ - **Maintainability**: Templates ensure all services follow same pattern
2474
+ - **Regeneration**: `yarn regenerateGraphql` updates all types automatically
2475
+
2476
+ **⚠️ CRITICAL**:
2477
+
2478
+ - Always run `yarn regenerateGraphql && yarn generateGraphql` from PROJECT ROOT (not module directory)
2479
+ - These commands must be run in sequence after creating DataLoader templates to generate proper types in common/server package
2480
+ - Running from module directory will fail - commands only work from project root
2481
+
2482
+ This comprehensive template covers all aspects of creating a backend service following the established patterns. Use this as a complete guide for LLM-assisted development of similar services.
2483
+
2484
+ ## 🧪 Phase 19: Service Testing
2485
+
2486
+ **File**: `packages-modules/{module}/server/src/services/{entity-name}-service.test.ts`
2487
+
2488
+ ```typescript
2489
+ import { MongoMemoryServer } from 'mongodb-memory-server';
2490
+ import mongoose from 'mongoose';
2491
+ import { CdmLogger } from '@cdm-logger/core';
2492
+ import {
2493
+ I{EntityName}Model,
2494
+ I{EntityName}Service,
2495
+ ICreate{EntityName}ServerInput,
2496
+ IUpdate{EntityName}ServerInput,
2497
+ I{EntityName}Filter,
2498
+ {EntityName}Status,
2499
+ } from 'common/server';
2500
+ import { {EntityName}Service } from './{entity-name}-service';
2501
+
2502
+ // Mock dependencies
2503
+ const mockBroker = {
2504
+ broadcast: jest.fn(),
2505
+ emit: jest.fn(),
2506
+ };
2507
+
2508
+ describe('{EntityName}Service', () => {
2509
+ let mongoServer: MongoMemoryServer;
2510
+ let connection: mongoose.Connection;
2511
+ let {entity}Service: {EntityName}Service;
2512
+ let mock{EntityName}Repository: any;
2513
+ let logger: CdmLogger.ILogger;
2514
+
2515
+ beforeAll(async () => {
2516
+ mongoServer = await MongoMemoryServer.create();
2517
+ const mongoUri = mongoServer.getUri();
2518
+ connection = await mongoose.createConnection(mongoUri);
2519
+ await new Promise((resolve) => setTimeout(resolve, 100));
2520
+ });
2521
+
2522
+ beforeEach(() => {
2523
+ // Initialize logger mock
2524
+ logger = {
2525
+ child: () => logger,
2526
+ info: jest.fn(),
2527
+ error: jest.fn(),
2528
+ warn: jest.fn(),
2529
+ debug: jest.fn(),
2530
+ } as any;
2531
+
2532
+ // Mock repository with all required methods
2533
+ mock{EntityName}Repository = {
2534
+ create: jest.fn(),
2535
+ update: jest.fn(),
2536
+ findById: jest.fn(),
2537
+ find: jest.fn(),
2538
+ delete: jest.fn(),
2539
+ exists: jest.fn(),
2540
+ updateStatus: jest.fn(),
2541
+ count: jest.fn(),
2542
+ };
2543
+
2544
+ // Initialize service with mocked dependencies
2545
+ {entity}Service = new {EntityName}Service(
2546
+ mock{EntityName}Repository,
2547
+ mockBroker as any,
2548
+ logger,
2549
+ );
2550
+ });
2551
+
2552
+ afterAll(async () => {
2553
+ await connection.close();
2554
+ await mongoServer.stop();
2555
+ });
2556
+
2557
+ // Test helpers
2558
+ const createMock{EntityName} = (overrides: Partial<I{EntityName}Model> = {}): I{EntityName}Model => ({
2559
+ _id: new mongoose.Types.ObjectId(),
2560
+ tenantId: 'test-tenant',
2561
+ {fieldName}: 'test-value',
2562
+ status: {EntityName}Status.Active,
2563
+ createdAt: new Date(),
2564
+ updatedAt: new Date(),
2565
+ createdBy: new mongoose.Types.ObjectId(),
2566
+ ...overrides,
2567
+ } as I{EntityName}Model);
2568
+
2569
+ const createInput = (overrides: Partial<ICreate{EntityName}ServerInput> = {}): ICreate{EntityName}ServerInput => ({
2570
+ {fieldName}: 'test-value',
2571
+ tenantId: 'test-tenant',
2572
+ createdBy: new mongoose.Types.ObjectId().toString(),
2573
+ ...overrides,
2574
+ });
2575
+
2576
+ describe('create{EntityName}', () => {
2577
+ it('should create a new {entity} successfully', async () => {
2578
+ // Arrange
2579
+ const input = createInput();
2580
+ const mock{EntityName} = createMock{EntityName}();
2581
+
2582
+ mock{EntityName}Repository.exists.mockResolvedValue(false);
2583
+ mock{EntityName}Repository.create.mockResolvedValue(mock{EntityName});
2584
+
2585
+ // Act
2586
+ const result = await {entity}Service.create{EntityName}(input);
2587
+
2588
+ // Assert
2589
+ expect(result).toBeDefined();
2590
+ expect(result.tenantId).toBe(input.tenantId);
2591
+ expect(mock{EntityName}Repository.create).toHaveBeenCalledWith(input);
2592
+ });
2593
+
2594
+ it('should throw error when {entity} already exists', async () => {
2595
+ // Arrange
2596
+ const input = createInput();
2597
+ mock{EntityName}Repository.exists.mockResolvedValue(true);
2598
+
2599
+ // Act & Assert
2600
+ await expect({entity}Service.create{EntityName}(input)).rejects.toThrow(
2601
+ '{EntityName} with {fieldName}'
2602
+ );
2603
+ });
2604
+ });
2605
+
2606
+ describe('update{EntityName}', () => {
2607
+ it('should update an existing {entity}', async () => {
2608
+ // Arrange
2609
+ const id = new mongoose.Types.ObjectId().toString();
2610
+ const tenantId = 'test-tenant';
2611
+ const current = createMock{EntityName}();
2612
+ const updated = { ...current, {fieldName}: 'updated-value' };
2613
+ const updateInput: IUpdate{EntityName}ServerInput = {
2614
+ {fieldName}: 'updated-value',
2615
+ updatedBy: new mongoose.Types.ObjectId().toString(),
2616
+ };
2617
+
2618
+ mock{EntityName}Repository.findById.mockResolvedValue(current);
2619
+ mock{EntityName}Repository.update.mockResolvedValue(updated);
2620
+
2621
+ // Act
2622
+ const result = await {entity}Service.update{EntityName}(id, updateInput);
2623
+
2624
+ // Assert
2625
+ expect(result).toBeDefined();
2626
+ expect(result.{fieldName}).toBe('updated-value');
2627
+ expect(mock{EntityName}Repository.update).toHaveBeenCalledWith(id, updateInput);
2628
+ });
2629
+
2630
+ it('should throw error when {entity} not found', async () => {
2631
+ // Arrange
2632
+ const id = new mongoose.Types.ObjectId().toString();
2633
+ const updateInput: IUpdate{EntityName}ServerInput = {
2634
+ {fieldName}: 'updated-value',
2635
+ updatedBy: new mongoose.Types.ObjectId().toString(),
2636
+ };
2637
+
2638
+ mock{EntityName}Repository.findById.mockResolvedValue(null);
2639
+
2640
+ // Act & Assert
2641
+ await expect({entity}Service.update{EntityName}(id, updateInput)).rejects.toThrow(
2642
+ '{EntityName} not found'
2643
+ );
2644
+ });
2645
+ });
2646
+
2647
+ describe('get{EntityName}', () => {
2648
+ it('should return {entity} when found', async () => {
2649
+ // Arrange
2650
+ const id = new mongoose.Types.ObjectId().toString();
2651
+ const tenantId = 'test-tenant';
2652
+ const mock{EntityName} = createMock{EntityName}();
2653
+
2654
+ mock{EntityName}Repository.findById.mockResolvedValue(mock{EntityName});
2655
+
2656
+ // Act
2657
+ const result = await {entity}Service.get{EntityName}(id, tenantId);
2658
+
2659
+ // Assert
2660
+ expect(result).toBeDefined();
2661
+ expect(result!.tenantId).toBe(tenantId);
2662
+ });
2663
+
2664
+ it('should return null when not found', async () => {
2665
+ // Arrange
2666
+ const id = new mongoose.Types.ObjectId().toString();
2667
+ const tenantId = 'test-tenant';
2668
+
2669
+ mock{EntityName}Repository.findById.mockResolvedValue(null);
2670
+
2671
+ // Act
2672
+ const result = await {entity}Service.get{EntityName}(id, tenantId);
2673
+
2674
+ // Assert
2675
+ expect(result).toBeNull();
2676
+ });
2677
+ });
2678
+
2679
+ describe('list{EntityName}s', () => {
2680
+ it('should return all {entity} entities for tenant', async () => {
2681
+ // Arrange
2682
+ const mock{EntityName}s = [
2683
+ createMock{EntityName}({ {fieldName}: 'entity-1' }),
2684
+ createMock{EntityName}({ {fieldName}: 'entity-2' }),
2685
+ ];
2686
+
2687
+ mock{EntityName}Repository.find.mockResolvedValue(mock{EntityName}s);
2688
+
2689
+ const filter: I{EntityName}Filter = { tenantId: 'test-tenant' };
2690
+
2691
+ // Act
2692
+ const result = await {entity}Service.list{EntityName}s(filter);
2693
+
2694
+ // Assert
2695
+ expect(result).toHaveLength(2);
2696
+ expect(result.every(e => e.tenantId === 'test-tenant')).toBe(true);
2697
+ });
2698
+
2699
+ it('should handle empty results', async () => {
2700
+ // Arrange
2701
+ mock{EntityName}Repository.find.mockResolvedValue([]);
2702
+
2703
+ // Act
2704
+ const result = await {entity}Service.list{EntityName}s({ tenantId: 'empty-tenant' });
2705
+
2706
+ // Assert
2707
+ expect(result).toEqual([]);
2708
+ });
2709
+ });
2710
+
2711
+ describe('delete{EntityName}', () => {
2712
+ it('should delete existing {entity}', async () => {
2713
+ // Arrange
2714
+ const id = new mongoose.Types.ObjectId().toString();
2715
+ const tenantId = 'test-tenant';
2716
+ const mock{EntityName} = createMock{EntityName}();
2717
+
2718
+ mock{EntityName}Repository.findById.mockResolvedValue(mock{EntityName});
2719
+ mock{EntityName}Repository.delete.mockResolvedValue(true);
2720
+
2721
+ // Act
2722
+ const result = await {entity}Service.delete{EntityName}(id, tenantId, 'user-id');
2723
+
2724
+ // Assert
2725
+ expect(result).toBe(true);
2726
+ expect(mock{EntityName}Repository.delete).toHaveBeenCalledWith(id, tenantId);
2727
+ });
2728
+
2729
+ it('should throw error when {entity} not found', async () => {
2730
+ // Arrange
2731
+ const id = new mongoose.Types.ObjectId().toString();
2732
+ const tenantId = 'test-tenant';
2733
+
2734
+ mock{EntityName}Repository.findById.mockResolvedValue(null);
2735
+
2736
+ // Act & Assert
2737
+ await expect({entity}Service.delete{EntityName}(id, tenantId, 'user-id')).rejects.toThrow(
2738
+ '{EntityName} not found'
2739
+ );
2740
+ });
2741
+ });
2742
+
2743
+ describe('error handling', () => {
2744
+ it('should handle repository errors gracefully', async () => {
2745
+ // Arrange
2746
+ const input = createInput();
2747
+ mock{EntityName}Repository.exists.mockRejectedValue(new Error('Database error'));
2748
+
2749
+ // Act & Assert
2750
+ await expect({entity}Service.create{EntityName}(input)).rejects.toThrow('Database error');
2751
+ });
2752
+ });
2753
+
2754
+ describe('event system', () => {
2755
+ it('should emit events on {entity} operations', async () => {
2756
+ // Arrange
2757
+ const input = createInput();
2758
+ const mock{EntityName} = createMock{EntityName}();
2759
+
2760
+ mock{EntityName}Repository.exists.mockResolvedValue(false);
2761
+ mock{EntityName}Repository.create.mockResolvedValue(mock{EntityName});
2762
+
2763
+ // Act
2764
+ await {entity}Service.create{EntityName}(input);
2765
+
2766
+ // Assert - Events are emitted through service emitters
2767
+ expect(mockBroker).toBeDefined();
2768
+ });
2769
+ });
2770
+ });
2771
+ ```
2772
+
2773
+ ### Test Running Commands
2774
+
2775
+ ```bash
2776
+ # Run specific service tests
2777
+ cd packages-modules/{module}/server
2778
+ yarn test --testPathPattern='{entity-name}-service.test.ts'
2779
+
2780
+ # Run all tests with coverage
2781
+ yarn test --coverage
2782
+
2783
+ # Run tests in watch mode during development
2784
+ yarn test --watch
2785
+ ```
2786
+
2787
+ ### Test Best Practices
2788
+
2789
+ 1. **Use Real Implementations**: Use actual MongoDB in-memory server for integration testing
2790
+ 2. **Mock Dependencies**: Mock external services and repositories for unit testing
2791
+ 3. **Test Business Logic**: Focus on service business logic, not repository implementation
2792
+ 4. **Event Testing**: Verify events are emitted correctly through service emitters
2793
+ 5. **Error Scenarios**: Test all error conditions and edge cases
2794
+ 6. **Data Validation**: Test input validation and business rule enforcement
2795
+
2796
+ This comprehensive template covers all aspects of creating a backend service following the established patterns. Use this as a complete guide for LLM-assisted development of similar services.
2797
+ {entityName}(id: ID!): {EntityName}
2798
+ {entityNamePlural}(filter: {EntityName}Filter): [{EntityName}!]!
2799
+ }
2800
+
2801
+ # Mutations
2802
+
2803
+ extend type Mutation {
2804
+ create{EntityName}(input: {EntityName}Input!): {EntityName}!
2805
+ update{EntityName}(id: ID!, input: Update{EntityName}Input!): {EntityName}!
2806
+ delete{EntityName}(id: ID!): Boolean!
2807
+ }
2808
+
2809
+ ````
2810
+
2811
+ **Key Principles**:
2812
+ - Use `@entity` for database-mapped types
2813
+ - Use `@column` for database fields
2814
+ - Use `@column(overrideType: "ObjectId")` for object references
2815
+ - Use `@column(overrideType: "Date")` for timestamps
2816
+ - Use `@embedded` for nested objects
2817
+ - No `@entity` directive for pure input types or event types
2818
+
2819
+ ### 2. Database Collection Names
2820
+ **Location**: `packages-modules/{module}/server/src/templates/constants/DB_COLL_NAMES.ts.template`
2821
+
2822
+ ```typescript
2823
+ // Add to existing DB_COLL_NAMES constant
2824
+ export const DB_COLL_NAMES = {
2825
+ // ... existing collections
2826
+ {EntityName}: '{module}_{entity_name}',
2827
+ // Example: InstalledExtension: 'marketplace_installed_extensions'
2828
+ } as const;
2829
+ ````
2830
+
2831
+ **Map in package.json cdecode**:
2832
+
2833
+ ```json
2834
+ {
2835
+ "cdecode": {
2836
+ "templates": {
2837
+ "constants": {
2838
+ "src": "src/templates/constants",
2839
+ "dest": "../../packages/common/src/constants"
2840
+ }
2841
+ }
2842
+ }
2843
+ }
2844
+ ```
2845
+
2846
+ ### 3. Mongoose Model
2847
+
2848
+ **Location**: `packages-modules/{module}/server/src/store/models/{entity}-model.ts`
2849
+
2850
+ ```typescript
2851
+ import {
2852
+ DB_COLL_NAMES,
2853
+ I{EntityName}Model,
2854
+ // Import embedded type interfaces
2855
+ } from 'common/server';
2856
+ import { Connection, Schema } from 'mongoose';
2857
+
2858
+ // Embedded Schemas First
2859
+ const {EmbeddedType}Schema = new Schema<I{EmbeddedType}Model>({
2860
+ {field}: {
2861
+ type: String,
2862
+ required: true,
2863
+ },
2864
+ {optionalField}: {
2865
+ type: String,
2866
+ },
2867
+ });
2868
+
2869
+ // Main Schema
2870
+ const {EntityName}Schema = new Schema<I{EntityName}Model>(
2871
+ {
2872
+ tenantId: {
2873
+ type: String,
2874
+ required: true,
2875
+ index: true,
2876
+ },
2877
+ {relationField}: {
2878
+ type: Schema.Types.ObjectId,
2879
+ ref: DB_COLL_NAMES.{RelatedEntity},
2880
+ required: true,
2881
+ index: true,
2882
+ },
2883
+ {fieldName}: {
2884
+ type: String,
2885
+ required: true,
2886
+ },
2887
+ {embeddedField}: {
2888
+ type: {EmbeddedType}Schema,
2889
+ default: () => ({}),
2890
+ },
2891
+ createdBy: {
2892
+ type: Schema.Types.ObjectId,
2893
+ ref: DB_COLL_NAMES.Accounts,
2894
+ required: true,
2895
+ },
2896
+ },
2897
+ {
2898
+ timestamps: { createdAt: 'createdAt', updatedAt: 'updatedAt' },
2899
+ collection: DB_COLL_NAMES.{EntityName},
2900
+ }
2901
+ );
2902
+
2903
+ // Indexes
2904
+ {EntityName}Schema.index({ tenantId: 1, {keyField}: 1 }, { unique: true });
2905
+
2906
+ // Virtuals (for GraphQL id field)
2907
+ {EntityName}Schema.virtual('id').get(function () {
2908
+ return this._id.toHexString();
2909
+ });
2910
+
2911
+ // Transform for JSON output
2912
+ {EntityName}Schema.set('toJSON', {
2913
+ virtuals: true,
2914
+ transform: (doc, ret) => {
2915
+ delete ret._id;
2916
+ delete ret.__v;
2917
+ return ret;
2918
+ },
2919
+ });
2920
+
2921
+ export const {EntityName}ModelFunc = (connection: Connection) => {
2922
+ return connection.model<I{EntityName}Model>(DB_COLL_NAMES.{EntityName}, {EntityName}Schema);
2923
+ };
2924
+ ```
2925
+
2926
+ ### 4. Service Templates
2927
+
2928
+ **Location**: `packages-modules/{module}/server/src/templates/services/{EntityName}Service.ts.template`
2929
+
2930
+ ```typescript
2931
+ import {
2932
+ I{EntityName}Model,
2933
+ I{EntityName}Input,
2934
+ IUpdate{EntityName}Input,
2935
+ I{EmbeddedType}Input,
2936
+ } from 'common/server';
2937
+
2938
+ /**
2939
+ * Input type for creating {entityName} with additional server-side fields
2940
+ */
2941
+ export interface ICreate{EntityName}Input extends I{EntityName}Input {
2942
+ tenantId: string;
2943
+ createdBy: string; // User ObjectId as string
2944
+ }
2945
+
2946
+ /**
2947
+ * Input type for updating {entityName}
2948
+ */
2949
+ export interface IUpdate{EntityName}Input {
2950
+ {field}?: string;
2951
+ {embeddedField}?: I{EmbeddedType}Input;
2952
+ updatedBy?: string;
2953
+ }
2954
+
2955
+ /**
2956
+ * Filter input for querying {entityName}
2957
+ */
2958
+ export interface I{EntityName}Filter {
2959
+ tenantId?: string;
2960
+ {keyField}?: string;
2961
+ createdBy?: string;
2962
+ }
2963
+
2964
+ /**
2965
+ * Service interface for managing {entityName}
2966
+ */
2967
+ export interface I{EntityName}Service {
2968
+ /**
2969
+ * Create a new {entityName}
2970
+ */
2971
+ create{EntityName}(input: ICreate{EntityName}Input): Promise<I{EntityName}Model>;
2972
+
2973
+ /**
2974
+ * Update an existing {entityName}
2975
+ */
2976
+ update{EntityName}(
2977
+ tenantId: string,
2978
+ id: string,
2979
+ update: IUpdate{EntityName}Input,
2980
+ ): Promise<I{EntityName}Model>;
2981
+
2982
+ /**
2983
+ * Get a specific {entityName}
2984
+ */
2985
+ get{EntityName}(tenantId: string, id: string): Promise<I{EntityName}Model | null>;
2986
+
2987
+ /**
2988
+ * Get all {entityName} for a tenant with optional filtering
2989
+ */
2990
+ get{EntityNamePlural}(filter: I{EntityName}Filter): Promise<I{EntityName}Model[]>;
2991
+
2992
+ /**
2993
+ * Delete a {entityName}
2994
+ */
2995
+ delete{EntityName}(tenantId: string, id: string, deletedBy: string): Promise<boolean>;
2996
+ }
2997
+ ```
2998
+
2999
+ ### 5. Repository Templates
3000
+
3001
+ **Location**: `packages-modules/{module}/server/src/templates/repositories/{EntityName}Repository.ts.template`
3002
+
3003
+ ```typescript
3004
+ import { ObjectId } from 'mongodb';
3005
+ import {
3006
+ I{EntityName}Model,
3007
+ } from 'common/server';
3008
+ import {
3009
+ ICreate{EntityName}Input,
3010
+ IUpdate{EntityName}Input,
3011
+ I{EntityName}Filter,
3012
+ } from '../services/{EntityName}Service';
3013
+
3014
+ /**
3015
+ * Repository interface for {entityName} data access layer
3016
+ */
3017
+ export interface I{EntityName}Repository {
3018
+ /**
3019
+ * Create a new {entityName} record
3020
+ */
3021
+ create(input: ICreate{EntityName}Input): Promise<I{EntityName}Model>;
3022
+
3023
+ /**
3024
+ * Update an existing {entityName}
3025
+ */
3026
+ updateById(
3027
+ tenantId: string,
3028
+ id: string,
3029
+ update: IUpdate{EntityName}Input,
3030
+ ): Promise<I{EntityName}Model | null>;
3031
+
3032
+ /**
3033
+ * Find a specific {entityName}
3034
+ */
3035
+ findById(tenantId: string, id: string): Promise<I{EntityName}Model | null>;
3036
+
3037
+ /**
3038
+ * Find {entityName} by criteria
3039
+ */
3040
+ findByCriteria(filter: I{EntityName}Filter): Promise<I{EntityName}Model[]>;
3041
+
3042
+ /**
3043
+ * Check if {entityName} exists
3044
+ */
3045
+ exists(tenantId: string, {keyField}: string): Promise<boolean>;
3046
+
3047
+ /**
3048
+ * Delete a {entityName}
3049
+ */
3050
+ deleteById(tenantId: string, id: string): Promise<boolean>;
3051
+
3052
+ /**
3053
+ * Count {entityName} by criteria
3054
+ */
3055
+ countByCriteria(filter: I{EntityName}Filter): Promise<number>;
3056
+ }
3057
+ ```
3058
+
3059
+ ### 6. Event Types (if needed)
3060
+
3061
+ **Location**: `packages-modules/{module}/server/src/graphql/schemas/service.graphql`
3062
+
3063
+ ```graphql
3064
+ # Add to existing service enum
3065
+ extend enum MoleculerServiceName {
3066
+ {EntityName}Service
3067
+ }
3068
+
3069
+ enum {EntityName}ServiceAction {
3070
+ On{EntityName}Created
3071
+ On{EntityName}Updated
3072
+ On{EntityName}Deleted
3073
+ }
3074
+
3075
+ # Event Types (no @entity directive for events)
3076
+ type {EntityName}CreatedEvent {
3077
+ {entityName}Id: String!
3078
+ tenantId: String!
3079
+ createdBy: String!
3080
+ createdAt: String!
3081
+ {relevantField}: String!
3082
+ }
3083
+
3084
+ type {EntityName}UpdatedEvent {
3085
+ {entityName}Id: String!
3086
+ tenantId: String!
3087
+ updatedBy: String!
3088
+ updatedAt: String!
3089
+ changes: JSON!
3090
+ }
3091
+
3092
+ type {EntityName}DeletedEvent {
3093
+ {entityName}Id: String!
3094
+ tenantId: String!
3095
+ deletedBy: String!
3096
+ deletedAt: String!
3097
+ }
3098
+ ```
3099
+
3100
+ ### 7. Template Configuration
3101
+
3102
+ **Location**: `packages-modules/{module}/server/package.json`
3103
+
3104
+ ```json
3105
+ {
3106
+ "cdecode": {
3107
+ "templates": {
3108
+ "services": {
3109
+ "src": "src/templates/services",
3110
+ "dest": "../../packages/common/src/services"
3111
+ },
3112
+ "repositories": {
3113
+ "src": "src/templates/repositories",
3114
+ "dest": "../../packages/common/src/repositories"
3115
+ },
3116
+ "constants": {
3117
+ "src": "src/templates/constants",
3118
+ "dest": "../../packages/common/src/constants"
3119
+ }
3120
+ }
3121
+ }
3122
+ }
3123
+ ```
3124
+
3125
+ ### 8. SERVER_TYPES Constants
3126
+
3127
+ **Location**: `packages/common/src/constants/SERVER_TYPES.ts` (add to existing)
3128
+
3129
+ ```typescript
3130
+ export const SERVER_TYPES = {
3131
+ // ... existing types
3132
+ I{EntityName}Service: Symbol.for('I{EntityName}Service'),
3133
+ I{EntityName}Repository: Symbol.for('I{EntityName}Repository'),
3134
+ I{EntityName}MongoConnection: Symbol.for('I{EntityName}MongoConnection'),
3135
+ } as const;
3136
+ ```
3137
+
3138
+ ### 9. Service Implementation
3139
+
3140
+ **Location**: `packages-modules/{module}/server/src/services/{entity}-service.ts`
3141
+
3142
+ ```typescript
3143
+ import { inject, injectable } from 'inversify';
3144
+ import { CdmLogger } from '@cdm-logger/core';
3145
+ import { ServiceBroker } from 'moleculer';
3146
+ import { CommonType } from '@common-stack/core';
3147
+ import { Disposable, DisposableCollection, Emitter } from '@adminide-stack/core';
3148
+ import {
3149
+ SERVER_TYPES,
3150
+ I{EntityName}Service,
3151
+ I{EntityName}Repository,
3152
+ I{EntityName}Model,
3153
+ ICreate{EntityName}Input,
3154
+ IUpdate{EntityName}Input,
3155
+ I{EntityName}Filter,
3156
+ // Event types if needed
3157
+ I{EntityName}CreatedEvent,
3158
+ I{EntityName}UpdatedEvent,
3159
+ I{EntityName}DeletedEvent,
3160
+ } from 'common/server';
3161
+
3162
+ @injectable()
3163
+ export class {EntityName}Service implements I{EntityName}Service, Disposable {
3164
+ // Event emitters (if needed)
3165
+ protected readonly on{EntityName}Created = new Emitter<I{EntityName}CreatedEvent>();
3166
+ protected readonly on{EntityName}Updated = new Emitter<I{EntityName}UpdatedEvent>();
3167
+ protected readonly on{EntityName}Deleted = new Emitter<I{EntityName}DeletedEvent>();
3168
+
3169
+ protected readonly toDispose = new DisposableCollection(
3170
+ this.on{EntityName}Created,
3171
+ this.on{EntityName}Updated,
3172
+ this.on{EntityName}Deleted,
3173
+ );
3174
+
3175
+ private logger: CdmLogger.ILogger;
3176
+
3177
+ constructor(
3178
+ @inject(SERVER_TYPES.I{EntityName}Repository)
3179
+ private {entityName}Repository: I{EntityName}Repository,
3180
+
3181
+ @inject(CommonType.MOLECULER_BROKER)
3182
+ protected broker: ServiceBroker,
3183
+
3184
+ @inject(CommonType.LOGGER)
3185
+ logger: CdmLogger.ILogger,
3186
+ ) {
3187
+ this.logger = logger.child({ className: {EntityName}Service.name });
3188
+ }
3189
+
3190
+ public dispose(): void {
3191
+ this.toDispose.dispose();
3192
+ }
3193
+
3194
+ public async create{EntityName}(input: ICreate{EntityName}Input): Promise<AsDomainType<I{EntityName}Model>> {
3195
+ this.logger.info(`Creating {entityName} for tenant ${input.tenantId}`);
3196
+
3197
+ // Validation logic here
3198
+
3199
+ const {entityName} = await this.{entityName}Repository.create(input);
3200
+
3201
+ // Fire creation event
3202
+ const event: I{EntityName}CreatedEvent = {
3203
+ {entityName}Id: {entityName}.id,
3204
+ tenantId: input.tenantId,
3205
+ createdBy: input.createdBy,
3206
+ createdAt: new Date().toISOString(),
3207
+ {relevantField}: {entityName}.{relevantField},
3208
+ };
3209
+
3210
+ this.on{EntityName}Created.fire(event);
3211
+
3212
+ this.logger.info(`Successfully created {entityName} ${{entityName}.id}`);
3213
+
3214
+ // CRITICAL: Cast repository result to AsDomainType for GraphQL compatibility
3215
+ return {entityName} as unknown as AsDomainType<I{EntityName}Model>;
3216
+ }
3217
+
3218
+ public async update{EntityName}(
3219
+ tenantId: string,
3220
+ id: string,
3221
+ update: IUpdate{EntityName}Input,
3222
+ ): Promise<AsDomainType<I{EntityName}Model>> {
3223
+ this.logger.info(`Updating {entityName} ${id} for tenant ${tenantId}`);
3224
+
3225
+ const updated{EntityName} = await this.{entityName}Repository.updateById(tenantId, id, update);
3226
+
3227
+ if (!updated{EntityName}) {
3228
+ throw new Error(`{EntityName} ${id} not found for tenant ${tenantId}`);
3229
+ }
3230
+
3231
+ // Fire update event
3232
+ const event: I{EntityName}UpdatedEvent = {
3233
+ {entityName}Id: id,
3234
+ tenantId,
3235
+ updatedBy: update.updatedBy || 'system',
3236
+ updatedAt: new Date().toISOString(),
3237
+ changes: update,
3238
+ };
3239
+
3240
+ this.on{EntityName}Updated.fire(event);
3241
+
3242
+ // CRITICAL: Cast repository result to AsDomainType for GraphQL compatibility
3243
+ return updated{EntityName} as unknown as AsDomainType<I{EntityName}Model>;
3244
+ }
3245
+
3246
+ public async get{EntityName}(tenantId: string, id: string): Promise<I{EntityName}Model | null> {
3247
+ return this.{entityName}Repository.findById(tenantId, id);
3248
+ }
3249
+
3250
+ public async get{EntityNamePlural}(filter: I{EntityName}Filter): Promise<I{EntityName}Model[]> {
3251
+ return this.{entityName}Repository.findByCriteria(filter);
3252
+ }
3253
+
3254
+ public async delete{EntityName}(tenantId: string, id: string, deletedBy: string): Promise<boolean> {
3255
+ this.logger.info(`Deleting {entityName} ${id} for tenant ${tenantId}`);
3256
+
3257
+ const {entityName} = await this.get{EntityName}(tenantId, id);
3258
+ if (!{entityName}) {
3259
+ throw new Error(`{EntityName} ${id} not found for tenant ${tenantId}`);
3260
+ }
3261
+
3262
+ const deleted = await this.{entityName}Repository.deleteById(tenantId, id);
3263
+
3264
+ if (deleted) {
3265
+ // Fire deletion event
3266
+ const event: I{EntityName}DeletedEvent = {
3267
+ {entityName}Id: id,
3268
+ tenantId,
3269
+ deletedBy,
3270
+ deletedAt: new Date().toISOString(),
3271
+ };
3272
+
3273
+ this.on{EntityName}Deleted.fire(event);
3274
+ this.logger.info(`Successfully deleted {entityName} ${id}`);
3275
+ }
3276
+
3277
+ return deleted;
3278
+ }
3279
+ }
3280
+ ```
3281
+
3282
+ ### 10. Container Module
3283
+
3284
+ **Location**: `packages-modules/{module}/server/src/containers/module.ts`
3285
+
3286
+ ```typescript
3287
+ import { ContainerModule, interfaces } from 'inversify';
3288
+ import {
3289
+ SERVER_TYPES,
3290
+ I{EntityName}Service,
3291
+ I{EntityName}Repository
3292
+ } from 'common/server';
3293
+ import { {EntityName}Service } from '../services/{entity}-service';
3294
+ import { {EntityName}ServiceExt } from '../services/{entity}-service-ext';
3295
+ import { {EntityName}Repository } from '../store/repositories/{entity}-repository';
3296
+
3297
+ export const {module}Module: (settings: any) => interfaces.ContainerModule = (settings: any) =>
3298
+ new ContainerModule((bind: interfaces.Bind) => {
3299
+ bind(SERVER_TYPES.I{EntityName}MongoConnection).toConstantValue(settings.mongoConnection);
3300
+
3301
+ // {EntityName} Service
3302
+ bind<I{EntityName}Service>(SERVER_TYPES.I{EntityName}Service)
3303
+ .to({EntityName}ServiceExt)
3304
+ .inSingletonScope()
3305
+ .whenTargetIsDefault();
3306
+
3307
+ // {EntityName} Repository
3308
+ bind<I{EntityName}Repository>(SERVER_TYPES.I{EntityName}Repository)
3309
+ .to({EntityName}Repository as any)
3310
+ .inSingletonScope()
3311
+ .whenTargetIsDefault();
3312
+ });
3313
+ ```
3314
+
3315
+ ## 🔄 Build Process
3316
+
3317
+ ### 1. Template Generation
3318
+
3319
+ ```bash
3320
+ cd /path/to/workspace
3321
+ yarn regenerateGraphql
3322
+ ```
3323
+
3324
+ ### 2. GraphQL Code Generation
3325
+
3326
+ ```bash
3327
+ yarn generateGraphql
3328
+ ```
3329
+
3330
+ ### 3. Verification Steps
3331
+
3332
+ 1. Check generated types in `packages/common/src/generated/generated-models.ts`
3333
+ 2. Verify interfaces in `packages/common/src/services/`
3334
+ 3. Verify interfaces in `packages/common/src/repositories/`
3335
+ 4. Check constants in `packages/common/src/constants/`
3336
+
3337
+ ## 🎯 Key Patterns and Principles
3338
+
3339
+ ### GraphQL Schema Design
3340
+
3341
+ - **Entity Types**: Use `@entity` for database-mapped types
3342
+ - **Object References**: Use `@column(overrideType: "ObjectId")` for database relations
3343
+ - **Date Fields**: Use `@column(overrideType: "Date")` for timestamps
3344
+ - **Embedded Types**: Use `@embedded` for nested objects, `@entity(embedded: true)` for the type definition
3345
+ - **Events**: Never use `@entity` directive for event types
3346
+ - **API vs Storage**: Use human-readable IDs in API (`extensionID: String!`) but store as ObjectId references (`extension: ObjectId`)
3347
+
3348
+ ### Service Layer Architecture
3349
+
3350
+ - **Interface First**: Always define service interfaces in templates
3351
+ - **Repository Pattern**: Services depend on repository interfaces, not implementations
3352
+ - **Event Driven**: Services emit events for important operations
3353
+ - **Validation**: Services handle business logic and validation
3354
+ - **Error Handling**: Services throw meaningful errors with context
3355
+
3356
+ ### Database Design
3357
+
3358
+ - **Collection Naming**: Use `{module}_{entity_name}` pattern
3359
+ - **Indexes**: Always index tenant + key fields
3360
+ - **References**: Use ObjectId references with proper ref
3361
+ - **Timestamps**: Use mongoose timestamps option
3362
+ - **Virtuals**: Add `id` virtual for GraphQL compatibility
3363
+
3364
+ ### Template System
3365
+
3366
+ - **Separation of Concerns**: Services define business interfaces, repositories define data access
3367
+ - **Code Generation**: Use templates to generate interfaces in common package
3368
+ - **Type Safety**: Generated types ensure consistency between GraphQL and TypeScript
3369
+ - **Reusability**: Templates create reusable patterns across all modules
3370
+
3371
+ ## 🚀 Usage Example
3372
+
3373
+ To create a new "ProjectTask" service in the "project-mgmt" module:
3374
+
3375
+ 1. Replace `{EntityName}` with `ProjectTask`
3376
+ 2. Replace `{entityName}` with `projectTask`
3377
+ 3. Replace `{entityNamePlural}` with `projectTasks`
3378
+ 4. Replace `{module}` with `project-mgmt`
3379
+ 5. Replace `{entity}` with `project-task`
3380
+ 6. Replace `{keyField}` with appropriate unique field (e.g., `taskId`)
3381
+ 7. Replace `{RelatedEntity}` with related entities (e.g., `Project`)
3382
+ 8. Follow all steps in the checklist
3383
+
3384
+ This template ensures consistency, type safety, and follows established patterns across the entire adminIde-stack architecture.