@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,377 @@
|
|
|
1
|
+
import { IStorage } from './IStorage'
|
|
2
|
+
import type { EntityRecord, ListParams, ListResult, StorageCapabilities } from '../../types'
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* SDK method result with data property
|
|
6
|
+
*/
|
|
7
|
+
export interface SdkMethodResult<T = unknown> {
|
|
8
|
+
data: T
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* SDK interface - generic SDK with callable methods
|
|
13
|
+
*/
|
|
14
|
+
export interface SdkInstance {
|
|
15
|
+
[methodName: string]: (params?: unknown) => Promise<SdkMethodResult>
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Method configuration - either string name or function callback
|
|
20
|
+
*/
|
|
21
|
+
export type SdkMethodConfig<T extends EntityRecord = EntityRecord> =
|
|
22
|
+
| string
|
|
23
|
+
| ((sdk: SdkInstance, params: unknown) => Promise<T | T[] | ListResult<T> | void>)
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Transform functions for request/response
|
|
27
|
+
*/
|
|
28
|
+
export interface SdkMethodTransforms<T extends EntityRecord = EntityRecord> {
|
|
29
|
+
request?: (params: unknown) => unknown
|
|
30
|
+
response?: (data: T | T[] | ListResult<T>) => T | T[] | ListResult<T>
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Global transform functions
|
|
35
|
+
*/
|
|
36
|
+
export type SdkGlobalRequestTransform = (operation: string, params: unknown) => unknown
|
|
37
|
+
export type SdkGlobalResponseTransform<T extends EntityRecord = EntityRecord> = (
|
|
38
|
+
operation: string,
|
|
39
|
+
data: T | T[] | ListResult<T>
|
|
40
|
+
) => T | T[] | ListResult<T>
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Response format configuration
|
|
44
|
+
*/
|
|
45
|
+
export interface SdkResponseFormat {
|
|
46
|
+
dataField?: string
|
|
47
|
+
totalField?: string
|
|
48
|
+
itemsField?: string
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* SdkStorage methods configuration
|
|
53
|
+
*/
|
|
54
|
+
export interface SdkMethods<T extends EntityRecord = EntityRecord> {
|
|
55
|
+
list?: SdkMethodConfig<T>
|
|
56
|
+
get?: SdkMethodConfig<T>
|
|
57
|
+
create?: SdkMethodConfig<T>
|
|
58
|
+
update?: SdkMethodConfig<T>
|
|
59
|
+
patch?: SdkMethodConfig<T>
|
|
60
|
+
delete?: SdkMethodConfig<T>
|
|
61
|
+
[key: string]: SdkMethodConfig<T> | undefined
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* SdkStorage options
|
|
66
|
+
*/
|
|
67
|
+
export interface SdkStorageOptions<T extends EntityRecord = EntityRecord> {
|
|
68
|
+
sdk?: SdkInstance | null
|
|
69
|
+
getSdk?: (() => SdkInstance) | null
|
|
70
|
+
methods?: SdkMethods<T>
|
|
71
|
+
transformRequest?: SdkGlobalRequestTransform | null
|
|
72
|
+
transformResponse?: SdkGlobalResponseTransform<T> | null
|
|
73
|
+
transforms?: Partial<Record<string, SdkMethodTransforms<T>>>
|
|
74
|
+
responseFormat?: SdkResponseFormat | null
|
|
75
|
+
clientSidePagination?: boolean
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* SdkStorage - hey-api SDK storage adapter
|
|
80
|
+
*
|
|
81
|
+
* Implements the storage interface using hey-api generated SDKs.
|
|
82
|
+
* Provides config-based method mapping with optional transform callbacks.
|
|
83
|
+
*/
|
|
84
|
+
export class SdkStorage<T extends EntityRecord = EntityRecord> extends IStorage<T> {
|
|
85
|
+
static storageName = 'SdkStorage'
|
|
86
|
+
|
|
87
|
+
static capabilities: StorageCapabilities = {
|
|
88
|
+
supportsTotal: true,
|
|
89
|
+
supportsFilters: true,
|
|
90
|
+
supportsPagination: true,
|
|
91
|
+
supportsCaching: true,
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/** @deprecated Use SdkStorage.capabilities.supportsCaching instead */
|
|
95
|
+
get supportsCaching(): boolean {
|
|
96
|
+
return SdkStorage.capabilities.supportsCaching
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
readonly clientSidePagination: boolean
|
|
100
|
+
|
|
101
|
+
protected _sdk: SdkInstance | null
|
|
102
|
+
protected _getSdk: (() => SdkInstance) | null
|
|
103
|
+
protected _methods: SdkMethods<T>
|
|
104
|
+
protected _transformRequest: SdkGlobalRequestTransform | null
|
|
105
|
+
protected _transformResponse: SdkGlobalResponseTransform<T> | null
|
|
106
|
+
protected _transforms: Partial<Record<string, SdkMethodTransforms<T>>>
|
|
107
|
+
protected _responseFormat: SdkResponseFormat | null
|
|
108
|
+
|
|
109
|
+
constructor(options: SdkStorageOptions<T> = {}) {
|
|
110
|
+
super()
|
|
111
|
+
const {
|
|
112
|
+
sdk = null,
|
|
113
|
+
getSdk = null,
|
|
114
|
+
methods = {},
|
|
115
|
+
transformRequest = null,
|
|
116
|
+
transformResponse = null,
|
|
117
|
+
transforms = {},
|
|
118
|
+
responseFormat = null,
|
|
119
|
+
clientSidePagination = false,
|
|
120
|
+
} = options
|
|
121
|
+
|
|
122
|
+
this._sdk = sdk
|
|
123
|
+
this._getSdk = getSdk
|
|
124
|
+
this._methods = methods
|
|
125
|
+
this._transformRequest = transformRequest
|
|
126
|
+
this._transformResponse = transformResponse
|
|
127
|
+
this._transforms = transforms
|
|
128
|
+
this._responseFormat = responseFormat
|
|
129
|
+
this.clientSidePagination = clientSidePagination
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
get sdk(): SdkInstance {
|
|
133
|
+
if (this._getSdk) {
|
|
134
|
+
return this._getSdk()
|
|
135
|
+
}
|
|
136
|
+
if (!this._sdk) {
|
|
137
|
+
throw new Error('SdkStorage: No SDK instance available')
|
|
138
|
+
}
|
|
139
|
+
return this._sdk
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
set sdk(value: SdkInstance) {
|
|
143
|
+
this._sdk = value
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
protected _getRequestTransform(
|
|
147
|
+
operation: string
|
|
148
|
+
): ((params: unknown) => unknown) | SdkGlobalRequestTransform | null {
|
|
149
|
+
const methodTransforms = this._transforms[operation]
|
|
150
|
+
if (methodTransforms?.request) {
|
|
151
|
+
return methodTransforms.request
|
|
152
|
+
}
|
|
153
|
+
return this._transformRequest
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
protected _getResponseTransform(
|
|
157
|
+
operation: string
|
|
158
|
+
):
|
|
159
|
+
| ((data: T | T[] | ListResult<T>) => T | T[] | ListResult<T>)
|
|
160
|
+
| SdkGlobalResponseTransform<T>
|
|
161
|
+
| null {
|
|
162
|
+
const methodTransforms = this._transforms[operation]
|
|
163
|
+
if (methodTransforms?.response) {
|
|
164
|
+
return methodTransforms.response
|
|
165
|
+
}
|
|
166
|
+
return this._transformResponse
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
protected _getNestedValue(obj: Record<string, unknown>, path: string): unknown {
|
|
170
|
+
if (!path || !obj) return undefined
|
|
171
|
+
return path.split('.').reduce((acc: unknown, part) => {
|
|
172
|
+
if (acc && typeof acc === 'object') {
|
|
173
|
+
return (acc as Record<string, unknown>)[part]
|
|
174
|
+
}
|
|
175
|
+
return undefined
|
|
176
|
+
}, obj)
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
protected _normalizeListResponse(data: unknown): ListResult<T> {
|
|
180
|
+
if (Array.isArray(data)) {
|
|
181
|
+
return { items: data as T[], total: data.length }
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
if (!data || typeof data !== 'object') {
|
|
185
|
+
return { items: [], total: 0 }
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
const dataObj = data as Record<string, unknown>
|
|
189
|
+
const format = this._responseFormat
|
|
190
|
+
let items: T[] | undefined
|
|
191
|
+
let total: number | undefined
|
|
192
|
+
|
|
193
|
+
if (format) {
|
|
194
|
+
if (format.itemsField) {
|
|
195
|
+
items = this._getNestedValue(dataObj, format.itemsField) as T[] | undefined
|
|
196
|
+
} else if (format.dataField) {
|
|
197
|
+
items = dataObj[format.dataField] as T[] | undefined
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
if (format.totalField) {
|
|
201
|
+
total = dataObj[format.totalField] as number | undefined
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
if (!Array.isArray(items)) {
|
|
206
|
+
items = (dataObj.items || dataObj.data || dataObj.results || []) as T[]
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
if (!Array.isArray(items)) {
|
|
210
|
+
items = []
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
if (total === undefined || total === null) {
|
|
214
|
+
total =
|
|
215
|
+
(dataObj.total as number | undefined) ??
|
|
216
|
+
(dataObj.count as number | undefined) ??
|
|
217
|
+
items.length
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
return { items, total }
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
protected async _execute(
|
|
224
|
+
operation: string,
|
|
225
|
+
params: unknown
|
|
226
|
+
): Promise<T | T[] | ListResult<T> | void> {
|
|
227
|
+
const methodConfig = this._methods[operation]
|
|
228
|
+
|
|
229
|
+
if (!methodConfig) {
|
|
230
|
+
throw new Error(`SdkStorage: No method configured for operation '${operation}'`)
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
const sdk = this.sdk
|
|
234
|
+
|
|
235
|
+
let transformedParams = params
|
|
236
|
+
const requestTransform = this._getRequestTransform(operation)
|
|
237
|
+
if (requestTransform) {
|
|
238
|
+
const isMethodSpecific = this._transforms[operation]?.request === requestTransform
|
|
239
|
+
transformedParams = isMethodSpecific
|
|
240
|
+
? (requestTransform as (params: unknown) => unknown)(params)
|
|
241
|
+
: (requestTransform as SdkGlobalRequestTransform)(operation, params)
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
let result: T | T[] | ListResult<T> | void
|
|
245
|
+
|
|
246
|
+
if (typeof methodConfig === 'function') {
|
|
247
|
+
result = await methodConfig(sdk, transformedParams)
|
|
248
|
+
} else if (typeof methodConfig === 'string') {
|
|
249
|
+
const sdkMethod = sdk[methodConfig]
|
|
250
|
+
if (typeof sdkMethod !== 'function') {
|
|
251
|
+
throw new Error(`SdkStorage: SDK method '${methodConfig}' not found`)
|
|
252
|
+
}
|
|
253
|
+
const response = await sdkMethod.call(sdk, transformedParams)
|
|
254
|
+
result = response.data as T | T[] | ListResult<T>
|
|
255
|
+
} else {
|
|
256
|
+
throw new Error(
|
|
257
|
+
`SdkStorage: Invalid method config for '${operation}' - expected string or function`
|
|
258
|
+
)
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
if (operation === 'list') {
|
|
262
|
+
result = this._normalizeListResponse(result)
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
const responseTransform = this._getResponseTransform(operation)
|
|
266
|
+
if (responseTransform && result !== undefined) {
|
|
267
|
+
const isMethodSpecific = this._transforms[operation]?.response === responseTransform
|
|
268
|
+
result = isMethodSpecific
|
|
269
|
+
? (
|
|
270
|
+
responseTransform as (data: T | T[] | ListResult<T>) => T | T[] | ListResult<T>
|
|
271
|
+
)(result as T | T[] | ListResult<T>)
|
|
272
|
+
: (responseTransform as SdkGlobalResponseTransform<T>)(
|
|
273
|
+
operation,
|
|
274
|
+
result as T | T[] | ListResult<T>
|
|
275
|
+
)
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
return result
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
async list(params: ListParams = {}): Promise<ListResult<T>> {
|
|
282
|
+
const { page = 1, page_size = 20, sort_by, sort_order = 'asc', filters = {} } = params
|
|
283
|
+
|
|
284
|
+
const queryParams = { query: { page, page_size, sort_by, sort_order, ...filters } }
|
|
285
|
+
const data = (await this._execute('list', queryParams)) as ListResult<T>
|
|
286
|
+
|
|
287
|
+
let { items, total } = data
|
|
288
|
+
|
|
289
|
+
if (!Array.isArray(items)) {
|
|
290
|
+
items = []
|
|
291
|
+
total = 0
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
if (this.clientSidePagination && items.length > 0) {
|
|
295
|
+
total = items.length
|
|
296
|
+
|
|
297
|
+
if (sort_by) {
|
|
298
|
+
items = [...items].sort((a, b) => {
|
|
299
|
+
const aVal = a[sort_by as keyof T]
|
|
300
|
+
const bVal = b[sort_by as keyof T]
|
|
301
|
+
if (aVal === undefined || aVal === null) return 1
|
|
302
|
+
if (bVal === undefined || bVal === null) return -1
|
|
303
|
+
if (aVal < bVal) return sort_order === 'asc' ? -1 : 1
|
|
304
|
+
if (aVal > bVal) return sort_order === 'asc' ? 1 : -1
|
|
305
|
+
return 0
|
|
306
|
+
})
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
for (const [key, value] of Object.entries(filters)) {
|
|
310
|
+
if (value === null || value === undefined || value === '') continue
|
|
311
|
+
items = items.filter((item) => {
|
|
312
|
+
const itemValue = item[key as keyof T]
|
|
313
|
+
if (typeof value === 'string' && typeof itemValue === 'string') {
|
|
314
|
+
return itemValue.toLowerCase().includes(value.toLowerCase())
|
|
315
|
+
}
|
|
316
|
+
return itemValue === value
|
|
317
|
+
})
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
total = items.length
|
|
321
|
+
|
|
322
|
+
const start = (page - 1) * page_size
|
|
323
|
+
items = items.slice(start, start + page_size)
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
return { items, total }
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
async get(id: string | number): Promise<T> {
|
|
330
|
+
const pathParams = { path: { id } }
|
|
331
|
+
return (await this._execute('get', pathParams)) as T
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
async create(data: Partial<T>): Promise<T> {
|
|
335
|
+
const bodyParams = { body: data }
|
|
336
|
+
return (await this._execute('create', bodyParams)) as T
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
async update(id: string | number, data: Partial<T>): Promise<T> {
|
|
340
|
+
const params = { path: { id }, body: data }
|
|
341
|
+
return (await this._execute('update', params)) as T
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
async patch(id: string | number, data: Partial<T>): Promise<T> {
|
|
345
|
+
const params = { path: { id }, body: data }
|
|
346
|
+
return (await this._execute('patch', params)) as T
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
async delete(id: string | number): Promise<void> {
|
|
350
|
+
const pathParams = { path: { id } }
|
|
351
|
+
await this._execute('delete', pathParams)
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
async request<R = unknown>(methodName: string, params: unknown = {}): Promise<R> {
|
|
355
|
+
if (this._methods[methodName]) {
|
|
356
|
+
return (await this._execute(methodName, params)) as R
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
const sdk = this.sdk
|
|
360
|
+
const sdkMethod = sdk[methodName]
|
|
361
|
+
if (typeof sdkMethod !== 'function') {
|
|
362
|
+
throw new Error(`SdkStorage: SDK method '${methodName}' not found`)
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
const response = await sdkMethod.call(sdk, params)
|
|
366
|
+
return response.data as R
|
|
367
|
+
}
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
/**
|
|
371
|
+
* Factory function to create an SdkStorage
|
|
372
|
+
*/
|
|
373
|
+
export function createSdkStorage<T extends EntityRecord = EntityRecord>(
|
|
374
|
+
options: SdkStorageOptions<T>
|
|
375
|
+
): SdkStorage<T> {
|
|
376
|
+
return new SdkStorage(options)
|
|
377
|
+
}
|
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
import { describe, it, expect, vi } from 'vitest'
|
|
2
|
+
import {
|
|
3
|
+
parseStoragePattern,
|
|
4
|
+
storageFactory,
|
|
5
|
+
defaultStorageResolver,
|
|
6
|
+
createStorageFactory,
|
|
7
|
+
storageTypes
|
|
8
|
+
} from './factory'
|
|
9
|
+
import { ApiStorage } from './ApiStorage'
|
|
10
|
+
import { LocalStorage } from './LocalStorage'
|
|
11
|
+
import { MemoryStorage } from './MemoryStorage'
|
|
12
|
+
import { MockApiStorage } from './MockApiStorage'
|
|
13
|
+
|
|
14
|
+
describe('parseStoragePattern', () => {
|
|
15
|
+
it('parses api pattern with endpoint', () => {
|
|
16
|
+
const result = parseStoragePattern('api:/api/bots')
|
|
17
|
+
expect(result).toEqual({ type: 'api', endpoint: '/api/bots' })
|
|
18
|
+
})
|
|
19
|
+
|
|
20
|
+
it('parses local pattern with key', () => {
|
|
21
|
+
const result = parseStoragePattern('local:myKey')
|
|
22
|
+
expect(result).toEqual({ type: 'local', key: 'myKey' })
|
|
23
|
+
})
|
|
24
|
+
|
|
25
|
+
it('parses memory pattern with key', () => {
|
|
26
|
+
const result = parseStoragePattern('memory:cache')
|
|
27
|
+
expect(result).toEqual({ type: 'memory', key: 'cache' })
|
|
28
|
+
})
|
|
29
|
+
|
|
30
|
+
it('parses mock pattern with entityName', () => {
|
|
31
|
+
const result = parseStoragePattern('mock:users')
|
|
32
|
+
expect(result).toEqual({ type: 'mock', entityName: 'users' })
|
|
33
|
+
})
|
|
34
|
+
|
|
35
|
+
it('parses sdk pattern with endpoint', () => {
|
|
36
|
+
const result = parseStoragePattern('sdk:users')
|
|
37
|
+
expect(result).toEqual({ type: 'sdk', endpoint: 'users' })
|
|
38
|
+
})
|
|
39
|
+
|
|
40
|
+
it('treats bare path as api endpoint', () => {
|
|
41
|
+
const result = parseStoragePattern('/api/tasks')
|
|
42
|
+
expect(result).toEqual({ type: 'api', endpoint: '/api/tasks' })
|
|
43
|
+
})
|
|
44
|
+
|
|
45
|
+
it('returns null for invalid pattern', () => {
|
|
46
|
+
const result = parseStoragePattern('invalid')
|
|
47
|
+
expect(result).toBeNull()
|
|
48
|
+
})
|
|
49
|
+
})
|
|
50
|
+
|
|
51
|
+
describe('defaultStorageResolver', () => {
|
|
52
|
+
it('creates ApiStorage for api type', () => {
|
|
53
|
+
const storage = defaultStorageResolver({ type: 'api', endpoint: '/api/bots' }, 'bots')
|
|
54
|
+
expect(storage).toBeInstanceOf(ApiStorage)
|
|
55
|
+
expect(storage.endpoint).toBe('/api/bots')
|
|
56
|
+
})
|
|
57
|
+
|
|
58
|
+
it('creates LocalStorage for local type', () => {
|
|
59
|
+
const storage = defaultStorageResolver({ type: 'local', key: 'myKey' }, 'items')
|
|
60
|
+
expect(storage).toBeInstanceOf(LocalStorage)
|
|
61
|
+
expect(storage.key).toBe('myKey')
|
|
62
|
+
})
|
|
63
|
+
|
|
64
|
+
it('creates MemoryStorage for memory type', () => {
|
|
65
|
+
const storage = defaultStorageResolver({ type: 'memory', key: 'cache' }, 'items')
|
|
66
|
+
expect(storage).toBeInstanceOf(MemoryStorage)
|
|
67
|
+
})
|
|
68
|
+
|
|
69
|
+
it('creates MockApiStorage for mock type', () => {
|
|
70
|
+
const storage = defaultStorageResolver({ type: 'mock', entityName: 'users' }, 'users')
|
|
71
|
+
expect(storage).toBeInstanceOf(MockApiStorage)
|
|
72
|
+
})
|
|
73
|
+
|
|
74
|
+
it('throws for unknown type', () => {
|
|
75
|
+
expect(() => defaultStorageResolver({ type: 'unknown' }, 'items'))
|
|
76
|
+
.toThrow('Unknown storage type: "unknown"')
|
|
77
|
+
})
|
|
78
|
+
})
|
|
79
|
+
|
|
80
|
+
describe('storageFactory', () => {
|
|
81
|
+
it('returns IStorage instance directly', () => {
|
|
82
|
+
const storage = new ApiStorage({ endpoint: '/api/test' })
|
|
83
|
+
const result = storageFactory(storage, 'test')
|
|
84
|
+
expect(result).toBe(storage)
|
|
85
|
+
})
|
|
86
|
+
|
|
87
|
+
it('returns duck-typed storage directly', () => {
|
|
88
|
+
const duckStorage = {
|
|
89
|
+
list: () => Promise.resolve({ items: [], total: 0 }),
|
|
90
|
+
get: () => Promise.resolve(null)
|
|
91
|
+
}
|
|
92
|
+
const result = storageFactory(duckStorage, 'test')
|
|
93
|
+
expect(result).toBe(duckStorage)
|
|
94
|
+
})
|
|
95
|
+
|
|
96
|
+
it('parses string pattern and creates storage', () => {
|
|
97
|
+
const result = storageFactory('api:/api/bots', 'bots')
|
|
98
|
+
expect(result).toBeInstanceOf(ApiStorage)
|
|
99
|
+
expect(result.endpoint).toBe('/api/bots')
|
|
100
|
+
})
|
|
101
|
+
|
|
102
|
+
it('handles bare path as api endpoint', () => {
|
|
103
|
+
const result = storageFactory('/api/tasks', 'tasks')
|
|
104
|
+
expect(result).toBeInstanceOf(ApiStorage)
|
|
105
|
+
expect(result.endpoint).toBe('/api/tasks')
|
|
106
|
+
})
|
|
107
|
+
|
|
108
|
+
it('handles config object with type', () => {
|
|
109
|
+
const result = storageFactory({ type: 'memory', key: 'cache' }, 'items')
|
|
110
|
+
expect(result).toBeInstanceOf(MemoryStorage)
|
|
111
|
+
})
|
|
112
|
+
|
|
113
|
+
it('handles config object with endpoint (infers api type)', () => {
|
|
114
|
+
const result = storageFactory({ endpoint: '/api/items' }, 'items')
|
|
115
|
+
expect(result).toBeInstanceOf(ApiStorage)
|
|
116
|
+
expect(result.endpoint).toBe('/api/items')
|
|
117
|
+
})
|
|
118
|
+
|
|
119
|
+
it('uses custom resolver when provided', () => {
|
|
120
|
+
const customResolver = vi.fn().mockReturnValue(new MemoryStorage())
|
|
121
|
+
const result = storageFactory('api:/test', 'test', customResolver)
|
|
122
|
+
expect(customResolver).toHaveBeenCalledWith(
|
|
123
|
+
{ type: 'api', endpoint: '/test' },
|
|
124
|
+
'test'
|
|
125
|
+
)
|
|
126
|
+
expect(result).toBeInstanceOf(MemoryStorage)
|
|
127
|
+
})
|
|
128
|
+
|
|
129
|
+
it('throws for invalid config', () => {
|
|
130
|
+
expect(() => storageFactory(123, 'test'))
|
|
131
|
+
.toThrow('Invalid storage config')
|
|
132
|
+
})
|
|
133
|
+
|
|
134
|
+
it('throws for unparseable string', () => {
|
|
135
|
+
expect(() => storageFactory('invalid', 'test'))
|
|
136
|
+
.toThrow('Invalid storage pattern')
|
|
137
|
+
})
|
|
138
|
+
})
|
|
139
|
+
|
|
140
|
+
describe('createStorageFactory', () => {
|
|
141
|
+
it('creates factory with bound context', () => {
|
|
142
|
+
const customResolver = vi.fn().mockReturnValue(new MemoryStorage())
|
|
143
|
+
const factory = createStorageFactory(customResolver)
|
|
144
|
+
|
|
145
|
+
const result = factory('api:/test', 'test')
|
|
146
|
+
|
|
147
|
+
expect(customResolver).toHaveBeenCalled()
|
|
148
|
+
expect(result).toBeInstanceOf(MemoryStorage)
|
|
149
|
+
})
|
|
150
|
+
})
|
|
151
|
+
|
|
152
|
+
describe('storageTypes', () => {
|
|
153
|
+
it('exports all storage type classes', () => {
|
|
154
|
+
expect(storageTypes.api).toBe(ApiStorage)
|
|
155
|
+
expect(storageTypes.local).toBe(LocalStorage)
|
|
156
|
+
expect(storageTypes.memory).toBe(MemoryStorage)
|
|
157
|
+
expect(storageTypes.mock).toBe(MockApiStorage)
|
|
158
|
+
})
|
|
159
|
+
})
|