@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,693 @@
1
+ /**
2
+ * OpenAPI Connector
3
+ *
4
+ * Parses OpenAPI 3.x specifications into UnifiedEntitySchema format.
5
+ * Configurable path patterns, data wrapper, and operation filtering.
6
+ *
7
+ * @module gen/connectors/OpenAPIConnector
8
+ */
9
+
10
+ import { BaseConnector, type ConnectorOptions, type ParseResult, type ParseWarning } from './BaseConnector'
11
+ import { getDefaultType, type CustomMappings } from '../FieldMapper'
12
+ import type { UnifiedEntitySchema, UnifiedFieldSchema, UnifiedFieldType } from '../schema'
13
+
14
+ /**
15
+ * Default path patterns for entity extraction
16
+ * Matches common REST patterns: /api/entities and /api/entities/{id}
17
+ */
18
+ const DEFAULT_PATH_PATTERNS: RegExp[] = [
19
+ /^\/api\/([a-z-]+)\/?$/, // /api/users/ or /api/users
20
+ /^\/api\/([a-z-]+)\/\{[^}]+\}$/, // /api/users/{id}
21
+ ]
22
+
23
+ /**
24
+ * HTTP methods that indicate CRUD operations
25
+ */
26
+ const CRUD_METHODS: Readonly<Record<string, string>> = Object.freeze({
27
+ get: 'read',
28
+ post: 'create',
29
+ put: 'update',
30
+ patch: 'update',
31
+ delete: 'delete',
32
+ })
33
+
34
+ /**
35
+ * Filter function for OpenAPI operations
36
+ */
37
+ export type OperationFilter = (path: string, method: string, operation: OpenAPIOperation) => boolean
38
+
39
+ /**
40
+ * OpenAPI Connector specific options
41
+ */
42
+ export interface OpenAPIConnectorOptions extends ConnectorOptions {
43
+ /** Path patterns to match entities (default: DEFAULT_PATH_PATTERNS) */
44
+ pathPatterns?: RegExp[]
45
+ /** Property name wrapping response data (default: 'data') */
46
+ dataWrapper?: string
47
+ /** Filter function for operations */
48
+ operationFilter?: OperationFilter | null
49
+ /** Custom type mappings for FieldMapper */
50
+ customMappings?: CustomMappings
51
+ }
52
+
53
+ /**
54
+ * Working data structure for entity extraction
55
+ */
56
+ interface EntityWorkingData {
57
+ /** Entity name */
58
+ name: string
59
+ /** Collection endpoint */
60
+ endpoint: string
61
+ /** Detected ID field from path parameter */
62
+ idField: string | null
63
+ /** Merged raw JSON Schema */
64
+ rawSchema: OpenAPISchema | null
65
+ /** Extracted fields */
66
+ fields: Map<string, UnifiedFieldSchema>
67
+ }
68
+
69
+ /**
70
+ * OpenAPI schema type
71
+ */
72
+ interface OpenAPISchema {
73
+ type?: string
74
+ format?: string
75
+ properties?: Record<string, OpenAPISchema>
76
+ items?: OpenAPISchema
77
+ required?: string[]
78
+ $ref?: string
79
+ description?: string
80
+ enum?: string[]
81
+ default?: unknown
82
+ readOnly?: boolean
83
+ [key: string]: unknown
84
+ }
85
+
86
+ /**
87
+ * OpenAPI operation type
88
+ */
89
+ interface OpenAPIOperation {
90
+ responses?: Record<
91
+ string,
92
+ {
93
+ content?: Record<
94
+ string,
95
+ {
96
+ schema?: OpenAPISchema
97
+ }
98
+ >
99
+ }
100
+ >
101
+ requestBody?: {
102
+ content?: Record<
103
+ string,
104
+ {
105
+ schema?: OpenAPISchema
106
+ }
107
+ >
108
+ }
109
+ tags?: string[]
110
+ [key: string]: unknown
111
+ }
112
+
113
+ /**
114
+ * OpenAPI path item type
115
+ */
116
+ interface OpenAPIPathItem {
117
+ get?: OpenAPIOperation
118
+ post?: OpenAPIOperation
119
+ put?: OpenAPIOperation
120
+ patch?: OpenAPIOperation
121
+ delete?: OpenAPIOperation
122
+ [key: string]: unknown
123
+ }
124
+
125
+ /**
126
+ * OpenAPI specification type
127
+ */
128
+ interface OpenAPISpec {
129
+ openapi?: string
130
+ paths: Record<string, OpenAPIPathItem>
131
+ components?: {
132
+ schemas?: Record<string, OpenAPISchema>
133
+ [key: string]: unknown
134
+ }
135
+ [key: string]: unknown
136
+ }
137
+
138
+ /**
139
+ * OpenAPI Connector for parsing OpenAPI 3.x specs into UnifiedEntitySchema.
140
+ *
141
+ * @extends BaseConnector
142
+ *
143
+ * @example
144
+ * // Basic usage with defaults
145
+ * const connector = new OpenAPIConnector()
146
+ * const schemas = connector.parse(openapiSpec)
147
+ *
148
+ * @example
149
+ * // Custom path patterns for versioned API
150
+ * const connector = new OpenAPIConnector({
151
+ * pathPatterns: [
152
+ * /^\/api\/v1\/([a-z-]+)\/?$/,
153
+ * /^\/api\/v1\/([a-z-]+)\/\{[^}]+\}$/
154
+ * ]
155
+ * })
156
+ *
157
+ * @example
158
+ * // Filter to only public operations
159
+ * const connector = new OpenAPIConnector({
160
+ * operationFilter: (path, method, op) => !op.tags?.includes('internal')
161
+ * })
162
+ */
163
+ export class OpenAPIConnector extends BaseConnector {
164
+ /** Path patterns to match entities */
165
+ pathPatterns: RegExp[]
166
+
167
+ /** Property name wrapping response data */
168
+ dataWrapper: string
169
+
170
+ /** Filter function for operations */
171
+ operationFilter: OperationFilter | null
172
+
173
+ /** Custom type mappings */
174
+ customMappings: CustomMappings
175
+
176
+ /**
177
+ * Create a new OpenAPI connector
178
+ *
179
+ * @param options - Connector options
180
+ */
181
+ constructor(options: OpenAPIConnectorOptions = {}) {
182
+ super(options)
183
+
184
+ this.pathPatterns = options.pathPatterns || DEFAULT_PATH_PATTERNS
185
+ this.dataWrapper = options.dataWrapper ?? 'data'
186
+ this.operationFilter = options.operationFilter || null
187
+ this.customMappings = options.customMappings || {}
188
+ }
189
+
190
+ /**
191
+ * Parse OpenAPI spec into UnifiedEntitySchema array
192
+ *
193
+ * @param source - OpenAPI 3.x specification object
194
+ * @returns Parsed entity schemas
195
+ * @throws If source is invalid or parsing fails in strict mode
196
+ *
197
+ * @example
198
+ * const schemas = connector.parse({
199
+ * openapi: '3.0.0',
200
+ * paths: { '/api/users': { get: { ... } } }
201
+ * })
202
+ */
203
+ parse(source: unknown): UnifiedEntitySchema[] {
204
+ const spec = source as OpenAPISpec
205
+ this._validateSource(spec)
206
+ const entities = this._extractEntities(spec)
207
+ return Array.from(entities.values())
208
+ }
209
+
210
+ /**
211
+ * Parse with warnings for detailed feedback
212
+ *
213
+ * @param source - OpenAPI 3.x specification object
214
+ * @returns Schemas and warnings
215
+ */
216
+ parseWithWarnings(source: unknown): ParseResult {
217
+ const warnings: ParseWarning[] = []
218
+ const spec = source as OpenAPISpec
219
+
220
+ this._validateSource(spec)
221
+ const entities = this._extractEntities(spec, warnings)
222
+
223
+ return {
224
+ schemas: Array.from(entities.values()),
225
+ warnings,
226
+ }
227
+ }
228
+
229
+ /**
230
+ * Validate OpenAPI source object
231
+ *
232
+ * @private
233
+ * @param source - Source to validate
234
+ * @throws If source is invalid
235
+ */
236
+ private _validateSource(source: OpenAPISpec): void {
237
+ if (!source || typeof source !== 'object') {
238
+ throw new Error(`${this.name}: source must be an object`)
239
+ }
240
+ if (!source.paths || typeof source.paths !== 'object') {
241
+ throw new Error(`${this.name}: source must have paths object`)
242
+ }
243
+ }
244
+
245
+ /**
246
+ * Extract all entities from OpenAPI spec
247
+ *
248
+ * @private
249
+ * @param source - OpenAPI spec
250
+ * @param warnings - Warning collector
251
+ * @returns Entity map
252
+ */
253
+ private _extractEntities(
254
+ source: OpenAPISpec,
255
+ warnings: ParseWarning[] = []
256
+ ): Map<string, UnifiedEntitySchema> {
257
+ const entityData: Map<string, EntityWorkingData> = new Map()
258
+
259
+ for (const [path, pathItem] of Object.entries(source.paths)) {
260
+ const entityName = this._extractEntityName(path)
261
+ if (!entityName) continue
262
+
263
+ if (!entityData.has(entityName)) {
264
+ entityData.set(entityName, {
265
+ name: entityName,
266
+ endpoint: this._buildEndpoint(path),
267
+ idField: null,
268
+ rawSchema: null,
269
+ fields: new Map(),
270
+ })
271
+ }
272
+
273
+ // Detect idField from path parameter (e.g., /users/{uuid} → 'uuid')
274
+ const data = entityData.get(entityName)!
275
+ const detectedIdField = this._extractIdField(path)
276
+ if (detectedIdField && !data.idField) {
277
+ data.idField = detectedIdField
278
+ }
279
+
280
+ this._processPath(source, path, pathItem, data, warnings)
281
+ }
282
+
283
+ // Convert working data to UnifiedEntitySchema
284
+ return this._convertToSchemas(entityData)
285
+ }
286
+
287
+ /**
288
+ * Extract entity name from path using configured patterns
289
+ *
290
+ * @private
291
+ * @param path - API path
292
+ * @returns Entity name or null
293
+ */
294
+ private _extractEntityName(path: string): string | null {
295
+ for (const pattern of this.pathPatterns) {
296
+ const match = path.match(pattern)
297
+ if (match && match[1]) {
298
+ return match[1]
299
+ }
300
+ }
301
+ return null
302
+ }
303
+
304
+ /**
305
+ * Build endpoint from path (use collection path, not item path)
306
+ *
307
+ * @private
308
+ * @param path - API path
309
+ * @returns Collection endpoint
310
+ */
311
+ private _buildEndpoint(path: string): string {
312
+ // Remove path parameter suffix to get collection endpoint
313
+ return path.replace(/\/\{[^}]+\}$/, '')
314
+ }
315
+
316
+ /**
317
+ * Extract idField from path parameter (e.g., /users/{uuid} → 'uuid')
318
+ *
319
+ * @private
320
+ * @param path - API path
321
+ * @returns Parameter name or null
322
+ */
323
+ private _extractIdField(path: string): string | null {
324
+ const match = path.match(/\/\{([^}]+)\}$/)
325
+ return match?.[1] ?? null
326
+ }
327
+
328
+ /**
329
+ * Process a path and extract schema info
330
+ *
331
+ * @private
332
+ * @param spec - Full OpenAPI spec (for $ref resolution)
333
+ * @param path - API path
334
+ * @param pathItem - OpenAPI path item
335
+ * @param entity - Working entity data
336
+ * @param warnings - Warning collector
337
+ */
338
+ private _processPath(
339
+ spec: OpenAPISpec,
340
+ path: string,
341
+ pathItem: OpenAPIPathItem,
342
+ entity: EntityWorkingData,
343
+ warnings: ParseWarning[]
344
+ ): void {
345
+ for (const [method, operation] of Object.entries(pathItem)) {
346
+ if (!CRUD_METHODS[method]) continue
347
+ if (typeof operation !== 'object') continue
348
+
349
+ const op = operation as OpenAPIOperation
350
+
351
+ // Apply operation filter
352
+ if (this.operationFilter && !this.operationFilter(path, method, op)) {
353
+ continue
354
+ }
355
+
356
+ const isList = this._isListOperation(path, method, op, spec)
357
+
358
+ // Extract response schema
359
+ const responseSchema = this._extractResponseSchema(op, spec)
360
+ if (responseSchema) {
361
+ const itemSchema = isList ? this._extractArrayItemSchema(responseSchema, spec) : responseSchema
362
+
363
+ if (itemSchema) {
364
+ entity.rawSchema = this._mergeSchemas(entity.rawSchema, itemSchema)
365
+ this._extractFields(itemSchema, entity.fields, spec, warnings, path)
366
+ }
367
+ }
368
+
369
+ // Extract request body schema
370
+ if (op.requestBody) {
371
+ const requestSchema = this._extractRequestSchema(op, spec)
372
+ if (requestSchema) {
373
+ this._extractFields(requestSchema, entity.fields, spec, warnings, path)
374
+ }
375
+ }
376
+ }
377
+ }
378
+
379
+ /**
380
+ * Check if operation returns a list
381
+ *
382
+ * @private
383
+ * @param path - API path
384
+ * @param method - HTTP method
385
+ * @param operation - OpenAPI operation
386
+ * @param spec - Full spec for ref resolution
387
+ * @returns True if list operation
388
+ */
389
+ private _isListOperation(
390
+ path: string,
391
+ method: string,
392
+ operation: OpenAPIOperation,
393
+ spec: OpenAPISpec
394
+ ): boolean {
395
+ // GET on collection path (no path parameter) is typically list
396
+ if (method === 'get' && !path.includes('{')) {
397
+ return true
398
+ }
399
+
400
+ // Check if response schema is array
401
+ const schema = this._extractResponseSchema(operation, spec)
402
+ if (schema?.type === 'array') {
403
+ return true
404
+ }
405
+
406
+ // Check for pagination indicators in full response
407
+ const fullSchema = this._extractFullResponseSchema(operation, spec)
408
+ if (fullSchema?.properties?.pagination || fullSchema?.properties?.meta) {
409
+ return true
410
+ }
411
+
412
+ return false
413
+ }
414
+
415
+ /**
416
+ * Extract response schema (unwrap from data wrapper)
417
+ *
418
+ * @private
419
+ * @param operation - OpenAPI operation
420
+ * @param spec - Full spec for ref resolution
421
+ * @returns Extracted schema
422
+ */
423
+ private _extractResponseSchema(
424
+ operation: OpenAPIOperation,
425
+ spec: OpenAPISpec
426
+ ): OpenAPISchema | null {
427
+ const fullSchema = this._extractFullResponseSchema(operation, spec)
428
+ if (!fullSchema) return null
429
+
430
+ // Unwrap from configured data wrapper
431
+ if (this.dataWrapper) {
432
+ const wrapped = fullSchema.properties?.[this.dataWrapper]
433
+ if (wrapped) {
434
+ return wrapped.$ref ? this._resolveRef(wrapped.$ref, spec) : wrapped
435
+ }
436
+ }
437
+
438
+ return fullSchema
439
+ }
440
+
441
+ /**
442
+ * Extract full response schema without unwrapping
443
+ *
444
+ * @private
445
+ * @param operation - OpenAPI operation
446
+ * @param spec - Full spec for ref resolution
447
+ * @returns Full response schema
448
+ */
449
+ private _extractFullResponseSchema(
450
+ operation: OpenAPIOperation,
451
+ spec: OpenAPISpec
452
+ ): OpenAPISchema | null {
453
+ const response = operation.responses?.['200'] || operation.responses?.['201']
454
+ if (!response) return null
455
+
456
+ const content = response.content?.['application/json']
457
+ if (!content) return null
458
+
459
+ if (content.schema?.$ref) {
460
+ return this._resolveRef(content.schema.$ref, spec)
461
+ }
462
+
463
+ return content.schema || null
464
+ }
465
+
466
+ /**
467
+ * Extract request body schema
468
+ *
469
+ * @private
470
+ * @param operation - OpenAPI operation
471
+ * @param spec - Full spec for ref resolution
472
+ * @returns Request schema
473
+ */
474
+ private _extractRequestSchema(
475
+ operation: OpenAPIOperation,
476
+ spec: OpenAPISpec
477
+ ): OpenAPISchema | null {
478
+ const content = operation.requestBody?.content?.['application/json']
479
+ if (!content) return null
480
+
481
+ if (content.schema?.$ref) {
482
+ return this._resolveRef(content.schema.$ref, spec)
483
+ }
484
+
485
+ return content.schema || null
486
+ }
487
+
488
+ /**
489
+ * Resolve a $ref to its schema
490
+ *
491
+ * @private
492
+ * @param ref - Reference string (e.g., '#/components/schemas/User')
493
+ * @param spec - Full spec
494
+ * @returns Resolved schema
495
+ */
496
+ private _resolveRef(ref: string, spec: OpenAPISpec): OpenAPISchema | null {
497
+ if (!ref.startsWith('#/')) return null
498
+
499
+ const parts = ref.slice(2).split('/')
500
+ let current: unknown = spec
501
+
502
+ for (const part of parts) {
503
+ if (!current || typeof current !== 'object') return null
504
+ current = (current as Record<string, unknown>)[part]
505
+ }
506
+
507
+ return (current as OpenAPISchema) || null
508
+ }
509
+
510
+ /**
511
+ * Extract item schema from array schema
512
+ *
513
+ * @private
514
+ * @param schema - Array schema
515
+ * @param spec - Full spec for ref resolution
516
+ * @returns Item schema
517
+ */
518
+ private _extractArrayItemSchema(schema: OpenAPISchema, spec: OpenAPISpec): OpenAPISchema | null {
519
+ if (schema.type === 'array' && schema.items) {
520
+ if (schema.items.$ref) {
521
+ return this._resolveRef(schema.items.$ref, spec)
522
+ }
523
+ return schema.items
524
+ }
525
+ return schema
526
+ }
527
+
528
+ /**
529
+ * Extract fields from JSON Schema and add to field map
530
+ *
531
+ * @private
532
+ * @param schema - JSON Schema object
533
+ * @param fields - Field map to populate
534
+ * @param spec - Full spec for ref resolution
535
+ * @param warnings - Warning collector
536
+ * @param sourcePath - Source path for warnings
537
+ * @param prefix - Prefix for nested field names
538
+ */
539
+ private _extractFields(
540
+ schema: OpenAPISchema,
541
+ fields: Map<string, UnifiedFieldSchema>,
542
+ spec: OpenAPISpec,
543
+ warnings: ParseWarning[],
544
+ sourcePath: string,
545
+ prefix: string = ''
546
+ ): void {
547
+ if (!schema || !schema.properties) {
548
+ return
549
+ }
550
+
551
+ const required = new Set(schema.required || [])
552
+
553
+ for (const [name, propSchema] of Object.entries(schema.properties)) {
554
+ const fieldName = prefix ? `${prefix}.${name}` : name
555
+
556
+ // Resolve $ref in property schema
557
+ const resolvedSchema = propSchema.$ref ? this._resolveRef(propSchema.$ref, spec) : propSchema
558
+
559
+ if (!resolvedSchema) {
560
+ warnings.push({
561
+ path: `${sourcePath}#${fieldName}`,
562
+ message: `Could not resolve schema for field: ${fieldName}`,
563
+ code: 'UNRESOLVED_REF',
564
+ })
565
+ continue
566
+ }
567
+
568
+ // Use FieldMapper for type conversion
569
+ const type = getDefaultType(resolvedSchema, this.customMappings)
570
+
571
+ const field: UnifiedFieldSchema = {
572
+ name: fieldName,
573
+ type: type as UnifiedFieldType,
574
+ required: required.has(name),
575
+ readOnly: resolvedSchema.readOnly || false,
576
+ }
577
+
578
+ // Add optional properties
579
+ if (resolvedSchema.description) {
580
+ field.label = resolvedSchema.description
581
+ }
582
+ if (resolvedSchema.format) {
583
+ field.format = resolvedSchema.format
584
+ }
585
+ if (resolvedSchema.enum) {
586
+ field.enum = resolvedSchema.enum
587
+ }
588
+ if (resolvedSchema.default !== undefined) {
589
+ field.default = resolvedSchema.default
590
+ }
591
+
592
+ // Merge with existing field (may have more info from other operations)
593
+ if (fields.has(fieldName)) {
594
+ const existing = fields.get(fieldName)!
595
+ this._mergeField(existing, field)
596
+ } else {
597
+ fields.set(fieldName, field)
598
+ }
599
+
600
+ // Handle nested objects (one level only to avoid complexity)
601
+ if (resolvedSchema.type === 'object' && resolvedSchema.properties && !prefix) {
602
+ this._extractFields(resolvedSchema, fields, spec, warnings, sourcePath, fieldName)
603
+ }
604
+ }
605
+ }
606
+
607
+ /**
608
+ * Merge field info from secondary source into primary
609
+ *
610
+ * @private
611
+ * @param primary - Primary field to merge into
612
+ * @param secondary - Secondary field with additional info
613
+ */
614
+ private _mergeField(primary: UnifiedFieldSchema, secondary: UnifiedFieldSchema): void {
615
+ if (!primary.label && secondary.label) {
616
+ primary.label = secondary.label
617
+ }
618
+ if (!primary.enum && secondary.enum) {
619
+ primary.enum = secondary.enum
620
+ }
621
+ if (!primary.required && secondary.required) {
622
+ primary.required = true
623
+ }
624
+ if (primary.default === undefined && secondary.default !== undefined) {
625
+ primary.default = secondary.default
626
+ }
627
+ }
628
+
629
+ /**
630
+ * Merge two JSON Schemas
631
+ *
632
+ * @private
633
+ * @param base - Base schema
634
+ * @param other - Schema to merge
635
+ * @returns Merged schema
636
+ */
637
+ private _mergeSchemas(base: OpenAPISchema | null, other: OpenAPISchema): OpenAPISchema {
638
+ if (!base) return other
639
+ if (!other) return base
640
+
641
+ const merged: OpenAPISchema = { ...base }
642
+
643
+ if (other.properties) {
644
+ merged.properties = {
645
+ ...(base.properties || {}),
646
+ ...other.properties,
647
+ }
648
+ }
649
+
650
+ if (other.required) {
651
+ merged.required = [...new Set([...(base.required || []), ...other.required])]
652
+ }
653
+
654
+ return merged
655
+ }
656
+
657
+ /**
658
+ * Convert working entity data to UnifiedEntitySchema
659
+ *
660
+ * @private
661
+ * @param entityData - Working data
662
+ * @returns Final schemas
663
+ */
664
+ private _convertToSchemas(
665
+ entityData: Map<string, EntityWorkingData>
666
+ ): Map<string, UnifiedEntitySchema> {
667
+ const schemas: Map<string, UnifiedEntitySchema> = new Map()
668
+
669
+ for (const [name, data] of entityData) {
670
+ const schema: UnifiedEntitySchema = {
671
+ name: data.name,
672
+ endpoint: data.endpoint,
673
+ fields: Object.fromEntries(data.fields),
674
+ }
675
+
676
+ // Add idField if detected from path parameter
677
+ if (data.idField) {
678
+ schema.idField = data.idField
679
+ }
680
+
681
+ // Add extensions if configured
682
+ if (Object.keys(this.extensions).length > 0) {
683
+ schema.extensions = { ...this.extensions }
684
+ }
685
+
686
+ schemas.set(name, schema)
687
+ }
688
+
689
+ return schemas
690
+ }
691
+ }
692
+
693
+ export default OpenAPIConnector