@quazardous/qdadm 1.19.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (291) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +111 -0
  3. package/package.json +101 -0
  4. package/src/assets/logo.svg +6 -0
  5. package/src/auth/SessionAuthAdapter.ts +459 -0
  6. package/src/auth/index.ts +15 -0
  7. package/src/chain/ActiveStack.ts +154 -0
  8. package/src/chain/StackHydrator.ts +292 -0
  9. package/src/chain/index.ts +25 -0
  10. package/src/chain/useActiveStack.ts +120 -0
  11. package/src/chain/useStackHydrator.ts +155 -0
  12. package/src/components/BoolCell.vue +34 -0
  13. package/src/components/InfoBanner.vue +73 -0
  14. package/src/components/QdadmRoot.vue +52 -0
  15. package/src/components/SeverityTag.vue +86 -0
  16. package/src/components/dialogs/BulkStatusDialog.vue +60 -0
  17. package/src/components/dialogs/MultiStepDialog.vue +331 -0
  18. package/src/components/dialogs/SimpleDialog.vue +122 -0
  19. package/src/components/dialogs/UnsavedChangesDialog.vue +80 -0
  20. package/src/components/display/BannerZone.vue +65 -0
  21. package/src/components/display/CardsGrid.vue +72 -0
  22. package/src/components/display/CopyableId.vue +84 -0
  23. package/src/components/display/EmptyState.vue +62 -0
  24. package/src/components/display/IntensityBar.vue +190 -0
  25. package/src/components/display/RichCardsGrid.vue +101 -0
  26. package/src/components/edit/FormActions.vue +78 -0
  27. package/src/components/edit/FormField.vue +82 -0
  28. package/src/components/edit/FormInput.vue +158 -0
  29. package/src/components/edit/FormPage.vue +260 -0
  30. package/src/components/edit/FormTab.vue +217 -0
  31. package/src/components/edit/FormTabs.vue +120 -0
  32. package/src/components/edit/LookupField.vue +406 -0
  33. package/src/components/edit/LookupPickerDialog.vue +230 -0
  34. package/src/components/editors/JsonEditorFoldable.vue +470 -0
  35. package/src/components/editors/JsonStructuredField.vue +264 -0
  36. package/src/components/editors/JsonViewer.vue +51 -0
  37. package/src/components/editors/KeyValueEditor.vue +314 -0
  38. package/src/components/editors/LanguageEditor.vue +259 -0
  39. package/src/components/editors/PermissionEditor.vue +558 -0
  40. package/src/components/editors/ScopeEditor.vue +372 -0
  41. package/src/components/editors/VanillaJsonEditor.vue +183 -0
  42. package/src/components/index.ts +83 -0
  43. package/src/components/item/FieldGroups.vue +392 -0
  44. package/src/components/layout/AppLayout.vue +1032 -0
  45. package/src/components/layout/BaseLayout.vue +190 -0
  46. package/src/components/layout/Breadcrumb.vue +55 -0
  47. package/src/components/layout/DashboardLayout.vue +104 -0
  48. package/src/components/layout/FormLayout.vue +256 -0
  49. package/src/components/layout/ListLayout.vue +278 -0
  50. package/src/components/layout/PageHeader.vue +156 -0
  51. package/src/components/layout/PageLayout.vue +126 -0
  52. package/src/components/layout/PageNav.vue +192 -0
  53. package/src/components/layout/SidebarBox.vue +87 -0
  54. package/src/components/layout/Zone.vue +182 -0
  55. package/src/components/layout/defaults/DefaultBreadcrumb.vue +149 -0
  56. package/src/components/layout/defaults/DefaultFooter.vue +89 -0
  57. package/src/components/layout/defaults/DefaultFormActions.vue +67 -0
  58. package/src/components/layout/defaults/DefaultHeader.vue +78 -0
  59. package/src/components/layout/defaults/DefaultMenu.vue +188 -0
  60. package/src/components/layout/defaults/DefaultPagination.vue +75 -0
  61. package/src/components/layout/defaults/DefaultTable.vue +137 -0
  62. package/src/components/layout/defaults/DefaultUserInfo.vue +111 -0
  63. package/src/components/layout/defaults/index.ts +16 -0
  64. package/src/components/lists/ActionButtons.vue +49 -0
  65. package/src/components/lists/ActionColumn.vue +40 -0
  66. package/src/components/lists/FilterBar.vue +54 -0
  67. package/src/components/lists/ListPage.vue +467 -0
  68. package/src/components/pages/LoginPage.vue +334 -0
  69. package/src/components/pages/NotFoundPage.vue +98 -0
  70. package/src/components/show/ShowDisplay.vue +343 -0
  71. package/src/components/show/ShowField.vue +67 -0
  72. package/src/components/show/ShowPage.vue +431 -0
  73. package/src/composables/index.ts +155 -0
  74. package/src/composables/useApp.ts +67 -0
  75. package/src/composables/useAuth.ts +98 -0
  76. package/src/composables/useBareForm.ts +319 -0
  77. package/src/composables/useBreadcrumb.ts +252 -0
  78. package/src/composables/useChildPage.ts +113 -0
  79. package/src/composables/useCurrentEntity.ts +60 -0
  80. package/src/composables/useDeferred.ts +94 -0
  81. package/src/composables/useDirtyState.ts +138 -0
  82. package/src/composables/useEntityItemFormPage.ts +942 -0
  83. package/src/composables/useEntityItemFormPage.types.ts +451 -0
  84. package/src/composables/useEntityItemPage.ts +438 -0
  85. package/src/composables/useEntityItemShowPage.ts +722 -0
  86. package/src/composables/useFieldManager.ts +692 -0
  87. package/src/composables/useForm.ts +544 -0
  88. package/src/composables/useGuardStore.ts +53 -0
  89. package/src/composables/useHooks.ts +56 -0
  90. package/src/composables/useInfoBanner.ts +217 -0
  91. package/src/composables/useJsonSyntax.ts +122 -0
  92. package/src/composables/useLayoutResolver.ts +329 -0
  93. package/src/composables/useListPage.ts +1535 -0
  94. package/src/composables/useListPage.types.ts +437 -0
  95. package/src/composables/useListPage.utils.ts +101 -0
  96. package/src/composables/useNavContext.ts +439 -0
  97. package/src/composables/useNavigation.ts +216 -0
  98. package/src/composables/useOptionsLookup.ts +323 -0
  99. package/src/composables/usePageTitle.ts +84 -0
  100. package/src/composables/useSSEBridge.ts +142 -0
  101. package/src/composables/useSemanticBreadcrumb.ts +222 -0
  102. package/src/composables/useSidebarState.ts +24 -0
  103. package/src/composables/useSignals.ts +52 -0
  104. package/src/composables/useUnsavedChangesGuard.ts +155 -0
  105. package/src/composables/useUserImpersonator.ts +317 -0
  106. package/src/composables/useZoneRegistry.ts +252 -0
  107. package/src/core/bundles.ts +558 -0
  108. package/src/core/decorator.ts +420 -0
  109. package/src/core/extension.ts +470 -0
  110. package/src/core/index.ts +59 -0
  111. package/src/deferred/DeferredRegistry.ts +353 -0
  112. package/src/deferred/index.ts +15 -0
  113. package/src/editors/index.ts +12 -0
  114. package/src/entity/EntityManager.cache.ts +530 -0
  115. package/src/entity/EntityManager.crud.ts +615 -0
  116. package/src/entity/EntityManager.interface.ts +78 -0
  117. package/src/entity/EntityManager.query.ts +221 -0
  118. package/src/entity/EntityManager.relations.ts +151 -0
  119. package/src/entity/EntityManager.ts +944 -0
  120. package/src/entity/EntityManager.types.ts +288 -0
  121. package/src/entity/auth/CompositeAuthAdapter.ts +194 -0
  122. package/src/entity/auth/EntityAuthAdapter.ts +119 -0
  123. package/src/entity/auth/PermissiveAdapter.ts +43 -0
  124. package/src/entity/auth/RoleHierarchy.ts +128 -0
  125. package/src/entity/auth/SecurityChecker.ts +202 -0
  126. package/src/entity/auth/factory.ts +203 -0
  127. package/src/entity/auth/index.ts +47 -0
  128. package/src/entity/factory.test.js +189 -0
  129. package/src/entity/factory.ts +146 -0
  130. package/src/entity/index.ts +49 -0
  131. package/src/entity/storage/ApiStorage.ts +228 -0
  132. package/src/entity/storage/IStorage.ts +100 -0
  133. package/src/entity/storage/LocalStorage.ts +200 -0
  134. package/src/entity/storage/MemoryStorage.ts +184 -0
  135. package/src/entity/storage/MockApiStorage.ts +318 -0
  136. package/src/entity/storage/SdkStorage.ts +377 -0
  137. package/src/entity/storage/factory.test.js +159 -0
  138. package/src/entity/storage/factory.ts +209 -0
  139. package/src/entity/storage/index.ts +75 -0
  140. package/src/gen/FieldMapper.ts +122 -0
  141. package/src/gen/StorageProfileFactory.ts +93 -0
  142. package/src/gen/connectors/BaseConnector.ts +150 -0
  143. package/src/gen/connectors/ManualConnector.test.js +499 -0
  144. package/src/gen/connectors/ManualConnector.ts +414 -0
  145. package/src/gen/connectors/OpenAPIConnector.test.js +836 -0
  146. package/src/gen/connectors/OpenAPIConnector.ts +693 -0
  147. package/src/gen/connectors/__fixtures__/sample-openapi.json +311 -0
  148. package/src/gen/connectors/index.ts +27 -0
  149. package/src/gen/createManagers.ts +245 -0
  150. package/src/gen/decorators.ts +134 -0
  151. package/src/gen/generateManagers.test.js +540 -0
  152. package/src/gen/generateManagers.ts +398 -0
  153. package/src/gen/index.ts +66 -0
  154. package/src/gen/schema.ts +248 -0
  155. package/src/gen/vite-plugin.ts +114 -0
  156. package/src/generated/managers/testManager.js +45 -0
  157. package/src/generated/managers/testManager.ts +46 -0
  158. package/src/hooks/HookRegistry.ts +16 -0
  159. package/src/hooks/index.ts +15 -0
  160. package/src/i18n/I18n.ts +344 -0
  161. package/src/i18n/IncrementalDomainProvider.ts +153 -0
  162. package/src/i18n/InlineTranslationProvider.ts +4 -0
  163. package/src/i18n/LazyTranslationProvider.ts +102 -0
  164. package/src/i18n/MessagesRegistry.ts +4 -0
  165. package/src/i18n/Resolver.ts +4 -0
  166. package/src/i18n/__tests__/I18n.test.ts +169 -0
  167. package/src/i18n/__tests__/IncrementalDomainProvider.test.ts +146 -0
  168. package/src/i18n/__tests__/LazyTranslationProvider.test.ts +100 -0
  169. package/src/i18n/__tests__/Resolver.test.ts +271 -0
  170. package/src/i18n/defaults/DefaultCoreProvider.ts +28 -0
  171. package/src/i18n/defaults/core.en.yml +55 -0
  172. package/src/i18n/defaults/core.fr.yml +55 -0
  173. package/src/i18n/index.ts +55 -0
  174. package/src/i18n/loaders/raw-modules.d.ts +15 -0
  175. package/src/i18n/loaders/yaml.ts +35 -0
  176. package/src/i18n/strategies.ts +4 -0
  177. package/src/i18n/types.ts +34 -0
  178. package/src/i18n/useI18n.ts +34 -0
  179. package/src/index.ts +453 -0
  180. package/src/kernel/EventRouter.ts +23 -0
  181. package/src/kernel/Kernel.api.ts +79 -0
  182. package/src/kernel/Kernel.auth.ts +120 -0
  183. package/src/kernel/Kernel.i18n.ts +29 -0
  184. package/src/kernel/Kernel.modules.ts +170 -0
  185. package/src/kernel/Kernel.registries.ts +237 -0
  186. package/src/kernel/Kernel.routing.ts +334 -0
  187. package/src/kernel/Kernel.ts +460 -0
  188. package/src/kernel/Kernel.types.ts +263 -0
  189. package/src/kernel/Kernel.vue.ts +304 -0
  190. package/src/kernel/KernelContext.entities.ts +80 -0
  191. package/src/kernel/KernelContext.events.ts +57 -0
  192. package/src/kernel/KernelContext.i18n.ts +37 -0
  193. package/src/kernel/KernelContext.permissions.ts +38 -0
  194. package/src/kernel/KernelContext.routing.ts +280 -0
  195. package/src/kernel/KernelContext.ts +252 -0
  196. package/src/kernel/KernelContext.types.ts +173 -0
  197. package/src/kernel/KernelContext.zones.ts +54 -0
  198. package/src/kernel/Module.ts +169 -0
  199. package/src/kernel/ModuleLoader.ts +645 -0
  200. package/src/kernel/SSEBridge.ts +11 -0
  201. package/src/kernel/SignalBus.ts +48 -0
  202. package/src/kernel/index.ts +56 -0
  203. package/src/module/index.ts +33 -0
  204. package/src/module/moduleRegistry.ts +492 -0
  205. package/src/modules/debug/AuthCollector.ts +501 -0
  206. package/src/modules/debug/Collector.ts +20 -0
  207. package/src/modules/debug/DebugBridge.ts +14 -0
  208. package/src/modules/debug/DebugModule.ts +220 -0
  209. package/src/modules/debug/EntitiesCollector.ts +839 -0
  210. package/src/modules/debug/ErrorCollector.ts +4 -0
  211. package/src/modules/debug/I18nCollector.ts +9 -0
  212. package/src/modules/debug/LocalStorageAdapter.ts +7 -0
  213. package/src/modules/debug/RouterCollector.ts +449 -0
  214. package/src/modules/debug/SignalCollector.ts +4 -0
  215. package/src/modules/debug/ToastCollector.ts +4 -0
  216. package/src/modules/debug/ZonesCollector.ts +447 -0
  217. package/src/modules/debug/components/DebugBar.vue +31 -0
  218. package/src/modules/debug/components/adminPanelsConfig.ts +42 -0
  219. package/src/modules/debug/components/index.ts +9 -0
  220. package/src/modules/debug/components/panels/AuthPanel.vue +267 -0
  221. package/src/modules/debug/components/panels/EntitiesPanel.vue +572 -0
  222. package/src/modules/debug/components/panels/I18nPanel.vue +738 -0
  223. package/src/modules/debug/components/panels/RouterPanel.vue +440 -0
  224. package/src/modules/debug/components/panels/ZonesPanel.vue +133 -0
  225. package/src/modules/debug/components/panels/index.ts +16 -0
  226. package/src/modules/debug/index.ts +58 -0
  227. package/src/modules/debug/styles.scss +2671 -0
  228. package/src/notifications/NotificationBadge.vue +35 -0
  229. package/src/notifications/NotificationListener.vue +71 -0
  230. package/src/notifications/NotificationModule.ts +76 -0
  231. package/src/notifications/NotificationPanel.vue +478 -0
  232. package/src/notifications/NotificationStore.ts +288 -0
  233. package/src/notifications/index.ts +42 -0
  234. package/src/notifications/styles.scss +32 -0
  235. package/src/orchestrator/Orchestrator.ts +427 -0
  236. package/src/orchestrator/index.ts +8 -0
  237. package/src/orchestrator/useOrchestrator.ts +80 -0
  238. package/src/plugin.ts +251 -0
  239. package/src/query/FilterQuery.ts +306 -0
  240. package/src/query/QueryExecutor.ts +341 -0
  241. package/src/query/index.ts +16 -0
  242. package/src/security/EntityRolesProvider.ts +385 -0
  243. package/src/security/PermissionMatcher.ts +152 -0
  244. package/src/security/PermissionRegistry.ts +273 -0
  245. package/src/security/PersistableRolesProvider.ts +584 -0
  246. package/src/security/RolesManager.ts +136 -0
  247. package/src/security/RolesProvider.ts +167 -0
  248. package/src/security/RolesProviderStorage.ts +211 -0
  249. package/src/security/SecurityModule.ts +94 -0
  250. package/src/security/StaticRolesProvider.ts +117 -0
  251. package/src/security/UsersManager.ts +163 -0
  252. package/src/security/index.ts +77 -0
  253. package/src/security/pages/RoleForm.vue +276 -0
  254. package/src/security/pages/RoleList.vue +123 -0
  255. package/src/styles/_alerts.scss +95 -0
  256. package/src/styles/_buttons.scss +391 -0
  257. package/src/styles/_cards.scss +241 -0
  258. package/src/styles/_code.scss +78 -0
  259. package/src/styles/_dialogs.scss +55 -0
  260. package/src/styles/_filter-bar.scss +70 -0
  261. package/src/styles/_forms.scss +434 -0
  262. package/src/styles/_helpers.scss +153 -0
  263. package/src/styles/_lists.scss +225 -0
  264. package/src/styles/_main.scss +356 -0
  265. package/src/styles/_markdown.scss +88 -0
  266. package/src/styles/_responsive.scss +255 -0
  267. package/src/styles/_show-pages.scss +186 -0
  268. package/src/styles/_sidebar.scss +380 -0
  269. package/src/styles/_states.scss +115 -0
  270. package/src/styles/_stats.scss +47 -0
  271. package/src/styles/_variables.scss +224 -0
  272. package/src/styles/index.scss +38 -0
  273. package/src/styles/theme/_components.scss +331 -0
  274. package/src/styles/theme/_index.scss +10 -0
  275. package/src/styles/theme/_tokens.scss +147 -0
  276. package/src/styles/theme/_utilities.scss +175 -0
  277. package/src/toast/ToastBridgeModule.ts +68 -0
  278. package/src/toast/ToastListener.vue +55 -0
  279. package/src/toast/index.ts +26 -0
  280. package/src/toast/useSignalToast.ts +154 -0
  281. package/src/types/index.ts +285 -0
  282. package/src/types/pluralize.d.ts +19 -0
  283. package/src/types/quarkernel.d.ts +50 -0
  284. package/src/utils/formatters.ts +174 -0
  285. package/src/utils/index.ts +35 -0
  286. package/src/utils/transformers.ts +131 -0
  287. package/src/vite/qdadmDebugPlugin.ts +401 -0
  288. package/src/vite-env.d.ts +50 -0
  289. package/src/zones/ZoneRegistry.ts +934 -0
  290. package/src/zones/index.ts +29 -0
  291. package/src/zones/zones.ts +191 -0
@@ -0,0 +1,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
+ })