@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,398 @@
1
+ /**
2
+ * Build-time Manager File Generation
3
+ *
4
+ * Generates individual EntityManager files for each entity in the configuration.
5
+ * This enables better IDE support and static analysis by creating explicit files
6
+ * rather than relying on runtime factory creation.
7
+ *
8
+ * Outputs TypeScript (.ts) files with auto-generated entity interfaces derived
9
+ * from the field schemas, providing end-to-end type safety.
10
+ *
11
+ * @module gen/generateManagers
12
+ */
13
+
14
+ import { mkdir, writeFile } from 'node:fs/promises'
15
+ import { join } from 'node:path'
16
+
17
+ import type { UnifiedEntitySchema, UnifiedFieldSchema, UnifiedFieldType } from './schema'
18
+ import type { EntityDecorator } from './decorators'
19
+
20
+ /**
21
+ * Default output directory for generated managers
22
+ */
23
+ const DEFAULT_OUTPUT_DIR = 'src/generated/managers/'
24
+
25
+ /**
26
+ * Entity configuration for generation
27
+ */
28
+ export interface GenerateManagersEntityConfig {
29
+ /** Entity schema */
30
+ schema: UnifiedEntitySchema
31
+ /** API endpoint path (e.g., '/users') */
32
+ endpoint: string
33
+ /** Import path for storage (e.g., 'qdadm' or './storage') */
34
+ storageImport: string
35
+ /** Storage class name (e.g., 'ApiStorage') */
36
+ storageClass: string
37
+ /** Additional storage constructor options */
38
+ storageOptions?: Record<string, unknown>
39
+ /** Field decorators to apply */
40
+ decorators?: EntityDecorator
41
+ /** Generate class instead of instance (for extension) */
42
+ classMode?: boolean
43
+ }
44
+
45
+ /**
46
+ * Configuration for generateManagers
47
+ */
48
+ export interface GenerateManagersConfig {
49
+ /** Output directory (default: 'src/generated/managers/') */
50
+ output?: string
51
+ /** Generate classes instead of instances (for extension pattern) */
52
+ classMode?: boolean
53
+ /** Entity configurations keyed by entity name */
54
+ entities: Record<string, GenerateManagersEntityConfig>
55
+ }
56
+
57
+ /**
58
+ * Convert entity name to PascalCase for class name
59
+ *
60
+ * @param name - Entity name (e.g., 'users', 'blog_posts')
61
+ * @returns PascalCase name (e.g., 'Users', 'BlogPosts')
62
+ */
63
+ function toPascalCase(name: string): string {
64
+ return name
65
+ .split(/[_-]/)
66
+ .map((part) => part.charAt(0).toUpperCase() + part.slice(1))
67
+ .join('')
68
+ }
69
+
70
+ /**
71
+ * Map UnifiedFieldType to TypeScript type string
72
+ */
73
+ export function fieldTypeToTsType(type: UnifiedFieldType, required: boolean): string {
74
+ const tsType = (() => {
75
+ switch (type) {
76
+ case 'text':
77
+ case 'email':
78
+ case 'url':
79
+ case 'uuid':
80
+ case 'date':
81
+ case 'datetime':
82
+ return 'string'
83
+ case 'number':
84
+ return 'number'
85
+ case 'boolean':
86
+ return 'boolean'
87
+ case 'array':
88
+ return 'unknown[]'
89
+ case 'object':
90
+ return 'Record<string, unknown>'
91
+ default:
92
+ // Fallback for types widened beyond UnifiedFieldType at runtime
93
+ // (e.g. OpenAPI oneOf/discriminator that FieldMapper couldn't classify).
94
+ // Without this, the IIFE returns undefined and we'd emit the literal
95
+ // string "undefined" into the generated .ts file.
96
+ return 'unknown'
97
+ }
98
+ })()
99
+ return required ? tsType : `${tsType} | null`
100
+ }
101
+
102
+ /**
103
+ * Generate a TypeScript interface from entity fields
104
+ */
105
+ export function generateEntityInterface(
106
+ entityName: string,
107
+ fields: Record<string, UnifiedFieldSchema>,
108
+ idField: string
109
+ ): string {
110
+ const className = toPascalCase(entityName)
111
+ const lines: string[] = []
112
+ lines.push(`export interface ${className}Entity extends EntityRecord {`)
113
+
114
+ for (const [fieldName, field] of Object.entries(fields)) {
115
+ if (field.hidden) continue
116
+ // Connectors (e.g. OpenAPIConnector) flatten one level of nested object
117
+ // properties as dotted keys ("filter.botUuids") for runtime form/column
118
+ // metadata. The parent object field is already typed as
119
+ // Record<string, unknown>, and dotted keys are not legal at the top level
120
+ // of a TS interface body — skip them here.
121
+ if (fieldName.includes('.')) continue
122
+ const isRequired = field.required || fieldName === idField
123
+ const tsType = fieldTypeToTsType(field.type, isRequired)
124
+ const optional = isRequired ? '' : '?'
125
+ lines.push(` ${fieldName}${optional}: ${tsType}`)
126
+ }
127
+
128
+ lines.push('}')
129
+ return lines.join('\n')
130
+ }
131
+
132
+ /**
133
+ * Serialize a value to JavaScript source code
134
+ *
135
+ * @param value - Value to serialize
136
+ * @param indent - Current indentation level
137
+ * @returns JavaScript source code
138
+ */
139
+ function serializeValue(value: unknown, indent: number = 0): string {
140
+ const spaces = ' '.repeat(indent)
141
+ const innerSpaces = ' '.repeat(indent + 1)
142
+
143
+ if (value === null) return 'null'
144
+ if (value === undefined) return 'undefined'
145
+ if (typeof value === 'string') return JSON.stringify(value)
146
+ if (typeof value === 'number' || typeof value === 'boolean') return String(value)
147
+ if (Array.isArray(value)) {
148
+ if (value.length === 0) return '[]'
149
+ const items = value.map((v) => serializeValue(v, indent + 1))
150
+ return `[\n${innerSpaces}${items.join(`,\n${innerSpaces}`)}\n${spaces}]`
151
+ }
152
+ if (typeof value === 'object') {
153
+ const obj = value as Record<string, unknown>
154
+ const keys = Object.keys(obj)
155
+ if (keys.length === 0) return '{}'
156
+ const entries = keys.map((key) => {
157
+ const safeKey = /^[a-zA-Z_$][a-zA-Z0-9_$]*$/.test(key) ? key : JSON.stringify(key)
158
+ return `${innerSpaces}${safeKey}: ${serializeValue(obj[key], indent + 1)}`
159
+ })
160
+ return `{\n${entries.join(',\n')}\n${spaces}}`
161
+ }
162
+ return String(value)
163
+ }
164
+
165
+ // Note: ManagerOptions interface removed - using inline Record<string, unknown> type instead
166
+
167
+ /**
168
+ * Generate the source code for a single entity manager file
169
+ *
170
+ * @param entityName - Entity name
171
+ * @param entityConfig - Entity configuration
172
+ * @param globalOptions - Global generation options
173
+ * @returns Generated TypeScript source code
174
+ */
175
+ function generateManagerSource(
176
+ entityName: string,
177
+ entityConfig: GenerateManagersEntityConfig,
178
+ globalOptions: { classMode?: boolean } = {}
179
+ ): string {
180
+ const { schema, endpoint, storageImport, storageClass, storageOptions = {}, decorators } = entityConfig
181
+ const classMode = entityConfig.classMode ?? globalOptions.classMode ?? false
182
+ const className = toPascalCase(entityName)
183
+
184
+ // Build storage options object
185
+ const fullStorageOptions: Record<string, unknown> = {
186
+ endpoint,
187
+ ...storageOptions,
188
+ }
189
+
190
+ // Apply decorators to schema if provided
191
+ let finalSchema = schema
192
+ if (decorators?.fields) {
193
+ // Create decorated fields inline
194
+ const decoratedFields: Record<string, UnifiedFieldSchema> = {}
195
+ for (const [fieldName, fieldSchema] of Object.entries(schema.fields)) {
196
+ const decorator = decorators.fields[fieldName]
197
+ if (decorator) {
198
+ decoratedFields[fieldName] = { ...fieldSchema, ...decorator }
199
+ } else {
200
+ decoratedFields[fieldName] = { ...fieldSchema }
201
+ }
202
+ }
203
+ finalSchema = { ...schema, fields: decoratedFields }
204
+ }
205
+
206
+ // Build EntityManager options
207
+ const managerOptions: Record<string, unknown> = {
208
+ name: finalSchema.name,
209
+ idField: finalSchema.idField || 'id',
210
+ label: finalSchema.label,
211
+ labelPlural: finalSchema.labelPlural,
212
+ labelField: finalSchema.labelField,
213
+ routePrefix: finalSchema.routePrefix,
214
+ readOnly: finalSchema.readOnly,
215
+ fields: finalSchema.fields,
216
+ }
217
+
218
+ // Remove undefined values
219
+ for (const key of Object.keys(managerOptions)) {
220
+ if (managerOptions[key] === undefined) {
221
+ delete managerOptions[key]
222
+ }
223
+ }
224
+
225
+ // Generate entity interface
226
+ const idField = finalSchema.idField || 'id'
227
+ const entityInterface = generateEntityInterface(entityName, finalSchema.fields, idField)
228
+
229
+ // Class-based template for extension pattern
230
+ if (classMode) {
231
+ return `/**
232
+ * Generated${className}Manager - Auto-generated base class
233
+ *
234
+ * Generated by qdadm/gen/generateManagers
235
+ * DO NOT EDIT MANUALLY - Changes will be overwritten
236
+ *
237
+ * Extend this class for custom logic.
238
+ *
239
+ * Entity: ${entityName}
240
+ * Endpoint: ${endpoint}
241
+ */
242
+
243
+ import { EntityManager } from '@quazardous/qdadm'
244
+ import type { EntityRecord, IStorage } from '@quazardous/qdadm'
245
+ import { ${storageClass} } from '${storageImport}'
246
+
247
+ ${entityInterface}
248
+
249
+ /**
250
+ * Generated base manager for ${entityName}
251
+ *
252
+ * @extends EntityManager
253
+ */
254
+ export class Generated${className}Manager extends EntityManager<${className}Entity> {
255
+ constructor(options: Partial<{ name: string; idField: string; fields: Record<string, unknown>; storage: IStorage<${className}Entity>; [key: string]: unknown }> = {}) {
256
+ super({
257
+ ...${serializeValue(managerOptions, 2)},
258
+ storage: new ${storageClass}<${className}Entity>(${serializeValue(fullStorageOptions, 2)}),
259
+ ...options
260
+ })
261
+ }
262
+ }
263
+ `
264
+ }
265
+
266
+ // Instance-based template (default)
267
+ return `/**
268
+ * ${className}Manager - Auto-generated EntityManager
269
+ *
270
+ * Generated by qdadm/gen/generateManagers
271
+ * DO NOT EDIT MANUALLY - Changes will be overwritten
272
+ *
273
+ * Entity: ${entityName}
274
+ * Endpoint: ${endpoint}
275
+ */
276
+
277
+ import { EntityManager } from '@quazardous/qdadm'
278
+ import type { EntityRecord } from '@quazardous/qdadm'
279
+ import { ${storageClass} } from '${storageImport}'
280
+
281
+ ${entityInterface}
282
+
283
+ /**
284
+ * Schema definition for ${entityName}
285
+ */
286
+ export const ${entityName}Schema = ${serializeValue(finalSchema, 0)} as const
287
+
288
+ /**
289
+ * Storage options for ${entityName}
290
+ */
291
+ const storageOptions = ${serializeValue(fullStorageOptions, 0)}
292
+
293
+ /**
294
+ * ${className}Manager instance
295
+ *
296
+ * Provides CRUD operations for ${entityName} entity.
297
+ */
298
+ export const ${entityName}Manager = new EntityManager<${className}Entity>({
299
+ ...${serializeValue(managerOptions, 0)},
300
+ storage: new ${storageClass}<${className}Entity>(storageOptions)
301
+ })
302
+ `
303
+ }
304
+
305
+ /**
306
+ * Generate EntityManager files from configuration
307
+ *
308
+ * Creates one TypeScript file per entity in the output directory.
309
+ * Each file exports the entity's interface, schema, and a configured EntityManager instance.
310
+ *
311
+ * @param config - Configuration object
312
+ * @returns List of generated file paths
313
+ * @throws If config is invalid or file operations fail
314
+ *
315
+ * @example
316
+ * ```ts
317
+ * import { generateManagers } from '@quazardous/qdadm/gen'
318
+ *
319
+ * const generatedFiles = await generateManagers({
320
+ * output: 'src/generated/managers/',
321
+ * entities: {
322
+ * users: {
323
+ * schema: usersSchema,
324
+ * endpoint: '/api/users',
325
+ * storageImport: '@quazardous/qdadm',
326
+ * storageClass: 'ApiStorage',
327
+ * storageOptions: { client: 'apiClient' },
328
+ * decorators: {
329
+ * fields: {
330
+ * password: { hidden: true }
331
+ * }
332
+ * }
333
+ * }
334
+ * }
335
+ * })
336
+ *
337
+ * console.log('Generated:', generatedFiles)
338
+ * // ['src/generated/managers/usersManager.ts']
339
+ * ```
340
+ */
341
+ export async function generateManagers(config: GenerateManagersConfig): Promise<string[]> {
342
+ // Validate config
343
+ if (!config || typeof config !== 'object') {
344
+ throw new Error('generateManagers: config is required and must be an object')
345
+ }
346
+
347
+ if (!config.entities || typeof config.entities !== 'object') {
348
+ throw new Error('generateManagers: config.entities is required and must be an object')
349
+ }
350
+
351
+ const outputDir = config.output || DEFAULT_OUTPUT_DIR
352
+ const globalOptions = { classMode: config.classMode ?? false }
353
+ const generatedFiles: string[] = []
354
+
355
+ // Validate each entity config
356
+ for (const [entityName, entityConfig] of Object.entries(config.entities)) {
357
+ if (!entityConfig || typeof entityConfig !== 'object') {
358
+ throw new Error(`generateManagers: entity '${entityName}' config must be an object`)
359
+ }
360
+
361
+ if (!entityConfig.schema || typeof entityConfig.schema !== 'object') {
362
+ throw new Error(`generateManagers: entity '${entityName}' requires 'schema' property`)
363
+ }
364
+
365
+ if (!entityConfig.endpoint || typeof entityConfig.endpoint !== 'string') {
366
+ throw new Error(`generateManagers: entity '${entityName}' requires 'endpoint' property`)
367
+ }
368
+
369
+ if (!entityConfig.storageImport || typeof entityConfig.storageImport !== 'string') {
370
+ throw new Error(`generateManagers: entity '${entityName}' requires 'storageImport' property`)
371
+ }
372
+
373
+ if (!entityConfig.storageClass || typeof entityConfig.storageClass !== 'string') {
374
+ throw new Error(`generateManagers: entity '${entityName}' requires 'storageClass' property`)
375
+ }
376
+ }
377
+
378
+ // Create output directory if it doesn't exist
379
+ await mkdir(outputDir, { recursive: true })
380
+
381
+ // Generate files for each entity
382
+ for (const [entityName, entityConfig] of Object.entries(config.entities)) {
383
+ const isClassMode = entityConfig.classMode ?? globalOptions.classMode
384
+ // Use entity.ts for classMode (matches existing pattern), entityManager.ts for instance mode
385
+ const fileName = isClassMode ? `${entityName}.ts` : `${entityName}Manager.ts`
386
+ const filePath = join(outputDir, fileName)
387
+
388
+ // Generate source code
389
+ const source = generateManagerSource(entityName, entityConfig, globalOptions)
390
+
391
+ // Write file
392
+ await writeFile(filePath, source, 'utf-8')
393
+
394
+ generatedFiles.push(filePath)
395
+ }
396
+
397
+ return generatedFiles
398
+ }
@@ -0,0 +1,66 @@
1
+ /**
2
+ * Gen Module - Code generation utilities
3
+ *
4
+ * Provides UnifiedSchema and FieldMapper for generating admin interfaces
5
+ * from database schemas.
6
+ *
7
+ * @module gen
8
+ */
9
+
10
+ // T00309: UnifiedSchema types and helpers
11
+ export {
12
+ UNIFIED_FIELD_TYPES,
13
+ isValidFieldType,
14
+ createFieldSchema,
15
+ createEntitySchema,
16
+ type UnifiedFieldType,
17
+ type UnifiedFieldReference,
18
+ type UnifiedFieldSchema,
19
+ type UnifiedEntitySchema,
20
+ } from './schema'
21
+
22
+ // T00310: FieldMapper - JSON Schema to UnifiedFieldSchema type conversion
23
+ export {
24
+ BASE_TYPE_MAPPINGS,
25
+ FORMAT_MAPPINGS,
26
+ getDefaultType,
27
+ type CustomMappings,
28
+ type SchemaProperty,
29
+ } from './FieldMapper'
30
+
31
+ // T00312: BaseConnector - Abstract base for schema connectors
32
+ export {
33
+ BaseConnector,
34
+ type ConnectorOptions,
35
+ type ParseWarning,
36
+ type ParseResult,
37
+ } from './connectors'
38
+
39
+ // T00313: ManualConnector - Inline entity/field definitions
40
+ export {
41
+ ManualConnector,
42
+ type ManualEntityInput,
43
+ type ManualFieldInput,
44
+ type ManualSourceInput,
45
+ } from './connectors'
46
+
47
+ // OpenAPIConnector - Parse OpenAPI 3.x specifications
48
+ export { OpenAPIConnector, type OpenAPIConnectorOptions, type OperationFilter } from './connectors'
49
+
50
+ // T00316: StorageProfileFactory - Type definitions for storage profile pattern
51
+ export { type StorageProfileFactory, type StorageProfileOptions } from './StorageProfileFactory'
52
+
53
+ // T00317: createManagers - Runtime factory for EntityManager creation
54
+ export { createManagers, type CreateManagersConfig, type EntityConfig } from './createManagers'
55
+
56
+ // T00318: Decorators - Per-entity field customization layer
57
+ export { applyDecorators, type FieldDecorator, type EntityDecorator } from './decorators'
58
+
59
+ // T00319: generateManagers - Build-time file generation for EntityManagers
60
+ export {
61
+ generateManagers,
62
+ fieldTypeToTsType,
63
+ generateEntityInterface,
64
+ type GenerateManagersConfig,
65
+ type GenerateManagersEntityConfig,
66
+ } from './generateManagers'
@@ -0,0 +1,248 @@
1
+ /**
2
+ * Unified Schema Types
3
+ *
4
+ * Core contracts that abstract differences between OpenAPI, Pydantic, and manual schema sources.
5
+ * These types provide a common format for entity and field definitions that can be used
6
+ * by connectors (schema sources) and generators (code output).
7
+ *
8
+ * @module gen/schema
9
+ */
10
+
11
+ /**
12
+ * Field Type Enumeration
13
+ *
14
+ * Unified field types that abstract source-specific types (OpenAPI string/integer,
15
+ * Pydantic str/int, etc.) into a common set.
16
+ */
17
+ export type UnifiedFieldType =
18
+ | 'text'
19
+ | 'number'
20
+ | 'boolean'
21
+ | 'date'
22
+ | 'datetime'
23
+ | 'email'
24
+ | 'url'
25
+ | 'uuid'
26
+ | 'array'
27
+ | 'object'
28
+
29
+ /**
30
+ * Reference Definition
31
+ *
32
+ * Describes a relation to another entity for foreign key fields.
33
+ */
34
+ export interface UnifiedFieldReference {
35
+ /** Target entity name (e.g., 'users', 'categories') */
36
+ entity: string
37
+ /** Field to display as label (e.g., 'name', 'title') */
38
+ labelField?: string
39
+ }
40
+
41
+ /**
42
+ * Unified Field Schema
43
+ *
44
+ * Common field definition that abstracts OpenAPI property, Pydantic field, or manual definition.
45
+ * Connectors transform their source-specific formats into this common shape.
46
+ *
47
+ * @example
48
+ * // Simple text field
49
+ * const nameField: UnifiedFieldSchema = {
50
+ * name: 'name',
51
+ * type: 'text',
52
+ * label: 'Full Name',
53
+ * required: true
54
+ * }
55
+ *
56
+ * @example
57
+ * // Enum field with options
58
+ * const statusField: UnifiedFieldSchema = {
59
+ * name: 'status',
60
+ * type: 'text',
61
+ * label: 'Status',
62
+ * enum: ['draft', 'published', 'archived'],
63
+ * default: 'draft'
64
+ * }
65
+ *
66
+ * @example
67
+ * // Foreign key with reference
68
+ * const authorField: UnifiedFieldSchema = {
69
+ * name: 'author_id',
70
+ * type: 'number',
71
+ * label: 'Author',
72
+ * reference: {
73
+ * entity: 'users',
74
+ * labelField: 'username'
75
+ * }
76
+ * }
77
+ */
78
+ export interface UnifiedFieldSchema {
79
+ /** Field name (e.g., 'email', 'created_at') */
80
+ name: string
81
+ /** Unified field type */
82
+ type: UnifiedFieldType
83
+ /** Human-readable label (e.g., 'Email Address') */
84
+ label?: string
85
+ /** Whether field is required (default: false) */
86
+ required?: boolean
87
+ /** Whether field is read-only (default: false) */
88
+ readOnly?: boolean
89
+ /** Whether field should be hidden in UI (default: false) */
90
+ hidden?: boolean
91
+ /** Original format hint from source (e.g., 'date-time', 'uri', 'email') */
92
+ format?: string
93
+ /** Allowed values for enumeration fields */
94
+ enum?: string[]
95
+ /** Default value for the field */
96
+ default?: unknown
97
+ /** Relation to another entity */
98
+ reference?: UnifiedFieldReference
99
+ /** Project-specific extensions */
100
+ extensions?: Record<string, unknown>
101
+ /** Field display order */
102
+ order?: number
103
+ /** Additional properties */
104
+ [key: string]: unknown
105
+ }
106
+
107
+ /**
108
+ * Unified Entity Schema
109
+ *
110
+ * Common entity definition that abstracts OpenAPI path/schema, Pydantic model, or manual definition.
111
+ * This is the primary contract between schema sources (connectors) and consumers (generators, runtime).
112
+ *
113
+ * @example
114
+ * // Complete entity schema
115
+ * const usersSchema: UnifiedEntitySchema = {
116
+ * name: 'users',
117
+ * endpoint: '/api/users',
118
+ * label: 'User',
119
+ * labelPlural: 'Users',
120
+ * labelField: 'username',
121
+ * routePrefix: 'user',
122
+ * idField: 'id',
123
+ * readOnly: false,
124
+ * fields: {
125
+ * id: { name: 'id', type: 'number', readOnly: true },
126
+ * username: { name: 'username', type: 'text', required: true },
127
+ * email: { name: 'email', type: 'email', required: true },
128
+ * created_at: { name: 'created_at', type: 'datetime', readOnly: true }
129
+ * }
130
+ * }
131
+ */
132
+ export interface UnifiedEntitySchema {
133
+ /** Entity name, typically plural lowercase (e.g., 'users', 'blog_posts') */
134
+ name: string
135
+ /** API endpoint path (e.g., '/users', '/api/v1/posts') */
136
+ endpoint: string
137
+ /** Human-readable singular label (e.g., 'User', 'Blog Post') */
138
+ label?: string
139
+ /** Human-readable plural label (e.g., 'Users', 'Blog Posts') */
140
+ labelPlural?: string
141
+ /** Field used as display label (e.g., 'name', 'title') */
142
+ labelField?: string
143
+ /** Route prefix for admin UI (e.g., 'user', 'blog-post') */
144
+ routePrefix?: string
145
+ /** Primary key field name (default: 'id') */
146
+ idField?: string
147
+ /** Whether entity is read-only (no create/update/delete) */
148
+ readOnly?: boolean
149
+ /** Field definitions keyed by field name */
150
+ fields: Record<string, UnifiedFieldSchema>
151
+ /** Project-specific extensions */
152
+ extensions?: Record<string, unknown>
153
+ }
154
+
155
+ /**
156
+ * Valid field types for UnifiedFieldSchema
157
+ *
158
+ * Used for validation and documentation. Maps to common UI input types:
159
+ * - text: Single-line text input
160
+ * - number: Numeric input
161
+ * - boolean: Checkbox/toggle
162
+ * - date: Date picker (date only)
163
+ * - datetime: Date-time picker
164
+ * - email: Email input with validation
165
+ * - url: URL input with validation
166
+ * - uuid: UUID text input
167
+ * - array: Multi-value field (tags, list)
168
+ * - object: Nested object (JSON editor or subform)
169
+ */
170
+ export const UNIFIED_FIELD_TYPES = [
171
+ 'text',
172
+ 'number',
173
+ 'boolean',
174
+ 'date',
175
+ 'datetime',
176
+ 'email',
177
+ 'url',
178
+ 'uuid',
179
+ 'array',
180
+ 'object',
181
+ ] as const
182
+
183
+ /**
184
+ * Check if a type is a valid UnifiedFieldType
185
+ *
186
+ * @param type - Type to validate
187
+ * @returns True if valid
188
+ *
189
+ * @example
190
+ * isValidFieldType('text') // true
191
+ * isValidFieldType('string') // false (OpenAPI type, not unified)
192
+ */
193
+ export function isValidFieldType(type: string): type is UnifiedFieldType {
194
+ return (UNIFIED_FIELD_TYPES as readonly string[]).includes(type)
195
+ }
196
+
197
+ /**
198
+ * Create a minimal field schema with defaults
199
+ *
200
+ * @param name - Field name
201
+ * @param type - Field type
202
+ * @param overrides - Additional field properties
203
+ * @returns Complete field schema
204
+ *
205
+ * @example
206
+ * const field = createFieldSchema('email', 'email', { required: true })
207
+ * // { name: 'email', type: 'email', required: true }
208
+ */
209
+ export function createFieldSchema(
210
+ name: string,
211
+ type: UnifiedFieldType,
212
+ overrides: Partial<UnifiedFieldSchema> = {}
213
+ ): UnifiedFieldSchema {
214
+ return {
215
+ name,
216
+ type,
217
+ ...overrides,
218
+ }
219
+ }
220
+
221
+ /**
222
+ * Create a minimal entity schema with defaults
223
+ *
224
+ * @param name - Entity name
225
+ * @param endpoint - API endpoint
226
+ * @param fields - Field definitions
227
+ * @param overrides - Additional entity properties
228
+ * @returns Complete entity schema
229
+ *
230
+ * @example
231
+ * const schema = createEntitySchema('users', '/api/users', {
232
+ * id: createFieldSchema('id', 'number', { readOnly: true }),
233
+ * name: createFieldSchema('name', 'text', { required: true })
234
+ * })
235
+ */
236
+ export function createEntitySchema(
237
+ name: string,
238
+ endpoint: string,
239
+ fields: Record<string, UnifiedFieldSchema>,
240
+ overrides: Partial<UnifiedEntitySchema> = {}
241
+ ): UnifiedEntitySchema {
242
+ return {
243
+ name,
244
+ endpoint,
245
+ fields,
246
+ ...overrides,
247
+ }
248
+ }