@cdmbase/wiki-browser 12.0.18-alpha.10

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,2781 @@
1
+ # AdminIDE Stack Preferences System - Complete LLM Guide
2
+
3
+ **Purpose:** This guide teaches how to create preferences in AdminIDE Stack, covering the complete workflow from flattened configuration keys to nested GraphQL types and database registration.
4
+
5
+ **Last Updated:** January 19, 2025 | **Version:** 1.0.0
6
+
7
+ ---
8
+
9
+ ## Table of Contents
10
+
11
+ 1. [Quick Start](#quick-start)
12
+ 2. [Architecture Overview](#architecture-overview)
13
+ 3. [Complete Implementation Guide](#complete-implementation-guide)
14
+ 4. [Mapping Rules & Patterns](#mapping-rules--patterns)
15
+ 5. [Real-World Examples](#real-world-examples)
16
+ 6. [Frontend Consumption - Practical UI Usage](#frontend-consumption---practical-ui-usage)
17
+ 7. [Testing & Validation](#testing--validation)
18
+ 8. [Best Practices](#best-practices)
19
+ 9. [Troubleshooting](#troubleshooting)
20
+ 10. [Reference](#reference)
21
+
22
+ ---
23
+
24
+ ## Quick Start
25
+
26
+ ### TL;DR - The 4-Step Pattern
27
+
28
+ ```
29
+ 1. Create flattened keys → *-contribution.ts
30
+ 2. Create migration → Add*ConfigurationsMigration.ts
31
+ 3. Create nested GraphQL → preferences.graphql
32
+ 4. Bind migration → module.ts
33
+ ```
34
+
35
+ ### Minimal Working Example
36
+
37
+ **Step 1: Contribution (Flattened Keys)**
38
+
39
+ ```typescript
40
+ // notifications-contribution.ts
41
+ import { IConfigurationSchemaMap } from '@adminide-stack/core';
42
+ import * as nls from '@vscode-alt/monaco-editor/esm/vs/nls';
43
+ import { ConfigurationScope } from 'common/server';
44
+
45
+ export const NotificationsContribution: IConfigurationSchemaMap = {
46
+ 'app.notifications.email.enabled': {
47
+ type: 'boolean',
48
+ default: true,
49
+ description: nls.localize('appNotificationsEmailEnabled', 'Enable email notifications'),
50
+ scope: ConfigurationScope.APPLICATION,
51
+ },
52
+ 'app.notifications.email.frequency': {
53
+ type: 'string',
54
+ enum: ['instant', 'daily', 'weekly'],
55
+ default: 'daily',
56
+ description: nls.localize('appNotificationsEmailFrequency', 'Email notification frequency'),
57
+ scope: ConfigurationScope.APPLICATION,
58
+ },
59
+ };
60
+ ```
61
+
62
+ **Step 2: Migration**
63
+
64
+ ```typescript
65
+ // AddNotificationsConfigurationsMigration.ts
66
+ import { IConfigurationRegistry } from '@adminide-stack/core';
67
+ import { inject, injectable } from 'inversify';
68
+ import type { Connection } from 'mongoose';
69
+ import { CdmLogger } from '@cdm-logger/core';
70
+ import { SERVER_TYPES, IRedisCacheManager } from 'common/server';
71
+ import { BaseConfigurationsMigration } from '@adminide-stack/platform-server';
72
+ import { NotificationsContribution } from '../preferences/settings';
73
+
74
+ @injectable()
75
+ export class AddNotificationsConfigurationsMigration extends BaseConfigurationsMigration {
76
+ name = 'notifications';
77
+
78
+ constructor(
79
+ @inject('MongoDBConnection') db: Connection,
80
+ @inject(SERVER_TYPES.IConfigurationRegistry) configurationRegistry: IConfigurationRegistry,
81
+ @inject('Logger') logger: CdmLogger.ILogger,
82
+ @inject(SERVER_TYPES.IRedisCacheManager) redisCacheManager: IRedisCacheManager,
83
+ ) {
84
+ super(db, configurationRegistry, redisCacheManager, logger);
85
+ }
86
+
87
+ get id() {
88
+ return `${AddNotificationsConfigurationsMigration.name}_20250119`;
89
+ }
90
+ get policies() {
91
+ return {};
92
+ }
93
+ get roles() {
94
+ return {};
95
+ }
96
+ get permissions() {
97
+ return {};
98
+ }
99
+ get permissionsOverride() {
100
+ return {};
101
+ }
102
+ get settings() {
103
+ return NotificationsContribution;
104
+ }
105
+ }
106
+ ```
107
+
108
+ **Step 3: GraphQL (Nested Structure)**
109
+
110
+ ```graphql
111
+ # preferences.graphql
112
+
113
+ """
114
+ Email notification settings
115
+ Maps to: app.notifications.email.*
116
+ """
117
+ type Preference_App_Notifications_Email {
118
+ """Maps to: app.notifications.email.enabled"""
119
+ enabled: Boolean
120
+
121
+ """Maps to: app.notifications.email.frequency"""
122
+ frequency: NotificationFrequency
123
+ }
124
+
125
+ enum NotificationFrequency {
126
+ INSTANT
127
+ DAILY
128
+ WEEKLY
129
+ }
130
+
131
+ """
132
+ Application notification preferences
133
+ Maps to: app.notifications.*
134
+ """
135
+ type Preference_App_Notifications {
136
+ email: Preference_App_Notifications_Email
137
+ }
138
+
139
+ """
140
+ Application preferences
141
+ Maps to: app.*
142
+ """
143
+ type Preference_App {
144
+ notifications: Preference_App_Notifications
145
+ }
146
+
147
+ """
148
+ Extend the global Preferences type
149
+ """
150
+ extend type Preferences {
151
+ app: Preference_App
152
+ }
153
+ ```
154
+
155
+ **Step 4: Bind in Container**
156
+
157
+ ```typescript
158
+ // module.ts
159
+ import { ContainerModule, interfaces } from 'inversify';
160
+ import { AddNotificationsConfigurationsMigration } from '../migrations';
161
+
162
+ export const notificationsModule = () =>
163
+ new ContainerModule((bind: interfaces.Bind) => {
164
+ // ... other bindings ...
165
+
166
+ bind('MongodbMigration')
167
+ .to(AddNotificationsConfigurationsMigration)
168
+ .whenTargetNamed(AddNotificationsConfigurationsMigration.name);
169
+ });
170
+ ```
171
+
172
+ ---
173
+
174
+ ## Architecture Overview
175
+
176
+ ### System Flow
177
+
178
+ ```
179
+ ┌─────────────────────────────────────────────────────────────┐
180
+ │ 1. CONTRIBUTION LAYER │
181
+ │ Flattened Keys: 'account.notification.primaryEmail' │
182
+ │ ───────────────────────────────────────────────────────── │
183
+ │ Files: preferences/settings/*-contribution.ts │
184
+ │ Format: IConfigurationSchemaMap with dot notation │
185
+ │ Purpose: Define configuration schema with types & defaults │
186
+ └──────────────────────────┬───────────────────────────────────┘
187
+
188
+
189
+ ┌─────────────────────────────────────────────────────────────┐
190
+ │ 2. MIGRATION LAYER │
191
+ │ Registers configurations to MongoDB │
192
+ │ ───────────────────────────────────────────────────────── │
193
+ │ Files: migrations/dbMigrations/Add*Configurations*.ts │
194
+ │ Extends: BaseConfigurationsMigration │
195
+ │ Purpose: Load settings into configuration registry │
196
+ └──────────────────────────┬───────────────────────────────────┘
197
+
198
+
199
+ ┌─────────────────────────────────────────────────────────────┐
200
+ │ 3. GRAPHQL LAYER │
201
+ │ Nested Types: Preference_Account.notification.primaryEmail │
202
+ │ ───────────────────────────────────────────────────────── │
203
+ │ Files: graphql/schema/preferences.graphql │
204
+ │ Format: Nested type definitions │
205
+ │ Purpose: Expose preferences via GraphQL API │
206
+ └──────────────────────────┬───────────────────────────────────┘
207
+
208
+
209
+ ┌─────────────────────────────────────────────────────────────┐
210
+ │ 4. DEPENDENCY INJECTION │
211
+ │ Binds migration to container │
212
+ │ ───────────────────────────────────────────────────────── │
213
+ │ Files: containers/module.ts │
214
+ │ Framework: InversifyJS │
215
+ │ Purpose: Make migration discoverable & executable │
216
+ └─────────────────────────────────────────────────────────────┘
217
+ ```
218
+
219
+ ### Key Components
220
+
221
+ | Component | Purpose | Format |
222
+ | ---------------- | --------------------------- | ----------------------------------- |
223
+ | **Contribution** | Define configuration schema | Flattened keys with metadata |
224
+ | **Migration** | Register to database | Extends BaseConfigurationsMigration |
225
+ | **GraphQL** | API exposure | Nested type definitions |
226
+ | **DI Binding** | Make discoverable | InversifyJS binding |
227
+
228
+ ---
229
+
230
+ ## Complete Implementation Guide
231
+
232
+ ### Step 1: Create Configuration Contribution
233
+
234
+ **Location:** `packages-modules/{module}/server/src/preferences/settings/{feature}-contribution.ts`
235
+
236
+ **Purpose:** Define all configuration properties with flattened dot-notation keys.
237
+
238
+ ```typescript
239
+ import { IConfigurationSchemaMap } from '@adminide-stack/core';
240
+ import * as nls from '@vscode-alt/monaco-editor/esm/vs/nls';
241
+ import { ConfigurationScope } from 'common/server';
242
+
243
+ /**
244
+ * Account preferences contribution
245
+ * Uses flattened dot notation for all configuration keys
246
+ * Pattern: {module}.{section}.{subsection}.{property}
247
+ */
248
+ export const AccountsContribution: IConfigurationSchemaMap = {
249
+ // ========================================
250
+ // Notification Settings
251
+ // ========================================
252
+ 'account.notification.primaryEmail': {
253
+ type: 'string',
254
+ default: '',
255
+ description: nls.localize('accountNotificationPrimaryEmail', 'Notification Primary Email'),
256
+ scope: ConfigurationScope.APPLICATION,
257
+ },
258
+ 'account.notification.onChangeAccountSettings': {
259
+ type: 'boolean',
260
+ default: true,
261
+ description: nls.localize(
262
+ 'notifyOnChangeAccountSettings',
263
+ 'Notifies you when you change your account settings',
264
+ ),
265
+ scope: ConfigurationScope.APPLICATION,
266
+ },
267
+ 'account.notification.billing': {
268
+ type: 'boolean',
269
+ default: true,
270
+ description: nls.localize('SubscribeToBillingNotifications', 'Subscribe to all billing changes'),
271
+ scope: ConfigurationScope.APPLICATION,
272
+ },
273
+
274
+ // Nested professional information
275
+ 'account.notification.professional.companyName': {
276
+ type: 'string',
277
+ default: '',
278
+ description: nls.localize('professionalCompanyName', 'Company Name'),
279
+ scope: ConfigurationScope.APPLICATION,
280
+ },
281
+ 'account.notification.professional.companyEmail': {
282
+ type: 'string',
283
+ default: '',
284
+ description: nls.localize('professionalCompanyEmail', 'Company Email'),
285
+ scope: ConfigurationScope.APPLICATION,
286
+ },
287
+ 'account.notification.professional.companyPhone': {
288
+ type: 'string',
289
+ default: '',
290
+ description: nls.localize('professionalCompanyPhone', 'Company Phone'),
291
+ scope: ConfigurationScope.APPLICATION,
292
+ },
293
+
294
+ // ========================================
295
+ // Default Settings
296
+ // ========================================
297
+ 'account.default.organization': {
298
+ type: 'string',
299
+ default: '',
300
+ description: nls.localize('accountDefaultOrganization', 'Default organization for your account'),
301
+ scope: ConfigurationScope.APPLICATION,
302
+ },
303
+ 'account.default.language': {
304
+ type: 'string',
305
+ default: 'en',
306
+ description: nls.localize('accountDefaultLanguage', 'Preferred language'),
307
+ scope: ConfigurationScope.APPLICATION,
308
+ },
309
+ 'account.default.timezone': {
310
+ type: 'string',
311
+ default: 'UTC',
312
+ description: nls.localize('accountDefaultTimezone', 'Default timezone'),
313
+ scope: ConfigurationScope.APPLICATION,
314
+ },
315
+ };
316
+
317
+ /**
318
+ * Organization preferences contribution
319
+ */
320
+ export const OrganizationContribution: IConfigurationSchemaMap = {
321
+ 'organization.notification.notifyOrgManagersOnUserJoined': {
322
+ type: 'boolean',
323
+ default: false,
324
+ description: nls.localize('notifyOrgManagersOnUserJoined', 'Notify organization manager about new member'),
325
+ scope: ConfigurationScope.WINDOW,
326
+ },
327
+ 'organization.notification.notifyOrgOwnerOnUserJoined': {
328
+ type: 'boolean',
329
+ default: false,
330
+ description: nls.localize('notifyOrgOwnerOnUserJoined', 'Notify owner about new member'),
331
+ scope: ConfigurationScope.WINDOW,
332
+ },
333
+ 'organization.teams.visibility': {
334
+ type: 'string',
335
+ enum: ['private', 'public'],
336
+ default: 'private',
337
+ enumDescriptions: [
338
+ nls.localize(
339
+ 'project.team.shareType.private',
340
+ 'Only people you give access to will be able to view this team',
341
+ ),
342
+ nls.localize('project.team.shareType.public', 'Anyone on the internet can view the team.'),
343
+ ],
344
+ description: nls.localize('organizationTeamsVisibility', 'Teams Visibility'),
345
+ scope: ConfigurationScope.RESOURCE,
346
+ },
347
+ };
348
+ ```
349
+
350
+ **Configuration Properties:**
351
+
352
+ | Property | Type | Required | Description |
353
+ | ------------------ | ------------------ | -------- | ----------------------------------------------------------- |
354
+ | `type` | string | Yes | Data type: 'string', 'boolean', 'number', 'array', 'object' |
355
+ | `default` | any | Yes | Default value for the setting |
356
+ | `description` | string | Yes | Human-readable description (use nls.localize) |
357
+ | `scope` | ConfigurationScope | Yes | Where the setting applies |
358
+ | `enum` | array | No | Allowed values for the setting |
359
+ | `enumDescriptions` | array | No | Descriptions for each enum value |
360
+
361
+ **Configuration Scopes:**
362
+
363
+ | Scope | Use Case | Example |
364
+ | ---------------------- | ------------------------------- | ------------------------ |
365
+ | `APPLICATION` | User-specific settings | Email preferences, theme |
366
+ | `WINDOW` | Workspace/Organization settings | Org notifications |
367
+ | `RESOURCE` | Project/Team specific | Project visibility |
368
+ | `LANGUAGE_OVERRIDABLE` | Per-language settings | Code formatting |
369
+ | `MACHINE` | System-wide settings | Installation path |
370
+ | `MACHINE_OVERRIDABLE` | System with user override | Proxy settings |
371
+
372
+ ---
373
+
374
+ ### Step 2: Export Contributions
375
+
376
+ **Location:** `packages-modules/{module}/server/src/preferences/settings/index.ts`
377
+
378
+ ```typescript
379
+ import { TeamsContribution } from './teams-contribution';
380
+ import { AccountsContribution } from './accounts-contribution';
381
+ import { OrganizationContribution } from './organization-contribution';
382
+ import { GlobalContribution } from './global-contribution';
383
+ import { UiLayoutContribution } from './ui-layout-contribution';
384
+
385
+ /**
386
+ * Combine all setting contributions
387
+ * These will be merged and registered in the migration
388
+ */
389
+ export const SettingsContributions = [
390
+ TeamsContribution,
391
+ AccountsContribution,
392
+ OrganizationContribution,
393
+ GlobalContribution,
394
+ ];
395
+
396
+ // Export special contributions separately if they need different handling
397
+ export { UiLayoutContribution };
398
+ ```
399
+
400
+ **Main Export:**
401
+
402
+ ```typescript
403
+ // preferences/index.ts
404
+ export * from './settings';
405
+ export * from './roles';
406
+ export * from './policies';
407
+ ```
408
+
409
+ ---
410
+
411
+ ### Step 3: Create Migration Script
412
+
413
+ **Location:** `packages-modules/{module}/server/src/migrations/dbMigrations/Add{Module}ConfigurationsMigration.ts`
414
+
415
+ ```typescript
416
+ import { IConfigurationRegistry } from '@adminide-stack/core';
417
+ import { inject, injectable } from 'inversify';
418
+ import type { Connection } from 'mongoose';
419
+ import { CdmLogger } from '@cdm-logger/core';
420
+ import { SERVER_TYPES, IRedisCacheManager, ContributionSchemaId, ContributionFragmentName } from 'common/server';
421
+ import { BaseConfigurationsMigration, ISchemaConfiguration, ISchemaOverride } from '@adminide-stack/platform-server';
422
+ import {
423
+ AccountPermissionsContribution,
424
+ AccountRolesContribution,
425
+ PoliciesContribution,
426
+ SettingsContributions,
427
+ AccountRolesPermissionOverwrite,
428
+ UiLayoutContribution,
429
+ } from '../../preferences';
430
+
431
+ /**
432
+ * Migration to register all account-related configurations
433
+ *
434
+ * This migration:
435
+ * 1. Registers settings, permissions, roles, and policies
436
+ * 2. Loads them into the configuration registry (MongoDB)
437
+ * 3. Provides up/down methods for migration control
438
+ *
439
+ * @injectable Marks this class for dependency injection
440
+ */
441
+ @injectable()
442
+ export class AddAccountsConfigurationsMigration extends BaseConfigurationsMigration {
443
+ /**
444
+ * Module name - used for namespacing configurations
445
+ * Must match the module identifier in the system
446
+ */
447
+ name = 'account';
448
+
449
+ constructor(
450
+ @inject('MongoDBConnection') db: Connection,
451
+ @inject(SERVER_TYPES.IConfigurationRegistry) configurationRegistry: IConfigurationRegistry,
452
+ @inject('Logger') logger: CdmLogger.ILogger,
453
+ @inject(SERVER_TYPES.IRedisCacheManager) redisCacheManager: IRedisCacheManager,
454
+ ) {
455
+ super(db, configurationRegistry, redisCacheManager, logger);
456
+ }
457
+
458
+ /**
459
+ * Unique ID for this migration
460
+ *
461
+ * Format: {ClassName}_{YYYYMMDD}
462
+ * IMPORTANT: Never reuse migration IDs, always update the date
463
+ */
464
+ get id() {
465
+ return `${AddAccountsConfigurationsMigration.name}_20250719`;
466
+ }
467
+
468
+ /**
469
+ * Policy configurations
470
+ * Define access policies for resources
471
+ *
472
+ * @returns Merged policy contributions
473
+ */
474
+ get policies() {
475
+ return PoliciesContribution.reduce(
476
+ (acc, curr) => ({
477
+ ...acc,
478
+ ...curr,
479
+ }),
480
+ {},
481
+ );
482
+ }
483
+
484
+ /**
485
+ * Role configurations
486
+ * Define available roles in the system
487
+ *
488
+ * Example structure:
489
+ * {
490
+ * 'account:admin': { description: 'Account Administrator', ... },
491
+ * 'account:user': { description: 'Regular User', ... }
492
+ * }
493
+ */
494
+ get roles() {
495
+ return AccountRolesContribution;
496
+ }
497
+
498
+ /**
499
+ * Permission configurations
500
+ * Define granular permissions
501
+ *
502
+ * Example structure:
503
+ * {
504
+ * 'account.read': { description: 'Read account data', ... },
505
+ * 'account.write': { description: 'Modify account data', ... }
506
+ * }
507
+ */
508
+ get permissions() {
509
+ return AccountPermissionsContribution;
510
+ }
511
+
512
+ /**
513
+ * Settings configurations
514
+ * Merge all setting contributions into a single object
515
+ *
516
+ * This combines:
517
+ * - AccountsContribution
518
+ * - OrganizationContribution
519
+ * - TeamsContribution
520
+ * - GlobalContribution
521
+ */
522
+ get settings() {
523
+ return SettingsContributions.reduce(
524
+ (acc, curr) => ({
525
+ ...acc,
526
+ ...curr,
527
+ }),
528
+ {},
529
+ );
530
+ }
531
+
532
+ /**
533
+ * Permission overrides
534
+ * Override default permissions for specific roles
535
+ *
536
+ * Example structure:
537
+ * {
538
+ * 'account:admin': {
539
+ * 'account.read': true,
540
+ * 'account.write': true,
541
+ * 'account.delete': true
542
+ * }
543
+ * }
544
+ */
545
+ get permissionsOverride() {
546
+ return AccountRolesPermissionOverwrite;
547
+ }
548
+
549
+ /**
550
+ * Additional schema configurations
551
+ * Use this to add custom schemas beyond the standard ones
552
+ * (settings, policies, permissions, roles)
553
+ *
554
+ * @returns Array of additional schema configurations
555
+ */
556
+ protected getAdditionalSchemaConfigurations(): ISchemaConfiguration[] {
557
+ return [
558
+ this.addSchemaConfiguration(
559
+ ContributionFragmentName.Settings,
560
+ ContributionSchemaId.UiLayout,
561
+ UiLayoutContribution,
562
+ {
563
+ priority: 50,
564
+ condition: () => true, // Always enable UI Layout
565
+ },
566
+ ),
567
+ ];
568
+ }
569
+
570
+ /**
571
+ * Additional schema overrides
572
+ * Use this to override schemas with custom values
573
+ *
574
+ * @returns Array of schema overrides
575
+ */
576
+ protected getAdditionalSchemaOverrides(): ISchemaOverride[] {
577
+ return [
578
+ this.addSchemaOverride(
579
+ ContributionSchemaId.UiLayout,
580
+ {}, // No overrides needed for UI Layout
581
+ { priority: 50 },
582
+ ),
583
+ ];
584
+ }
585
+ }
586
+ ```
587
+
588
+ **Migration Lifecycle:**
589
+
590
+ ```typescript
591
+ // Migration is automatically discovered and executed
592
+ // Up: migration.up() - Registers configurations
593
+ // Down: migration.down() - Deregisters configurations and clears cache
594
+ ```
595
+
596
+ ---
597
+
598
+ ### Step 4: Create GraphQL Schema
599
+
600
+ **Location:** `packages-modules/{module}/server/src/graphql/schema/preferences.graphql`
601
+
602
+ **Purpose:** Define nested GraphQL types that map to flattened configuration keys.
603
+
604
+ ```graphql
605
+ # ============================================
606
+ # SYSTEM EXTENSIONS
607
+ # ============================================
608
+
609
+ """
610
+ Extend the system contribution names
611
+ Add your module name here
612
+ """
613
+ extend enum SystemContributionExtensionNames {
614
+ account
615
+ organization
616
+ }
617
+
618
+ # ============================================
619
+ # ACCOUNT PREFERENCES
620
+ # Flattened: account.*
621
+ # ============================================
622
+
623
+ """
624
+ Professional account information
625
+ Maps to flattened keys: account.notification.professional.*
626
+ """
627
+ type Preference_Account_Professional {
628
+ """
629
+ Company name
630
+ Flattened key: account.notification.professional.companyName
631
+ """
632
+ companyName: String
633
+
634
+ """
635
+ Company email address
636
+ Flattened key: account.notification.professional.companyEmail
637
+ """
638
+ companyEmail: String
639
+
640
+ """
641
+ Company phone number
642
+ Flattened key: account.notification.professional.companyPhone
643
+ """
644
+ companyPhone: String
645
+
646
+ """
647
+ Company website URL
648
+ Flattened key: account.notification.professional.companyWebsite
649
+ """
650
+ companyWebsite: String
651
+ }
652
+
653
+ """
654
+ Account notification preferences
655
+ Maps to flattened keys: account.notification.*
656
+ """
657
+ type Preference_Account_Notification {
658
+ """
659
+ Primary email for notifications
660
+ Flattened key: account.notification.primaryEmail
661
+ """
662
+ primaryEmail: String
663
+
664
+ """
665
+ Notify on account settings changes
666
+ Flattened key: account.notification.onChangeAccountSettings
667
+ """
668
+ onChangeAccountSettings: Boolean
669
+
670
+ """
671
+ Subscribe to billing notifications
672
+ Flattened key: account.notification.billing
673
+ """
674
+ billing: Boolean
675
+
676
+ """
677
+ Professional information (nested object)
678
+ Maps to: account.notification.professional.*
679
+ """
680
+ professional: Preference_Account_Professional
681
+ }
682
+
683
+ """
684
+ Account default settings
685
+ Maps to flattened keys: account.default.*
686
+ """
687
+ type Preference_Account_Default {
688
+ """
689
+ Preferred language
690
+ Flattened key: account.default.language
691
+ """
692
+ language: String
693
+
694
+ """
695
+ Default timezone
696
+ Flattened key: account.default.timezone
697
+ """
698
+ timezone: String
699
+
700
+ """
701
+ Default organization ID
702
+ Flattened key: account.default.organization
703
+ """
704
+ organization: String
705
+ }
706
+
707
+ """
708
+ Root account preferences
709
+ Maps to flattened keys: account.*
710
+ """
711
+ type Preference_Account {
712
+ """
713
+ Account roles (array)
714
+ Flattened key: account.roles
715
+ """
716
+ roles: [String]
717
+
718
+ """
719
+ Whether account is soft-deleted
720
+ Flattened key: account.isDeleted
721
+ """
722
+ isDeleted: Boolean
723
+
724
+ """
725
+ Whether account is verified
726
+ Flattened key: account.isVerified
727
+ """
728
+ isVerified: Boolean
729
+
730
+ """
731
+ Default settings
732
+ Maps to: account.default.*
733
+ """
734
+ default: Preference_Account_Default
735
+
736
+ """
737
+ Notification settings
738
+ Maps to: account.notification.*
739
+ """
740
+ notification: Preference_Account_Notification
741
+ }
742
+
743
+ # ============================================
744
+ # ORGANIZATION PREFERENCES
745
+ # Flattened: organization.*
746
+ # ============================================
747
+
748
+ """
749
+ Organization notification preferences
750
+ Maps to flattened keys: organization.notification.*
751
+ """
752
+ type Pref_Org_Notification {
753
+ """
754
+ Notify managers when new user joins
755
+ Flattened key: organization.notification.notifyOrgManagersOnUserJoined
756
+ """
757
+ notifyOrgManagersOnUserJoined: Boolean
758
+
759
+ """
760
+ Notify owner when new user joins
761
+ Flattened key: organization.notification.notifyOrgOwnerOnUserJoined
762
+ """
763
+ notifyOrgOwnerOnUserJoined: Boolean
764
+ }
765
+
766
+ """
767
+ Team preferences
768
+ Maps to flattened keys: organization.teams.*
769
+ """
770
+ type Preference_Teams {
771
+ """
772
+ Team visibility setting
773
+ Flattened key: organization.teams.visibility
774
+ """
775
+ visibility: TeamVisibility
776
+
777
+ """
778
+ How new members can join the team
779
+ """
780
+ joinMethod: TeamJoinMethod
781
+
782
+ """
783
+ Invite code for link-based joining
784
+ """
785
+ inviteCode: String
786
+ }
787
+
788
+ """
789
+ Organization-level preferences
790
+ Groups organization.* preferences
791
+ """
792
+ type OrganizationPreferences {
793
+ """
794
+ Notification settings
795
+ Maps to: organization.notification.*
796
+ """
797
+ notification: Pref_Org_Notification
798
+
799
+ """
800
+ Team settings
801
+ Maps to: organization.teams.*
802
+ """
803
+ teams: Preference_Teams
804
+ }
805
+
806
+ # ============================================
807
+ # EXTEND BASE PREFERENCES TYPE
808
+ # ============================================
809
+
810
+ """
811
+ Extend the global Preferences type
812
+ This is the root query type for all preferences
813
+ Each module extends this with its own preferences
814
+ """
815
+ extend type Preferences {
816
+ """
817
+ Account-specific preferences
818
+ Contains all account.* settings from AccountsContribution
819
+ """
820
+ account: Preference_Account
821
+
822
+ """
823
+ Organization-specific preferences
824
+ Contains all organization.* settings from OrganizationContribution
825
+ """
826
+ organization: OrganizationPreferences
827
+ }
828
+
829
+ # ============================================
830
+ # INPUT TYPES (for mutations)
831
+ # ============================================
832
+
833
+ """
834
+ Input type for updating professional information
835
+ """
836
+ input Preference_Account_ProfessionalInput {
837
+ companyName: String
838
+ companyEmail: String
839
+ companyPhone: String
840
+ companyWebsite: String
841
+ }
842
+
843
+ """
844
+ Input type for updating notification preferences
845
+ """
846
+ input Preference_Account_NotificationInput {
847
+ primaryEmail: String
848
+ onChangeAccountSettings: Boolean
849
+ billing: Boolean
850
+ professional: Preference_Account_ProfessionalInput
851
+ }
852
+
853
+ """
854
+ Input type for updating default settings
855
+ """
856
+ input Preference_Account_DefaultInput {
857
+ language: String
858
+ timezone: String
859
+ organization: String
860
+ }
861
+
862
+ """
863
+ Input type for updating account preferences
864
+ """
865
+ input Preference_AccountInput {
866
+ default: Preference_Account_DefaultInput
867
+ notification: Preference_Account_NotificationInput
868
+ }
869
+
870
+ """
871
+ Input type for updating organization notification preferences
872
+ """
873
+ input Pref_Org_NotificationInput {
874
+ notifyOrgManagersOnUserJoined: Boolean
875
+ notifyOrgOwnerOnUserJoined: Boolean
876
+ }
877
+
878
+ """
879
+ Input type for updating team preferences
880
+ """
881
+ input Preference_TeamsInput {
882
+ visibility: TeamVisibility
883
+ joinMethod: TeamJoinMethod
884
+ inviteCode: String
885
+ }
886
+
887
+ """
888
+ Input type for updating organization preferences
889
+ """
890
+ input OrganizationPreferencesInput {
891
+ notification: Pref_Org_NotificationInput
892
+ teams: Preference_TeamsInput
893
+ }
894
+
895
+ # ============================================
896
+ # ENUMS
897
+ # ============================================
898
+
899
+ enum TeamVisibility {
900
+ PRIVATE
901
+ PUBLIC
902
+ }
903
+
904
+ enum TeamJoinMethod {
905
+ INVITE_ONLY
906
+ LINK
907
+ AUTO
908
+ }
909
+ ```
910
+
911
+ ---
912
+
913
+ ### Step 5: Register in DI Container
914
+
915
+ **Location:** `packages-modules/{module}/server/src/containers/module.ts`
916
+
917
+ ```typescript
918
+ import { ContainerModule, interfaces } from 'inversify';
919
+ import {
920
+ AddAccountsRoleMigration,
921
+ AddStrategyPrefixToAliasesMigration,
922
+ MigrateOrganizationMembersToCollection,
923
+ AddOrganizationRolesMigration,
924
+ AddTeamRolesAndRenameMembersMigration,
925
+ AddAccountsConfigurationsMigration,
926
+ MigrateTeamsToNewSchema,
927
+ DropTeamsIndexMigration,
928
+ } from '../migrations';
929
+
930
+ export const accountsModule: (settings: any, pubsub) => interfaces.ContainerModule = (settings) =>
931
+ new ContainerModule((bind: interfaces.Bind, unbind: interfaces.Unbind) => {
932
+ // ... other bindings (repositories, services, data loaders) ...
933
+
934
+ // =============================================
935
+ // REGISTER MIGRATIONS
936
+ // =============================================
937
+
938
+ // Configuration Migration - MUST be registered
939
+ bind('MongodbMigration')
940
+ .to(AddAccountsConfigurationsMigration)
941
+ .whenTargetNamed(AddAccountsConfigurationsMigration.name);
942
+
943
+ // Other migrations
944
+ bind('MongodbMigration').to(AddAccountsRoleMigration).whenTargetNamed(AddAccountsRoleMigration.name);
945
+
946
+ bind('MongodbMigration').to(AddOrganizationRolesMigration).whenTargetNamed(AddOrganizationRolesMigration.name);
947
+
948
+ // ... more migrations ...
949
+ });
950
+ ```
951
+
952
+ **Key Points:**
953
+
954
+ - Use `bind('MongodbMigration')` as the service identifier
955
+ - Use `.whenTargetNamed()` with migration class name
956
+ - Migrations are automatically discovered and executed by the system
957
+
958
+ ---
959
+
960
+ ## Mapping Rules & Patterns
961
+
962
+ ### Flattened to Nested Conversion
963
+
964
+ | Flattened Key (TypeScript) | GraphQL Nested Path | GraphQL Type |
965
+ | ------------------------------- | ------------------------------------------ | --------------------------------- |
966
+ | `account.notification.email` | `Preference_Account.notification.email` | `Preference_Account_Notification` |
967
+ | `account.default.organization` | `Preference_Account.default.organization` | `Preference_Account_Default` |
968
+ | `organization.teams.visibility` | `OrganizationPreferences.teams.visibility` | `Preference_Teams` |
969
+ | `app.settings.ui.theme` | `Preference_App.settings.ui.theme` | `Preference_App_Settings_Ui` |
970
+
971
+ ### Type Naming Convention
972
+
973
+ **Pattern:** `Preference_{Module}_{Section}_{Subsection}`
974
+
975
+ **Examples:**
976
+
977
+ ```graphql
978
+ # Simple: account.notification.email
979
+ type Preference_Account_Notification {
980
+ email: String
981
+ }
982
+
983
+ # Nested: account.notification.professional.companyName
984
+ type Preference_Account_Notification_Professional {
985
+ companyName: String
986
+ }
987
+ type Preference_Account_Notification {
988
+ professional: Preference_Account_Notification_Professional
989
+ }
990
+
991
+ # Deep nesting: app.settings.editor.formatting.indentSize
992
+ type Preference_App_Settings_Editor_Formatting {
993
+ indentSize: Int
994
+ }
995
+ ```
996
+
997
+ ### Conversion Algorithm
998
+
999
+ ```typescript
1000
+ /**
1001
+ * Convert flattened key to GraphQL path
1002
+ *
1003
+ * @param flatKey - Flattened key (e.g., 'account.notification.email')
1004
+ * @returns GraphQL path (e.g., 'Preference_Account.notification.email')
1005
+ */
1006
+ function flattenedToGraphQL(flatKey: string): string {
1007
+ const parts = flatKey.split('.');
1008
+ const typeName = `Preference_${parts.map(capitalize).join('_')}`;
1009
+ const path = parts.join('.');
1010
+ return `${typeName} => ${path}`;
1011
+ }
1012
+
1013
+ // Example:
1014
+ flattenedToGraphQL('account.notification.email');
1015
+ // Returns: "Preference_Account_Notification_Email => account.notification.email"
1016
+ ```
1017
+
1018
+ ---
1019
+
1020
+ ## Real-World Examples
1021
+
1022
+ ### Example 1: Billing Module
1023
+
1024
+ **Contribution:**
1025
+
1026
+ ```typescript
1027
+ // billing-contribution.ts
1028
+ export const BillingContribution: IConfigurationSchemaMap = {
1029
+ 'billing.payment.defaultMethod': {
1030
+ type: 'string',
1031
+ enum: ['credit_card', 'bank_transfer', 'paypal'],
1032
+ default: 'credit_card',
1033
+ description: nls.localize('billingDefaultPaymentMethod', 'Default payment method'),
1034
+ scope: ConfigurationScope.APPLICATION,
1035
+ },
1036
+ 'billing.payment.autoPay': {
1037
+ type: 'boolean',
1038
+ default: false,
1039
+ description: nls.localize('billingAutoPay', 'Enable automatic payments'),
1040
+ scope: ConfigurationScope.APPLICATION,
1041
+ },
1042
+ 'billing.notification.invoiceEmail': {
1043
+ type: 'string',
1044
+ default: '',
1045
+ description: nls.localize('billingInvoiceEmail', 'Email for invoice notifications'),
1046
+ scope: ConfigurationScope.APPLICATION,
1047
+ },
1048
+ 'billing.notification.sendReminders': {
1049
+ type: 'boolean',
1050
+ default: true,
1051
+ description: nls.localize('billingSendReminders', 'Send payment reminders'),
1052
+ scope: ConfigurationScope.APPLICATION,
1053
+ },
1054
+ 'billing.subscription.plan': {
1055
+ type: 'string',
1056
+ enum: ['free', 'pro', 'enterprise'],
1057
+ default: 'free',
1058
+ description: nls.localize('billingSubscriptionPlan', 'Subscription plan'),
1059
+ scope: ConfigurationScope.APPLICATION,
1060
+ },
1061
+ };
1062
+ ```
1063
+
1064
+ **Migration:**
1065
+
1066
+ ```typescript
1067
+ // AddBillingConfigurationsMigration.ts
1068
+ @injectable()
1069
+ export class AddBillingConfigurationsMigration extends BaseConfigurationsMigration {
1070
+ name = 'billing';
1071
+
1072
+ constructor(
1073
+ @inject('MongoDBConnection') db: Connection,
1074
+ @inject(SERVER_TYPES.IConfigurationRegistry) configurationRegistry: IConfigurationRegistry,
1075
+ @inject('Logger') logger: CdmLogger.ILogger,
1076
+ @inject(SERVER_TYPES.IRedisCacheManager) redisCacheManager: IRedisCacheManager,
1077
+ ) {
1078
+ super(db, configurationRegistry, redisCacheManager, logger);
1079
+ }
1080
+
1081
+ get id() {
1082
+ return `${AddBillingConfigurationsMigration.name}_20250119`;
1083
+ }
1084
+ get policies() {
1085
+ return {};
1086
+ }
1087
+ get roles() {
1088
+ return {};
1089
+ }
1090
+ get permissions() {
1091
+ return {};
1092
+ }
1093
+ get permissionsOverride() {
1094
+ return {};
1095
+ }
1096
+
1097
+ get settings() {
1098
+ return BillingContribution;
1099
+ }
1100
+ }
1101
+ ```
1102
+
1103
+ **GraphQL:**
1104
+
1105
+ ```graphql
1106
+ # billing.graphql
1107
+
1108
+ enum PaymentMethod {
1109
+ CREDIT_CARD
1110
+ BANK_TRANSFER
1111
+ PAYPAL
1112
+ }
1113
+
1114
+ enum SubscriptionPlan {
1115
+ FREE
1116
+ PRO
1117
+ ENTERPRISE
1118
+ }
1119
+
1120
+ type Preference_Billing_Payment {
1121
+ """
1122
+ Maps to: billing.payment.defaultMethod
1123
+ """
1124
+ defaultMethod: PaymentMethod
1125
+
1126
+ """
1127
+ Maps to: billing.payment.autoPay
1128
+ """
1129
+ autoPay: Boolean
1130
+ }
1131
+
1132
+ type Preference_Billing_Notification {
1133
+ """
1134
+ Maps to: billing.notification.invoiceEmail
1135
+ """
1136
+ invoiceEmail: String
1137
+
1138
+ """
1139
+ Maps to: billing.notification.sendReminders
1140
+ """
1141
+ sendReminders: Boolean
1142
+ }
1143
+
1144
+ type Preference_Billing_Subscription {
1145
+ """
1146
+ Maps to: billing.subscription.plan
1147
+ """
1148
+ plan: SubscriptionPlan
1149
+ }
1150
+
1151
+ type Preference_Billing {
1152
+ payment: Preference_Billing_Payment
1153
+ notification: Preference_Billing_Notification
1154
+ subscription: Preference_Billing_Subscription
1155
+ }
1156
+
1157
+ extend type Preferences {
1158
+ billing: Preference_Billing
1159
+ }
1160
+ ```
1161
+
1162
+ **Query Example:**
1163
+
1164
+ ```graphql
1165
+ query GetBillingPreferences {
1166
+ preferences {
1167
+ billing {
1168
+ payment {
1169
+ defaultMethod
1170
+ autoPay
1171
+ }
1172
+ notification {
1173
+ invoiceEmail
1174
+ sendReminders
1175
+ }
1176
+ subscription {
1177
+ plan
1178
+ }
1179
+ }
1180
+ }
1181
+ }
1182
+ ```
1183
+
1184
+ ### Example 2: Project Management with Deep Nesting
1185
+
1186
+ **Contribution:**
1187
+
1188
+ ```typescript
1189
+ // project-contribution.ts
1190
+ export const ProjectContribution: IConfigurationSchemaMap = {
1191
+ // Three levels deep
1192
+ 'project.settings.access.defaultVisibility': {
1193
+ type: 'string',
1194
+ enum: ['private', 'public', 'team'],
1195
+ default: 'private',
1196
+ description: nls.localize('projectDefaultVisibility', 'Default project visibility'),
1197
+ scope: ConfigurationScope.RESOURCE,
1198
+ },
1199
+ 'project.settings.access.allowExternalCollaborators': {
1200
+ type: 'boolean',
1201
+ default: false,
1202
+ description: nls.localize('projectAllowExternal', 'Allow external collaborators'),
1203
+ scope: ConfigurationScope.RESOURCE,
1204
+ },
1205
+ 'project.settings.notifications.emailOnUpdate': {
1206
+ type: 'boolean',
1207
+ default: true,
1208
+ description: nls.localize('projectEmailOnUpdate', 'Email on project updates'),
1209
+ scope: ConfigurationScope.RESOURCE,
1210
+ },
1211
+ 'project.settings.notifications.digestFrequency': {
1212
+ type: 'string',
1213
+ enum: ['instant', 'daily', 'weekly', 'never'],
1214
+ default: 'daily',
1215
+ description: nls.localize('projectDigestFrequency', 'Notification digest frequency'),
1216
+ scope: ConfigurationScope.RESOURCE,
1217
+ },
1218
+ 'project.defaults.template': {
1219
+ type: 'string',
1220
+ default: '',
1221
+ description: nls.localize('projectDefaultTemplate', 'Default project template'),
1222
+ scope: ConfigurationScope.APPLICATION,
1223
+ },
1224
+ };
1225
+ ```
1226
+
1227
+ **GraphQL:**
1228
+
1229
+ ```graphql
1230
+ # project.graphql
1231
+
1232
+ enum ProjectVisibility {
1233
+ PRIVATE
1234
+ PUBLIC
1235
+ TEAM
1236
+ }
1237
+
1238
+ enum DigestFrequency {
1239
+ INSTANT
1240
+ DAILY
1241
+ WEEKLY
1242
+ NEVER
1243
+ }
1244
+
1245
+ type Preference_Project_Settings_Access {
1246
+ """
1247
+ Maps to: project.settings.access.defaultVisibility
1248
+ """
1249
+ defaultVisibility: ProjectVisibility
1250
+
1251
+ """
1252
+ Maps to: project.settings.access.allowExternalCollaborators
1253
+ """
1254
+ allowExternalCollaborators: Boolean
1255
+ }
1256
+
1257
+ type Preference_Project_Settings_Notifications {
1258
+ """
1259
+ Maps to: project.settings.notifications.emailOnUpdate
1260
+ """
1261
+ emailOnUpdate: Boolean
1262
+
1263
+ """
1264
+ Maps to: project.settings.notifications.digestFrequency
1265
+ """
1266
+ digestFrequency: DigestFrequency
1267
+ }
1268
+
1269
+ type Preference_Project_Settings {
1270
+ access: Preference_Project_Settings_Access
1271
+ notifications: Preference_Project_Settings_Notifications
1272
+ }
1273
+
1274
+ type Preference_Project_Defaults {
1275
+ """
1276
+ Maps to: project.defaults.template
1277
+ """
1278
+ template: String
1279
+ }
1280
+
1281
+ type Preference_Project {
1282
+ settings: Preference_Project_Settings
1283
+ defaults: Preference_Project_Defaults
1284
+ }
1285
+
1286
+ extend type Preferences {
1287
+ project: Preference_Project
1288
+ }
1289
+ ```
1290
+
1291
+ ### Example 3: Multiple Contributions in One Module
1292
+
1293
+ **Index File:**
1294
+
1295
+ ```typescript
1296
+ // preferences/settings/index.ts
1297
+ import { AccountsContribution } from './accounts-contribution';
1298
+ import { OrganizationContribution } from './organization-contribution';
1299
+ import { TeamsContribution } from './teams-contribution';
1300
+ import { ProfileContribution } from './profile-contribution';
1301
+
1302
+ export const SettingsContributions = [
1303
+ AccountsContribution,
1304
+ OrganizationContribution,
1305
+ TeamsContribution,
1306
+ ProfileContribution,
1307
+ ];
1308
+ ```
1309
+
1310
+ **Migration:**
1311
+
1312
+ ```typescript
1313
+ // Automatically merges all contributions
1314
+ get settings() {
1315
+ return SettingsContributions.reduce(
1316
+ (acc, curr) => ({
1317
+ ...acc,
1318
+ ...curr,
1319
+ }),
1320
+ {},
1321
+ );
1322
+ }
1323
+ ```
1324
+
1325
+ ---
1326
+
1327
+ ## Frontend Consumption - Practical UI Usage
1328
+
1329
+ ### Overview
1330
+
1331
+ The preferences system provides two hooks for consuming configurations in React components:
1332
+
1333
+ 1. **`useSettingsLoader`** - Uses data pre-loaded via Remix loader (faster, no GraphQL call)
1334
+ 2. **`useSettings`** - Makes a GraphQL call to fetch settings (flexible, not pre-loaded)
1335
+
1336
+ ### Hook 1: useSettingsLoader (Recommended for Performance)
1337
+
1338
+ **Purpose:** Access pre-loaded configurations that were fetched during Remix route loading.
1339
+
1340
+ **Requirements:**
1341
+
1342
+ - Must configure `configurations` in the route definition (compute.ts)
1343
+ - Data is available immediately without GraphQL calls
1344
+ - Best for frequently accessed preferences
1345
+
1346
+ #### Configuration in Route (Required)
1347
+
1348
+ **File:** `packages-modules/{module}/browser/src/modules/{feature}/compute.ts`
1349
+
1350
+ ```typescript
1351
+ import { IMenuPosition, IRouteModule } from '@common-stack/core';
1352
+ import { PreDefineAccountPermissions } from '@adminide-stack/core/lib/modules/account-api/enums/index.js';
1353
+ import { ConfigCollectionName, ConfigFragmentName } from 'common';
1354
+
1355
+ export const accountPageStore: IRouteModule[] = [
1356
+ {
1357
+ name: 'menu.account_settings',
1358
+ key: 'account-settings',
1359
+ path: `${ORG_STD_ROUTES.USER_BASE_PATH}/account-settings`,
1360
+ tab: 'Settings',
1361
+ auth: true,
1362
+ authority: [PreDefineAccountPermissions.viewSettings],
1363
+ extraPermissions: [PreDefineAccountPermissions.editSettings],
1364
+ component: () => import('./components/Settings/AccountsSettings'),
1365
+
1366
+ // ============================================
1367
+ // CONFIGURE WHICH SETTINGS TO PRE-LOAD
1368
+ // ============================================
1369
+
1370
+ // Option 1: Load ALL configurations
1371
+ configurations: ['*'],
1372
+
1373
+ // Option 2: Load specific top-level keys only
1374
+ // configurations: ['account', 'organization', 'billing'],
1375
+
1376
+ extraParams: {
1377
+ resourceParams: {
1378
+ resourceType: ConfigCollectionName.Accounts,
1379
+ resourceId: '$params.orgName',
1380
+ organization: '$params.orgName',
1381
+ fragment: ConfigFragmentName.Settings,
1382
+ },
1383
+ },
1384
+ },
1385
+ ];
1386
+ ```
1387
+
1388
+ **Configuration Options:**
1389
+
1390
+ | Value | Behavior | Use Case |
1391
+ | ----------------------------- | ---------------------------- | ------------------------------------ |
1392
+ | `['*']` | Load all configurations | Settings pages with many preferences |
1393
+ | `['account']` | Load only `account.*` keys | Page only uses account preferences |
1394
+ | `['account', 'organization']` | Load multiple top-level keys | Page uses multiple modules |
1395
+ | Not set or `[]` | No pre-loading | Use `useSettings` instead |
1396
+
1397
+ #### Usage in Components
1398
+
1399
+ **Complete Key - Get Final Value:**
1400
+
1401
+ ```typescript
1402
+ import React from 'react';
1403
+ import { useSettingsLoader } from '@adminide-stack/platform-client';
1404
+
1405
+ export const NotificationSettings = () => {
1406
+ // Get a specific string value with full key path
1407
+ const { data: primaryEmail, loading, error, updateConfiguration } = useSettingsLoader({
1408
+ configKey: 'account.notification.primaryEmail',
1409
+ });
1410
+
1411
+ // data type: string (inferred from flattened key)
1412
+ console.log(primaryEmail); // "user@example.com"
1413
+
1414
+ const handleUpdateEmail = async (newEmail: string) => {
1415
+ await updateConfiguration({
1416
+ value: { 'account.notification.primaryEmail': newEmail },
1417
+ target: 'user',
1418
+ });
1419
+ };
1420
+
1421
+ if (loading) return <div>Loading...</div>;
1422
+ if (error) return <div>Error: {error.message}</div>;
1423
+
1424
+ return (
1425
+ <div>
1426
+ <h3>Primary Email</h3>
1427
+ <input
1428
+ type="email"
1429
+ value={primaryEmail || ''}
1430
+ onChange={(e) => handleUpdateEmail(e.target.value)}
1431
+ />
1432
+ </div>
1433
+ );
1434
+ };
1435
+ ```
1436
+
1437
+ **Higher-Level Key - Get Object:**
1438
+
1439
+ ```typescript
1440
+ import React from 'react';
1441
+ import { useSettingsLoader } from '@adminide-stack/platform-client';
1442
+
1443
+ export const InvitationPage = ({ decoded }) => {
1444
+ // Get entire notification object with partial key
1445
+ const { data: notification, loading, error } = useSettingsLoader({
1446
+ configKey: 'organization.notification',
1447
+ });
1448
+
1449
+ // data type: IOrganizationNotificationValues (inferred)
1450
+ // notification = {
1451
+ // notifyOrgManagersOnUserJoined: true,
1452
+ // notifyOrgOwnerOnUserJoined: false
1453
+ // }
1454
+
1455
+ const acceptInvitation = () => {
1456
+ accept({
1457
+ variables: {
1458
+ id: decoded.invitationId,
1459
+ notification, // Pass entire object to mutation
1460
+ },
1461
+ });
1462
+ };
1463
+
1464
+ if (loading) return <div>Loading...</div>;
1465
+
1466
+ return (
1467
+ <div>
1468
+ <h3>Invitation for {decoded.orgName}</h3>
1469
+ <p>Manager notifications: {notification?.notifyOrgManagersOnUserJoined ? 'Yes' : 'No'}</p>
1470
+ <p>Owner notifications: {notification?.notifyOrgOwnerOnUserJoined ? 'Yes' : 'No'}</p>
1471
+ <button onClick={acceptInvitation}>Accept</button>
1472
+ </div>
1473
+ );
1474
+ };
1475
+ ```
1476
+
1477
+ **Complete Example with All Features:**
1478
+
1479
+ ```typescript
1480
+ import React, { useState } from 'react';
1481
+ import { useSettingsLoader } from '@adminide-stack/platform-client';
1482
+
1483
+ export const AccountSettingsPage = () => {
1484
+ // Get entire account preferences object
1485
+ const {
1486
+ data: accountPrefs,
1487
+ loading,
1488
+ error,
1489
+ updateConfiguration,
1490
+ preferencesInput
1491
+ } = useSettingsLoader({
1492
+ configKey: 'account',
1493
+ });
1494
+
1495
+ // accountPrefs type: Preference_Account
1496
+ // accountPrefs = {
1497
+ // notification: {
1498
+ // primaryEmail: "user@example.com",
1499
+ // billing: true,
1500
+ // onChangeAccountSettings: true,
1501
+ // professional: {
1502
+ // companyName: "Acme Corp",
1503
+ // companyEmail: "info@acme.com"
1504
+ // }
1505
+ // },
1506
+ // default: {
1507
+ // organization: "my-org",
1508
+ // language: "en",
1509
+ // timezone: "UTC"
1510
+ // }
1511
+ // }
1512
+
1513
+ const [email, setEmail] = useState(accountPrefs?.notification?.primaryEmail || '');
1514
+
1515
+ const handleSave = async () => {
1516
+ try {
1517
+ await updateConfiguration({
1518
+ value: {
1519
+ 'account.notification.primaryEmail': email,
1520
+ 'account.notification.billing': true,
1521
+ },
1522
+ target: 'user',
1523
+ });
1524
+ console.log('Settings updated successfully');
1525
+ } catch (err) {
1526
+ console.error('Failed to update settings:', err);
1527
+ }
1528
+ };
1529
+
1530
+ if (loading) return <div>Loading preferences...</div>;
1531
+ if (error) return <div>Error: {error.message}</div>;
1532
+
1533
+ return (
1534
+ <div>
1535
+ <h2>Account Settings</h2>
1536
+
1537
+ {/* Access nested values */}
1538
+ <section>
1539
+ <h3>Notification Settings</h3>
1540
+ <div>
1541
+ <label>Primary Email:</label>
1542
+ <input
1543
+ type="email"
1544
+ value={email}
1545
+ onChange={(e) => setEmail(e.target.value)}
1546
+ />
1547
+ </div>
1548
+ <div>
1549
+ <label>
1550
+ <input
1551
+ type="checkbox"
1552
+ checked={accountPrefs?.notification?.billing}
1553
+ />
1554
+ Billing Notifications
1555
+ </label>
1556
+ </div>
1557
+ </section>
1558
+
1559
+ {/* Access professional info */}
1560
+ <section>
1561
+ <h3>Professional Information</h3>
1562
+ <p>Company: {accountPrefs?.notification?.professional?.companyName || 'Not set'}</p>
1563
+ <p>Email: {accountPrefs?.notification?.professional?.companyEmail || 'Not set'}</p>
1564
+ </section>
1565
+
1566
+ {/* Access default settings */}
1567
+ <section>
1568
+ <h3>Default Settings</h3>
1569
+ <p>Organization: {accountPrefs?.default?.organization}</p>
1570
+ <p>Language: {accountPrefs?.default?.language}</p>
1571
+ <p>Timezone: {accountPrefs?.default?.timezone}</p>
1572
+ </section>
1573
+
1574
+ <button onClick={handleSave}>Save Changes</button>
1575
+ </div>
1576
+ );
1577
+ };
1578
+ ```
1579
+
1580
+ **Type Safety & Autocomplete:**
1581
+
1582
+ ```typescript
1583
+ import { useSettingsLoader } from '@adminide-stack/platform-client';
1584
+ import type { IConfigurationsFlattenedKeys, GetFlattenedValue } from 'common';
1585
+
1586
+ export const TypeSafeComponent = () => {
1587
+ // TypeScript provides autocomplete for all flattened keys
1588
+ const { data } = useSettingsLoader({
1589
+ configKey: 'account.notification.primaryEmail', // Autocomplete available!
1590
+ // ^-- Type: IConfigurationsFlattenedKeys
1591
+ });
1592
+
1593
+ // Return type is automatically inferred
1594
+ // data type: string (from 'account.notification.primaryEmail' configuration)
1595
+
1596
+ // Get partial object with type inference
1597
+ const { data: notification } = useSettingsLoader({
1598
+ configKey: 'organization.notification',
1599
+ // ^-- Autocomplete shows: organization.notification.notifyOrgManagersOnUserJoined
1600
+ // organization.notification.notifyOrgOwnerOnUserJoined
1601
+ });
1602
+
1603
+ // notification type: { notifyOrgManagersOnUserJoined?: boolean, notifyOrgOwnerOnUserJoined?: boolean }
1604
+ };
1605
+ ```
1606
+
1607
+ ### Hook 2: useSettings (For Non-Pre-loaded Settings)
1608
+
1609
+ **Purpose:** Fetch settings via GraphQL when they're not pre-loaded in the route.
1610
+
1611
+ **Use Cases:**
1612
+
1613
+ - Dynamic components not tied to specific routes
1614
+ - Modal dialogs or popups
1615
+ - Components that need settings on-demand
1616
+ - When you don't want to pre-load all settings
1617
+
1618
+ #### Usage
1619
+
1620
+ ```typescript
1621
+ import React from 'react';
1622
+ import { useSettings } from '@adminide-stack/platform-client';
1623
+ import { URI } from '@adminide-stack/core';
1624
+
1625
+ export const DynamicSettingsComponent = ({ resourceUri }: { resourceUri: string }) => {
1626
+ const {
1627
+ data, // The specific value from configKey
1628
+ settings, // All settings object
1629
+ pageSettings, // Complete page settings with metadata
1630
+ loading,
1631
+ error,
1632
+ updateConfiguration
1633
+ } = useSettings({
1634
+ resourceUri: resourceUri as URI,
1635
+ options: {
1636
+ configKey: 'organization.notification.notifyOrgOwnerOnUserJoined',
1637
+ },
1638
+ });
1639
+
1640
+ // data type: boolean (the value of the specific key)
1641
+ console.log(data); // true or false
1642
+
1643
+ // settings: complete settings object
1644
+ console.log(settings); // { account: {...}, organization: {...}, ... }
1645
+
1646
+ const handleToggle = async () => {
1647
+ await updateConfiguration({
1648
+ value: {
1649
+ 'organization.notification.notifyOrgOwnerOnUserJoined': !data
1650
+ },
1651
+ target: 'organization',
1652
+ });
1653
+ };
1654
+
1655
+ if (loading) return <div>Loading...</div>;
1656
+ if (error) return <div>Error: {error.message}</div>;
1657
+
1658
+ return (
1659
+ <div>
1660
+ <label>
1661
+ <input
1662
+ type="checkbox"
1663
+ checked={data || false}
1664
+ onChange={handleToggle}
1665
+ />
1666
+ Notify Owner on User Joined
1667
+ </label>
1668
+ </div>
1669
+ );
1670
+ };
1671
+ ```
1672
+
1673
+ **Get Complete Settings Object:**
1674
+
1675
+ ```typescript
1676
+ export const CompleteSettingsView = ({ resourceUri }: { resourceUri: string }) => {
1677
+ // Don't provide configKey to get all settings
1678
+ const { settings, loading } = useSettings({
1679
+ resourceUri: resourceUri as URI,
1680
+ options: {
1681
+ // No configKey = get all settings
1682
+ },
1683
+ });
1684
+
1685
+ // settings contains all configuration data
1686
+ console.log(settings?.account?.notification?.primaryEmail);
1687
+ console.log(settings?.organization?.teams?.visibility);
1688
+
1689
+ return (
1690
+ <div>
1691
+ <h2>All Settings</h2>
1692
+ <pre>{JSON.stringify(settings, null, 2)}</pre>
1693
+ </div>
1694
+ );
1695
+ };
1696
+ ```
1697
+
1698
+ ### Comparison: useSettingsLoader vs useSettings
1699
+
1700
+ | Feature | useSettingsLoader | useSettings |
1701
+ | -------------------------- | ---------------------------------- | -------------------------- |
1702
+ | **Data Source** | Remix loader (pre-loaded) | GraphQL query (on-demand) |
1703
+ | **Performance** | ⚡ Instant (no network call) | 🔄 Network call required |
1704
+ | **Configuration Required** | ✅ Yes (`configurations` in route) | ❌ No route config needed |
1705
+ | **Use Case** | Route-based pages | Dynamic components, modals |
1706
+ | **Type Safety** | ✅ Full autocomplete | ✅ Full autocomplete |
1707
+ | **Cache** | Loader cache | Apollo cache |
1708
+ | **Updates** | Via updateConfiguration | Via updateConfiguration |
1709
+
1710
+ ### Update Configuration Pattern
1711
+
1712
+ Both hooks provide `updateConfiguration` method with the same API:
1713
+
1714
+ ```typescript
1715
+ const { updateConfiguration } = useSettingsLoader({ configKey: 'account' });
1716
+ // or
1717
+ const { updateConfiguration } = useSettings({ resourceUri, options: {} });
1718
+
1719
+ // Update single value
1720
+ await updateConfiguration({
1721
+ value: {
1722
+ 'account.notification.primaryEmail': 'new@email.com',
1723
+ },
1724
+ target: 'user', // or 'organization', 'team', etc.
1725
+ });
1726
+
1727
+ // Update multiple values
1728
+ await updateConfiguration({
1729
+ value: {
1730
+ 'account.notification.primaryEmail': 'new@email.com',
1731
+ 'account.notification.billing': true,
1732
+ 'account.default.timezone': 'America/New_York',
1733
+ },
1734
+ target: 'user',
1735
+ updateOverrides: {
1736
+ resource: 'org://my-org/settings',
1737
+ },
1738
+ });
1739
+ ```
1740
+
1741
+ ### Real-World Examples
1742
+
1743
+ #### Example 1: Settings Page with Pre-loaded Data
1744
+
1745
+ **Route Configuration:**
1746
+
1747
+ ```typescript
1748
+ // compute.ts
1749
+ {
1750
+ key: 'account-settings',
1751
+ path: '/settings/account',
1752
+ component: () => import('./AccountSettings'),
1753
+ configurations: ['account', 'organization'], // Pre-load these keys
1754
+ }
1755
+ ```
1756
+
1757
+ **Component:**
1758
+
1759
+ ```typescript
1760
+ // AccountSettings.tsx
1761
+ import { useSettingsLoader } from '@adminide-stack/platform-client';
1762
+
1763
+ export const AccountSettings = () => {
1764
+ // All data is pre-loaded, no GraphQL call
1765
+ const { data: account } = useSettingsLoader({ configKey: 'account' });
1766
+ const { data: org } = useSettingsLoader({ configKey: 'organization' });
1767
+
1768
+ return (
1769
+ <div>
1770
+ <h2>Account Settings</h2>
1771
+ <p>Email: {account?.notification?.primaryEmail}</p>
1772
+ <p>Org Notifications: {org?.notification?.notifyOrgOwnerOnUserJoined ? 'On' : 'Off'}</p>
1773
+ </div>
1774
+ );
1775
+ };
1776
+ ```
1777
+
1778
+ #### Example 2: Modal with Dynamic Settings
1779
+
1780
+ **No route configuration needed:**
1781
+
1782
+ ```typescript
1783
+ // NotificationModal.tsx
1784
+ import { useSettings } from '@adminide-stack/platform-client';
1785
+
1786
+ export const NotificationModal = ({ isOpen, orgUri }) => {
1787
+ // Makes GraphQL call when modal opens
1788
+ const { data: notifyOwner, updateConfiguration } = useSettings({
1789
+ resourceUri: orgUri,
1790
+ options: {
1791
+ configKey: 'organization.notification.notifyOrgOwnerOnUserJoined',
1792
+ },
1793
+ skip: !isOpen, // Don't fetch until modal opens
1794
+ });
1795
+
1796
+ if (!isOpen) return null;
1797
+
1798
+ return (
1799
+ <div className="modal">
1800
+ <h3>Notification Settings</h3>
1801
+ <label>
1802
+ <input
1803
+ type="checkbox"
1804
+ checked={notifyOwner || false}
1805
+ onChange={async (e) => {
1806
+ await updateConfiguration({
1807
+ value: {
1808
+ 'organization.notification.notifyOrgOwnerOnUserJoined': e.target.checked
1809
+ },
1810
+ target: 'organization',
1811
+ });
1812
+ }}
1813
+ />
1814
+ Notify owner when users join
1815
+ </label>
1816
+ </div>
1817
+ );
1818
+ };
1819
+ ```
1820
+
1821
+ #### Example 3: Invitation Page (From Your Codebase)
1822
+
1823
+ ```typescript
1824
+ import React from 'react';
1825
+ import { useSettingsLoader } from '@adminide-stack/platform-client';
1826
+ import { useAcceptOrganizationInvitationMutation } from 'common/graphql';
1827
+
1828
+ export const InvitationPage = ({ decoded }) => {
1829
+ // Pre-loaded via route configuration
1830
+ const { data: notification } = useSettingsLoader({
1831
+ configKey: 'organization.notification',
1832
+ });
1833
+
1834
+ const [accept] = useAcceptOrganizationInvitationMutation();
1835
+
1836
+ const acceptInvitation = () => {
1837
+ // Pass entire notification object to mutation
1838
+ accept({
1839
+ variables: {
1840
+ id: decoded.invitationId,
1841
+ notification, // { notifyOrgManagersOnUserJoined, notifyOrgOwnerOnUserJoined }
1842
+ },
1843
+ });
1844
+ };
1845
+
1846
+ return (
1847
+ <div>
1848
+ <h2>Join {decoded.orgName}?</h2>
1849
+ <p>Invited by: {decoded.invitedBy}</p>
1850
+ <button onClick={acceptInvitation}>Accept Invitation</button>
1851
+ </div>
1852
+ );
1853
+ };
1854
+ ```
1855
+
1856
+ ### Best Practices for Frontend
1857
+
1858
+ 1. **Use `useSettingsLoader` for route-based pages:**
1859
+
1860
+ ```typescript
1861
+ // ✅ Good: Pre-load in route
1862
+ configurations: ['account', 'organization'];
1863
+ ```
1864
+
1865
+ 2. **Use `useSettings` for dynamic components:**
1866
+
1867
+ ```typescript
1868
+ // ✅ Good: Modal that appears conditionally
1869
+ const { data } = useSettings({ resourceUri, options: { configKey: '...' }, skip: !isOpen });
1870
+ ```
1871
+
1872
+ 3. **Get objects for related settings:**
1873
+
1874
+ ```typescript
1875
+ // ✅ Good: Get object when you need multiple related values
1876
+ const { data: notification } = useSettingsLoader({
1877
+ configKey: 'organization.notification',
1878
+ });
1879
+
1880
+ // ❌ Bad: Multiple calls for related values
1881
+ const { data: notify1 } = useSettingsLoader({
1882
+ configKey: 'organization.notification.notifyOrgOwnerOnUserJoined',
1883
+ });
1884
+ const { data: notify2 } = useSettingsLoader({
1885
+ configKey: 'organization.notification.notifyOrgManagersOnUserJoined',
1886
+ });
1887
+ ```
1888
+
1889
+ 4. **Configure route with minimal keys:**
1890
+
1891
+ ```typescript
1892
+ // ✅ Good: Only load what you need
1893
+ configurations: ['account']; // Page only uses account settings
1894
+
1895
+ // ❌ Bad: Load everything when you only need one module
1896
+ configurations: ['*']; // Loads all settings unnecessarily
1897
+ ```
1898
+
1899
+ 5. **Use skip option to defer loading:**
1900
+ ```typescript
1901
+ // ✅ Good: Don't fetch until needed
1902
+ const { data } = useSettings({
1903
+ resourceUri,
1904
+ options: { configKey: '...' },
1905
+ skip: !shouldFetch,
1906
+ });
1907
+ ```
1908
+
1909
+ ### Type Definitions Reference
1910
+
1911
+ ```typescript
1912
+ // Hook signatures
1913
+ function useSettingsLoader<K extends IConfigurationsFlattenedKeys, R = IPreferences>(
1914
+ settingsVariable: ISettingsLoaderVariable<K>,
1915
+ ): ISettingsLoaderResponse<K, GetFlattenedValue<R, K>>;
1916
+
1917
+ function useSettings<T = any>(
1918
+ variables: IPageSettingsConfigKeyVariable,
1919
+ ): {
1920
+ data: T;
1921
+ settings: any;
1922
+ pageSettings: any;
1923
+ loading: boolean;
1924
+ error: any;
1925
+ updateConfiguration: (params) => Promise<any>;
1926
+ };
1927
+
1928
+ // Variable types
1929
+ interface ISettingsLoaderVariable<K> {
1930
+ configKey: K; // Autocomplete available
1931
+ skip?: boolean;
1932
+ }
1933
+
1934
+ interface IPageSettingsConfigKeyVariable {
1935
+ resourceUri: URI | string;
1936
+ options?: {
1937
+ configKey?: string;
1938
+ };
1939
+ skip?: boolean;
1940
+ }
1941
+ ```
1942
+
1943
+ ---
1944
+
1945
+ ## Testing & Validation
1946
+
1947
+ ### Unit Tests for Migration
1948
+
1949
+ ```typescript
1950
+ // AddAccountsConfigurationsMigration.test.ts
1951
+ import { Container } from 'inversify';
1952
+ import { AddAccountsConfigurationsMigration } from './AddAccountsConfigurationsMigration';
1953
+
1954
+ describe('AddAccountsConfigurationsMigration', () => {
1955
+ let migration: AddAccountsConfigurationsMigration;
1956
+ let container: Container;
1957
+
1958
+ beforeEach(() => {
1959
+ container = new Container();
1960
+ // Mock dependencies
1961
+ container.bind('MongoDBConnection').toConstantValue(mockConnection);
1962
+ container.bind(SERVER_TYPES.IConfigurationRegistry).toConstantValue(mockRegistry);
1963
+ container.bind('Logger').toConstantValue(mockLogger);
1964
+ container.bind(SERVER_TYPES.IRedisCacheManager).toConstantValue(mockCache);
1965
+
1966
+ migration = container.resolve(AddAccountsConfigurationsMigration);
1967
+ });
1968
+
1969
+ it('should have correct migration id with date', () => {
1970
+ expect(migration.id).toBe('AddAccountsConfigurationsMigration_20250719');
1971
+ expect(migration.id).toMatch(/_\d{8}$/);
1972
+ });
1973
+
1974
+ it('should have module name', () => {
1975
+ expect(migration.name).toBe('account');
1976
+ });
1977
+
1978
+ it('should merge all settings contributions', () => {
1979
+ const settings = migration.settings;
1980
+
1981
+ // Check AccountsContribution keys
1982
+ expect(settings).toHaveProperty('account.notification.primaryEmail');
1983
+ expect(settings).toHaveProperty('account.default.organization');
1984
+
1985
+ // Check OrganizationContribution keys
1986
+ expect(settings).toHaveProperty('organization.notification.notifyOrgOwnerOnUserJoined');
1987
+
1988
+ // Check TeamsContribution keys
1989
+ expect(settings).toHaveProperty('organization.teams.visibility');
1990
+ });
1991
+
1992
+ it('should have valid configuration schema', () => {
1993
+ const settings = migration.settings;
1994
+
1995
+ Object.entries(settings).forEach(([key, config]) => {
1996
+ expect(config).toHaveProperty('type');
1997
+ expect(config).toHaveProperty('default');
1998
+ expect(config).toHaveProperty('description');
1999
+ expect(config).toHaveProperty('scope');
2000
+ });
2001
+ });
2002
+
2003
+ it('should register configurations successfully', async () => {
2004
+ const spy = jest.spyOn(mockRegistry, 'registerConfiguration');
2005
+
2006
+ await migration.up();
2007
+
2008
+ expect(spy).toHaveBeenCalled();
2009
+ expect(spy.mock.calls.length).toBeGreaterThan(0);
2010
+ });
2011
+
2012
+ it('should deregister configurations on down', async () => {
2013
+ const spy = jest.spyOn(mockRegistry, 'deregisterConfigurations');
2014
+
2015
+ await migration.down();
2016
+
2017
+ expect(spy).toHaveBeenCalled();
2018
+ });
2019
+
2020
+ it('should clear cache on down', async () => {
2021
+ const spy = jest.spyOn(mockCache, 'del');
2022
+
2023
+ await migration.down();
2024
+
2025
+ expect(spy).toHaveBeenCalled();
2026
+ });
2027
+ });
2028
+ ```
2029
+
2030
+ ### GraphQL Schema Tests
2031
+
2032
+ ```graphql
2033
+ # test-preferences.graphql
2034
+
2035
+ # Test query to verify all nested structures
2036
+ query TestCompletePreferences {
2037
+ preferences {
2038
+ account {
2039
+ notification {
2040
+ primaryEmail
2041
+ onChangeAccountSettings
2042
+ billing
2043
+ professional {
2044
+ companyName
2045
+ companyEmail
2046
+ companyPhone
2047
+ companyWebsite
2048
+ }
2049
+ }
2050
+ default {
2051
+ organization
2052
+ language
2053
+ timezone
2054
+ }
2055
+ roles
2056
+ isDeleted
2057
+ isVerified
2058
+ }
2059
+ organization {
2060
+ notification {
2061
+ notifyOrgOwnerOnUserJoined
2062
+ notifyOrgManagersOnUserJoined
2063
+ }
2064
+ teams {
2065
+ visibility
2066
+ joinMethod
2067
+ inviteCode
2068
+ }
2069
+ }
2070
+ }
2071
+ }
2072
+
2073
+ # Test mutation
2074
+ mutation UpdateAccountPreferences($input: Preference_AccountInput!) {
2075
+ updateAccountPreferences(input: $input) {
2076
+ account {
2077
+ notification {
2078
+ primaryEmail
2079
+ billing
2080
+ }
2081
+ default {
2082
+ organization
2083
+ }
2084
+ }
2085
+ }
2086
+ }
2087
+ ```
2088
+
2089
+ ### Integration Tests
2090
+
2091
+ ```typescript
2092
+ // test-preferences-integration.ts
2093
+ import { gql } from 'apollo-server-express';
2094
+ import { createTestClient } from 'apollo-server-testing';
2095
+
2096
+ describe('Preferences Integration', () => {
2097
+ let testClient;
2098
+
2099
+ beforeAll(async () => {
2100
+ // Setup test server
2101
+ testClient = createTestClient(server);
2102
+ });
2103
+
2104
+ it('should query nested preferences', async () => {
2105
+ const query = gql`
2106
+ query {
2107
+ preferences {
2108
+ account {
2109
+ notification {
2110
+ primaryEmail
2111
+ }
2112
+ }
2113
+ }
2114
+ }
2115
+ `;
2116
+
2117
+ const { data } = await testClient.query({ query });
2118
+
2119
+ expect(data.preferences.account.notification).toBeDefined();
2120
+ expect(data.preferences.account.notification.primaryEmail).toBeDefined();
2121
+ });
2122
+
2123
+ it('should update preferences', async () => {
2124
+ const mutation = gql`
2125
+ mutation ($input: Preference_AccountInput!) {
2126
+ updateAccountPreferences(input: $input) {
2127
+ account {
2128
+ notification {
2129
+ primaryEmail
2130
+ }
2131
+ }
2132
+ }
2133
+ }
2134
+ `;
2135
+
2136
+ const { data } = await testClient.mutate({
2137
+ mutation,
2138
+ variables: {
2139
+ input: {
2140
+ notification: {
2141
+ primaryEmail: 'test@example.com',
2142
+ },
2143
+ },
2144
+ },
2145
+ });
2146
+
2147
+ expect(data.updateAccountPreferences.account.notification.primaryEmail).toBe('test@example.com');
2148
+ });
2149
+ });
2150
+ ```
2151
+
2152
+ ### Manual Testing Commands
2153
+
2154
+ ```bash
2155
+ # Run migrations
2156
+ npm run migrate:up
2157
+
2158
+ # Rollback migrations
2159
+ npm run migrate:down
2160
+
2161
+ # Test GraphQL schema
2162
+ npm run test:graphql
2163
+
2164
+ # Run integration tests
2165
+ npm run test:integration
2166
+
2167
+ # Check MongoDB for registered configurations
2168
+ mongo
2169
+ > use your_database
2170
+ > db.configuration_registry.find({ 'context.extensionId': 'account' })
2171
+ ```
2172
+
2173
+ ---
2174
+
2175
+ ## Best Practices
2176
+
2177
+ ### 1. Naming Conventions
2178
+
2179
+ **Contributions:**
2180
+
2181
+ - Format: `{Feature}Contribution`
2182
+ - Examples: `AccountsContribution`, `BillingContribution`, `ProjectContribution`
2183
+
2184
+ **Migrations:**
2185
+
2186
+ - Format: `Add{Module}ConfigurationsMigration`
2187
+ - Examples: `AddAccountsConfigurationsMigration`, `AddBillingConfigurationsMigration`
2188
+
2189
+ **GraphQL Types:**
2190
+
2191
+ - Format: `Preference_{Module}_{Section}_{Subsection}`
2192
+ - Examples: `Preference_Account_Notification`, `Preference_Billing_Payment`
2193
+
2194
+ **Flattened Keys:**
2195
+
2196
+ - Format: `{module}.{section}.{property}`
2197
+ - Examples: `account.notification.email`, `billing.payment.method`
2198
+ - Max depth: 4 levels (recommended)
2199
+
2200
+ ### 2. Configuration Schema Best Practices
2201
+
2202
+ ```typescript
2203
+ // ✅ GOOD: Clear, specific, with localization
2204
+ 'account.notification.primaryEmail': {
2205
+ type: 'string',
2206
+ default: '',
2207
+ description: nls.localize('accountNotificationPrimaryEmail', 'Notification Primary Email'),
2208
+ scope: ConfigurationScope.APPLICATION,
2209
+ }
2210
+
2211
+ // ❌ BAD: No localization, vague description
2212
+ 'account.email': {
2213
+ type: 'string',
2214
+ default: '',
2215
+ description: 'Email',
2216
+ scope: ConfigurationScope.APPLICATION,
2217
+ }
2218
+
2219
+ // ✅ GOOD: Enum with descriptions
2220
+ 'billing.payment.method': {
2221
+ type: 'string',
2222
+ enum: ['credit_card', 'paypal', 'bank_transfer'],
2223
+ enumDescriptions: [
2224
+ nls.localize('paymentCreditCard', 'Pay with credit or debit card'),
2225
+ nls.localize('paymentPaypal', 'Pay with PayPal account'),
2226
+ nls.localize('paymentBank', 'Direct bank transfer'),
2227
+ ],
2228
+ default: 'credit_card',
2229
+ description: nls.localize('billingPaymentMethod', 'Preferred payment method'),
2230
+ scope: ConfigurationScope.APPLICATION,
2231
+ }
2232
+ ```
2233
+
2234
+ ### 3. GraphQL Schema Best Practices
2235
+
2236
+ ```graphql
2237
+ # ✅ GOOD: Clear documentation with mapping
2238
+ """
2239
+ Account notification preferences
2240
+ Maps to flattened keys: account.notification.*
2241
+ """
2242
+ type Preference_Account_Notification {
2243
+ """
2244
+ Primary email for notifications
2245
+ Flattened key: account.notification.primaryEmail
2246
+ """
2247
+ primaryEmail: String
2248
+ }
2249
+
2250
+ # ❌ BAD: No documentation
2251
+ type Preference_Account_Notification {
2252
+ primaryEmail: String
2253
+ }
2254
+
2255
+ # ✅ GOOD: Use enums for restricted values
2256
+ enum PaymentMethod {
2257
+ CREDIT_CARD
2258
+ BANK_TRANSFER
2259
+ PAYPAL
2260
+ }
2261
+
2262
+ type Preference_Billing_Payment {
2263
+ defaultMethod: PaymentMethod
2264
+ }
2265
+
2266
+ # ❌ BAD: Use String without restriction
2267
+ type Preference_Billing_Payment {
2268
+ defaultMethod: String
2269
+ }
2270
+ ```
2271
+
2272
+ ### 4. Migration Best Practices
2273
+
2274
+ ```typescript
2275
+ // ✅ GOOD: Date-based versioning
2276
+ get id() {
2277
+ return `${AddAccountsConfigurationsMigration.name}_20250119`;
2278
+ }
2279
+
2280
+ // ❌ BAD: No versioning
2281
+ get id() {
2282
+ return AddAccountsConfigurationsMigration.name;
2283
+ }
2284
+
2285
+ // ✅ GOOD: Merge multiple contributions
2286
+ get settings() {
2287
+ return SettingsContributions.reduce(
2288
+ (acc, curr) => ({
2289
+ ...acc,
2290
+ ...curr,
2291
+ }),
2292
+ {},
2293
+ );
2294
+ }
2295
+
2296
+ // ❌ BAD: Return single contribution
2297
+ get settings() {
2298
+ return AccountsContribution; // Missing other contributions
2299
+ }
2300
+ ```
2301
+
2302
+ ### 5. Scope Selection Guidelines
2303
+
2304
+ | Setting Type | Recommended Scope | Example |
2305
+ | ------------------ | ---------------------- | ---------------------------------- |
2306
+ | User preferences | `APPLICATION` | Theme, language, email |
2307
+ | Workspace settings | `WINDOW` | Org notifications, team defaults |
2308
+ | Resource settings | `RESOURCE` | Project visibility, access control |
2309
+ | Format overrides | `LANGUAGE_OVERRIDABLE` | Per-language formatting |
2310
+ | System config | `MACHINE` | Installation paths, system proxy |
2311
+
2312
+ ### 6. Key Hierarchy Guidelines
2313
+
2314
+ ```typescript
2315
+ // ✅ GOOD: Logical hierarchy (max 4 levels)
2316
+ 'account.notification.email.frequency';
2317
+ 'organization.teams.access.defaultVisibility';
2318
+ 'project.settings.editor.formatting';
2319
+
2320
+ // ❌ BAD: Too deep (5+ levels)
2321
+ 'app.user.account.settings.notification.email.delivery.frequency';
2322
+
2323
+ // ❌ BAD: Inconsistent structure
2324
+ 'account.notificationEmail'; // Should be: account.notification.email
2325
+ 'accountNotificationFrequency'; // Should be: account.notification.frequency
2326
+ ```
2327
+
2328
+ ### 7. Documentation Best Practices
2329
+
2330
+ ```typescript
2331
+ /**
2332
+ * Account preferences contribution
2333
+ *
2334
+ * Defines user-level account settings including:
2335
+ * - Notification preferences
2336
+ * - Default organization
2337
+ * - Professional information
2338
+ *
2339
+ * All settings use ConfigurationScope.APPLICATION as they are user-specific.
2340
+ *
2341
+ * @example
2342
+ * // Accessing in code:
2343
+ * const email = configService.getValue('account.notification.primaryEmail');
2344
+ *
2345
+ * // In GraphQL:
2346
+ * query {
2347
+ * preferences {
2348
+ * account {
2349
+ * notification {
2350
+ * primaryEmail
2351
+ * }
2352
+ * }
2353
+ * }
2354
+ * }
2355
+ */
2356
+ export const AccountsContribution: IConfigurationSchemaMap = {
2357
+ // ... settings
2358
+ };
2359
+ ```
2360
+
2361
+ ---
2362
+
2363
+ ## Troubleshooting
2364
+
2365
+ ### Common Issues and Solutions
2366
+
2367
+ #### Issue 1: Settings not appearing in database
2368
+
2369
+ **Symptoms:**
2370
+
2371
+ - Migration runs successfully but settings don't appear in configuration registry
2372
+ - GraphQL queries return null for preferences
2373
+
2374
+ **Solutions:**
2375
+
2376
+ 1. Check migration binding:
2377
+
2378
+ ```typescript
2379
+ // Verify in module.ts
2380
+ bind('MongodbMigration')
2381
+ .to(AddAccountsConfigurationsMigration)
2382
+ .whenTargetNamed(AddAccountsConfigurationsMigration.name);
2383
+ ```
2384
+
2385
+ 2. Verify migration execution:
2386
+
2387
+ ```bash
2388
+ # Check logs for migration execution
2389
+ npm run migrate:up -- --verbose
2390
+ ```
2391
+
2392
+ 3. Check MongoDB:
2393
+
2394
+ ```javascript
2395
+ // Connect to MongoDB
2396
+ db.configuration_registry.find({ 'context.extensionId': 'account' });
2397
+ ```
2398
+
2399
+ 4. Verify contribution export:
2400
+ ```typescript
2401
+ // In settings/index.ts
2402
+ export const SettingsContributions = [
2403
+ AccountsContribution, // Must be included
2404
+ // ...
2405
+ ];
2406
+ ```
2407
+
2408
+ #### Issue 2: GraphQL type doesn't match flattened keys
2409
+
2410
+ **Symptoms:**
2411
+
2412
+ - GraphQL queries fail with "Cannot query field"
2413
+ - Type structure doesn't align with configuration keys
2414
+
2415
+ **Solution:**
2416
+ Review the mapping systematically:
2417
+
2418
+ ```typescript
2419
+ // Flattened key
2420
+ 'account.notification.professional.companyName'
2421
+
2422
+ // Should map to GraphQL (split on dots):
2423
+ // 1. account → Preference_Account
2424
+ // 2. notification → Preference_Account_Notification
2425
+ // 3. professional → Preference_Account_Professional
2426
+ // 4. companyName → companyName: String
2427
+
2428
+ type Preference_Account_Professional {
2429
+ companyName: String // ✅ Correct
2430
+ }
2431
+ ```
2432
+
2433
+ #### Issue 3: Cache not clearing after migration
2434
+
2435
+ **Symptoms:**
2436
+
2437
+ - Old preference values persist after migration
2438
+ - Changes don't reflect in queries
2439
+
2440
+ **Solutions:**
2441
+
2442
+ 1. Verify Redis connection:
2443
+
2444
+ ```typescript
2445
+ // Check redisCacheManager injection
2446
+ @inject(SERVER_TYPES.IRedisCacheManager)
2447
+ redisCacheManager: IRedisCacheManager,
2448
+ ```
2449
+
2450
+ 2. Manually clear cache:
2451
+
2452
+ ```bash
2453
+ # Connect to Redis
2454
+ redis-cli
2455
+ > FLUSHDB
2456
+ ```
2457
+
2458
+ 3. Check cache clearing in down():
2459
+ ```typescript
2460
+ async down() {
2461
+ await this.dropCache(); // Must be called
2462
+ // ... deregister configurations
2463
+ }
2464
+ ```
2465
+
2466
+ #### Issue 4: Migration fails with "No matching bindings"
2467
+
2468
+ **Symptoms:**
2469
+
2470
+ ```
2471
+ Error: No matching bindings found for serviceIdentifier: Symbol(IConfigurationRegistry)
2472
+ ```
2473
+
2474
+ **Solution:**
2475
+ Ensure all dependencies are properly injected:
2476
+
2477
+ ```typescript
2478
+ constructor(
2479
+ @inject('MongoDBConnection') db: Connection,
2480
+ @inject(SERVER_TYPES.IConfigurationRegistry) configurationRegistry: IConfigurationRegistry,
2481
+ @inject('Logger') logger: CdmLogger.ILogger,
2482
+ @inject(SERVER_TYPES.IRedisCacheManager) redisCacheManager: IRedisCacheManager,
2483
+ ) {
2484
+ super(db, configurationRegistry, redisCacheManager, logger);
2485
+ }
2486
+ ```
2487
+
2488
+ #### Issue 5: Enum values not matching
2489
+
2490
+ **Symptoms:**
2491
+
2492
+ - GraphQL returns enum values in wrong case
2493
+ - Enum validation fails
2494
+
2495
+ **Solution:**
2496
+ Ensure consistency between TypeScript and GraphQL:
2497
+
2498
+ ```typescript
2499
+ // TypeScript contribution
2500
+ enum: ['private', 'public'] // lowercase
2501
+
2502
+ // GraphQL enum - convert to UPPERCASE
2503
+ enum TeamVisibility {
2504
+ PRIVATE # Maps to 'private'
2505
+ PUBLIC # Maps to 'public'
2506
+ }
2507
+ ```
2508
+
2509
+ #### Issue 6: Nested objects not saving
2510
+
2511
+ **Symptoms:**
2512
+
2513
+ - Top-level preferences save, but nested objects don't persist
2514
+ - Mutations succeed but queries return null for nested fields
2515
+
2516
+ **Solution:**
2517
+ Ensure complete input type hierarchy:
2518
+
2519
+ ```graphql
2520
+ # Need complete chain of input types
2521
+ input Preference_Account_ProfessionalInput {
2522
+ companyName: String
2523
+ }
2524
+
2525
+ input Preference_Account_NotificationInput {
2526
+ professional: Preference_Account_ProfessionalInput # Must include nested
2527
+ }
2528
+
2529
+ input Preference_AccountInput {
2530
+ notification: Preference_Account_NotificationInput # Must include parent
2531
+ }
2532
+ ```
2533
+
2534
+ ---
2535
+
2536
+ ## Reference
2537
+
2538
+ ### File Structure
2539
+
2540
+ ```
2541
+ packages-modules/{module-name}/
2542
+ ├── server/
2543
+ │ ├── src/
2544
+ │ │ ├── preferences/
2545
+ │ │ │ ├── index.ts # Export all preferences
2546
+ │ │ │ ├── settings/
2547
+ │ │ │ │ ├── index.ts # Export settings contributions
2548
+ │ │ │ │ ├── {feature}-contribution.ts # Flattened keys
2549
+ │ │ │ │ └── ...
2550
+ │ │ │ ├── roles/
2551
+ │ │ │ │ └── index.ts # Role definitions
2552
+ │ │ │ └── policies/
2553
+ │ │ │ └── index.ts # Policy definitions
2554
+ │ │ ├── migrations/
2555
+ │ │ │ └── dbMigrations/
2556
+ │ │ │ └── Add{Module}ConfigurationsMigration.ts
2557
+ │ │ ├── graphql/
2558
+ │ │ │ └── schema/
2559
+ │ │ │ ├── preferences.graphql # Nested types
2560
+ │ │ │ └── ...
2561
+ │ │ └── containers/
2562
+ │ │ └── module.ts # DI binding
2563
+ ```
2564
+
2565
+ ### Quick Reference Table
2566
+
2567
+ | Component | File Location | Key Pattern | Export Format |
2568
+ | ------------------ | ---------------------------------------- | --------------------------- | ------------------------- |
2569
+ | **Contribution** | `preferences/settings/*-contribution.ts` | `module.section.property` | `IConfigurationSchemaMap` |
2570
+ | **Settings Index** | `preferences/settings/index.ts` | N/A | `SettingsContributions[]` |
2571
+ | **Migration** | `migrations/dbMigrations/Add*.ts` | N/A | `@injectable() class` |
2572
+ | **GraphQL Types** | `graphql/schema/preferences.graphql` | `Preference_Module_Section` | GraphQL SDL |
2573
+ | **DI Binding** | `containers/module.ts` | N/A | InversifyJS binding |
2574
+
2575
+ ### Configuration Scope Reference
2576
+
2577
+ ```typescript
2578
+ export enum ConfigurationScope {
2579
+ APPLICATION = 1, // User-specific settings
2580
+ WINDOW = 2, // Workspace/Organization settings
2581
+ RESOURCE = 3, // Project/Team/Resource settings
2582
+ LANGUAGE_OVERRIDABLE = 4, // Can be overridden per language
2583
+ MACHINE = 5, // System-wide settings
2584
+ MACHINE_OVERRIDABLE = 6, // System settings with user override
2585
+ }
2586
+ ```
2587
+
2588
+ ### Common Imports
2589
+
2590
+ ```typescript
2591
+ // Contributions
2592
+ import { IConfigurationSchemaMap } from '@adminide-stack/core';
2593
+ import * as nls from '@vscode-alt/monaco-editor/esm/vs/nls';
2594
+ import { ConfigurationScope } from 'common/server';
2595
+
2596
+ // Migrations
2597
+ import { IConfigurationRegistry } from '@adminide-stack/core';
2598
+ import { inject, injectable } from 'inversify';
2599
+ import type { Connection } from 'mongoose';
2600
+ import { CdmLogger } from '@cdm-logger/core';
2601
+ import { SERVER_TYPES, IRedisCacheManager, ContributionSchemaId, ContributionFragmentName } from 'common/server';
2602
+ import { BaseConfigurationsMigration, ISchemaConfiguration, ISchemaOverride } from '@adminide-stack/platform-server';
2603
+
2604
+ // DI Container
2605
+ import { ContainerModule, interfaces } from 'inversify';
2606
+ ```
2607
+
2608
+ ### Useful Commands
2609
+
2610
+ ```bash
2611
+ # Development
2612
+ npm run dev # Start development server
2613
+ npm run build # Build the project
2614
+
2615
+ # Migrations
2616
+ npm run migrate:up # Run all pending migrations
2617
+ npm run migrate:down # Rollback last migration
2618
+ npm run migrate:status # Check migration status
2619
+
2620
+ # Testing
2621
+ npm run test # Run all tests
2622
+ npm run test:unit # Run unit tests
2623
+ npm run test:integration # Run integration tests
2624
+ npm run test:graphql # Test GraphQL schema
2625
+
2626
+ # Database
2627
+ npm run db:seed # Seed database
2628
+ npm run db:reset # Reset database
2629
+
2630
+ # Debugging
2631
+ npm run migrate:up -- --verbose # Verbose migration output
2632
+ npm run test -- --watch # Watch mode for tests
2633
+ ```
2634
+
2635
+ ### GraphQL Query Examples
2636
+
2637
+ ```graphql
2638
+ # Get all preferences
2639
+ query GetAllPreferences {
2640
+ preferences {
2641
+ account {
2642
+ notification {
2643
+ primaryEmail
2644
+ billing
2645
+ }
2646
+ default {
2647
+ organization
2648
+ }
2649
+ }
2650
+ organization {
2651
+ notification {
2652
+ notifyOrgOwnerOnUserJoined
2653
+ }
2654
+ }
2655
+ }
2656
+ }
2657
+
2658
+ # Get specific preference section
2659
+ query GetAccountNotifications {
2660
+ preferences {
2661
+ account {
2662
+ notification {
2663
+ primaryEmail
2664
+ onChangeAccountSettings
2665
+ billing
2666
+ }
2667
+ }
2668
+ }
2669
+ }
2670
+
2671
+ # Update preferences
2672
+ mutation UpdatePreferences($input: Preference_AccountInput!) {
2673
+ updateAccountPreferences(input: $input) {
2674
+ account {
2675
+ notification {
2676
+ primaryEmail
2677
+ }
2678
+ }
2679
+ }
2680
+ }
2681
+ ```
2682
+
2683
+ ### MongoDB Queries
2684
+
2685
+ ```javascript
2686
+ // Find all configurations for a module
2687
+ db.configuration_registry.find({
2688
+ 'context.extensionId': 'account',
2689
+ });
2690
+
2691
+ // Find specific configuration
2692
+ db.configuration_registry.findOne({
2693
+ 'node.id': 'account.notification.primaryEmail',
2694
+ });
2695
+
2696
+ // Check migration status
2697
+ db.migrations.find().sort({ executedAt: -1 });
2698
+
2699
+ // Clear configuration cache
2700
+ db.configuration_cache.deleteMany({});
2701
+ ```
2702
+
2703
+ ---
2704
+
2705
+ ## Summary Checklist
2706
+
2707
+ When creating new preferences, ensure you:
2708
+
2709
+ ### Configuration Phase
2710
+
2711
+ - [ ] Create `{feature}-contribution.ts` with flattened keys
2712
+ - [ ] Use proper naming: `module.section.property`
2713
+ - [ ] Add type, default, description, and scope for each key
2714
+ - [ ] Use `nls.localize()` for all descriptions
2715
+ - [ ] Export in `settings/index.ts`
2716
+ - [ ] Add to `SettingsContributions` array
2717
+
2718
+ ### Migration Phase
2719
+
2720
+ - [ ] Create migration extending `BaseConfigurationsMigration`
2721
+ - [ ] Set unique migration ID with date: `{ClassName}_YYYYMMDD`
2722
+ - [ ] Implement all required getters
2723
+ - [ ] Merge contributions in `settings` getter
2724
+ - [ ] Add `@injectable()` decorator
2725
+
2726
+ ### GraphQL Phase
2727
+
2728
+ - [ ] Create nested types matching flattened structure
2729
+ - [ ] Follow naming: `Preference_{Module}_{Section}`
2730
+ - [ ] Add GraphQL descriptions with flattened key mappings
2731
+ - [ ] Create corresponding input types for mutations
2732
+ - [ ] Extend base `Preferences` type
2733
+ - [ ] Define necessary enums
2734
+
2735
+ ### Integration Phase
2736
+
2737
+ - [ ] Bind migration in `module.ts` with `whenTargetNamed()`
2738
+ - [ ] Ensure all dependencies are injected
2739
+ - [ ] Add to module exports if needed
2740
+
2741
+ ### Testing Phase
2742
+
2743
+ - [ ] Write unit tests for migration
2744
+ - [ ] Test GraphQL schema with queries
2745
+ - [ ] Verify database registration
2746
+ - [ ] Test cache clearing
2747
+ - [ ] Run integration tests
2748
+
2749
+ ### Frontend Integration Phase
2750
+
2751
+ - [ ] Configure `configurations` in route (compute.ts)
2752
+ - [ ] Choose between `useSettingsLoader` (pre-loaded) or `useSettings` (on-demand)
2753
+ - [ ] Test with complete keys for specific values
2754
+ - [ ] Test with partial keys for nested objects
2755
+ - [ ] Verify type autocomplete works
2756
+ - [ ] Test `updateConfiguration` mutation
2757
+ - [ ] Handle loading and error states
2758
+
2759
+ ### Documentation Phase
2760
+
2761
+ - [ ] Document flattened keys with examples
2762
+ - [ ] Add JSDoc to contribution
2763
+ - [ ] Document GraphQL type mappings
2764
+ - [ ] Document frontend usage patterns
2765
+ - [ ] Update module README if needed
2766
+
2767
+ ---
2768
+
2769
+ ## Additional Resources
2770
+
2771
+ - **TypeScript Configuration**: `@adminide-stack/core` package
2772
+ - **Base Migration**: `packages/adminide-platform/server/src/migrations/BaseConfigurationService.ts`
2773
+ - **GraphQL Base Types**: Search for base `Preferences` type in schema files
2774
+ - **InversifyJS Docs**: https://inversify.io/
2775
+ - **MongoDB Docs**: https://docs.mongodb.com/
2776
+
2777
+ ---
2778
+
2779
+ **Document Version:** 1.0.0
2780
+ **Last Updated:** January 19, 2025
2781
+ **Maintained By:** AdminIDE Stack Team