@quazardous/qdadm 1.19.3

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 (291) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +111 -0
  3. package/package.json +101 -0
  4. package/src/assets/logo.svg +6 -0
  5. package/src/auth/SessionAuthAdapter.ts +459 -0
  6. package/src/auth/index.ts +15 -0
  7. package/src/chain/ActiveStack.ts +154 -0
  8. package/src/chain/StackHydrator.ts +292 -0
  9. package/src/chain/index.ts +25 -0
  10. package/src/chain/useActiveStack.ts +120 -0
  11. package/src/chain/useStackHydrator.ts +155 -0
  12. package/src/components/BoolCell.vue +34 -0
  13. package/src/components/InfoBanner.vue +73 -0
  14. package/src/components/QdadmRoot.vue +52 -0
  15. package/src/components/SeverityTag.vue +86 -0
  16. package/src/components/dialogs/BulkStatusDialog.vue +60 -0
  17. package/src/components/dialogs/MultiStepDialog.vue +331 -0
  18. package/src/components/dialogs/SimpleDialog.vue +122 -0
  19. package/src/components/dialogs/UnsavedChangesDialog.vue +80 -0
  20. package/src/components/display/BannerZone.vue +65 -0
  21. package/src/components/display/CardsGrid.vue +72 -0
  22. package/src/components/display/CopyableId.vue +84 -0
  23. package/src/components/display/EmptyState.vue +62 -0
  24. package/src/components/display/IntensityBar.vue +190 -0
  25. package/src/components/display/RichCardsGrid.vue +101 -0
  26. package/src/components/edit/FormActions.vue +78 -0
  27. package/src/components/edit/FormField.vue +82 -0
  28. package/src/components/edit/FormInput.vue +158 -0
  29. package/src/components/edit/FormPage.vue +260 -0
  30. package/src/components/edit/FormTab.vue +217 -0
  31. package/src/components/edit/FormTabs.vue +120 -0
  32. package/src/components/edit/LookupField.vue +406 -0
  33. package/src/components/edit/LookupPickerDialog.vue +230 -0
  34. package/src/components/editors/JsonEditorFoldable.vue +470 -0
  35. package/src/components/editors/JsonStructuredField.vue +264 -0
  36. package/src/components/editors/JsonViewer.vue +51 -0
  37. package/src/components/editors/KeyValueEditor.vue +314 -0
  38. package/src/components/editors/LanguageEditor.vue +259 -0
  39. package/src/components/editors/PermissionEditor.vue +558 -0
  40. package/src/components/editors/ScopeEditor.vue +372 -0
  41. package/src/components/editors/VanillaJsonEditor.vue +183 -0
  42. package/src/components/index.ts +83 -0
  43. package/src/components/item/FieldGroups.vue +392 -0
  44. package/src/components/layout/AppLayout.vue +1032 -0
  45. package/src/components/layout/BaseLayout.vue +190 -0
  46. package/src/components/layout/Breadcrumb.vue +55 -0
  47. package/src/components/layout/DashboardLayout.vue +104 -0
  48. package/src/components/layout/FormLayout.vue +256 -0
  49. package/src/components/layout/ListLayout.vue +278 -0
  50. package/src/components/layout/PageHeader.vue +156 -0
  51. package/src/components/layout/PageLayout.vue +126 -0
  52. package/src/components/layout/PageNav.vue +192 -0
  53. package/src/components/layout/SidebarBox.vue +87 -0
  54. package/src/components/layout/Zone.vue +182 -0
  55. package/src/components/layout/defaults/DefaultBreadcrumb.vue +149 -0
  56. package/src/components/layout/defaults/DefaultFooter.vue +89 -0
  57. package/src/components/layout/defaults/DefaultFormActions.vue +67 -0
  58. package/src/components/layout/defaults/DefaultHeader.vue +78 -0
  59. package/src/components/layout/defaults/DefaultMenu.vue +188 -0
  60. package/src/components/layout/defaults/DefaultPagination.vue +75 -0
  61. package/src/components/layout/defaults/DefaultTable.vue +137 -0
  62. package/src/components/layout/defaults/DefaultUserInfo.vue +111 -0
  63. package/src/components/layout/defaults/index.ts +16 -0
  64. package/src/components/lists/ActionButtons.vue +49 -0
  65. package/src/components/lists/ActionColumn.vue +40 -0
  66. package/src/components/lists/FilterBar.vue +54 -0
  67. package/src/components/lists/ListPage.vue +467 -0
  68. package/src/components/pages/LoginPage.vue +334 -0
  69. package/src/components/pages/NotFoundPage.vue +98 -0
  70. package/src/components/show/ShowDisplay.vue +343 -0
  71. package/src/components/show/ShowField.vue +67 -0
  72. package/src/components/show/ShowPage.vue +431 -0
  73. package/src/composables/index.ts +155 -0
  74. package/src/composables/useApp.ts +67 -0
  75. package/src/composables/useAuth.ts +98 -0
  76. package/src/composables/useBareForm.ts +319 -0
  77. package/src/composables/useBreadcrumb.ts +252 -0
  78. package/src/composables/useChildPage.ts +113 -0
  79. package/src/composables/useCurrentEntity.ts +60 -0
  80. package/src/composables/useDeferred.ts +94 -0
  81. package/src/composables/useDirtyState.ts +138 -0
  82. package/src/composables/useEntityItemFormPage.ts +942 -0
  83. package/src/composables/useEntityItemFormPage.types.ts +451 -0
  84. package/src/composables/useEntityItemPage.ts +438 -0
  85. package/src/composables/useEntityItemShowPage.ts +722 -0
  86. package/src/composables/useFieldManager.ts +692 -0
  87. package/src/composables/useForm.ts +544 -0
  88. package/src/composables/useGuardStore.ts +53 -0
  89. package/src/composables/useHooks.ts +56 -0
  90. package/src/composables/useInfoBanner.ts +217 -0
  91. package/src/composables/useJsonSyntax.ts +122 -0
  92. package/src/composables/useLayoutResolver.ts +329 -0
  93. package/src/composables/useListPage.ts +1535 -0
  94. package/src/composables/useListPage.types.ts +437 -0
  95. package/src/composables/useListPage.utils.ts +101 -0
  96. package/src/composables/useNavContext.ts +439 -0
  97. package/src/composables/useNavigation.ts +216 -0
  98. package/src/composables/useOptionsLookup.ts +323 -0
  99. package/src/composables/usePageTitle.ts +84 -0
  100. package/src/composables/useSSEBridge.ts +142 -0
  101. package/src/composables/useSemanticBreadcrumb.ts +222 -0
  102. package/src/composables/useSidebarState.ts +24 -0
  103. package/src/composables/useSignals.ts +52 -0
  104. package/src/composables/useUnsavedChangesGuard.ts +155 -0
  105. package/src/composables/useUserImpersonator.ts +317 -0
  106. package/src/composables/useZoneRegistry.ts +252 -0
  107. package/src/core/bundles.ts +558 -0
  108. package/src/core/decorator.ts +420 -0
  109. package/src/core/extension.ts +470 -0
  110. package/src/core/index.ts +59 -0
  111. package/src/deferred/DeferredRegistry.ts +353 -0
  112. package/src/deferred/index.ts +15 -0
  113. package/src/editors/index.ts +12 -0
  114. package/src/entity/EntityManager.cache.ts +530 -0
  115. package/src/entity/EntityManager.crud.ts +615 -0
  116. package/src/entity/EntityManager.interface.ts +78 -0
  117. package/src/entity/EntityManager.query.ts +221 -0
  118. package/src/entity/EntityManager.relations.ts +151 -0
  119. package/src/entity/EntityManager.ts +944 -0
  120. package/src/entity/EntityManager.types.ts +288 -0
  121. package/src/entity/auth/CompositeAuthAdapter.ts +194 -0
  122. package/src/entity/auth/EntityAuthAdapter.ts +119 -0
  123. package/src/entity/auth/PermissiveAdapter.ts +43 -0
  124. package/src/entity/auth/RoleHierarchy.ts +128 -0
  125. package/src/entity/auth/SecurityChecker.ts +202 -0
  126. package/src/entity/auth/factory.ts +203 -0
  127. package/src/entity/auth/index.ts +47 -0
  128. package/src/entity/factory.test.js +189 -0
  129. package/src/entity/factory.ts +146 -0
  130. package/src/entity/index.ts +49 -0
  131. package/src/entity/storage/ApiStorage.ts +228 -0
  132. package/src/entity/storage/IStorage.ts +100 -0
  133. package/src/entity/storage/LocalStorage.ts +200 -0
  134. package/src/entity/storage/MemoryStorage.ts +184 -0
  135. package/src/entity/storage/MockApiStorage.ts +318 -0
  136. package/src/entity/storage/SdkStorage.ts +377 -0
  137. package/src/entity/storage/factory.test.js +159 -0
  138. package/src/entity/storage/factory.ts +209 -0
  139. package/src/entity/storage/index.ts +75 -0
  140. package/src/gen/FieldMapper.ts +122 -0
  141. package/src/gen/StorageProfileFactory.ts +93 -0
  142. package/src/gen/connectors/BaseConnector.ts +150 -0
  143. package/src/gen/connectors/ManualConnector.test.js +499 -0
  144. package/src/gen/connectors/ManualConnector.ts +414 -0
  145. package/src/gen/connectors/OpenAPIConnector.test.js +836 -0
  146. package/src/gen/connectors/OpenAPIConnector.ts +693 -0
  147. package/src/gen/connectors/__fixtures__/sample-openapi.json +311 -0
  148. package/src/gen/connectors/index.ts +27 -0
  149. package/src/gen/createManagers.ts +245 -0
  150. package/src/gen/decorators.ts +134 -0
  151. package/src/gen/generateManagers.test.js +540 -0
  152. package/src/gen/generateManagers.ts +398 -0
  153. package/src/gen/index.ts +66 -0
  154. package/src/gen/schema.ts +248 -0
  155. package/src/gen/vite-plugin.ts +114 -0
  156. package/src/generated/managers/testManager.js +45 -0
  157. package/src/generated/managers/testManager.ts +46 -0
  158. package/src/hooks/HookRegistry.ts +16 -0
  159. package/src/hooks/index.ts +15 -0
  160. package/src/i18n/I18n.ts +344 -0
  161. package/src/i18n/IncrementalDomainProvider.ts +153 -0
  162. package/src/i18n/InlineTranslationProvider.ts +4 -0
  163. package/src/i18n/LazyTranslationProvider.ts +102 -0
  164. package/src/i18n/MessagesRegistry.ts +4 -0
  165. package/src/i18n/Resolver.ts +4 -0
  166. package/src/i18n/__tests__/I18n.test.ts +169 -0
  167. package/src/i18n/__tests__/IncrementalDomainProvider.test.ts +146 -0
  168. package/src/i18n/__tests__/LazyTranslationProvider.test.ts +100 -0
  169. package/src/i18n/__tests__/Resolver.test.ts +271 -0
  170. package/src/i18n/defaults/DefaultCoreProvider.ts +28 -0
  171. package/src/i18n/defaults/core.en.yml +55 -0
  172. package/src/i18n/defaults/core.fr.yml +55 -0
  173. package/src/i18n/index.ts +55 -0
  174. package/src/i18n/loaders/raw-modules.d.ts +15 -0
  175. package/src/i18n/loaders/yaml.ts +35 -0
  176. package/src/i18n/strategies.ts +4 -0
  177. package/src/i18n/types.ts +34 -0
  178. package/src/i18n/useI18n.ts +34 -0
  179. package/src/index.ts +453 -0
  180. package/src/kernel/EventRouter.ts +23 -0
  181. package/src/kernel/Kernel.api.ts +79 -0
  182. package/src/kernel/Kernel.auth.ts +120 -0
  183. package/src/kernel/Kernel.i18n.ts +29 -0
  184. package/src/kernel/Kernel.modules.ts +170 -0
  185. package/src/kernel/Kernel.registries.ts +237 -0
  186. package/src/kernel/Kernel.routing.ts +334 -0
  187. package/src/kernel/Kernel.ts +460 -0
  188. package/src/kernel/Kernel.types.ts +263 -0
  189. package/src/kernel/Kernel.vue.ts +304 -0
  190. package/src/kernel/KernelContext.entities.ts +80 -0
  191. package/src/kernel/KernelContext.events.ts +57 -0
  192. package/src/kernel/KernelContext.i18n.ts +37 -0
  193. package/src/kernel/KernelContext.permissions.ts +38 -0
  194. package/src/kernel/KernelContext.routing.ts +280 -0
  195. package/src/kernel/KernelContext.ts +252 -0
  196. package/src/kernel/KernelContext.types.ts +173 -0
  197. package/src/kernel/KernelContext.zones.ts +54 -0
  198. package/src/kernel/Module.ts +169 -0
  199. package/src/kernel/ModuleLoader.ts +645 -0
  200. package/src/kernel/SSEBridge.ts +11 -0
  201. package/src/kernel/SignalBus.ts +48 -0
  202. package/src/kernel/index.ts +56 -0
  203. package/src/module/index.ts +33 -0
  204. package/src/module/moduleRegistry.ts +492 -0
  205. package/src/modules/debug/AuthCollector.ts +501 -0
  206. package/src/modules/debug/Collector.ts +20 -0
  207. package/src/modules/debug/DebugBridge.ts +14 -0
  208. package/src/modules/debug/DebugModule.ts +220 -0
  209. package/src/modules/debug/EntitiesCollector.ts +839 -0
  210. package/src/modules/debug/ErrorCollector.ts +4 -0
  211. package/src/modules/debug/I18nCollector.ts +9 -0
  212. package/src/modules/debug/LocalStorageAdapter.ts +7 -0
  213. package/src/modules/debug/RouterCollector.ts +449 -0
  214. package/src/modules/debug/SignalCollector.ts +4 -0
  215. package/src/modules/debug/ToastCollector.ts +4 -0
  216. package/src/modules/debug/ZonesCollector.ts +447 -0
  217. package/src/modules/debug/components/DebugBar.vue +31 -0
  218. package/src/modules/debug/components/adminPanelsConfig.ts +42 -0
  219. package/src/modules/debug/components/index.ts +9 -0
  220. package/src/modules/debug/components/panels/AuthPanel.vue +267 -0
  221. package/src/modules/debug/components/panels/EntitiesPanel.vue +572 -0
  222. package/src/modules/debug/components/panels/I18nPanel.vue +738 -0
  223. package/src/modules/debug/components/panels/RouterPanel.vue +440 -0
  224. package/src/modules/debug/components/panels/ZonesPanel.vue +133 -0
  225. package/src/modules/debug/components/panels/index.ts +16 -0
  226. package/src/modules/debug/index.ts +58 -0
  227. package/src/modules/debug/styles.scss +2671 -0
  228. package/src/notifications/NotificationBadge.vue +35 -0
  229. package/src/notifications/NotificationListener.vue +71 -0
  230. package/src/notifications/NotificationModule.ts +76 -0
  231. package/src/notifications/NotificationPanel.vue +478 -0
  232. package/src/notifications/NotificationStore.ts +288 -0
  233. package/src/notifications/index.ts +42 -0
  234. package/src/notifications/styles.scss +32 -0
  235. package/src/orchestrator/Orchestrator.ts +427 -0
  236. package/src/orchestrator/index.ts +8 -0
  237. package/src/orchestrator/useOrchestrator.ts +80 -0
  238. package/src/plugin.ts +251 -0
  239. package/src/query/FilterQuery.ts +306 -0
  240. package/src/query/QueryExecutor.ts +341 -0
  241. package/src/query/index.ts +16 -0
  242. package/src/security/EntityRolesProvider.ts +385 -0
  243. package/src/security/PermissionMatcher.ts +152 -0
  244. package/src/security/PermissionRegistry.ts +273 -0
  245. package/src/security/PersistableRolesProvider.ts +584 -0
  246. package/src/security/RolesManager.ts +136 -0
  247. package/src/security/RolesProvider.ts +167 -0
  248. package/src/security/RolesProviderStorage.ts +211 -0
  249. package/src/security/SecurityModule.ts +94 -0
  250. package/src/security/StaticRolesProvider.ts +117 -0
  251. package/src/security/UsersManager.ts +163 -0
  252. package/src/security/index.ts +77 -0
  253. package/src/security/pages/RoleForm.vue +276 -0
  254. package/src/security/pages/RoleList.vue +123 -0
  255. package/src/styles/_alerts.scss +95 -0
  256. package/src/styles/_buttons.scss +391 -0
  257. package/src/styles/_cards.scss +241 -0
  258. package/src/styles/_code.scss +78 -0
  259. package/src/styles/_dialogs.scss +55 -0
  260. package/src/styles/_filter-bar.scss +70 -0
  261. package/src/styles/_forms.scss +434 -0
  262. package/src/styles/_helpers.scss +153 -0
  263. package/src/styles/_lists.scss +225 -0
  264. package/src/styles/_main.scss +356 -0
  265. package/src/styles/_markdown.scss +88 -0
  266. package/src/styles/_responsive.scss +255 -0
  267. package/src/styles/_show-pages.scss +186 -0
  268. package/src/styles/_sidebar.scss +380 -0
  269. package/src/styles/_states.scss +115 -0
  270. package/src/styles/_stats.scss +47 -0
  271. package/src/styles/_variables.scss +224 -0
  272. package/src/styles/index.scss +38 -0
  273. package/src/styles/theme/_components.scss +331 -0
  274. package/src/styles/theme/_index.scss +10 -0
  275. package/src/styles/theme/_tokens.scss +147 -0
  276. package/src/styles/theme/_utilities.scss +175 -0
  277. package/src/toast/ToastBridgeModule.ts +68 -0
  278. package/src/toast/ToastListener.vue +55 -0
  279. package/src/toast/index.ts +26 -0
  280. package/src/toast/useSignalToast.ts +154 -0
  281. package/src/types/index.ts +285 -0
  282. package/src/types/pluralize.d.ts +19 -0
  283. package/src/types/quarkernel.d.ts +50 -0
  284. package/src/utils/formatters.ts +174 -0
  285. package/src/utils/index.ts +35 -0
  286. package/src/utils/transformers.ts +131 -0
  287. package/src/vite/qdadmDebugPlugin.ts +401 -0
  288. package/src/vite-env.d.ts +50 -0
  289. package/src/zones/ZoneRegistry.ts +934 -0
  290. package/src/zones/index.ts +29 -0
  291. package/src/zones/zones.ts +191 -0
@@ -0,0 +1,114 @@
1
+ /**
2
+ * Vite Plugin for qdadm EntityManager Generation
3
+ *
4
+ * Integrates generateManagers with Vite's build pipeline, triggering
5
+ * entity manager file generation during the buildStart hook.
6
+ *
7
+ * @module gen/vite-plugin
8
+ */
9
+
10
+ import { generateManagers, type GenerateManagersConfig } from './generateManagers'
11
+ import { pathToFileURL } from 'node:url'
12
+ import { resolve } from 'node:path'
13
+ import type { Plugin } from 'vite'
14
+
15
+ /**
16
+ * Plugin options for qdadmGen
17
+ */
18
+ export interface QdadmGenOptions {
19
+ /** Path to qdadm config file (default: 'qdadm.config.js') */
20
+ config?: string
21
+ /** Override output directory for generated files */
22
+ output?: string
23
+ }
24
+
25
+ /**
26
+ * Vite plugin for generating EntityManager files at build time
27
+ *
28
+ * Loads the qdadm configuration file and calls generateManagers during
29
+ * Vite's buildStart hook. Supports both ESM and CommonJS config files.
30
+ *
31
+ * @param options - Plugin options
32
+ * @returns Vite plugin object
33
+ *
34
+ * @example
35
+ * ```ts
36
+ * // vite.config.js
37
+ * import { defineConfig } from 'vite'
38
+ * import { qdadmGen } from '@quazardous/qdadm/gen/vite-plugin'
39
+ *
40
+ * export default defineConfig({
41
+ * plugins: [
42
+ * qdadmGen({
43
+ * config: './qdadm.config.js',
44
+ * output: 'src/generated/managers/'
45
+ * })
46
+ * ]
47
+ * })
48
+ * ```
49
+ *
50
+ * @example
51
+ * ```ts
52
+ * // qdadm.config.js
53
+ * export default {
54
+ * output: 'src/generated/managers/',
55
+ * entities: {
56
+ * users: {
57
+ * schema: { name: 'users', fields: { id: { type: 'integer' } } },
58
+ * endpoint: '/api/users',
59
+ * storageImport: '@quazardous/qdadm',
60
+ * storageClass: 'ApiStorage'
61
+ * }
62
+ * }
63
+ * }
64
+ * ```
65
+ */
66
+ export function qdadmGen(options: QdadmGenOptions = {}): Plugin {
67
+ const configPath = options.config || 'qdadm.config.js'
68
+
69
+ return {
70
+ name: 'qdadm-gen',
71
+
72
+ async buildStart() {
73
+ try {
74
+ // Resolve config path relative to cwd
75
+ const resolvedConfigPath = resolve(process.cwd(), configPath)
76
+
77
+ // Load config file dynamically
78
+ const configUrl = pathToFileURL(resolvedConfigPath).href
79
+ const configModule = (await import(configUrl)) as {
80
+ default?: GenerateManagersConfig
81
+ } & GenerateManagersConfig
82
+ const loadedConfig = configModule.default || configModule
83
+
84
+ // Validate loaded config
85
+ if (!loadedConfig || typeof loadedConfig !== 'object') {
86
+ throw new Error(`Invalid qdadm config at '${configPath}': must export an object`)
87
+ }
88
+
89
+ if (!loadedConfig.entities) {
90
+ throw new Error(`Invalid qdadm config at '${configPath}': missing 'entities' property`)
91
+ }
92
+
93
+ // Merge plugin options with config (plugin options take precedence)
94
+ const mergedConfig: GenerateManagersConfig = {
95
+ ...loadedConfig,
96
+ ...(options.output && { output: options.output }),
97
+ }
98
+
99
+ // Generate managers
100
+ const generatedFiles = await generateManagers(mergedConfig)
101
+
102
+ // Log results
103
+ console.log(`[qdadm-gen] Generated ${generatedFiles.length} manager file(s)`)
104
+ for (const file of generatedFiles) {
105
+ console.log(` - ${file}`)
106
+ }
107
+ } catch (error) {
108
+ // Wrap error with plugin context for clearer messages
109
+ const message = error instanceof Error ? error.message : String(error)
110
+ throw new Error(`[qdadm-gen] Failed to generate managers: ${message}`)
111
+ }
112
+ },
113
+ }
114
+ }
@@ -0,0 +1,45 @@
1
+ /**
2
+ * TestManager - Auto-generated EntityManager
3
+ *
4
+ * Generated by qdadm/gen/generateManagers
5
+ * DO NOT EDIT MANUALLY - Changes will be overwritten
6
+ *
7
+ * Entity: test
8
+ * Endpoint: /test
9
+ */
10
+
11
+ import { EntityManager } from '@quazardous/qdadm'
12
+ import { ApiStorage } from '@quazardous/qdadm'
13
+
14
+ /**
15
+ * Schema definition for test
16
+ * @type {import('qdadm/gen').UnifiedEntitySchema}
17
+ */
18
+ export const testSchema = {
19
+ name: "test",
20
+ endpoint: "/test",
21
+ fields: {}
22
+ }
23
+
24
+ /**
25
+ * Storage options for test
26
+ */
27
+ const storageOptions = {
28
+ endpoint: "/test"
29
+ }
30
+
31
+ /**
32
+ * TestManager instance
33
+ *
34
+ * Provides CRUD operations for test entity.
35
+ *
36
+ * @type {EntityManager}
37
+ */
38
+ export const testManager = new EntityManager({
39
+ ...{
40
+ name: "test",
41
+ idField: "id",
42
+ fields: {}
43
+ },
44
+ storage: new ApiStorage(storageOptions)
45
+ })
@@ -0,0 +1,46 @@
1
+ /**
2
+ * TestManager - Auto-generated EntityManager
3
+ *
4
+ * Generated by qdadm/gen/generateManagers
5
+ * DO NOT EDIT MANUALLY - Changes will be overwritten
6
+ *
7
+ * Entity: test
8
+ * Endpoint: /test
9
+ */
10
+
11
+ import { EntityManager } from '@quazardous/qdadm'
12
+ import type { EntityRecord } from '@quazardous/qdadm'
13
+ import { ApiStorage } from '@quazardous/qdadm'
14
+
15
+ export interface TestEntity extends EntityRecord {
16
+ }
17
+
18
+ /**
19
+ * Schema definition for test
20
+ */
21
+ export const testSchema = {
22
+ name: "test",
23
+ endpoint: "/test",
24
+ fields: {}
25
+ } as const
26
+
27
+ /**
28
+ * Storage options for test
29
+ */
30
+ const storageOptions = {
31
+ endpoint: "/test"
32
+ }
33
+
34
+ /**
35
+ * TestManager instance
36
+ *
37
+ * Provides CRUD operations for test entity.
38
+ */
39
+ export const testManager = new EntityManager<TestEntity>({
40
+ ...{
41
+ name: "test",
42
+ idField: "id",
43
+ fields: {}
44
+ },
45
+ storage: new ApiStorage<TestEntity>(storageOptions)
46
+ })
@@ -0,0 +1,16 @@
1
+ /**
2
+ * HookRegistry — qdadm re-export.
3
+ *
4
+ * The actual implementation now lives in `@quazardous/qdcore` so it can be
5
+ * shared with qdcms. This module re-exports the public API to preserve
6
+ * existing qdadm imports (`from '../hooks/HookRegistry'`).
7
+ */
8
+
9
+ export { HookRegistry, createHookRegistry, HOOK_PRIORITY } from '@quazardous/qdcore'
10
+ export type {
11
+ HookHandler,
12
+ HookRegistrationOptions,
13
+ HookRegistryOptions,
14
+ InvokeOptions,
15
+ AlterOptions,
16
+ } from '@quazardous/qdcore'
@@ -0,0 +1,15 @@
1
+ /**
2
+ * Hooks Module
3
+ *
4
+ * Drupal-inspired hook system for extensibility.
5
+ * Built on QuarKernel signal bus.
6
+ */
7
+
8
+ export { HookRegistry, createHookRegistry, HOOK_PRIORITY } from './HookRegistry'
9
+ export type {
10
+ HookHandler,
11
+ HookRegistrationOptions,
12
+ HookRegistryOptions,
13
+ InvokeOptions,
14
+ AlterOptions,
15
+ } from './HookRegistry'
@@ -0,0 +1,344 @@
1
+ /**
2
+ * I18n - Top-level orchestrator (exposed as `kernel.i18n`).
3
+ *
4
+ * Owns:
5
+ * - the active locale (reactive ref)
6
+ * - the MessagesRegistry
7
+ * - registered TranslationProviders
8
+ * - the active strategy's pattern aliases
9
+ * - the Resolver
10
+ *
11
+ * Public API:
12
+ * - t(key, params?)
13
+ * - locale (ref)
14
+ * - availableLocales
15
+ * - resolve(key) (debug trace)
16
+ * - dump(locale) (export merged bundle)
17
+ * - changeLocale(locale) (programmatic; the public way is signals.emit('locale:change', loc))
18
+ * - addProvider(p)
19
+ * - addMessages(locale, bundle)
20
+ * - addAliases(patterns)
21
+ * - registerEntityModule(entity, module) (for the 'module' strategy)
22
+ */
23
+
24
+ import { ref, type Ref } from 'vue'
25
+
26
+ import {
27
+ MessagesRegistry,
28
+ Resolver,
29
+ InlineTranslationProvider,
30
+ resolveStrategy,
31
+ } from '@quazardous/qdcore'
32
+ import type {
33
+ AliasPattern,
34
+ MessagesBundle,
35
+ ResolveTrace,
36
+ TranslateParams,
37
+ TranslationProvider,
38
+ } from '@quazardous/qdcore'
39
+ import { createDefaultCoreProvider } from './defaults/DefaultCoreProvider'
40
+ import { isDomainAwareProvider } from './IncrementalDomainProvider'
41
+ import type { I18nOptions } from './types'
42
+
43
+ export interface I18nDeps {
44
+ /** SignalBus instance for emitting locale:changed and i18n:missing. */
45
+ signals?: {
46
+ emit: (signal: string, payload?: unknown) => void | Promise<void>
47
+ on: (signal: string, handler: (e: { data: unknown }) => void) => () => void
48
+ } | null
49
+ }
50
+
51
+ export class I18n {
52
+ readonly inline: InlineTranslationProvider
53
+ readonly locale: Ref<string>
54
+
55
+ private _registry: MessagesRegistry
56
+ private _resolver: Resolver
57
+ private _providers: TranslationProvider[]
58
+ private _strategyName: string | string[]
59
+ private _appAliases: AliasPattern[]
60
+ private _entityToModule: Map<string, string> = new Map()
61
+ private _signals: I18nDeps['signals']
62
+ private _defaultLocale: string
63
+ private _fallbackLocale: string
64
+ private _loadedLocales: Set<string> = new Set()
65
+ // Track which (locale::domain) pairs have already been resolved through a
66
+ // domain-aware provider, so we don't re-trigger an async load on every
67
+ // subsequent miss for the same domain.
68
+ private _loadedDomains: Set<string> = new Set()
69
+ // Concurrent domain loads — share one in-flight promise per (locale, domain)
70
+ // pair across providers and t() callers.
71
+ private _inflightDomains: Map<string, Promise<void>> = new Map()
72
+
73
+ constructor(options: I18nOptions = {}, deps: I18nDeps = {}) {
74
+ this._defaultLocale = options.defaultLocale ?? 'en'
75
+ this._fallbackLocale = options.fallbackLocale ?? this._defaultLocale
76
+ this._strategyName = options.keyStrategy ?? 'global'
77
+ this._appAliases = options.aliases ? [...options.aliases] : []
78
+ this._signals = deps.signals ?? null
79
+
80
+ this._registry = new MessagesRegistry()
81
+ this.inline = new InlineTranslationProvider()
82
+
83
+ // Provider chain: framework defaults (lazy) → inline (ctx.messages) → user.
84
+ // _loadLocale merges providers in order, so later entries override earlier
85
+ // ones. The default core is first so app-level overrides always win.
86
+ const defaultCoreProviders: TranslationProvider[] = options.disableDefaultCoreBundle
87
+ ? []
88
+ : [createDefaultCoreProvider()]
89
+ this._providers = [
90
+ ...defaultCoreProviders,
91
+ this.inline,
92
+ ...(options.providers ?? []),
93
+ ]
94
+
95
+ this.locale = ref(this._defaultLocale)
96
+
97
+ this._resolver = new Resolver(this._registry, {
98
+ defaultLocale: this._defaultLocale,
99
+ fallbackLocale: this._fallbackLocale,
100
+ globalAliases: this._currentStrategyAliases(),
101
+ onMissing: (key, locale) => {
102
+ this._signals?.emit('i18n:missing', { key, locale })
103
+ },
104
+ })
105
+
106
+ // App-level messages from kernel options.
107
+ if (options.messages) {
108
+ for (const [loc, bundle] of Object.entries(options.messages)) {
109
+ this.inline.push(loc, bundle)
110
+ }
111
+ }
112
+
113
+ // Listen for locale:change requests on the bus, if present.
114
+ if (this._signals) {
115
+ this._signals.on('locale:change', (event) => {
116
+ const loc = typeof event.data === 'string' ? event.data : null
117
+ if (loc) void this.changeLocale(loc)
118
+ })
119
+ }
120
+ }
121
+
122
+ // ---------------------------------------------------------------------------
123
+ // Public API
124
+ // ---------------------------------------------------------------------------
125
+
126
+ /**
127
+ * Translate a convention-derived key.
128
+ *
129
+ * On a miss whose top-level segment matches a domain known to one of the
130
+ * registered domain-aware providers (e.g. `IncrementalDomainProvider`),
131
+ * triggers a lazy load for that (locale, domain) pair in the background
132
+ * and emits `i18n:domain-loaded` once merged. The current call still
133
+ * returns the raw key — callers that want to react to the late arrival
134
+ * should listen to `i18n:domain-loaded`.
135
+ */
136
+ t(key: string, params?: TranslateParams): string {
137
+ const trace = this._resolver.resolve(key, this.locale.value, params)
138
+ if (!trace.hit) {
139
+ this._maybeEnsureDomain(key, this.locale.value)
140
+ }
141
+ return trace.result
142
+ }
143
+
144
+ /**
145
+ * Explicitly request a domain to be loaded for a locale, ahead of any
146
+ * `t()` miss that would have triggered it. Useful when a screen is about
147
+ * to need a rare domain and the dev wants to avoid the placeholder flash.
148
+ *
149
+ * Resolves once the domain is merged into the registry (or rejects if no
150
+ * domain-aware provider knows it).
151
+ */
152
+ loadDomain(domain: string, locale: string = this.locale.value): Promise<void> {
153
+ return this._ensureDomain(locale, domain)
154
+ }
155
+
156
+ /**
157
+ * Return the resolution trace (for debugging).
158
+ */
159
+ resolve(key: string, params?: TranslateParams): ResolveTrace {
160
+ return this._resolver.resolve(key, this.locale.value, params)
161
+ }
162
+
163
+ /**
164
+ * Union of available locales across registered providers.
165
+ */
166
+ async availableLocales(): Promise<string[]> {
167
+ const set = new Set<string>()
168
+ for (const p of this._providers) {
169
+ if (typeof p.availableLocales === 'function') {
170
+ const locs = await p.availableLocales()
171
+ for (const l of locs) set.add(l)
172
+ }
173
+ }
174
+ return Array.from(set)
175
+ }
176
+
177
+ /**
178
+ * Switch to a new locale. Loads bundles from providers if not yet cached.
179
+ * Public callers should prefer `signals.emit('locale:change', loc)`.
180
+ */
181
+ async changeLocale(locale: string): Promise<void> {
182
+ if (this.locale.value === locale && this._loadedLocales.has(locale)) return
183
+ await this._loadLocale(locale)
184
+ this.locale.value = locale
185
+ this._signals?.emit('locale:changed', locale)
186
+ }
187
+
188
+ /**
189
+ * Initial bootstrap: load default + fallback locales from providers.
190
+ * Called by the kernel during warmup.
191
+ */
192
+ async bootstrap(): Promise<void> {
193
+ const targets = new Set<string>([this._defaultLocale, this._fallbackLocale])
194
+ for (const loc of targets) {
195
+ await this._loadLocale(loc)
196
+ }
197
+ }
198
+
199
+ /**
200
+ * Register an additional provider after construction.
201
+ */
202
+ addProvider(provider: TranslationProvider): void {
203
+ this._providers.push(provider)
204
+ }
205
+
206
+ /**
207
+ * Push messages into the inline provider (used by ctx.messages()).
208
+ * Bundles merge into the registry on next load. If the locale is already
209
+ * loaded, the bundle is also merged immediately for hot updates.
210
+ */
211
+ addMessages(locale: string, bundle: MessagesBundle): void {
212
+ this.inline.push(locale, bundle)
213
+ if (this._loadedLocales.has(locale)) {
214
+ this._registry.merge(locale, bundle)
215
+ }
216
+ }
217
+
218
+ /**
219
+ * Append app/module-level pattern aliases to the active strategy.
220
+ */
221
+ addAliases(patterns: AliasPattern[]): void {
222
+ this._appAliases.push(...patterns)
223
+ this._resolver.setOptions({ globalAliases: this._currentStrategyAliases() })
224
+ }
225
+
226
+ /**
227
+ * Track which module owns which entity. Used to materialize the 'module'
228
+ * key strategy at runtime (entities.<entity>.* → modules.<module>.*).
229
+ */
230
+ registerEntityModule(entity: string, module: string): void {
231
+ this._entityToModule.set(entity, module)
232
+ if (this._strategyIncludes('module')) {
233
+ this._resolver.setOptions({ globalAliases: this._currentStrategyAliases() })
234
+ }
235
+ }
236
+
237
+ /**
238
+ * Export the currently merged bundle for a locale (for translator workflows).
239
+ */
240
+ dump(locale: string): MessagesBundle {
241
+ return this.inline.load(locale)
242
+ }
243
+
244
+ // ---------------------------------------------------------------------------
245
+ // Internals
246
+ // ---------------------------------------------------------------------------
247
+
248
+ private async _loadLocale(locale: string): Promise<void> {
249
+ for (const p of this._providers) {
250
+ const bundle = await p.load(locale)
251
+ if (bundle && Object.keys(bundle).length > 0) {
252
+ this._registry.merge(locale, bundle)
253
+ }
254
+ }
255
+ this._loadedLocales.add(locale)
256
+ }
257
+
258
+ /**
259
+ * Fire-and-forget: invoked from t() on a miss to opportunistically pull in
260
+ * a domain-aware provider's bundle for the missed top-level segment.
261
+ * Errors are swallowed (logged as a missing-domain signal) — the current
262
+ * t() call has already returned.
263
+ */
264
+ private _maybeEnsureDomain(key: string, locale: string): void {
265
+ const dot = key.indexOf('.')
266
+ const domain = dot === -1 ? key : key.slice(0, dot)
267
+ if (!domain) return
268
+ if (this._loadedDomains.has(`${locale}::${domain}`)) return
269
+ if (!this._providers.some((p) => isDomainAwareProvider(p) && p.knowsDomain(domain))) {
270
+ return
271
+ }
272
+ void this._ensureDomain(locale, domain).catch(() => {
273
+ // Already surfaced via i18n:missing on the original miss; silent here.
274
+ })
275
+ }
276
+
277
+ /**
278
+ * Resolves once a domain has been pulled from any domain-aware provider
279
+ * that knows it. Safe to call concurrently — loads are deduped per
280
+ * (locale, domain) pair. Emits `i18n:domain-loaded` after the merge.
281
+ */
282
+ private _ensureDomain(locale: string, domain: string): Promise<void> {
283
+ const cacheKey = `${locale}::${domain}`
284
+ if (this._loadedDomains.has(cacheKey)) return Promise.resolve()
285
+ const existing = this._inflightDomains.get(cacheKey)
286
+ if (existing) return existing
287
+
288
+ const promise = (async () => {
289
+ let merged = false
290
+ for (const p of this._providers) {
291
+ if (!isDomainAwareProvider(p) || !p.knowsDomain(domain)) continue
292
+ const bundle = await p.loadDomain(locale, domain)
293
+ if (bundle && Object.keys(bundle).length > 0) {
294
+ this._registry.merge(locale, bundle)
295
+ merged = true
296
+ }
297
+ }
298
+ this._loadedDomains.add(cacheKey)
299
+ this._inflightDomains.delete(cacheKey)
300
+ if (merged) {
301
+ this._signals?.emit('i18n:domain-loaded', { locale, domain })
302
+ }
303
+ })()
304
+
305
+ this._inflightDomains.set(cacheKey, promise)
306
+ return promise
307
+ }
308
+
309
+ private _currentStrategyAliases(): AliasPattern[] {
310
+ const fromStrategy = resolveStrategy(this._strategyName)
311
+ const fromModuleStrategy = this._strategyIncludes('module')
312
+ ? this._materializeModuleAliases()
313
+ : []
314
+ return [...fromStrategy, ...fromModuleStrategy, ...this._appAliases]
315
+ }
316
+
317
+ private _strategyIncludes(name: string): boolean {
318
+ if (this._strategyName === name) return true
319
+ if (Array.isArray(this._strategyName)) return this._strategyName.includes(name)
320
+ return false
321
+ }
322
+
323
+ /**
324
+ * For each known entity → module mapping, emit two pattern aliases:
325
+ * entities.<entity>.actions.* → modules.<module>.actions.$1
326
+ * entities.<entity>.errors.* → modules.<module>.errors.$1
327
+ * (plus a more specific actions/errors per-module fallback to core.*.)
328
+ */
329
+ private _materializeModuleAliases(): AliasPattern[] {
330
+ const out: AliasPattern[] = []
331
+ for (const [entity, module] of this._entityToModule) {
332
+ out.push(
333
+ { pattern: `entities.${entity}.actions.*`, target: `modules.${module}.actions.$1` },
334
+ { pattern: `entities.${entity}.errors.*`, target: `modules.${module}.errors.$1` }
335
+ )
336
+ }
337
+ // Module-level fallbacks to core.
338
+ out.push(
339
+ { pattern: 'modules.*.actions.*', target: 'core.actions.$2' },
340
+ { pattern: 'modules.*.errors.*', target: 'core.errors.$2' }
341
+ )
342
+ return out
343
+ }
344
+ }