@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.
- package/LICENSE +21 -0
- package/README.md +111 -0
- package/package.json +101 -0
- package/src/assets/logo.svg +6 -0
- package/src/auth/SessionAuthAdapter.ts +459 -0
- package/src/auth/index.ts +15 -0
- package/src/chain/ActiveStack.ts +154 -0
- package/src/chain/StackHydrator.ts +292 -0
- package/src/chain/index.ts +25 -0
- package/src/chain/useActiveStack.ts +120 -0
- package/src/chain/useStackHydrator.ts +155 -0
- package/src/components/BoolCell.vue +34 -0
- package/src/components/InfoBanner.vue +73 -0
- package/src/components/QdadmRoot.vue +52 -0
- package/src/components/SeverityTag.vue +86 -0
- package/src/components/dialogs/BulkStatusDialog.vue +60 -0
- package/src/components/dialogs/MultiStepDialog.vue +331 -0
- package/src/components/dialogs/SimpleDialog.vue +122 -0
- package/src/components/dialogs/UnsavedChangesDialog.vue +80 -0
- package/src/components/display/BannerZone.vue +65 -0
- package/src/components/display/CardsGrid.vue +72 -0
- package/src/components/display/CopyableId.vue +84 -0
- package/src/components/display/EmptyState.vue +62 -0
- package/src/components/display/IntensityBar.vue +190 -0
- package/src/components/display/RichCardsGrid.vue +101 -0
- package/src/components/edit/FormActions.vue +78 -0
- package/src/components/edit/FormField.vue +82 -0
- package/src/components/edit/FormInput.vue +158 -0
- package/src/components/edit/FormPage.vue +260 -0
- package/src/components/edit/FormTab.vue +217 -0
- package/src/components/edit/FormTabs.vue +120 -0
- package/src/components/edit/LookupField.vue +406 -0
- package/src/components/edit/LookupPickerDialog.vue +230 -0
- package/src/components/editors/JsonEditorFoldable.vue +470 -0
- package/src/components/editors/JsonStructuredField.vue +264 -0
- package/src/components/editors/JsonViewer.vue +51 -0
- package/src/components/editors/KeyValueEditor.vue +314 -0
- package/src/components/editors/LanguageEditor.vue +259 -0
- package/src/components/editors/PermissionEditor.vue +558 -0
- package/src/components/editors/ScopeEditor.vue +372 -0
- package/src/components/editors/VanillaJsonEditor.vue +183 -0
- package/src/components/index.ts +83 -0
- package/src/components/item/FieldGroups.vue +392 -0
- package/src/components/layout/AppLayout.vue +1032 -0
- package/src/components/layout/BaseLayout.vue +190 -0
- package/src/components/layout/Breadcrumb.vue +55 -0
- package/src/components/layout/DashboardLayout.vue +104 -0
- package/src/components/layout/FormLayout.vue +256 -0
- package/src/components/layout/ListLayout.vue +278 -0
- package/src/components/layout/PageHeader.vue +156 -0
- package/src/components/layout/PageLayout.vue +126 -0
- package/src/components/layout/PageNav.vue +192 -0
- package/src/components/layout/SidebarBox.vue +87 -0
- package/src/components/layout/Zone.vue +182 -0
- package/src/components/layout/defaults/DefaultBreadcrumb.vue +149 -0
- package/src/components/layout/defaults/DefaultFooter.vue +89 -0
- package/src/components/layout/defaults/DefaultFormActions.vue +67 -0
- package/src/components/layout/defaults/DefaultHeader.vue +78 -0
- package/src/components/layout/defaults/DefaultMenu.vue +188 -0
- package/src/components/layout/defaults/DefaultPagination.vue +75 -0
- package/src/components/layout/defaults/DefaultTable.vue +137 -0
- package/src/components/layout/defaults/DefaultUserInfo.vue +111 -0
- package/src/components/layout/defaults/index.ts +16 -0
- package/src/components/lists/ActionButtons.vue +49 -0
- package/src/components/lists/ActionColumn.vue +40 -0
- package/src/components/lists/FilterBar.vue +54 -0
- package/src/components/lists/ListPage.vue +467 -0
- package/src/components/pages/LoginPage.vue +334 -0
- package/src/components/pages/NotFoundPage.vue +98 -0
- package/src/components/show/ShowDisplay.vue +343 -0
- package/src/components/show/ShowField.vue +67 -0
- package/src/components/show/ShowPage.vue +431 -0
- package/src/composables/index.ts +155 -0
- package/src/composables/useApp.ts +67 -0
- package/src/composables/useAuth.ts +98 -0
- package/src/composables/useBareForm.ts +319 -0
- package/src/composables/useBreadcrumb.ts +252 -0
- package/src/composables/useChildPage.ts +113 -0
- package/src/composables/useCurrentEntity.ts +60 -0
- package/src/composables/useDeferred.ts +94 -0
- package/src/composables/useDirtyState.ts +138 -0
- package/src/composables/useEntityItemFormPage.ts +942 -0
- package/src/composables/useEntityItemFormPage.types.ts +451 -0
- package/src/composables/useEntityItemPage.ts +438 -0
- package/src/composables/useEntityItemShowPage.ts +722 -0
- package/src/composables/useFieldManager.ts +692 -0
- package/src/composables/useForm.ts +544 -0
- package/src/composables/useGuardStore.ts +53 -0
- package/src/composables/useHooks.ts +56 -0
- package/src/composables/useInfoBanner.ts +217 -0
- package/src/composables/useJsonSyntax.ts +122 -0
- package/src/composables/useLayoutResolver.ts +329 -0
- package/src/composables/useListPage.ts +1535 -0
- package/src/composables/useListPage.types.ts +437 -0
- package/src/composables/useListPage.utils.ts +101 -0
- package/src/composables/useNavContext.ts +439 -0
- package/src/composables/useNavigation.ts +216 -0
- package/src/composables/useOptionsLookup.ts +323 -0
- package/src/composables/usePageTitle.ts +84 -0
- package/src/composables/useSSEBridge.ts +142 -0
- package/src/composables/useSemanticBreadcrumb.ts +222 -0
- package/src/composables/useSidebarState.ts +24 -0
- package/src/composables/useSignals.ts +52 -0
- package/src/composables/useUnsavedChangesGuard.ts +155 -0
- package/src/composables/useUserImpersonator.ts +317 -0
- package/src/composables/useZoneRegistry.ts +252 -0
- package/src/core/bundles.ts +558 -0
- package/src/core/decorator.ts +420 -0
- package/src/core/extension.ts +470 -0
- package/src/core/index.ts +59 -0
- package/src/deferred/DeferredRegistry.ts +353 -0
- package/src/deferred/index.ts +15 -0
- package/src/editors/index.ts +12 -0
- package/src/entity/EntityManager.cache.ts +530 -0
- package/src/entity/EntityManager.crud.ts +615 -0
- package/src/entity/EntityManager.interface.ts +78 -0
- package/src/entity/EntityManager.query.ts +221 -0
- package/src/entity/EntityManager.relations.ts +151 -0
- package/src/entity/EntityManager.ts +944 -0
- package/src/entity/EntityManager.types.ts +288 -0
- package/src/entity/auth/CompositeAuthAdapter.ts +194 -0
- package/src/entity/auth/EntityAuthAdapter.ts +119 -0
- package/src/entity/auth/PermissiveAdapter.ts +43 -0
- package/src/entity/auth/RoleHierarchy.ts +128 -0
- package/src/entity/auth/SecurityChecker.ts +202 -0
- package/src/entity/auth/factory.ts +203 -0
- package/src/entity/auth/index.ts +47 -0
- package/src/entity/factory.test.js +189 -0
- package/src/entity/factory.ts +146 -0
- package/src/entity/index.ts +49 -0
- package/src/entity/storage/ApiStorage.ts +228 -0
- package/src/entity/storage/IStorage.ts +100 -0
- package/src/entity/storage/LocalStorage.ts +200 -0
- package/src/entity/storage/MemoryStorage.ts +184 -0
- package/src/entity/storage/MockApiStorage.ts +318 -0
- package/src/entity/storage/SdkStorage.ts +377 -0
- package/src/entity/storage/factory.test.js +159 -0
- package/src/entity/storage/factory.ts +209 -0
- package/src/entity/storage/index.ts +75 -0
- package/src/gen/FieldMapper.ts +122 -0
- package/src/gen/StorageProfileFactory.ts +93 -0
- package/src/gen/connectors/BaseConnector.ts +150 -0
- package/src/gen/connectors/ManualConnector.test.js +499 -0
- package/src/gen/connectors/ManualConnector.ts +414 -0
- package/src/gen/connectors/OpenAPIConnector.test.js +836 -0
- package/src/gen/connectors/OpenAPIConnector.ts +693 -0
- package/src/gen/connectors/__fixtures__/sample-openapi.json +311 -0
- package/src/gen/connectors/index.ts +27 -0
- package/src/gen/createManagers.ts +245 -0
- package/src/gen/decorators.ts +134 -0
- package/src/gen/generateManagers.test.js +540 -0
- package/src/gen/generateManagers.ts +398 -0
- package/src/gen/index.ts +66 -0
- package/src/gen/schema.ts +248 -0
- package/src/gen/vite-plugin.ts +114 -0
- package/src/generated/managers/testManager.js +45 -0
- package/src/generated/managers/testManager.ts +46 -0
- package/src/hooks/HookRegistry.ts +16 -0
- package/src/hooks/index.ts +15 -0
- package/src/i18n/I18n.ts +344 -0
- package/src/i18n/IncrementalDomainProvider.ts +153 -0
- package/src/i18n/InlineTranslationProvider.ts +4 -0
- package/src/i18n/LazyTranslationProvider.ts +102 -0
- package/src/i18n/MessagesRegistry.ts +4 -0
- package/src/i18n/Resolver.ts +4 -0
- package/src/i18n/__tests__/I18n.test.ts +169 -0
- package/src/i18n/__tests__/IncrementalDomainProvider.test.ts +146 -0
- package/src/i18n/__tests__/LazyTranslationProvider.test.ts +100 -0
- package/src/i18n/__tests__/Resolver.test.ts +271 -0
- package/src/i18n/defaults/DefaultCoreProvider.ts +28 -0
- package/src/i18n/defaults/core.en.yml +55 -0
- package/src/i18n/defaults/core.fr.yml +55 -0
- package/src/i18n/index.ts +55 -0
- package/src/i18n/loaders/raw-modules.d.ts +15 -0
- package/src/i18n/loaders/yaml.ts +35 -0
- package/src/i18n/strategies.ts +4 -0
- package/src/i18n/types.ts +34 -0
- package/src/i18n/useI18n.ts +34 -0
- package/src/index.ts +453 -0
- package/src/kernel/EventRouter.ts +23 -0
- package/src/kernel/Kernel.api.ts +79 -0
- package/src/kernel/Kernel.auth.ts +120 -0
- package/src/kernel/Kernel.i18n.ts +29 -0
- package/src/kernel/Kernel.modules.ts +170 -0
- package/src/kernel/Kernel.registries.ts +237 -0
- package/src/kernel/Kernel.routing.ts +334 -0
- package/src/kernel/Kernel.ts +460 -0
- package/src/kernel/Kernel.types.ts +263 -0
- package/src/kernel/Kernel.vue.ts +304 -0
- package/src/kernel/KernelContext.entities.ts +80 -0
- package/src/kernel/KernelContext.events.ts +57 -0
- package/src/kernel/KernelContext.i18n.ts +37 -0
- package/src/kernel/KernelContext.permissions.ts +38 -0
- package/src/kernel/KernelContext.routing.ts +280 -0
- package/src/kernel/KernelContext.ts +252 -0
- package/src/kernel/KernelContext.types.ts +173 -0
- package/src/kernel/KernelContext.zones.ts +54 -0
- package/src/kernel/Module.ts +169 -0
- package/src/kernel/ModuleLoader.ts +645 -0
- package/src/kernel/SSEBridge.ts +11 -0
- package/src/kernel/SignalBus.ts +48 -0
- package/src/kernel/index.ts +56 -0
- package/src/module/index.ts +33 -0
- package/src/module/moduleRegistry.ts +492 -0
- package/src/modules/debug/AuthCollector.ts +501 -0
- package/src/modules/debug/Collector.ts +20 -0
- package/src/modules/debug/DebugBridge.ts +14 -0
- package/src/modules/debug/DebugModule.ts +220 -0
- package/src/modules/debug/EntitiesCollector.ts +839 -0
- package/src/modules/debug/ErrorCollector.ts +4 -0
- package/src/modules/debug/I18nCollector.ts +9 -0
- package/src/modules/debug/LocalStorageAdapter.ts +7 -0
- package/src/modules/debug/RouterCollector.ts +449 -0
- package/src/modules/debug/SignalCollector.ts +4 -0
- package/src/modules/debug/ToastCollector.ts +4 -0
- package/src/modules/debug/ZonesCollector.ts +447 -0
- package/src/modules/debug/components/DebugBar.vue +31 -0
- package/src/modules/debug/components/adminPanelsConfig.ts +42 -0
- package/src/modules/debug/components/index.ts +9 -0
- package/src/modules/debug/components/panels/AuthPanel.vue +267 -0
- package/src/modules/debug/components/panels/EntitiesPanel.vue +572 -0
- package/src/modules/debug/components/panels/I18nPanel.vue +738 -0
- package/src/modules/debug/components/panels/RouterPanel.vue +440 -0
- package/src/modules/debug/components/panels/ZonesPanel.vue +133 -0
- package/src/modules/debug/components/panels/index.ts +16 -0
- package/src/modules/debug/index.ts +58 -0
- package/src/modules/debug/styles.scss +2671 -0
- package/src/notifications/NotificationBadge.vue +35 -0
- package/src/notifications/NotificationListener.vue +71 -0
- package/src/notifications/NotificationModule.ts +76 -0
- package/src/notifications/NotificationPanel.vue +478 -0
- package/src/notifications/NotificationStore.ts +288 -0
- package/src/notifications/index.ts +42 -0
- package/src/notifications/styles.scss +32 -0
- package/src/orchestrator/Orchestrator.ts +427 -0
- package/src/orchestrator/index.ts +8 -0
- package/src/orchestrator/useOrchestrator.ts +80 -0
- package/src/plugin.ts +251 -0
- package/src/query/FilterQuery.ts +306 -0
- package/src/query/QueryExecutor.ts +341 -0
- package/src/query/index.ts +16 -0
- package/src/security/EntityRolesProvider.ts +385 -0
- package/src/security/PermissionMatcher.ts +152 -0
- package/src/security/PermissionRegistry.ts +273 -0
- package/src/security/PersistableRolesProvider.ts +584 -0
- package/src/security/RolesManager.ts +136 -0
- package/src/security/RolesProvider.ts +167 -0
- package/src/security/RolesProviderStorage.ts +211 -0
- package/src/security/SecurityModule.ts +94 -0
- package/src/security/StaticRolesProvider.ts +117 -0
- package/src/security/UsersManager.ts +163 -0
- package/src/security/index.ts +77 -0
- package/src/security/pages/RoleForm.vue +276 -0
- package/src/security/pages/RoleList.vue +123 -0
- package/src/styles/_alerts.scss +95 -0
- package/src/styles/_buttons.scss +391 -0
- package/src/styles/_cards.scss +241 -0
- package/src/styles/_code.scss +78 -0
- package/src/styles/_dialogs.scss +55 -0
- package/src/styles/_filter-bar.scss +70 -0
- package/src/styles/_forms.scss +434 -0
- package/src/styles/_helpers.scss +153 -0
- package/src/styles/_lists.scss +225 -0
- package/src/styles/_main.scss +356 -0
- package/src/styles/_markdown.scss +88 -0
- package/src/styles/_responsive.scss +255 -0
- package/src/styles/_show-pages.scss +186 -0
- package/src/styles/_sidebar.scss +380 -0
- package/src/styles/_states.scss +115 -0
- package/src/styles/_stats.scss +47 -0
- package/src/styles/_variables.scss +224 -0
- package/src/styles/index.scss +38 -0
- package/src/styles/theme/_components.scss +331 -0
- package/src/styles/theme/_index.scss +10 -0
- package/src/styles/theme/_tokens.scss +147 -0
- package/src/styles/theme/_utilities.scss +175 -0
- package/src/toast/ToastBridgeModule.ts +68 -0
- package/src/toast/ToastListener.vue +55 -0
- package/src/toast/index.ts +26 -0
- package/src/toast/useSignalToast.ts +154 -0
- package/src/types/index.ts +285 -0
- package/src/types/pluralize.d.ts +19 -0
- package/src/types/quarkernel.d.ts +50 -0
- package/src/utils/formatters.ts +174 -0
- package/src/utils/index.ts +35 -0
- package/src/utils/transformers.ts +131 -0
- package/src/vite/qdadmDebugPlugin.ts +401 -0
- package/src/vite-env.d.ts +50 -0
- package/src/zones/ZoneRegistry.ts +934 -0
- package/src/zones/index.ts +29 -0
- package/src/zones/zones.ts +191 -0
|
@@ -0,0 +1,839 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* EntitiesCollector - Debug collector for Entity Managers
|
|
3
|
+
*
|
|
4
|
+
* This collector displays registered entity managers and their state:
|
|
5
|
+
* - All registered managers from Orchestrator
|
|
6
|
+
* - Storage type and capabilities
|
|
7
|
+
* - Cache status (enabled, valid, items, threshold)
|
|
8
|
+
* - Permissions (readOnly, canCreate, canUpdate, canDelete)
|
|
9
|
+
* - Relations (parent, children, parents)
|
|
10
|
+
*
|
|
11
|
+
* Shows current state rather than historical events.
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
import {
|
|
15
|
+
Collector,
|
|
16
|
+
type CollectorContext,
|
|
17
|
+
type CollectorEntry,
|
|
18
|
+
type CollectorManifest,
|
|
19
|
+
type CollectorOptions,
|
|
20
|
+
type CollectorSnapshot,
|
|
21
|
+
} from './Collector'
|
|
22
|
+
import type { EntityManager } from '../../entity/EntityManager'
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Storage info structure
|
|
26
|
+
*/
|
|
27
|
+
export interface StorageInfo {
|
|
28
|
+
type: string
|
|
29
|
+
endpoint: string | null
|
|
30
|
+
capabilities: Record<string, unknown>
|
|
31
|
+
hasNormalize: boolean
|
|
32
|
+
hasDenormalize: boolean
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Multi-storage entry
|
|
37
|
+
*/
|
|
38
|
+
export interface MultiStorageEntry {
|
|
39
|
+
name: string
|
|
40
|
+
type: string
|
|
41
|
+
endpoint: string | null
|
|
42
|
+
hasNormalize: boolean
|
|
43
|
+
hasDenormalize: boolean
|
|
44
|
+
capabilities: Record<string, unknown>
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Multi-storage info
|
|
49
|
+
*/
|
|
50
|
+
export interface MultiStorageInfo {
|
|
51
|
+
enabled: boolean
|
|
52
|
+
storages: MultiStorageEntry[]
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Cache info structure
|
|
57
|
+
*/
|
|
58
|
+
export interface DetailCacheInfo {
|
|
59
|
+
enabled: boolean
|
|
60
|
+
ttlMs: number
|
|
61
|
+
size: number
|
|
62
|
+
maxSize: number
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
export interface CacheInfo {
|
|
66
|
+
enabled: boolean
|
|
67
|
+
valid: boolean
|
|
68
|
+
itemCount: number
|
|
69
|
+
total: number
|
|
70
|
+
threshold: number
|
|
71
|
+
overflow: boolean
|
|
72
|
+
loadedAt: string | null
|
|
73
|
+
items: unknown[]
|
|
74
|
+
/** TTL in milliseconds (0=disabled, -1=infinite, >0=TTL) */
|
|
75
|
+
ttlMs: number
|
|
76
|
+
/** Timestamp when cache expires (null if no TTL or infinite) */
|
|
77
|
+
expiresAt: number | null
|
|
78
|
+
/** Whether the cache has expired based on TTL */
|
|
79
|
+
expired: boolean
|
|
80
|
+
/** Whether the entity uses asymmetric mode */
|
|
81
|
+
asymmetric: boolean
|
|
82
|
+
/** Detail cache info (only when asymmetric + detail cache enabled) */
|
|
83
|
+
detailCache: DetailCacheInfo | null
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* Permissions info
|
|
88
|
+
*/
|
|
89
|
+
export interface PermissionsInfo {
|
|
90
|
+
readOnly: boolean
|
|
91
|
+
canCreate: boolean
|
|
92
|
+
canUpdate: boolean
|
|
93
|
+
canDelete: boolean
|
|
94
|
+
canList: boolean
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* Warmup info
|
|
99
|
+
*/
|
|
100
|
+
export interface WarmupInfo {
|
|
101
|
+
enabled: boolean
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* Stats info
|
|
106
|
+
*/
|
|
107
|
+
export interface StatsInfo {
|
|
108
|
+
list: number
|
|
109
|
+
get: number
|
|
110
|
+
create: number
|
|
111
|
+
update: number
|
|
112
|
+
delete: number
|
|
113
|
+
cacheHits: number
|
|
114
|
+
cacheMisses: number
|
|
115
|
+
detailCacheHits: number
|
|
116
|
+
detailCacheMisses: number
|
|
117
|
+
maxItemsSeen: number
|
|
118
|
+
maxTotal: number
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
/**
|
|
122
|
+
* Fields info
|
|
123
|
+
*/
|
|
124
|
+
export interface FieldsInfo {
|
|
125
|
+
count: number
|
|
126
|
+
names: string[]
|
|
127
|
+
required: string[]
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
/**
|
|
131
|
+
* Relation parent info
|
|
132
|
+
*/
|
|
133
|
+
export interface RelationParentInfo {
|
|
134
|
+
entity: string
|
|
135
|
+
foreignKey: string
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
/**
|
|
139
|
+
* Relation parents entry
|
|
140
|
+
*/
|
|
141
|
+
export interface RelationParentsEntry {
|
|
142
|
+
key: string
|
|
143
|
+
entity: string
|
|
144
|
+
foreignKey: string
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
/**
|
|
148
|
+
* Relation children entry
|
|
149
|
+
*/
|
|
150
|
+
export interface RelationChildrenEntry {
|
|
151
|
+
key: string
|
|
152
|
+
entity: string
|
|
153
|
+
endpoint: string | null
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
/**
|
|
157
|
+
* Field reference entry
|
|
158
|
+
*/
|
|
159
|
+
export interface FieldReferenceEntry {
|
|
160
|
+
field: string
|
|
161
|
+
entity: string
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
/**
|
|
165
|
+
* Relations info
|
|
166
|
+
*/
|
|
167
|
+
export interface RelationsInfo {
|
|
168
|
+
parent: RelationParentInfo | null
|
|
169
|
+
parents: RelationParentsEntry[]
|
|
170
|
+
children: RelationChildrenEntry[]
|
|
171
|
+
references: FieldReferenceEntry[]
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
/**
|
|
175
|
+
* Entity entry for display
|
|
176
|
+
*/
|
|
177
|
+
export interface EntityEntry extends CollectorEntry {
|
|
178
|
+
name?: string
|
|
179
|
+
system?: boolean
|
|
180
|
+
hasActivity?: boolean
|
|
181
|
+
label?: string
|
|
182
|
+
labelPlural?: string
|
|
183
|
+
routePrefix?: string
|
|
184
|
+
idField?: string
|
|
185
|
+
storage?: StorageInfo
|
|
186
|
+
multiStorage?: MultiStorageInfo | null
|
|
187
|
+
cache?: CacheInfo
|
|
188
|
+
permissions?: PermissionsInfo
|
|
189
|
+
authSensitive?: boolean
|
|
190
|
+
warmup?: WarmupInfo
|
|
191
|
+
stats?: StatsInfo
|
|
192
|
+
fields?: FieldsInfo
|
|
193
|
+
relations?: RelationsInfo
|
|
194
|
+
type?: 'status'
|
|
195
|
+
message?: string
|
|
196
|
+
error?: string
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
/**
|
|
200
|
+
* Orchestrator interface for entity operations
|
|
201
|
+
*/
|
|
202
|
+
interface EntityOrchestrator {
|
|
203
|
+
getRegisteredNames(): string[]
|
|
204
|
+
get(name: string): EntityManager
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
/**
|
|
208
|
+
* Collector for Entity Manager state visualization
|
|
209
|
+
*/
|
|
210
|
+
export class EntitiesCollector extends Collector<EntityEntry> {
|
|
211
|
+
static override collectorName = 'entities'
|
|
212
|
+
static override records = false
|
|
213
|
+
|
|
214
|
+
private _signalCleanups: Array<() => void> = []
|
|
215
|
+
private _lastUpdate = 0
|
|
216
|
+
private _previousStats: Map<string, StatsInfo> = new Map()
|
|
217
|
+
private _activeEntities: Set<string> = new Set()
|
|
218
|
+
|
|
219
|
+
constructor(options: CollectorOptions = {}) {
|
|
220
|
+
super(options)
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
/**
|
|
224
|
+
* Internal install - store context reference and subscribe to signals
|
|
225
|
+
*/
|
|
226
|
+
protected override _doInstall(ctx: CollectorContext): void {
|
|
227
|
+
this._ctx = ctx
|
|
228
|
+
this._setupSignals()
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
/**
|
|
232
|
+
* Setup signal listeners (deferred until signals available)
|
|
233
|
+
*/
|
|
234
|
+
private _setupSignals(): void {
|
|
235
|
+
const signals = this._ctx?.signals
|
|
236
|
+
if (!signals) {
|
|
237
|
+
setTimeout(() => this._setupSignals(), 100)
|
|
238
|
+
return
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
// Listen to all entity lifecycle events
|
|
242
|
+
const entityCleanup = signals.on('entity:*', () => {
|
|
243
|
+
this._lastUpdate = Date.now()
|
|
244
|
+
this.notifyChange()
|
|
245
|
+
})
|
|
246
|
+
this._signalCleanups.push(entityCleanup)
|
|
247
|
+
|
|
248
|
+
// Listen to entity data changes (CRUD operations)
|
|
249
|
+
const dataCleanup = signals.on('entity:data-invalidate', () => {
|
|
250
|
+
this._lastUpdate = Date.now()
|
|
251
|
+
this.notifyChange()
|
|
252
|
+
})
|
|
253
|
+
this._signalCleanups.push(dataCleanup)
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
/**
|
|
257
|
+
* Internal uninstall - cleanup signal subscriptions
|
|
258
|
+
*/
|
|
259
|
+
protected override _doUninstall(): void {
|
|
260
|
+
for (const cleanup of this._signalCleanups) {
|
|
261
|
+
if (typeof cleanup === 'function') cleanup()
|
|
262
|
+
}
|
|
263
|
+
this._signalCleanups = []
|
|
264
|
+
this._ctx = null
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
/**
|
|
268
|
+
* Get orchestrator lazily (may not exist at install time)
|
|
269
|
+
*/
|
|
270
|
+
private get _orchestrator(): EntityOrchestrator | null {
|
|
271
|
+
return (this._ctx as CollectorContext & { orchestrator?: EntityOrchestrator })?.orchestrator ?? null
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
/**
|
|
275
|
+
* Get badge - show count of entities with unseen activity
|
|
276
|
+
*/
|
|
277
|
+
override getBadge(): number {
|
|
278
|
+
if (!this._orchestrator) return 0
|
|
279
|
+
this._detectActivity()
|
|
280
|
+
return this._activeEntities.size
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
/**
|
|
284
|
+
* Detect activity by comparing current stats with previous snapshot
|
|
285
|
+
*/
|
|
286
|
+
private _detectActivity(): void {
|
|
287
|
+
if (!this._orchestrator) return
|
|
288
|
+
|
|
289
|
+
for (const name of this._orchestrator.getRegisteredNames()) {
|
|
290
|
+
try {
|
|
291
|
+
const manager = this._orchestrator.get(name) as EntityManager & { getStats?: () => StatsInfo }
|
|
292
|
+
const currentStats = manager.getStats?.()
|
|
293
|
+
if (!currentStats) continue
|
|
294
|
+
|
|
295
|
+
const prevStats = this._previousStats.get(name)
|
|
296
|
+
if (!prevStats) {
|
|
297
|
+
this._previousStats.set(name, { ...currentStats })
|
|
298
|
+
continue
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
const hasChanged =
|
|
302
|
+
currentStats.list !== prevStats.list ||
|
|
303
|
+
currentStats.get !== prevStats.get ||
|
|
304
|
+
currentStats.create !== prevStats.create ||
|
|
305
|
+
currentStats.update !== prevStats.update ||
|
|
306
|
+
currentStats.delete !== prevStats.delete ||
|
|
307
|
+
currentStats.cacheHits !== prevStats.cacheHits ||
|
|
308
|
+
currentStats.cacheMisses !== prevStats.cacheMisses ||
|
|
309
|
+
currentStats.detailCacheHits !== prevStats.detailCacheHits ||
|
|
310
|
+
currentStats.detailCacheMisses !== prevStats.detailCacheMisses
|
|
311
|
+
|
|
312
|
+
if (hasChanged) {
|
|
313
|
+
this._activeEntities.add(name)
|
|
314
|
+
this._previousStats.set(name, { ...currentStats })
|
|
315
|
+
}
|
|
316
|
+
} catch {
|
|
317
|
+
// Skip failed managers
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
/**
|
|
323
|
+
* Get total count - number of registered entities
|
|
324
|
+
*/
|
|
325
|
+
getTotalCount(): number {
|
|
326
|
+
if (!this._orchestrator) return 0
|
|
327
|
+
return this._orchestrator.getRegisteredNames().length
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
/**
|
|
331
|
+
* Get all entity information for display
|
|
332
|
+
*/
|
|
333
|
+
override getEntries(): EntityEntry[] {
|
|
334
|
+
if (!this._orchestrator) {
|
|
335
|
+
return [{ timestamp: Date.now(), type: 'status', message: 'No orchestrator configured' }]
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
const names = this._orchestrator.getRegisteredNames()
|
|
339
|
+
if (names.length === 0) {
|
|
340
|
+
return [{ timestamp: Date.now(), type: 'status', message: 'No entities registered' }]
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
const entries: EntityEntry[] = []
|
|
344
|
+
|
|
345
|
+
for (const name of names.sort()) {
|
|
346
|
+
try {
|
|
347
|
+
const manager = this._orchestrator.get(name)
|
|
348
|
+
entries.push(this._buildEntityInfo(name, manager))
|
|
349
|
+
} catch (e) {
|
|
350
|
+
entries.push({
|
|
351
|
+
timestamp: Date.now(),
|
|
352
|
+
name,
|
|
353
|
+
error: (e as Error).message
|
|
354
|
+
})
|
|
355
|
+
}
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
// Sort: system entities first, then alphabetically
|
|
359
|
+
entries.sort((a, b) => {
|
|
360
|
+
if (a.system && !b.system) return -1
|
|
361
|
+
if (!a.system && b.system) return 1
|
|
362
|
+
return (a.name || '').localeCompare(b.name || '')
|
|
363
|
+
})
|
|
364
|
+
|
|
365
|
+
return entries
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
/**
|
|
369
|
+
* Build entity info object from manager
|
|
370
|
+
*/
|
|
371
|
+
private _buildEntityInfo(name: string, manager: EntityManager): EntityEntry {
|
|
372
|
+
const extManager = manager as EntityManager & {
|
|
373
|
+
getCacheInfo?: () => {
|
|
374
|
+
enabled?: boolean
|
|
375
|
+
valid?: boolean
|
|
376
|
+
itemCount?: number
|
|
377
|
+
total?: number
|
|
378
|
+
threshold?: number
|
|
379
|
+
overflow?: boolean
|
|
380
|
+
loadedAt?: number
|
|
381
|
+
ttlMs?: number
|
|
382
|
+
expiresAt?: number | null
|
|
383
|
+
expired?: boolean
|
|
384
|
+
asymmetric?: boolean
|
|
385
|
+
detailCache?: { enabled: boolean; ttlMs: number; size: number; maxSize: number } | null
|
|
386
|
+
}
|
|
387
|
+
getStats?: () => StatsInfo
|
|
388
|
+
getRequiredFields?: () => string[]
|
|
389
|
+
system?: boolean
|
|
390
|
+
label?: string
|
|
391
|
+
labelPlural?: string
|
|
392
|
+
routePrefix?: string
|
|
393
|
+
idField?: string
|
|
394
|
+
readOnly?: boolean
|
|
395
|
+
canCreate?: () => boolean
|
|
396
|
+
canUpdate?: () => boolean
|
|
397
|
+
canDelete?: () => boolean
|
|
398
|
+
canList?: () => boolean
|
|
399
|
+
_authSensitive?: boolean
|
|
400
|
+
warmupEnabled?: boolean
|
|
401
|
+
fields?: Record<string, { reference?: { entity?: string } }>
|
|
402
|
+
_parent?: { entity: string; foreignKey: string }
|
|
403
|
+
_parents?: Record<string, { entity: string; foreignKey: string }>
|
|
404
|
+
_children?: Record<string, { entity: string; endpoint?: string }>
|
|
405
|
+
_cache?: unknown[]
|
|
406
|
+
_cacheMap?: Map<unknown, unknown>
|
|
407
|
+
cache?: unknown[]
|
|
408
|
+
_cachedItems?: unknown[]
|
|
409
|
+
resolveStorage?: (method: string, context: unknown) => { storage: unknown }
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
const cache = extManager.getCacheInfo?.() || {}
|
|
413
|
+
const storage = extManager.storage as {
|
|
414
|
+
constructor?: { storageName?: string; name?: string; capabilities?: Record<string, unknown> }
|
|
415
|
+
endpoint?: string
|
|
416
|
+
_endpoint?: string
|
|
417
|
+
capabilities?: Record<string, unknown>
|
|
418
|
+
_normalize?: unknown
|
|
419
|
+
_denormalize?: unknown
|
|
420
|
+
} | null
|
|
421
|
+
|
|
422
|
+
return {
|
|
423
|
+
timestamp: Date.now(),
|
|
424
|
+
name,
|
|
425
|
+
system: extManager.system ?? false,
|
|
426
|
+
hasActivity: this._activeEntities.has(name),
|
|
427
|
+
label: extManager.label,
|
|
428
|
+
labelPlural: extManager.labelPlural,
|
|
429
|
+
routePrefix: extManager.routePrefix,
|
|
430
|
+
idField: extManager.idField,
|
|
431
|
+
|
|
432
|
+
storage: {
|
|
433
|
+
type: storage?.constructor?.storageName || storage?.constructor?.name || 'None',
|
|
434
|
+
endpoint: storage?.endpoint || storage?._endpoint || null,
|
|
435
|
+
capabilities: storage?.capabilities || storage?.constructor?.capabilities || {},
|
|
436
|
+
hasNormalize: !!(storage?._normalize),
|
|
437
|
+
hasDenormalize: !!(storage?._denormalize)
|
|
438
|
+
},
|
|
439
|
+
|
|
440
|
+
multiStorage: this._detectMultiStorage(extManager),
|
|
441
|
+
|
|
442
|
+
cache: {
|
|
443
|
+
enabled: cache.enabled ?? false,
|
|
444
|
+
valid: cache.valid ?? false,
|
|
445
|
+
itemCount: cache.itemCount ?? 0,
|
|
446
|
+
total: cache.total ?? 0,
|
|
447
|
+
threshold: cache.threshold ?? 0,
|
|
448
|
+
overflow: cache.overflow ?? false,
|
|
449
|
+
loadedAt: cache.loadedAt ? new Date(cache.loadedAt).toLocaleTimeString() : null,
|
|
450
|
+
items: this._getCacheItems(extManager, 50),
|
|
451
|
+
ttlMs: cache.ttlMs ?? -1,
|
|
452
|
+
expiresAt: cache.expiresAt ?? null,
|
|
453
|
+
expired: cache.expired ?? false,
|
|
454
|
+
asymmetric: cache.asymmetric ?? false,
|
|
455
|
+
detailCache: cache.detailCache ?? null,
|
|
456
|
+
},
|
|
457
|
+
|
|
458
|
+
permissions: {
|
|
459
|
+
readOnly: extManager.readOnly ?? false,
|
|
460
|
+
canCreate: extManager.canCreate?.() ?? true,
|
|
461
|
+
canUpdate: extManager.canUpdate?.() ?? true,
|
|
462
|
+
canDelete: extManager.canDelete?.() ?? true,
|
|
463
|
+
canList: extManager.canList?.() ?? true
|
|
464
|
+
},
|
|
465
|
+
|
|
466
|
+
authSensitive: extManager._authSensitive ?? false,
|
|
467
|
+
|
|
468
|
+
warmup: {
|
|
469
|
+
enabled: extManager.warmupEnabled ?? false
|
|
470
|
+
},
|
|
471
|
+
|
|
472
|
+
stats: extManager.getStats?.() || {
|
|
473
|
+
list: 0,
|
|
474
|
+
get: 0,
|
|
475
|
+
create: 0,
|
|
476
|
+
update: 0,
|
|
477
|
+
delete: 0,
|
|
478
|
+
cacheHits: 0,
|
|
479
|
+
cacheMisses: 0,
|
|
480
|
+
detailCacheHits: 0,
|
|
481
|
+
detailCacheMisses: 0,
|
|
482
|
+
maxItemsSeen: 0,
|
|
483
|
+
maxTotal: 0
|
|
484
|
+
},
|
|
485
|
+
|
|
486
|
+
fields: {
|
|
487
|
+
count: Object.keys(extManager.fields || {}).length,
|
|
488
|
+
names: Object.keys(extManager.fields || {}).slice(0, 10),
|
|
489
|
+
required: extManager.getRequiredFields?.() || []
|
|
490
|
+
},
|
|
491
|
+
|
|
492
|
+
relations: {
|
|
493
|
+
parent: extManager._parent ? {
|
|
494
|
+
entity: extManager._parent.entity,
|
|
495
|
+
foreignKey: extManager._parent.foreignKey
|
|
496
|
+
} : null,
|
|
497
|
+
parents: Object.entries(extManager._parents || {}).map(([key, config]) => ({
|
|
498
|
+
key,
|
|
499
|
+
entity: config.entity,
|
|
500
|
+
foreignKey: config.foreignKey
|
|
501
|
+
})),
|
|
502
|
+
children: Object.entries(extManager._children || {}).map(([key, config]) => ({
|
|
503
|
+
key,
|
|
504
|
+
entity: config.entity,
|
|
505
|
+
endpoint: config.endpoint || null
|
|
506
|
+
})),
|
|
507
|
+
references: Object.entries(extManager.fields || {})
|
|
508
|
+
.filter(([, field]) => field.reference?.entity)
|
|
509
|
+
.map(([fieldName, field]) => ({
|
|
510
|
+
field: fieldName,
|
|
511
|
+
entity: field.reference!.entity!
|
|
512
|
+
}))
|
|
513
|
+
}
|
|
514
|
+
}
|
|
515
|
+
}
|
|
516
|
+
|
|
517
|
+
/**
|
|
518
|
+
* Detect if a manager uses multi-storage pattern
|
|
519
|
+
*/
|
|
520
|
+
private _detectMultiStorage(manager: EntityManager): MultiStorageInfo | null {
|
|
521
|
+
const extManager = manager as EntityManager & {
|
|
522
|
+
resolveStorage?: (method: string, context: unknown) => { storage: unknown }
|
|
523
|
+
storage?: unknown
|
|
524
|
+
}
|
|
525
|
+
|
|
526
|
+
const hasCustomResolve = extManager.resolveStorage &&
|
|
527
|
+
extManager.resolveStorage.toString() !== 'resolveStorage(method, context) {\n return { storage: this.storage };\n }'
|
|
528
|
+
|
|
529
|
+
if (!hasCustomResolve) {
|
|
530
|
+
const additionalStorages = this._findAdditionalStorages(manager)
|
|
531
|
+
if (additionalStorages.length === 0) {
|
|
532
|
+
return null
|
|
533
|
+
}
|
|
534
|
+
}
|
|
535
|
+
|
|
536
|
+
const storages = this._findAdditionalStorages(manager)
|
|
537
|
+
|
|
538
|
+
return {
|
|
539
|
+
enabled: true,
|
|
540
|
+
storages: storages.map(s => ({
|
|
541
|
+
name: s.name,
|
|
542
|
+
type: s.storage?.constructor?.storageName || s.storage?.constructor?.name || 'Unknown',
|
|
543
|
+
endpoint: s.storage?.endpoint || null,
|
|
544
|
+
hasNormalize: !!(s.storage?._normalize),
|
|
545
|
+
hasDenormalize: !!(s.storage?._denormalize),
|
|
546
|
+
capabilities: s.storage?.capabilities || s.storage?.constructor?.capabilities || {}
|
|
547
|
+
}))
|
|
548
|
+
}
|
|
549
|
+
}
|
|
550
|
+
|
|
551
|
+
/**
|
|
552
|
+
* Find additional storage instances on a manager
|
|
553
|
+
*/
|
|
554
|
+
private _findAdditionalStorages(manager: EntityManager): Array<{
|
|
555
|
+
name: string
|
|
556
|
+
storage: {
|
|
557
|
+
constructor?: { storageName?: string; name?: string; capabilities?: Record<string, unknown> }
|
|
558
|
+
endpoint?: string
|
|
559
|
+
capabilities?: Record<string, unknown>
|
|
560
|
+
_normalize?: unknown
|
|
561
|
+
_denormalize?: unknown
|
|
562
|
+
list?: (params: unknown) => unknown
|
|
563
|
+
get?: (id: unknown) => unknown
|
|
564
|
+
fetch?: (params: unknown) => unknown
|
|
565
|
+
}
|
|
566
|
+
}> {
|
|
567
|
+
const storages: Array<{
|
|
568
|
+
name: string
|
|
569
|
+
storage: {
|
|
570
|
+
constructor?: { storageName?: string; name?: string; capabilities?: Record<string, unknown> }
|
|
571
|
+
endpoint?: string
|
|
572
|
+
capabilities?: Record<string, unknown>
|
|
573
|
+
_normalize?: unknown
|
|
574
|
+
_denormalize?: unknown
|
|
575
|
+
list?: (params: unknown) => unknown
|
|
576
|
+
get?: (id: unknown) => unknown
|
|
577
|
+
fetch?: (params: unknown) => unknown
|
|
578
|
+
}
|
|
579
|
+
}> = []
|
|
580
|
+
const extManager = manager as EntityManager & { storage?: unknown } & Record<string, unknown>
|
|
581
|
+
const mainStorage = extManager.storage
|
|
582
|
+
|
|
583
|
+
for (const key of Object.keys(extManager)) {
|
|
584
|
+
if (key.startsWith('_') || key === 'storage') continue
|
|
585
|
+
|
|
586
|
+
const value = extManager[key] as {
|
|
587
|
+
constructor?: { storageName?: string; name?: string; capabilities?: Record<string, unknown> }
|
|
588
|
+
endpoint?: string
|
|
589
|
+
capabilities?: Record<string, unknown>
|
|
590
|
+
_normalize?: unknown
|
|
591
|
+
_denormalize?: unknown
|
|
592
|
+
list?: (params: unknown) => unknown
|
|
593
|
+
get?: (id: unknown) => unknown
|
|
594
|
+
fetch?: (params: unknown) => unknown
|
|
595
|
+
} | null
|
|
596
|
+
if (!value || typeof value !== 'object') continue
|
|
597
|
+
|
|
598
|
+
const isStorage = (
|
|
599
|
+
(typeof value.list === 'function' && typeof value.get === 'function') ||
|
|
600
|
+
(value.endpoint && typeof value.fetch === 'function')
|
|
601
|
+
)
|
|
602
|
+
|
|
603
|
+
if (isStorage && value !== mainStorage) {
|
|
604
|
+
storages.push({ name: key, storage: value })
|
|
605
|
+
}
|
|
606
|
+
}
|
|
607
|
+
|
|
608
|
+
return storages
|
|
609
|
+
}
|
|
610
|
+
|
|
611
|
+
/**
|
|
612
|
+
* Get cached items from a manager
|
|
613
|
+
*/
|
|
614
|
+
private _getCacheItems(manager: EntityManager, limit = 50): unknown[] {
|
|
615
|
+
const extManager = manager as EntityManager & {
|
|
616
|
+
_cache?: unknown[]
|
|
617
|
+
_cacheMap?: Map<unknown, unknown>
|
|
618
|
+
cache?: unknown[]
|
|
619
|
+
_cachedItems?: unknown[]
|
|
620
|
+
getCacheInfo?: () => { valid?: boolean }
|
|
621
|
+
}
|
|
622
|
+
|
|
623
|
+
try {
|
|
624
|
+
if (extManager._cache && Array.isArray(extManager._cache)) {
|
|
625
|
+
return extManager._cache.slice(0, limit)
|
|
626
|
+
}
|
|
627
|
+
if (extManager._cacheMap && extManager._cacheMap instanceof Map) {
|
|
628
|
+
return Array.from(extManager._cacheMap.values()).slice(0, limit)
|
|
629
|
+
}
|
|
630
|
+
if (extManager.cache && Array.isArray(extManager.cache)) {
|
|
631
|
+
return extManager.cache.slice(0, limit)
|
|
632
|
+
}
|
|
633
|
+
if (extManager.getCacheInfo?.()?.valid && extManager._cachedItems) {
|
|
634
|
+
return extManager._cachedItems.slice(0, limit)
|
|
635
|
+
}
|
|
636
|
+
return []
|
|
637
|
+
} catch {
|
|
638
|
+
return []
|
|
639
|
+
}
|
|
640
|
+
}
|
|
641
|
+
|
|
642
|
+
/**
|
|
643
|
+
* Force refresh a specific entity's cache
|
|
644
|
+
*/
|
|
645
|
+
async refreshCache(entityName: string, reload = false): Promise<boolean> {
|
|
646
|
+
if (!this._orchestrator) return false
|
|
647
|
+
try {
|
|
648
|
+
const manager = this._orchestrator.get(entityName) as EntityManager & {
|
|
649
|
+
invalidateCache: () => void
|
|
650
|
+
ensureCache: () => Promise<void>
|
|
651
|
+
}
|
|
652
|
+
manager.invalidateCache()
|
|
653
|
+
if (reload) {
|
|
654
|
+
await manager.ensureCache()
|
|
655
|
+
}
|
|
656
|
+
this.notifyChange()
|
|
657
|
+
return true
|
|
658
|
+
} catch (e) {
|
|
659
|
+
console.error('[EntitiesCollector] Failed to refresh cache:', e)
|
|
660
|
+
return false
|
|
661
|
+
}
|
|
662
|
+
}
|
|
663
|
+
|
|
664
|
+
/**
|
|
665
|
+
* Invalidate a specific entity's cache
|
|
666
|
+
*/
|
|
667
|
+
invalidateCache(entityName: string): void {
|
|
668
|
+
if (!this._orchestrator) return
|
|
669
|
+
try {
|
|
670
|
+
const manager = this._orchestrator.get(entityName) as EntityManager & {
|
|
671
|
+
invalidateCache: () => void
|
|
672
|
+
}
|
|
673
|
+
manager.invalidateCache()
|
|
674
|
+
} catch (e) {
|
|
675
|
+
console.error('[EntitiesCollector] Failed to invalidate cache:', e)
|
|
676
|
+
}
|
|
677
|
+
}
|
|
678
|
+
|
|
679
|
+
/**
|
|
680
|
+
* Mark all entities as seen (clear activity state)
|
|
681
|
+
*/
|
|
682
|
+
markSeen(): void {
|
|
683
|
+
this._activeEntities.clear()
|
|
684
|
+
}
|
|
685
|
+
|
|
686
|
+
/**
|
|
687
|
+
* Mark a specific entity as seen
|
|
688
|
+
*/
|
|
689
|
+
markEntitySeen(entityName: string): void {
|
|
690
|
+
this._activeEntities.delete(entityName)
|
|
691
|
+
}
|
|
692
|
+
|
|
693
|
+
/**
|
|
694
|
+
* Test fetch data from a specific entity
|
|
695
|
+
*/
|
|
696
|
+
async testFetch(entityName: string): Promise<{ success: boolean; count?: number; error?: string; status?: number }> {
|
|
697
|
+
if (!this._orchestrator) {
|
|
698
|
+
return { success: false, error: 'No orchestrator' }
|
|
699
|
+
}
|
|
700
|
+
try {
|
|
701
|
+
const manager = this._orchestrator.get(entityName) as EntityManager & {
|
|
702
|
+
storage: { list: (params: { page: number; page_size: number }) => Promise<{ total?: number; items?: unknown[] }> }
|
|
703
|
+
}
|
|
704
|
+
const result = await manager.storage.list({ page: 1, page_size: 1 })
|
|
705
|
+
return {
|
|
706
|
+
success: true,
|
|
707
|
+
count: result.total ?? result.items?.length ?? 0
|
|
708
|
+
}
|
|
709
|
+
} catch (e) {
|
|
710
|
+
return {
|
|
711
|
+
success: false,
|
|
712
|
+
error: (e as Error).message,
|
|
713
|
+
status: (e as Error & { status?: number }).status
|
|
714
|
+
}
|
|
715
|
+
}
|
|
716
|
+
}
|
|
717
|
+
|
|
718
|
+
override describe(): CollectorManifest {
|
|
719
|
+
return {
|
|
720
|
+
name: this.name,
|
|
721
|
+
records: false,
|
|
722
|
+
summary:
|
|
723
|
+
'Lists every registered EntityManager with its storage, cache state, permissions, stats, fields and relations.',
|
|
724
|
+
entryShape: {
|
|
725
|
+
name: 'string',
|
|
726
|
+
system: 'boolean',
|
|
727
|
+
hasActivity: 'boolean',
|
|
728
|
+
label: 'string?',
|
|
729
|
+
labelPlural: 'string?',
|
|
730
|
+
routePrefix: 'string?',
|
|
731
|
+
idField: 'string?',
|
|
732
|
+
storage: 'StorageInfo',
|
|
733
|
+
multiStorage: 'MultiStorageInfo?',
|
|
734
|
+
cache: 'CacheInfo',
|
|
735
|
+
permissions: 'PermissionsInfo',
|
|
736
|
+
authSensitive: 'boolean',
|
|
737
|
+
warmup: 'WarmupInfo',
|
|
738
|
+
stats: 'StatsInfo',
|
|
739
|
+
fields: 'FieldsInfo',
|
|
740
|
+
relations: 'RelationsInfo',
|
|
741
|
+
},
|
|
742
|
+
stateShape: {
|
|
743
|
+
registered: 'string[] (entity names)',
|
|
744
|
+
activeEntities: 'string[] (entities with unseen activity)',
|
|
745
|
+
},
|
|
746
|
+
actions: [
|
|
747
|
+
...this._builtinActionManifests(),
|
|
748
|
+
{
|
|
749
|
+
name: 'refreshCache',
|
|
750
|
+
summary: 'Invalidate one entity\'s cache (and optionally reload).',
|
|
751
|
+
args: { entity: 'string', reload: 'boolean?' },
|
|
752
|
+
mutates: true,
|
|
753
|
+
},
|
|
754
|
+
{
|
|
755
|
+
name: 'invalidateCache',
|
|
756
|
+
summary: 'Mark one entity\'s cache invalid without reloading.',
|
|
757
|
+
args: { entity: 'string' },
|
|
758
|
+
mutates: true,
|
|
759
|
+
},
|
|
760
|
+
{
|
|
761
|
+
name: 'testFetch',
|
|
762
|
+
summary: 'Probe an entity\'s primary storage with a list({page:1,page_size:1}).',
|
|
763
|
+
args: { entity: 'string' },
|
|
764
|
+
},
|
|
765
|
+
{
|
|
766
|
+
name: 'testStorageFetch',
|
|
767
|
+
summary: 'Probe a specific named storage on an entity.',
|
|
768
|
+
args: { entity: 'string', storage: 'string' },
|
|
769
|
+
},
|
|
770
|
+
],
|
|
771
|
+
}
|
|
772
|
+
}
|
|
773
|
+
|
|
774
|
+
override snapshot(): CollectorSnapshot {
|
|
775
|
+
const entries = this.getEntries()
|
|
776
|
+
const registered = this._orchestrator?.getRegisteredNames() ?? []
|
|
777
|
+
return {
|
|
778
|
+
name: this.name,
|
|
779
|
+
entries,
|
|
780
|
+
count: entries.length,
|
|
781
|
+
unseen: this.getBadge(),
|
|
782
|
+
state: {
|
|
783
|
+
registered,
|
|
784
|
+
activeEntities: Array.from(this._activeEntities),
|
|
785
|
+
},
|
|
786
|
+
}
|
|
787
|
+
}
|
|
788
|
+
|
|
789
|
+
override async call(actionName: string, args: Record<string, unknown> = {}): Promise<unknown> {
|
|
790
|
+
if (actionName === 'refreshCache') {
|
|
791
|
+
const ok = await this.refreshCache(String(args.entity ?? ''), Boolean(args.reload))
|
|
792
|
+
return { ok }
|
|
793
|
+
}
|
|
794
|
+
if (actionName === 'invalidateCache') {
|
|
795
|
+
this.invalidateCache(String(args.entity ?? ''))
|
|
796
|
+
return { ok: true }
|
|
797
|
+
}
|
|
798
|
+
if (actionName === 'testFetch') {
|
|
799
|
+
return await this.testFetch(String(args.entity ?? ''))
|
|
800
|
+
}
|
|
801
|
+
if (actionName === 'testStorageFetch') {
|
|
802
|
+
return await this.testStorageFetch(String(args.entity ?? ''), String(args.storage ?? ''))
|
|
803
|
+
}
|
|
804
|
+
return super.call(actionName, args)
|
|
805
|
+
}
|
|
806
|
+
|
|
807
|
+
/**
|
|
808
|
+
* Test fetch data from a specific storage of an entity
|
|
809
|
+
*/
|
|
810
|
+
async testStorageFetch(
|
|
811
|
+
entityName: string,
|
|
812
|
+
storageName: string
|
|
813
|
+
): Promise<{ success: boolean; count?: number; error?: string; status?: number }> {
|
|
814
|
+
if (!this._orchestrator) {
|
|
815
|
+
return { success: false, error: 'No orchestrator' }
|
|
816
|
+
}
|
|
817
|
+
try {
|
|
818
|
+
const manager = this._orchestrator.get(entityName) as EntityManager & Record<string, unknown>
|
|
819
|
+
const storage = storageName === 'storage'
|
|
820
|
+
? manager.storage as { list: (params: { page: number; page_size: number }) => Promise<{ total?: number; items?: unknown[] }> }
|
|
821
|
+
: manager[storageName] as { list: (params: { page: number; page_size: number }) => Promise<{ total?: number; items?: unknown[] }> } | undefined
|
|
822
|
+
|
|
823
|
+
if (!storage) {
|
|
824
|
+
return { success: false, error: `Storage '${storageName}' not found` }
|
|
825
|
+
}
|
|
826
|
+
const result = await storage.list({ page: 1, page_size: 1 })
|
|
827
|
+
return {
|
|
828
|
+
success: true,
|
|
829
|
+
count: result.total ?? result.items?.length ?? 0
|
|
830
|
+
}
|
|
831
|
+
} catch (e) {
|
|
832
|
+
return {
|
|
833
|
+
success: false,
|
|
834
|
+
error: (e as Error).message,
|
|
835
|
+
status: (e as Error & { status?: number }).status
|
|
836
|
+
}
|
|
837
|
+
}
|
|
838
|
+
}
|
|
839
|
+
}
|