@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,189 @@
|
|
|
1
|
+
import { describe, it, expect, vi } from 'vitest'
|
|
2
|
+
import {
|
|
3
|
+
managerFactory,
|
|
4
|
+
defaultManagerResolver,
|
|
5
|
+
createManagerFactory,
|
|
6
|
+
createManagers
|
|
7
|
+
} from './factory'
|
|
8
|
+
import { EntityManager } from './EntityManager'
|
|
9
|
+
import { ApiStorage } from './storage/ApiStorage'
|
|
10
|
+
import { MemoryStorage } from './storage/MemoryStorage'
|
|
11
|
+
|
|
12
|
+
describe('defaultManagerResolver', () => {
|
|
13
|
+
it('creates EntityManager with config', () => {
|
|
14
|
+
const storage = new MemoryStorage()
|
|
15
|
+
const config = { storage, label: 'Bot' }
|
|
16
|
+
|
|
17
|
+
const manager = defaultManagerResolver(config, 'bots')
|
|
18
|
+
|
|
19
|
+
expect(manager).toBeInstanceOf(EntityManager)
|
|
20
|
+
expect(manager.name).toBe('bots')
|
|
21
|
+
expect(manager.storage).toBe(storage)
|
|
22
|
+
})
|
|
23
|
+
|
|
24
|
+
it('uses registered manager class from registry', () => {
|
|
25
|
+
class CustomManager extends EntityManager {}
|
|
26
|
+
const storage = new MemoryStorage()
|
|
27
|
+
const config = { storage }
|
|
28
|
+
const context = { managerRegistry: { bots: CustomManager } }
|
|
29
|
+
|
|
30
|
+
const manager = defaultManagerResolver(config, 'bots', context)
|
|
31
|
+
|
|
32
|
+
expect(manager).toBeInstanceOf(CustomManager)
|
|
33
|
+
})
|
|
34
|
+
|
|
35
|
+
it('falls back to EntityManager when not in registry', () => {
|
|
36
|
+
const storage = new MemoryStorage()
|
|
37
|
+
const config = { storage }
|
|
38
|
+
const context = { managerRegistry: { other: class extends EntityManager {} } }
|
|
39
|
+
|
|
40
|
+
const manager = defaultManagerResolver(config, 'bots', context)
|
|
41
|
+
|
|
42
|
+
expect(manager).toBeInstanceOf(EntityManager)
|
|
43
|
+
expect(manager.constructor).toBe(EntityManager)
|
|
44
|
+
})
|
|
45
|
+
})
|
|
46
|
+
|
|
47
|
+
describe('managerFactory', () => {
|
|
48
|
+
it('returns EntityManager instance directly', () => {
|
|
49
|
+
const manager = new EntityManager({ name: 'test', storage: new MemoryStorage() })
|
|
50
|
+
const result = managerFactory(manager, 'test')
|
|
51
|
+
expect(result).toBe(manager)
|
|
52
|
+
})
|
|
53
|
+
|
|
54
|
+
it('returns duck-typed manager directly', () => {
|
|
55
|
+
const duckManager = {
|
|
56
|
+
storage: new MemoryStorage(),
|
|
57
|
+
list: () => Promise.resolve({ items: [], total: 0 }),
|
|
58
|
+
get: () => Promise.resolve(null)
|
|
59
|
+
}
|
|
60
|
+
const result = managerFactory(duckManager, 'test')
|
|
61
|
+
expect(result).toBe(duckManager)
|
|
62
|
+
})
|
|
63
|
+
|
|
64
|
+
it('creates manager from string pattern', () => {
|
|
65
|
+
const result = managerFactory('api:/api/bots', 'bots')
|
|
66
|
+
expect(result).toBeInstanceOf(EntityManager)
|
|
67
|
+
expect(result.storage).toBeInstanceOf(ApiStorage)
|
|
68
|
+
expect(result.storage.endpoint).toBe('/api/bots')
|
|
69
|
+
})
|
|
70
|
+
|
|
71
|
+
it('creates manager from config object with storage string', () => {
|
|
72
|
+
const result = managerFactory({
|
|
73
|
+
storage: 'api:/api/tasks',
|
|
74
|
+
label: 'Task'
|
|
75
|
+
}, 'tasks')
|
|
76
|
+
|
|
77
|
+
expect(result).toBeInstanceOf(EntityManager)
|
|
78
|
+
expect(result.storage).toBeInstanceOf(ApiStorage)
|
|
79
|
+
})
|
|
80
|
+
|
|
81
|
+
it('uses storage instance directly from config', () => {
|
|
82
|
+
const storage = new MemoryStorage()
|
|
83
|
+
const result = managerFactory({ storage }, 'items')
|
|
84
|
+
|
|
85
|
+
expect(result).toBeInstanceOf(EntityManager)
|
|
86
|
+
expect(result.storage).toBe(storage)
|
|
87
|
+
})
|
|
88
|
+
|
|
89
|
+
it('uses custom storageResolver', () => {
|
|
90
|
+
const customStorage = new MemoryStorage()
|
|
91
|
+
const storageResolver = vi.fn().mockReturnValue(customStorage)
|
|
92
|
+
const context = { storageResolver }
|
|
93
|
+
|
|
94
|
+
const result = managerFactory('api:/test', 'test', context)
|
|
95
|
+
|
|
96
|
+
expect(storageResolver).toHaveBeenCalled()
|
|
97
|
+
expect(result.storage).toBe(customStorage)
|
|
98
|
+
})
|
|
99
|
+
|
|
100
|
+
it('uses custom managerResolver', () => {
|
|
101
|
+
class CustomManager extends EntityManager {}
|
|
102
|
+
const managerResolver = vi.fn().mockImplementation((config, name) => {
|
|
103
|
+
return new CustomManager({ name, ...config })
|
|
104
|
+
})
|
|
105
|
+
const context = { managerResolver }
|
|
106
|
+
|
|
107
|
+
const result = managerFactory('api:/test', 'test', context)
|
|
108
|
+
|
|
109
|
+
expect(managerResolver).toHaveBeenCalled()
|
|
110
|
+
expect(result).toBeInstanceOf(CustomManager)
|
|
111
|
+
})
|
|
112
|
+
|
|
113
|
+
it('uses managerRegistry for generated classes', () => {
|
|
114
|
+
class BotManager extends EntityManager {}
|
|
115
|
+
const context = { managerRegistry: { bots: BotManager } }
|
|
116
|
+
|
|
117
|
+
const result = managerFactory('api:/api/bots', 'bots', context)
|
|
118
|
+
|
|
119
|
+
expect(result).toBeInstanceOf(BotManager)
|
|
120
|
+
})
|
|
121
|
+
|
|
122
|
+
it('throws for invalid config', () => {
|
|
123
|
+
expect(() => managerFactory(123, 'test'))
|
|
124
|
+
.toThrow('Invalid manager config')
|
|
125
|
+
})
|
|
126
|
+
})
|
|
127
|
+
|
|
128
|
+
describe('createManagerFactory', () => {
|
|
129
|
+
it('creates factory with bound context', () => {
|
|
130
|
+
class CustomManager extends EntityManager {}
|
|
131
|
+
const context = { managerRegistry: { bots: CustomManager } }
|
|
132
|
+
const factory = createManagerFactory(context)
|
|
133
|
+
|
|
134
|
+
const result = factory('api:/api/bots', 'bots')
|
|
135
|
+
|
|
136
|
+
expect(result).toBeInstanceOf(CustomManager)
|
|
137
|
+
})
|
|
138
|
+
})
|
|
139
|
+
|
|
140
|
+
describe('createManagers', () => {
|
|
141
|
+
it('creates managers from config object', () => {
|
|
142
|
+
const config = {
|
|
143
|
+
bots: 'api:/api/bots',
|
|
144
|
+
tasks: { storage: 'memory:tasks', label: 'Task' }
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
const managers = createManagers(config)
|
|
148
|
+
|
|
149
|
+
expect(managers.bots).toBeInstanceOf(EntityManager)
|
|
150
|
+
expect(managers.bots.storage).toBeInstanceOf(ApiStorage)
|
|
151
|
+
expect(managers.tasks).toBeInstanceOf(EntityManager)
|
|
152
|
+
expect(managers.tasks.storage).toBeInstanceOf(MemoryStorage)
|
|
153
|
+
})
|
|
154
|
+
|
|
155
|
+
it('uses context for all managers', () => {
|
|
156
|
+
class BotManager extends EntityManager {}
|
|
157
|
+
class TaskManager extends EntityManager {}
|
|
158
|
+
const context = {
|
|
159
|
+
managerRegistry: { bots: BotManager, tasks: TaskManager }
|
|
160
|
+
}
|
|
161
|
+
const config = {
|
|
162
|
+
bots: 'api:/api/bots',
|
|
163
|
+
tasks: 'api:/api/tasks'
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
const managers = createManagers(config, context)
|
|
167
|
+
|
|
168
|
+
expect(managers.bots).toBeInstanceOf(BotManager)
|
|
169
|
+
expect(managers.tasks).toBeInstanceOf(TaskManager)
|
|
170
|
+
})
|
|
171
|
+
|
|
172
|
+
it('passes through existing manager instances', () => {
|
|
173
|
+
const existingManager = new EntityManager({ name: 'existing', storage: new MemoryStorage() })
|
|
174
|
+
const config = {
|
|
175
|
+
bots: 'api:/api/bots',
|
|
176
|
+
existing: existingManager
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
const managers = createManagers(config)
|
|
180
|
+
|
|
181
|
+
expect(managers.existing).toBe(existingManager)
|
|
182
|
+
expect(managers.bots).toBeInstanceOf(EntityManager)
|
|
183
|
+
})
|
|
184
|
+
|
|
185
|
+
it('returns empty object for empty config', () => {
|
|
186
|
+
const managers = createManagers({})
|
|
187
|
+
expect(managers).toEqual({})
|
|
188
|
+
})
|
|
189
|
+
})
|
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Manager Factory/Resolver Pattern
|
|
3
|
+
*
|
|
4
|
+
* Enables declarative manager configuration with auto-resolution.
|
|
5
|
+
* Works with storageFactory for complete auto-wiring.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
/* eslint-disable @typescript-eslint/no-explicit-any */
|
|
9
|
+
|
|
10
|
+
import { EntityManager } from './EntityManager'
|
|
11
|
+
import { storageFactory } from './storage/factory'
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Manager config interface
|
|
15
|
+
*/
|
|
16
|
+
export interface ManagerConfig {
|
|
17
|
+
name?: string
|
|
18
|
+
storage?: any
|
|
19
|
+
label?: string
|
|
20
|
+
labelPlural?: string
|
|
21
|
+
fields?: Record<string, unknown>
|
|
22
|
+
[key: string]: unknown
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Manager factory context
|
|
27
|
+
*/
|
|
28
|
+
export interface ManagerFactoryContext {
|
|
29
|
+
managerRegistry?: Record<string, typeof EntityManager>
|
|
30
|
+
storageResolver?: (config: any, entityName: string) => any
|
|
31
|
+
managerResolver?: ManagerResolver
|
|
32
|
+
[key: string]: unknown
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Manager resolver function type
|
|
37
|
+
*/
|
|
38
|
+
export type ManagerResolver = (
|
|
39
|
+
config: ManagerConfig,
|
|
40
|
+
entityName: string,
|
|
41
|
+
context: ManagerFactoryContext
|
|
42
|
+
) => EntityManager
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Default manager resolver - creates manager instance from config
|
|
46
|
+
*/
|
|
47
|
+
export function defaultManagerResolver(
|
|
48
|
+
config: ManagerConfig,
|
|
49
|
+
entityName: string,
|
|
50
|
+
context: ManagerFactoryContext = {}
|
|
51
|
+
): EntityManager {
|
|
52
|
+
const { managerRegistry = {} } = context
|
|
53
|
+
|
|
54
|
+
// Look up registered manager class
|
|
55
|
+
const ManagerClass = managerRegistry[entityName] || EntityManager
|
|
56
|
+
|
|
57
|
+
// Ensure name is set
|
|
58
|
+
const managerConfig = {
|
|
59
|
+
name: entityName,
|
|
60
|
+
...config
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
return new ManagerClass(managerConfig as any)
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Manager factory - normalizes input and delegates to resolver
|
|
68
|
+
*/
|
|
69
|
+
export function managerFactory(
|
|
70
|
+
config: EntityManager | string | ManagerConfig,
|
|
71
|
+
entityName: string,
|
|
72
|
+
context: ManagerFactoryContext = {}
|
|
73
|
+
): EntityManager {
|
|
74
|
+
const {
|
|
75
|
+
storageResolver,
|
|
76
|
+
managerResolver = defaultManagerResolver
|
|
77
|
+
} = context
|
|
78
|
+
|
|
79
|
+
// Already a Manager instance → return directly
|
|
80
|
+
if (config instanceof EntityManager) {
|
|
81
|
+
return config
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
// Also check for duck-typed manager
|
|
85
|
+
if (
|
|
86
|
+
config &&
|
|
87
|
+
typeof config === 'object' &&
|
|
88
|
+
'storage' in config &&
|
|
89
|
+
typeof (config as any).list === 'function'
|
|
90
|
+
) {
|
|
91
|
+
return config as any
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
// String pattern → treat as storage config
|
|
95
|
+
if (typeof config === 'string') {
|
|
96
|
+
const storage = storageFactory(config, entityName, storageResolver as any)
|
|
97
|
+
return managerResolver({ storage }, entityName, context)
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
// Config object → resolve storage first, then manager
|
|
101
|
+
if (config && typeof config === 'object') {
|
|
102
|
+
const resolvedConfig = { ...config }
|
|
103
|
+
|
|
104
|
+
// Resolve storage if provided as string/config
|
|
105
|
+
if (config.storage && typeof config.storage === 'string') {
|
|
106
|
+
resolvedConfig.storage = storageFactory(config.storage, entityName, storageResolver as any)
|
|
107
|
+
} else if (
|
|
108
|
+
config.storage &&
|
|
109
|
+
typeof config.storage === 'object' &&
|
|
110
|
+
typeof (config.storage as any).list !== 'function'
|
|
111
|
+
) {
|
|
112
|
+
resolvedConfig.storage = storageFactory(config.storage as any, entityName, storageResolver as any)
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
return managerResolver(resolvedConfig, entityName, context)
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
throw new Error(
|
|
119
|
+
`Invalid manager config for "${entityName}": ${typeof config}. Expected string, object, or Manager instance.`
|
|
120
|
+
)
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
/**
|
|
124
|
+
* Create a custom manager factory with context
|
|
125
|
+
*/
|
|
126
|
+
export function createManagerFactory(
|
|
127
|
+
context: ManagerFactoryContext
|
|
128
|
+
): (config: EntityManager | string | ManagerConfig, entityName: string) => EntityManager {
|
|
129
|
+
return (config, entityName) => managerFactory(config, entityName, context)
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
/**
|
|
133
|
+
* Create all managers from a config object
|
|
134
|
+
*/
|
|
135
|
+
export function createManagers(
|
|
136
|
+
managersConfig: Record<string, EntityManager | string | ManagerConfig>,
|
|
137
|
+
context: ManagerFactoryContext = {}
|
|
138
|
+
): Record<string, EntityManager> {
|
|
139
|
+
const managers: Record<string, EntityManager> = {}
|
|
140
|
+
|
|
141
|
+
for (const [entityName, config] of Object.entries(managersConfig)) {
|
|
142
|
+
managers[entityName] = managerFactory(config, entityName, context)
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
return managers
|
|
146
|
+
}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Entity Module
|
|
3
|
+
*
|
|
4
|
+
* EntityManager and storage adapters for data abstraction.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
// EntityManager
|
|
8
|
+
export {
|
|
9
|
+
EntityManager,
|
|
10
|
+
createEntityManager,
|
|
11
|
+
type EntityManagerOptions,
|
|
12
|
+
type RoutingContext,
|
|
13
|
+
type PresaveContext,
|
|
14
|
+
type PostsaveContext,
|
|
15
|
+
type PredeleteContext,
|
|
16
|
+
type QueryOptions,
|
|
17
|
+
type WhitelistContext,
|
|
18
|
+
type CacheInfo
|
|
19
|
+
} from './EntityManager'
|
|
20
|
+
|
|
21
|
+
// Re-export types from types module
|
|
22
|
+
export type {
|
|
23
|
+
EntityRecord,
|
|
24
|
+
ListParams,
|
|
25
|
+
ListResult,
|
|
26
|
+
FieldConfig,
|
|
27
|
+
ChildConfig,
|
|
28
|
+
ParentConfig,
|
|
29
|
+
RelationConfig,
|
|
30
|
+
NavConfig,
|
|
31
|
+
StorageCapabilities
|
|
32
|
+
} from '../types'
|
|
33
|
+
|
|
34
|
+
// Manager Factory
|
|
35
|
+
export {
|
|
36
|
+
managerFactory,
|
|
37
|
+
defaultManagerResolver,
|
|
38
|
+
createManagerFactory,
|
|
39
|
+
createManagers,
|
|
40
|
+
type ManagerConfig,
|
|
41
|
+
type ManagerFactoryContext,
|
|
42
|
+
type ManagerResolver
|
|
43
|
+
} from './factory'
|
|
44
|
+
|
|
45
|
+
// Storage adapters
|
|
46
|
+
export * from './storage/index'
|
|
47
|
+
|
|
48
|
+
// Auth adapters
|
|
49
|
+
export * from './auth'
|
|
@@ -0,0 +1,228 @@
|
|
|
1
|
+
import { IStorage } from './IStorage'
|
|
2
|
+
import type { EntityRecord, ListParams, ListResult, StorageCapabilities } from '../../types'
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* HTTP client interface compatible with axios
|
|
6
|
+
*/
|
|
7
|
+
export interface HttpClient {
|
|
8
|
+
get<T = unknown>(url: string, config?: { params?: Record<string, unknown> }): Promise<{ data: T }>
|
|
9
|
+
post<T = unknown>(url: string, data?: unknown): Promise<{ data: T }>
|
|
10
|
+
put<T = unknown>(url: string, data?: unknown): Promise<{ data: T }>
|
|
11
|
+
patch<T = unknown>(url: string, data?: unknown): Promise<{ data: T }>
|
|
12
|
+
delete<T = unknown>(url: string): Promise<{ data: T }>
|
|
13
|
+
request<T = unknown>(config: {
|
|
14
|
+
method: string
|
|
15
|
+
url: string
|
|
16
|
+
data?: unknown
|
|
17
|
+
params?: Record<string, unknown>
|
|
18
|
+
headers?: Record<string, string>
|
|
19
|
+
}): Promise<{ data: T }>
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Routing context for normalize functions
|
|
24
|
+
*/
|
|
25
|
+
export interface RoutingContext {
|
|
26
|
+
parentChain?: Array<{ entity: string; id: string }>
|
|
27
|
+
path?: string
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* ApiStorage options
|
|
32
|
+
*/
|
|
33
|
+
export interface ApiStorageOptions<T extends EntityRecord = EntityRecord> {
|
|
34
|
+
endpoint: string
|
|
35
|
+
client?: HttpClient | null
|
|
36
|
+
getClient?: (() => HttpClient) | null
|
|
37
|
+
responseItemsKey?: string
|
|
38
|
+
responseTotalKey?: string
|
|
39
|
+
paramMapping?: Record<string, string>
|
|
40
|
+
normalize?: ((data: T, context?: RoutingContext | null) => T) | null
|
|
41
|
+
denormalize?: ((data: Partial<T>) => Partial<T>) | null
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* ApiStorage - REST API storage adapter
|
|
46
|
+
*
|
|
47
|
+
* Implements the storage interface for REST APIs.
|
|
48
|
+
* Expects standard response format: { items: [], total: number, page: number }
|
|
49
|
+
*/
|
|
50
|
+
export class ApiStorage<T extends EntityRecord = EntityRecord> extends IStorage<T> {
|
|
51
|
+
static storageName = 'ApiStorage'
|
|
52
|
+
|
|
53
|
+
static capabilities: StorageCapabilities = {
|
|
54
|
+
supportsTotal: true,
|
|
55
|
+
supportsFilters: true,
|
|
56
|
+
supportsPagination: true,
|
|
57
|
+
supportsCaching: true,
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/** @deprecated Use static ApiStorage.capabilities.supportsCaching instead */
|
|
61
|
+
get supportsCaching(): boolean {
|
|
62
|
+
return ApiStorage.capabilities.supportsCaching
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
readonly endpoint: string
|
|
66
|
+
readonly responseItemsKey: string
|
|
67
|
+
readonly responseTotalKey: string
|
|
68
|
+
readonly paramMapping: Record<string, string>
|
|
69
|
+
|
|
70
|
+
protected _client: HttpClient | null
|
|
71
|
+
protected _getClient: (() => HttpClient) | null
|
|
72
|
+
protected _normalize: ((data: T, context?: RoutingContext | null) => T) | null
|
|
73
|
+
protected _denormalize: ((data: Partial<T>) => Partial<T>) | null
|
|
74
|
+
|
|
75
|
+
constructor(options: ApiStorageOptions<T>) {
|
|
76
|
+
super()
|
|
77
|
+
const {
|
|
78
|
+
endpoint,
|
|
79
|
+
client = null,
|
|
80
|
+
getClient = null,
|
|
81
|
+
responseItemsKey = 'items',
|
|
82
|
+
responseTotalKey = 'total',
|
|
83
|
+
paramMapping = {},
|
|
84
|
+
normalize = null,
|
|
85
|
+
denormalize = null,
|
|
86
|
+
} = options
|
|
87
|
+
|
|
88
|
+
this.endpoint = endpoint
|
|
89
|
+
this._client = client
|
|
90
|
+
this._getClient = getClient
|
|
91
|
+
this.responseItemsKey = responseItemsKey
|
|
92
|
+
this.responseTotalKey = responseTotalKey
|
|
93
|
+
this.paramMapping = paramMapping
|
|
94
|
+
this._normalize = normalize
|
|
95
|
+
this._denormalize = denormalize
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* Apply parameter mapping to transform filter names
|
|
100
|
+
*/
|
|
101
|
+
protected _applyParamMapping(params: Record<string, unknown>): Record<string, unknown> {
|
|
102
|
+
if (!this.paramMapping || Object.keys(this.paramMapping).length === 0) {
|
|
103
|
+
return params
|
|
104
|
+
}
|
|
105
|
+
const mapped: Record<string, unknown> = {}
|
|
106
|
+
for (const [key, value] of Object.entries(params)) {
|
|
107
|
+
const mappedKey = this.paramMapping[key] || key
|
|
108
|
+
mapped[mappedKey] = value
|
|
109
|
+
}
|
|
110
|
+
return mapped
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* Normalize API response data to internal format
|
|
115
|
+
*/
|
|
116
|
+
protected _normalizeData(data: T | T[], context: RoutingContext | null = null): T | T[] {
|
|
117
|
+
if (!this._normalize) return data
|
|
118
|
+
if (Array.isArray(data)) {
|
|
119
|
+
return data.map((item) => this._normalize!(item, context))
|
|
120
|
+
}
|
|
121
|
+
return this._normalize(data, context)
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
/**
|
|
125
|
+
* Denormalize internal data to API format
|
|
126
|
+
*/
|
|
127
|
+
protected _denormalizeData(data: Partial<T>): Partial<T> {
|
|
128
|
+
if (!this._denormalize) return data
|
|
129
|
+
return this._denormalize(data)
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
get client(): HttpClient {
|
|
133
|
+
if (this._getClient) {
|
|
134
|
+
return this._getClient()
|
|
135
|
+
}
|
|
136
|
+
if (!this._client) {
|
|
137
|
+
throw new Error('ApiStorage: No HTTP client configured')
|
|
138
|
+
}
|
|
139
|
+
return this._client
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
set client(value: HttpClient) {
|
|
143
|
+
this._client = value
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
async list(params: ListParams = {}, context: RoutingContext | null = null): Promise<ListResult<T>> {
|
|
147
|
+
const { page = 1, page_size = 20, sort_by, sort_order, filters = {} } = params
|
|
148
|
+
|
|
149
|
+
const mappedFilters = this._applyParamMapping(filters)
|
|
150
|
+
|
|
151
|
+
const response = await this.client.get<Record<string, unknown>>(this.endpoint, {
|
|
152
|
+
params: { page, page_size, sort_by, sort_order, ...mappedFilters },
|
|
153
|
+
})
|
|
154
|
+
|
|
155
|
+
const data = response.data
|
|
156
|
+
const rawItems = (data[this.responseItemsKey] || data.items || data) as T[]
|
|
157
|
+
const items = this._normalizeData(rawItems, context) as T[]
|
|
158
|
+
|
|
159
|
+
return {
|
|
160
|
+
items,
|
|
161
|
+
total:
|
|
162
|
+
(data[this.responseTotalKey] as number) ||
|
|
163
|
+
(data.total as number) ||
|
|
164
|
+
(Array.isArray(data) ? data.length : 0),
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
async get(id: string | number, context: RoutingContext | null = null): Promise<T> {
|
|
169
|
+
const response = await this.client.get<T>(`${this.endpoint}/${id}`)
|
|
170
|
+
return this._normalizeData(response.data, context) as T
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
async create(data: Partial<T>): Promise<T> {
|
|
174
|
+
const apiData = this._denormalizeData(data)
|
|
175
|
+
const response = await this.client.post<T>(this.endpoint, apiData)
|
|
176
|
+
return this._normalizeData(response.data) as T
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
async update(id: string | number, data: Partial<T>): Promise<T> {
|
|
180
|
+
const apiData = this._denormalizeData(data)
|
|
181
|
+
const response = await this.client.put<T>(`${this.endpoint}/${id}`, apiData)
|
|
182
|
+
return this._normalizeData(response.data) as T
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
async patch(id: string | number, data: Partial<T>): Promise<T> {
|
|
186
|
+
const apiData = this._denormalizeData(data)
|
|
187
|
+
const response = await this.client.patch<T>(`${this.endpoint}/${id}`, apiData)
|
|
188
|
+
return this._normalizeData(response.data) as T
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
async delete(id: string | number): Promise<void> {
|
|
192
|
+
await this.client.delete(`${this.endpoint}/${id}`)
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
/**
|
|
196
|
+
* Generic request for special operations
|
|
197
|
+
*/
|
|
198
|
+
async request<R = T>(
|
|
199
|
+
method: string,
|
|
200
|
+
endpoint: string,
|
|
201
|
+
options: {
|
|
202
|
+
data?: unknown
|
|
203
|
+
params?: Record<string, unknown>
|
|
204
|
+
headers?: Record<string, string>
|
|
205
|
+
context?: RoutingContext | null
|
|
206
|
+
} = {}
|
|
207
|
+
): Promise<R> {
|
|
208
|
+
const { context, ...requestOptions } = options
|
|
209
|
+
const url = endpoint.startsWith('/') ? endpoint : `${this.endpoint}/${endpoint}`
|
|
210
|
+
const response = await this.client.request<R>({
|
|
211
|
+
method,
|
|
212
|
+
url,
|
|
213
|
+
data: requestOptions.data,
|
|
214
|
+
params: requestOptions.params,
|
|
215
|
+
headers: requestOptions.headers,
|
|
216
|
+
})
|
|
217
|
+
return this._normalizeData(response.data as unknown as T, context) as unknown as R
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
/**
|
|
222
|
+
* Factory function to create an ApiStorage
|
|
223
|
+
*/
|
|
224
|
+
export function createApiStorage<T extends EntityRecord = EntityRecord>(
|
|
225
|
+
options: ApiStorageOptions<T>
|
|
226
|
+
): ApiStorage<T> {
|
|
227
|
+
return new ApiStorage(options)
|
|
228
|
+
}
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* IStorage - Base class for storage adapters
|
|
3
|
+
*
|
|
4
|
+
* Provides instanceof checking for storageFactory.
|
|
5
|
+
* All storage adapters should extend this class.
|
|
6
|
+
*
|
|
7
|
+
* Usage:
|
|
8
|
+
* ```ts
|
|
9
|
+
* class MyStorage extends IStorage<MyEntity> {
|
|
10
|
+
* static capabilities = { supportsTotal: true, ... }
|
|
11
|
+
* async list(params) { ... }
|
|
12
|
+
* async get(id) { ... }
|
|
13
|
+
* async create(data) { ... }
|
|
14
|
+
* async update(id, data) { ... }
|
|
15
|
+
* async delete(id) { ... }
|
|
16
|
+
* }
|
|
17
|
+
* ```
|
|
18
|
+
*/
|
|
19
|
+
|
|
20
|
+
import type { EntityRecord, ListParams, ListResult, StorageCapabilities } from '../../types'
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Abstract base class for storage adapters
|
|
24
|
+
*/
|
|
25
|
+
export abstract class IStorage<T extends EntityRecord = EntityRecord> {
|
|
26
|
+
/**
|
|
27
|
+
* Default capabilities - override in subclass
|
|
28
|
+
*/
|
|
29
|
+
static capabilities: StorageCapabilities = {
|
|
30
|
+
supportsTotal: false,
|
|
31
|
+
supportsFilters: false,
|
|
32
|
+
supportsPagination: false,
|
|
33
|
+
supportsCaching: false,
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* List entities with optional filtering/pagination
|
|
38
|
+
* @param params - Query parameters
|
|
39
|
+
* @param _context - Optional context (e.g., parent entity)
|
|
40
|
+
* @returns Promise with items array and optional total count
|
|
41
|
+
*/
|
|
42
|
+
async list(_params: ListParams = {}, _context?: unknown): Promise<ListResult<T>> {
|
|
43
|
+
throw new Error('list() not implemented')
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Get single entity by ID
|
|
48
|
+
* @param _id - Entity ID
|
|
49
|
+
* @param _context - Optional context
|
|
50
|
+
* @returns Promise with entity or null if not found
|
|
51
|
+
*/
|
|
52
|
+
async get(_id: string | number, _context?: unknown): Promise<T | null> {
|
|
53
|
+
throw new Error('get() not implemented')
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Create new entity
|
|
58
|
+
* @param _data - Entity data
|
|
59
|
+
* @returns Promise with created entity including ID
|
|
60
|
+
*/
|
|
61
|
+
async create(_data: Partial<T>): Promise<T> {
|
|
62
|
+
throw new Error('create() not implemented')
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Update entity by ID (full replacement)
|
|
67
|
+
* @param _id - Entity ID
|
|
68
|
+
* @param _data - Updated data
|
|
69
|
+
* @returns Promise with updated entity
|
|
70
|
+
*/
|
|
71
|
+
async update(_id: string | number, _data: Partial<T>): Promise<T> {
|
|
72
|
+
throw new Error('update() not implemented')
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* Patch entity by ID (partial update)
|
|
77
|
+
* @param _id - Entity ID
|
|
78
|
+
* @param _data - Partial data to merge
|
|
79
|
+
* @returns Promise with updated entity
|
|
80
|
+
*/
|
|
81
|
+
async patch(_id: string | number, _data: Partial<T>): Promise<T> {
|
|
82
|
+
throw new Error('patch() not implemented')
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* Delete entity by ID
|
|
87
|
+
* @param _id - Entity ID
|
|
88
|
+
* @returns Promise that resolves when deleted
|
|
89
|
+
*/
|
|
90
|
+
async delete(_id: string | number): Promise<void> {
|
|
91
|
+
throw new Error('delete() not implemented')
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* Reset storage state (for testing/development)
|
|
96
|
+
*/
|
|
97
|
+
reset(): void {
|
|
98
|
+
// Default no-op, override if needed
|
|
99
|
+
}
|
|
100
|
+
}
|