@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,288 @@
1
+ import type {
2
+ EntityRecord,
3
+ ListResult,
4
+ IStorage,
5
+ FieldConfig,
6
+ ChildConfig,
7
+ ParentConfig,
8
+ RelationConfig,
9
+ NavConfig,
10
+ StorageCapabilities,
11
+ } from '../types'
12
+ import type { AuthUser, EntityAuthAdapter as IEntityAuthAdapter } from './auth/EntityAuthAdapter'
13
+ import type { SignalBus } from '../kernel/SignalBus'
14
+ import type { HookRegistry } from '../hooks/HookRegistry'
15
+
16
+ // Circular type import — safe because `import type` is erased at runtime
17
+ import type { EntityManager } from './EntityManager'
18
+
19
+ // ============ INTERNAL TYPES ============
20
+
21
+ /**
22
+ * Cache state for local filtering
23
+ */
24
+ export interface CacheState<T> {
25
+ items: T[]
26
+ total: number
27
+ loadedAt: number | null
28
+ valid: boolean
29
+ }
30
+
31
+ /**
32
+ * Detail cache entry for asymmetric entities (full detail per item)
33
+ */
34
+ export interface DetailCacheEntry<T> {
35
+ item: T
36
+ loadedAt: number
37
+ }
38
+
39
+ /**
40
+ * Detail cache state for asymmetric entities
41
+ */
42
+ export interface DetailCacheState<T> {
43
+ items: Map<string, DetailCacheEntry<T>>
44
+ }
45
+
46
+ /**
47
+ * Operation stats for debugging
48
+ */
49
+ export interface OperationStats {
50
+ list: number
51
+ get: number
52
+ create: number
53
+ update: number
54
+ delete: number
55
+ cacheHits: number
56
+ cacheMisses: number
57
+ detailCacheHits: number
58
+ detailCacheMisses: number
59
+ maxItemsSeen: number
60
+ maxTotal: number
61
+ }
62
+
63
+ /**
64
+ * Resolved storage result
65
+ */
66
+ export interface ResolvedStorage<T extends EntityRecord> {
67
+ storage: IStorage<T> | null | undefined
68
+ endpoint?: string
69
+ params?: Record<string, unknown>
70
+ isDynamic?: boolean
71
+ }
72
+
73
+ /**
74
+ * Storage resolution result (what resolveStorage returns)
75
+ */
76
+ export type StorageResolution<T extends EntityRecord> =
77
+ | string
78
+ | ((context: RoutingContext | undefined) => string)
79
+ | {
80
+ storage?: IStorage<T> | null
81
+ endpoint?: string | ((context: RoutingContext | undefined) => string)
82
+ params?: Record<string, unknown>
83
+ }
84
+ | null
85
+ | undefined
86
+
87
+ /**
88
+ * Routing context for multi-storage patterns
89
+ */
90
+ export interface RoutingContext {
91
+ parentChain?: Array<{ entity: string; id: string | number }>
92
+ [key: string]: unknown
93
+ }
94
+
95
+ /**
96
+ * Presave hook context
97
+ */
98
+ export interface PresaveContext<T extends EntityRecord = EntityRecord> {
99
+ entity: string
100
+ record: Partial<T>
101
+ isNew: boolean
102
+ id?: string | number
103
+ manager: EntityManager<T>
104
+ }
105
+
106
+ /**
107
+ * Postsave hook context
108
+ */
109
+ export interface PostsaveContext<T extends EntityRecord = EntityRecord> {
110
+ entity: string
111
+ record: Partial<T>
112
+ result: T
113
+ isNew: boolean
114
+ id?: string | number
115
+ manager: EntityManager<T>
116
+ }
117
+
118
+ /**
119
+ * Predelete hook context
120
+ */
121
+ export interface PredeleteContext<T extends EntityRecord = EntityRecord> {
122
+ entity: string
123
+ id: string | number
124
+ manager: EntityManager<T>
125
+ }
126
+
127
+ /**
128
+ * Query options
129
+ */
130
+ export interface QueryOptions {
131
+ context?: WhitelistContext
132
+ routingContext?: RoutingContext | null
133
+ }
134
+
135
+ /**
136
+ * Whitelist check context
137
+ */
138
+ export interface WhitelistContext {
139
+ module?: string
140
+ scope?: string
141
+ form?: string
142
+ field?: string
143
+ bypassPermissions?: boolean
144
+ reason?: string
145
+ }
146
+
147
+ /**
148
+ * Cache info for debugging
149
+ */
150
+ export interface CacheInfo {
151
+ enabled: boolean
152
+ storageSupportsTotal: boolean
153
+ threshold: number
154
+ valid: boolean
155
+ overflow: boolean
156
+ itemCount: number
157
+ total: number
158
+ loadedAt: number | null
159
+ /** Effective TTL in milliseconds (0 = no TTL) */
160
+ ttlMs: number
161
+ /** Timestamp when cache expires (null if no TTL) */
162
+ expiresAt: number | null
163
+ /** Whether the cache has expired based on TTL */
164
+ expired: boolean
165
+ /** Whether the entity uses asymmetric mode (list vs detail separation) */
166
+ asymmetric: boolean
167
+ /** Detail cache info (only populated when asymmetric + detail cache enabled) */
168
+ detailCache: {
169
+ enabled: boolean
170
+ ttlMs: number
171
+ size: number
172
+ maxSize: number
173
+ } | null
174
+ }
175
+
176
+ /**
177
+ * Badge descriptor for entity item headers
178
+ */
179
+ export interface EntityBadge {
180
+ label: string
181
+ severity?: 'secondary' | 'info' | 'success' | 'warn' | 'danger' | 'contrast'
182
+ }
183
+
184
+ /**
185
+ * Rich severity descriptor for a field value.
186
+ * Extends beyond a simple severity string to support icons and label overrides.
187
+ */
188
+ export interface SeverityDescriptor {
189
+ severity: string
190
+ icon?: string
191
+ label?: string
192
+ }
193
+
194
+ /** A severity map value: plain string (backward compat) or rich descriptor */
195
+ export type SeverityMapValue = string | SeverityDescriptor
196
+
197
+ /** Severity map: field value → severity string or descriptor */
198
+ export type SeverityMap = Record<string, SeverityMapValue>
199
+
200
+ /**
201
+ * EntityManager constructor options
202
+ */
203
+ export interface EntityManagerOptions<T extends EntityRecord = EntityRecord> {
204
+ name: string
205
+ storage?: IStorage<T> | null
206
+ idField?: string
207
+ label?: string
208
+ labelPlural?: string
209
+ routePrefix?: string
210
+ labelField?: string | ((entity: T) => string)
211
+ /** Callback to compute badges for an entity item header */
212
+ badges?: (entity: T) => EntityBadge[]
213
+ fields?: Record<string, FieldConfig>
214
+ localFilterThreshold?: number | null
215
+ /** Cache TTL in milliseconds (0=disabled, -1=infinite, >0=TTL). Overrides global, overridden by storage. */
216
+ cacheTtlMs?: number | null
217
+ /** Asymmetric mode: list() and get() return different structures. get() skips list cache. */
218
+ asymmetric?: boolean
219
+ /** Detail cache TTL in milliseconds (0=disabled (default), -1=infinite, >0=TTL). Only used when asymmetric=true. */
220
+ detailCacheTtlMs?: number
221
+ /** Detail cache max size (0=unlimited (default), >0=max items). Oldest entries are evicted when exceeded. Only used when asymmetric=true. */
222
+ detailCacheMaxSize?: number
223
+ readOnly?: boolean
224
+ warmup?: boolean
225
+ authSensitive?: boolean
226
+ system?: boolean
227
+ scopeWhitelist?: string[] | null
228
+ isOwn?: ((record: T, user: AuthUser) => boolean) | null
229
+ children?: Record<string, ChildConfig>
230
+ parent?: ParentConfig | null
231
+ parents?: Record<string, ParentConfig>
232
+ relations?: Record<string, RelationConfig>
233
+ authAdapter?: IEntityAuthAdapter | null
234
+ nav?: NavConfig
235
+ }
236
+
237
+ // Forward declaration for Orchestrator (to avoid circular deps)
238
+ export interface Orchestrator {
239
+ get<T extends EntityRecord>(name: string): EntityManager<T> | undefined
240
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
241
+ kernel?: any
242
+ deferred?: DeferredRegistry | null
243
+ }
244
+
245
+ // Forward declaration for DeferredRegistry
246
+ export interface DeferredRegistry {
247
+ has(key: string): boolean
248
+ await(key: string): Promise<unknown>
249
+ queue<T>(key: string, fn: () => Promise<T>): Promise<T>
250
+ }
251
+
252
+ /**
253
+ * Exposes protected members of EntityManager for use by prototype-patched helper methods.
254
+ * @internal
255
+ */
256
+ export type EntityManagerInternal<T extends EntityRecord = EntityRecord> = EntityManager<T> & {
257
+ // Protected fields accessed by helpers
258
+ _cache: CacheState<T>
259
+ _cacheLoading: Promise<boolean> | null
260
+ _cacheTtlMs: number | null
261
+ _asymmetric: boolean
262
+ _detailCache: DetailCacheState<T>
263
+ _detailCacheTtlMs: number
264
+ _detailCacheMaxSize: number
265
+ _detailInflight: Map<string, Promise<T>>
266
+ _orchestrator: Orchestrator | null
267
+ _parents: Record<string, ParentConfig>
268
+ _parent: ParentConfig | null
269
+ _children: Record<string, ChildConfig>
270
+ _authSensitive: boolean
271
+ _signals: SignalBus | null
272
+ _signalCleanup: (() => void) | null
273
+ _warmup: boolean
274
+ _stats: OperationStats
275
+
276
+ // Protected methods that stay in the class body
277
+ _normalizeResolveResult(result: StorageResolution<T>, context?: RoutingContext): ResolvedStorage<T>
278
+ _buildPresaveContext(data: Partial<T>, isNew: boolean, id?: string | number | null): PresaveContext<T>
279
+ _buildPostsaveContext(data: Partial<T>, result: T, isNew: boolean, id?: string | number | null): PostsaveContext<T>
280
+ _buildPredeleteContext(id: string | number): PredeleteContext<T>
281
+ _emitSignal(action: 'created' | 'updated' | 'deleted', data: Record<string, unknown>): void
282
+ _emitDataInvalidate(action: 'created' | 'updated' | 'deleted', id: string | number | undefined): void
283
+ _invokeHook(hookName: string, context: Record<string, unknown>): Promise<void>
284
+ }
285
+
286
+ // Re-export types used by helpers
287
+ export type { EntityRecord, ListResult, IStorage, StorageCapabilities }
288
+ export type { SignalBus, HookRegistry }
@@ -0,0 +1,194 @@
1
+ /**
2
+ * CompositeAuthAdapter - Routes to sub-adapters based on entity patterns
3
+ *
4
+ * Enables multi-source authentication where different entities may use
5
+ * different auth backends (internal JWT, external API keys, OAuth, etc.)
6
+ *
7
+ * Pattern matching:
8
+ * - Exact match: 'products' matches only 'products'
9
+ * - Prefix glob: 'external-*' matches 'external-products', 'external-orders'
10
+ * - Suffix glob: '*-readonly' matches 'products-readonly'
11
+ */
12
+
13
+ import { EntityAuthAdapter } from './EntityAuthAdapter'
14
+ import { authFactory, type AuthFactoryContext } from './factory'
15
+
16
+ /**
17
+ * Pattern match entry
18
+ */
19
+ interface PatternEntry {
20
+ pattern: string
21
+ regex: RegExp
22
+ adapter: EntityAuthAdapter
23
+ }
24
+
25
+ /**
26
+ * Composite adapter config
27
+ */
28
+ export interface CompositeAuthConfig {
29
+ default: EntityAuthAdapter | string | Record<string, unknown>
30
+ mapping?: Record<string, EntityAuthAdapter | string | Record<string, unknown>>
31
+ }
32
+
33
+ /**
34
+ * CompositeAuthAdapter - Routes to sub-adapters based on entity patterns
35
+ */
36
+ export class CompositeAuthAdapter extends EntityAuthAdapter {
37
+ protected _default: EntityAuthAdapter
38
+ protected _exactMatches: Map<string, EntityAuthAdapter>
39
+ protected _patterns: PatternEntry[]
40
+
41
+ constructor(config: CompositeAuthConfig, context: AuthFactoryContext = {}) {
42
+ super()
43
+
44
+ const { default: defaultConfig, mapping = {} } = config
45
+
46
+ if (!defaultConfig) {
47
+ throw new Error('[CompositeAuthAdapter] "default" adapter is required')
48
+ }
49
+
50
+ // Resolve default adapter via factory
51
+ this._default = authFactory(defaultConfig, context)
52
+
53
+ // Resolve mapped adapters
54
+ this._exactMatches = new Map()
55
+ this._patterns = []
56
+
57
+ for (const [pattern, adapterConfig] of Object.entries(mapping)) {
58
+ const adapter = authFactory(adapterConfig, context)
59
+
60
+ if (pattern.includes('*')) {
61
+ // Glob pattern: convert to regex
62
+ const regexPattern = pattern
63
+ .replace(/[.+?^${}()|[\]\\]/g, '\\$&') // Escape regex special chars
64
+ .replace(/\*/g, '.*') // * → .*
65
+ const regex = new RegExp(`^${regexPattern}$`)
66
+ this._patterns.push({ pattern, regex, adapter })
67
+ } else {
68
+ // Exact match
69
+ this._exactMatches.set(pattern, adapter)
70
+ }
71
+ }
72
+ }
73
+
74
+ /**
75
+ * Get the adapter for a specific entity
76
+ */
77
+ protected _getAdapter(entity: string): EntityAuthAdapter {
78
+ // 1. Exact match
79
+ const exactMatch = this._exactMatches.get(entity)
80
+ if (exactMatch) {
81
+ return exactMatch
82
+ }
83
+
84
+ // 2. Pattern match (first wins)
85
+ for (const { regex, adapter } of this._patterns) {
86
+ if (regex.test(entity)) {
87
+ return adapter
88
+ }
89
+ }
90
+
91
+ // 3. Default
92
+ return this._default
93
+ }
94
+
95
+ /**
96
+ * Check if user can perform action on entity type (scope check)
97
+ */
98
+ canPerform(entity: string, action: string): boolean {
99
+ return this._getAdapter(entity).canPerform(entity, action)
100
+ }
101
+
102
+ /**
103
+ * Check if user can access specific record (silo check)
104
+ */
105
+ canAccessRecord(entity: string, record: unknown): boolean {
106
+ return this._getAdapter(entity).canAccessRecord(entity, record)
107
+ }
108
+
109
+ /**
110
+ * Get current user from the default adapter
111
+ */
112
+ getCurrentUser() {
113
+ return this._default.getCurrentUser()
114
+ }
115
+
116
+ /**
117
+ * Check if user is granted an attribute (role or permission)
118
+ */
119
+ isGranted(attribute: string, subject: unknown = null): boolean {
120
+ // For entity-specific permissions (entity:action), route to appropriate adapter
121
+ if (attribute.includes(':') && !attribute.startsWith('ROLE_')) {
122
+ const parts = attribute.split(':')
123
+ const entity = parts[0] || ''
124
+ const adapter = this._getAdapter(entity)
125
+ if ('isGranted' in adapter && typeof adapter.isGranted === 'function') {
126
+ return adapter.isGranted(attribute, subject)
127
+ }
128
+ }
129
+
130
+ // Global permissions use default adapter
131
+ if ('isGranted' in this._default && typeof this._default.isGranted === 'function') {
132
+ return this._default.isGranted(attribute, subject)
133
+ }
134
+
135
+ // Fallback for adapters without isGranted
136
+ return true
137
+ }
138
+
139
+ /**
140
+ * Get the default adapter
141
+ */
142
+ get defaultAdapter(): EntityAuthAdapter {
143
+ return this._default
144
+ }
145
+
146
+ /**
147
+ * Get adapter info for debugging
148
+ */
149
+ getAdapterInfo(): {
150
+ default: string
151
+ exactMatches: Record<string, string>
152
+ patterns: Array<{ pattern: string; adapter: string }>
153
+ } {
154
+ const info: {
155
+ default: string
156
+ exactMatches: Record<string, string>
157
+ patterns: Array<{ pattern: string; adapter: string }>
158
+ } = {
159
+ default: this._getAdapterName(this._default),
160
+ exactMatches: {},
161
+ patterns: [],
162
+ }
163
+
164
+ for (const [entity, adapter] of this._exactMatches) {
165
+ info.exactMatches[entity] = this._getAdapterName(adapter)
166
+ }
167
+
168
+ for (const { pattern, adapter } of this._patterns) {
169
+ info.patterns.push({
170
+ pattern,
171
+ adapter: this._getAdapterName(adapter),
172
+ })
173
+ }
174
+
175
+ return info
176
+ }
177
+
178
+ /**
179
+ * Get adapter name for debugging
180
+ */
181
+ protected _getAdapterName(adapter: EntityAuthAdapter): string {
182
+ return adapter.constructor?.name || 'Unknown'
183
+ }
184
+ }
185
+
186
+ /**
187
+ * Factory function to create CompositeAuthAdapter
188
+ */
189
+ export function createCompositeAuthAdapter(
190
+ config: CompositeAuthConfig,
191
+ context: AuthFactoryContext = {}
192
+ ): CompositeAuthAdapter {
193
+ return new CompositeAuthAdapter(config, context)
194
+ }
@@ -0,0 +1,119 @@
1
+ import type { SecurityChecker } from './SecurityChecker'
2
+
3
+ /**
4
+ * User type for auth checks
5
+ */
6
+ export interface AuthUser {
7
+ id?: string | number
8
+ role?: string
9
+ roles?: string[]
10
+ permissions?: string[]
11
+ [key: string]: unknown
12
+ }
13
+
14
+ /**
15
+ * EntityAuthAdapter options
16
+ */
17
+ export interface EntityAuthAdapterOptions {
18
+ getCurrentUser?: () => AuthUser | null
19
+ }
20
+
21
+ /**
22
+ * EntityAuthAdapter - Thin layer for entity-level permission checks
23
+ *
24
+ * Provides entity-level permission checking by delegating to SecurityChecker.
25
+ * All methods have sensible defaults - subclass only if you need custom behavior.
26
+ */
27
+ export class EntityAuthAdapter {
28
+ protected _securityChecker: SecurityChecker | null = null
29
+ protected _getCurrentUserCallback: (() => AuthUser | null) | null = null
30
+
31
+ constructor(options: EntityAuthAdapterOptions = {}) {
32
+ if (options.getCurrentUser) {
33
+ this._getCurrentUserCallback = options.getCurrentUser
34
+ }
35
+ }
36
+
37
+ /**
38
+ * Set the SecurityChecker instance for isGranted() delegation
39
+ */
40
+ setSecurityChecker(checker: SecurityChecker): void {
41
+ this._securityChecker = checker
42
+ }
43
+
44
+ /**
45
+ * Check if current user is granted an attribute (Symfony-like contract)
46
+ *
47
+ * This is the unified permission check method. It delegates to SecurityChecker
48
+ * if one is configured, otherwise returns true (permissive fallback).
49
+ */
50
+ isGranted(attribute: string, subject: unknown = null): boolean {
51
+ if (!this._securityChecker) return true // Permissive if not configured
52
+ return this._securityChecker.isGranted(attribute, subject)
53
+ }
54
+
55
+ /**
56
+ * Check if current user can assign a specific role
57
+ */
58
+ canAssignRole(targetRole: string): boolean {
59
+ if (!this._securityChecker) return true
60
+ return this._securityChecker.canAssignRole(targetRole)
61
+ }
62
+
63
+ /**
64
+ * Get all roles that current user can assign
65
+ */
66
+ getAssignableRoles(): string[] {
67
+ if (!this._securityChecker) return []
68
+ return this._securityChecker.getAssignableRoles()
69
+ }
70
+
71
+ /**
72
+ * Check if the current user can perform an action on an entity type (scope check)
73
+ *
74
+ * Default: delegates to isGranted('entity:{entity}:{action}')
75
+ * Override for custom authentication requirements or business rules.
76
+ */
77
+ canPerform(_entity: string, _action: string): boolean {
78
+ return this.isGranted(`entity:${_entity}:${_action}`)
79
+ }
80
+
81
+ /**
82
+ * Check if the current user can access a specific record (silo check)
83
+ *
84
+ * Default: delegates to isGranted('entity:{entity}:read', record)
85
+ * Note: Ownership checks are typically handled via EntityManager's isOwn callback.
86
+ */
87
+ canAccessRecord(_entity: string, record: unknown): boolean {
88
+ return this.isGranted(`entity:${_entity}:read`, record)
89
+ }
90
+
91
+ /**
92
+ * Get the current authenticated user
93
+ *
94
+ * Returns user from:
95
+ * 1. _getCurrentUserCallback if provided in constructor
96
+ * 2. Subclass override if using class extension pattern
97
+ * 3. null if neither is configured
98
+ */
99
+ getCurrentUser(): AuthUser | null {
100
+ if (this._getCurrentUserCallback) {
101
+ return this._getCurrentUserCallback()
102
+ }
103
+ // Subclasses can override this method
104
+ return null
105
+ }
106
+ }
107
+
108
+ /**
109
+ * Valid action types for scope checks
110
+ */
111
+ export const AuthActions = Object.freeze({
112
+ READ: 'read',
113
+ CREATE: 'create',
114
+ UPDATE: 'update',
115
+ DELETE: 'delete',
116
+ LIST: 'list',
117
+ } as const)
118
+
119
+ export type AuthAction = (typeof AuthActions)[keyof typeof AuthActions]
@@ -0,0 +1,43 @@
1
+ /**
2
+ * PermissiveAuthAdapter - Default adapter that allows all operations
3
+ *
4
+ * This adapter is used when no custom EntityAuthAdapter is provided to EntityManager.
5
+ * It returns `true` for all permission checks, effectively disabling authorization.
6
+ *
7
+ * Use cases:
8
+ * - Development/prototyping without auth setup
9
+ * - Backward compatibility with existing apps that don't use auth
10
+ * - Internal admin tools with implicit trust
11
+ * - Testing environments
12
+ */
13
+ import { EntityAuthAdapter, type AuthUser } from './EntityAuthAdapter'
14
+
15
+ export class PermissiveAuthAdapter extends EntityAuthAdapter {
16
+ /**
17
+ * Always allows any action on any entity type
18
+ */
19
+ canPerform(_entity: string, _action: string): boolean {
20
+ return true
21
+ }
22
+
23
+ /**
24
+ * Always allows access to any record
25
+ */
26
+ canAccessRecord(_entity: string, _record: unknown): boolean {
27
+ return true
28
+ }
29
+
30
+ /**
31
+ * Returns null (no authenticated user in permissive mode)
32
+ */
33
+ getCurrentUser(): AuthUser | null {
34
+ return null
35
+ }
36
+ }
37
+
38
+ /**
39
+ * Factory function to create a PermissiveAuthAdapter
40
+ */
41
+ export function createPermissiveAdapter(): PermissiveAuthAdapter {
42
+ return new PermissiveAuthAdapter()
43
+ }