@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,221 @@
|
|
|
1
|
+
import { QueryExecutor, type QueryObject } from '../query/QueryExecutor'
|
|
2
|
+
import type { ListParams, ListResult } from '../types'
|
|
3
|
+
import type { EntityManagerInternal, OperationStats, QueryOptions } from './EntityManager.types'
|
|
4
|
+
|
|
5
|
+
type Self = EntityManagerInternal<any>
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Patch EntityManager prototype with query-related methods.
|
|
9
|
+
*/
|
|
10
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
11
|
+
export function applyQueryMethods(EntityManagerClass: { prototype: any }): void {
|
|
12
|
+
const proto = EntityManagerClass.prototype as Self
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Query entities with automatic cache/API decision
|
|
16
|
+
*/
|
|
17
|
+
proto.query = async function (
|
|
18
|
+
this: Self,
|
|
19
|
+
params: ListParams = {},
|
|
20
|
+
options: QueryOptions = {}
|
|
21
|
+
): Promise<ListResult<any>> {
|
|
22
|
+
const { context = {}, routingContext = null } = options
|
|
23
|
+
|
|
24
|
+
// Check if this context uses a dynamic endpoint (no caching possible)
|
|
25
|
+
const resolved = this._normalizeResolveResult(
|
|
26
|
+
this.resolveStorage('list', routingContext ?? undefined),
|
|
27
|
+
routingContext ?? undefined
|
|
28
|
+
)
|
|
29
|
+
const isDynamicEndpoint = !!resolved.endpoint
|
|
30
|
+
|
|
31
|
+
// Ensure cache is filled (via list) — skip for dynamic endpoints
|
|
32
|
+
// since list() disables caching when resolveStorage provides an endpoint
|
|
33
|
+
const cache = this._cache
|
|
34
|
+
if (!isDynamicEndpoint && !cache.valid && this.isCacheEnabled) {
|
|
35
|
+
await this.list(
|
|
36
|
+
{ page_size: this.effectiveThreshold },
|
|
37
|
+
routingContext ?? undefined
|
|
38
|
+
)
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
let result: ListResult<any>
|
|
42
|
+
|
|
43
|
+
// Dynamic endpoint, overflow, or cache disabled → use API directly
|
|
44
|
+
if (isDynamicEndpoint || this.overflow || !this.isCacheEnabled) {
|
|
45
|
+
result = await this.list(params, routingContext ?? undefined)
|
|
46
|
+
} else {
|
|
47
|
+
// Full cache available - filter locally
|
|
48
|
+
const filtered = this._filterLocally(cache.items, params)
|
|
49
|
+
result = { ...filtered, fromCache: true }
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
// Call hook if defined (for context-based customization)
|
|
53
|
+
if (this.onQueryResult) {
|
|
54
|
+
result = this.onQueryResult(result, context) || result
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
return result
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Build MongoDB-like query from filters object
|
|
62
|
+
* @internal
|
|
63
|
+
*/
|
|
64
|
+
proto._buildQuery = function (
|
|
65
|
+
this: Self,
|
|
66
|
+
filters: Record<string, unknown>
|
|
67
|
+
): QueryObject {
|
|
68
|
+
const query: QueryObject = {}
|
|
69
|
+
for (const [field, value] of Object.entries(filters)) {
|
|
70
|
+
if (value === null || value === undefined || value === '') continue
|
|
71
|
+
// Cast value to QueryCondition - QueryExecutor handles type validation
|
|
72
|
+
query[field] = value as QueryObject[string]
|
|
73
|
+
}
|
|
74
|
+
return query
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* Apply filters, search, sort and pagination locally
|
|
79
|
+
* @internal
|
|
80
|
+
*/
|
|
81
|
+
proto._filterLocally = function (
|
|
82
|
+
this: Self,
|
|
83
|
+
items: any[],
|
|
84
|
+
params: ListParams & { searchFields?: string[] | null } = {}
|
|
85
|
+
): { items: any[]; total: number } {
|
|
86
|
+
const {
|
|
87
|
+
search = '',
|
|
88
|
+
searchFields: overrideSearchFields = null,
|
|
89
|
+
filters = {},
|
|
90
|
+
sort_by = null,
|
|
91
|
+
sort_order = 'asc',
|
|
92
|
+
page = 1,
|
|
93
|
+
page_size = 20,
|
|
94
|
+
} = params
|
|
95
|
+
|
|
96
|
+
let result = [...items]
|
|
97
|
+
|
|
98
|
+
// Apply search
|
|
99
|
+
if (search) {
|
|
100
|
+
const searchLower = search.toLowerCase()
|
|
101
|
+
const searchFields = overrideSearchFields ?? this.storageSearchFields
|
|
102
|
+
|
|
103
|
+
result = result.filter((item: any) => {
|
|
104
|
+
if (searchFields) {
|
|
105
|
+
const { ownFields, parentFields } =
|
|
106
|
+
this._parseSearchFields(searchFields)
|
|
107
|
+
|
|
108
|
+
// Search own fields
|
|
109
|
+
for (const field of ownFields) {
|
|
110
|
+
const value = item[field]
|
|
111
|
+
if (
|
|
112
|
+
typeof value === 'string' &&
|
|
113
|
+
value.toLowerCase().includes(searchLower)
|
|
114
|
+
) {
|
|
115
|
+
return true
|
|
116
|
+
}
|
|
117
|
+
if (
|
|
118
|
+
typeof value === 'number' &&
|
|
119
|
+
value.toString().includes(search)
|
|
120
|
+
) {
|
|
121
|
+
return true
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
// Search cached parent fields (in item._search)
|
|
126
|
+
const itemWithSearch = item as {
|
|
127
|
+
_search?: Record<string, string>
|
|
128
|
+
}
|
|
129
|
+
if (itemWithSearch._search) {
|
|
130
|
+
for (const [parentKey, fields] of Object.entries(parentFields)) {
|
|
131
|
+
for (const field of fields) {
|
|
132
|
+
const key = `${parentKey}.${field}`
|
|
133
|
+
const value = itemWithSearch._search[key]
|
|
134
|
+
// _search values are always strings
|
|
135
|
+
if (value && value.toLowerCase().includes(searchLower)) {
|
|
136
|
+
return true
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
return false
|
|
143
|
+
} else {
|
|
144
|
+
// No searchFields declared: search all string/number fields
|
|
145
|
+
return Object.values(item).some((value: unknown) => {
|
|
146
|
+
if (typeof value === 'string') {
|
|
147
|
+
return value.toLowerCase().includes(searchLower)
|
|
148
|
+
}
|
|
149
|
+
if (typeof value === 'number') {
|
|
150
|
+
return value.toString().includes(search)
|
|
151
|
+
}
|
|
152
|
+
return false
|
|
153
|
+
})
|
|
154
|
+
}
|
|
155
|
+
})
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
// Build query and apply filters using QueryExecutor
|
|
159
|
+
const queryObj = this._buildQuery(filters)
|
|
160
|
+
if (Object.keys(queryObj).length > 0) {
|
|
161
|
+
const { items: filtered } = QueryExecutor.execute(result, queryObj)
|
|
162
|
+
result = filtered as any[]
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
// Total after filtering (before pagination)
|
|
166
|
+
const total = result.length
|
|
167
|
+
|
|
168
|
+
// Apply sort (QueryExecutor does not sort)
|
|
169
|
+
if (sort_by) {
|
|
170
|
+
result.sort((a: any, b: any) => {
|
|
171
|
+
const aVal = a[sort_by]
|
|
172
|
+
const bVal = b[sort_by]
|
|
173
|
+
|
|
174
|
+
// Handle nulls
|
|
175
|
+
if (aVal == null && bVal == null) return 0
|
|
176
|
+
if (aVal == null) return sort_order === 'asc' ? 1 : -1
|
|
177
|
+
if (bVal == null) return sort_order === 'asc' ? -1 : 1
|
|
178
|
+
|
|
179
|
+
// Compare
|
|
180
|
+
if (typeof aVal === 'string' && typeof bVal === 'string') {
|
|
181
|
+
const cmp = aVal.localeCompare(bVal)
|
|
182
|
+
return sort_order === 'asc' ? cmp : -cmp
|
|
183
|
+
}
|
|
184
|
+
const cmp = aVal < bVal ? -1 : aVal > bVal ? 1 : 0
|
|
185
|
+
return sort_order === 'asc' ? cmp : -cmp
|
|
186
|
+
})
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
// Apply pagination
|
|
190
|
+
const start = (page - 1) * page_size
|
|
191
|
+
const paged = result.slice(start, start + page_size)
|
|
192
|
+
|
|
193
|
+
return { items: paged, total }
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
/**
|
|
197
|
+
* Get operation stats (for debug panel)
|
|
198
|
+
*/
|
|
199
|
+
proto.getStats = function (this: Self): OperationStats {
|
|
200
|
+
return { ...this._stats }
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
/**
|
|
204
|
+
* Reset operation stats
|
|
205
|
+
*/
|
|
206
|
+
proto.resetStats = function (this: Self): void {
|
|
207
|
+
this._stats = {
|
|
208
|
+
list: 0,
|
|
209
|
+
get: 0,
|
|
210
|
+
create: 0,
|
|
211
|
+
update: 0,
|
|
212
|
+
delete: 0,
|
|
213
|
+
cacheHits: 0,
|
|
214
|
+
cacheMisses: 0,
|
|
215
|
+
detailCacheHits: 0,
|
|
216
|
+
detailCacheMisses: 0,
|
|
217
|
+
maxItemsSeen: 0,
|
|
218
|
+
maxTotal: 0,
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
}
|
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
import type { EntityRecord, ListParams, ListResult, ChildConfig, ParentConfig } from '../types'
|
|
2
|
+
import type { EntityManager } from './EntityManager'
|
|
3
|
+
import type { EntityManagerInternal } from './EntityManager.types'
|
|
4
|
+
|
|
5
|
+
type Self = EntityManagerInternal<any>
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Patch EntityManager prototype with relation-related methods.
|
|
9
|
+
*/
|
|
10
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
11
|
+
export function applyRelationsMethods(EntityManagerClass: { prototype: any }): void {
|
|
12
|
+
const proto = EntityManagerClass.prototype as Self
|
|
13
|
+
|
|
14
|
+
proto.getChildConfig = function (this: Self, childName: string): ChildConfig | undefined {
|
|
15
|
+
return this._children[childName]
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
proto.getChildNames = function (this: Self): string[] {
|
|
19
|
+
return Object.keys(this._children)
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
proto.getParentConfig = function (this: Self): ParentConfig | null {
|
|
23
|
+
return this._parent
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
proto.listChildren = async function (
|
|
27
|
+
this: Self,
|
|
28
|
+
parentId: string | number,
|
|
29
|
+
childName: string,
|
|
30
|
+
params: ListParams = {}
|
|
31
|
+
): Promise<ListResult<EntityRecord>> {
|
|
32
|
+
const childConfig = this._children[childName]
|
|
33
|
+
if (!childConfig) {
|
|
34
|
+
throw new Error(
|
|
35
|
+
`[EntityManager:${this.name}] Unknown child relation "${childName}"`
|
|
36
|
+
)
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
// Build endpoint path
|
|
40
|
+
const childEndpoint = childConfig.endpoint || `${parentId}/${childName}`
|
|
41
|
+
|
|
42
|
+
if (this.storage?.request) {
|
|
43
|
+
return this.storage.request('GET', childEndpoint, {
|
|
44
|
+
params,
|
|
45
|
+
}) as Promise<ListResult<EntityRecord>>
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
throw new Error(
|
|
49
|
+
`[EntityManager:${this.name}] listChildren() requires storage with request()`
|
|
50
|
+
)
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
proto.getChild = async function (
|
|
54
|
+
this: Self,
|
|
55
|
+
parentId: string | number,
|
|
56
|
+
childName: string,
|
|
57
|
+
childId: string | number
|
|
58
|
+
): Promise<EntityRecord> {
|
|
59
|
+
const childConfig = this._children[childName]
|
|
60
|
+
if (!childConfig) {
|
|
61
|
+
throw new Error(
|
|
62
|
+
`[EntityManager:${this.name}] Unknown child relation "${childName}"`
|
|
63
|
+
)
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
const childEndpoint = childConfig.endpoint || `${parentId}/${childName}`
|
|
67
|
+
|
|
68
|
+
if (this.storage?.request) {
|
|
69
|
+
return this.storage.request(
|
|
70
|
+
'GET',
|
|
71
|
+
`${childEndpoint}/${childId}`
|
|
72
|
+
) as Promise<EntityRecord>
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
throw new Error(
|
|
76
|
+
`[EntityManager:${this.name}] getChild() requires storage with request()`
|
|
77
|
+
)
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
proto.createChild = async function (
|
|
81
|
+
this: Self,
|
|
82
|
+
parentId: string | number,
|
|
83
|
+
childName: string,
|
|
84
|
+
data: Record<string, unknown>
|
|
85
|
+
): Promise<EntityRecord> {
|
|
86
|
+
const childConfig = this._children[childName]
|
|
87
|
+
if (!childConfig) {
|
|
88
|
+
throw new Error(
|
|
89
|
+
`[EntityManager:${this.name}] Unknown child relation "${childName}"`
|
|
90
|
+
)
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
const childEndpoint = childConfig.endpoint || `${parentId}/${childName}`
|
|
94
|
+
|
|
95
|
+
if (this.storage?.request) {
|
|
96
|
+
return this.storage.request('POST', childEndpoint, {
|
|
97
|
+
data,
|
|
98
|
+
}) as Promise<EntityRecord>
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
throw new Error(
|
|
102
|
+
`[EntityManager:${this.name}] createChild() requires storage with request()`
|
|
103
|
+
)
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
proto.deleteChild = async function (
|
|
107
|
+
this: Self,
|
|
108
|
+
parentId: string | number,
|
|
109
|
+
childName: string,
|
|
110
|
+
childId: string | number
|
|
111
|
+
): Promise<void> {
|
|
112
|
+
const childConfig = this._children[childName]
|
|
113
|
+
if (!childConfig) {
|
|
114
|
+
throw new Error(
|
|
115
|
+
`[EntityManager:${this.name}] Unknown child relation "${childName}"`
|
|
116
|
+
)
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
const childEndpoint = childConfig.endpoint || `${parentId}/${childName}`
|
|
120
|
+
|
|
121
|
+
if (this.storage?.request) {
|
|
122
|
+
await this.storage.request('DELETE', `${childEndpoint}/${childId}`)
|
|
123
|
+
return
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
throw new Error(
|
|
127
|
+
`[EntityManager:${this.name}] deleteChild() requires storage with request()`
|
|
128
|
+
)
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
proto.getParentManager = function (this: Self): EntityManager<EntityRecord> | null {
|
|
132
|
+
const parent = this._parent
|
|
133
|
+
const orchestrator = this._orchestrator
|
|
134
|
+
if (!parent || !orchestrator) return null
|
|
135
|
+
return (
|
|
136
|
+
orchestrator.get<EntityRecord>(parent.entity) ?? null
|
|
137
|
+
)
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
proto.getChildManager = function (
|
|
141
|
+
this: Self,
|
|
142
|
+
childName: string
|
|
143
|
+
): EntityManager<EntityRecord> | null {
|
|
144
|
+
const childConfig = this._children[childName]
|
|
145
|
+
const orchestrator = this._orchestrator
|
|
146
|
+
if (!childConfig || !orchestrator) return null
|
|
147
|
+
return (
|
|
148
|
+
orchestrator.get<EntityRecord>(childConfig.entity) ?? null
|
|
149
|
+
)
|
|
150
|
+
}
|
|
151
|
+
}
|