qdadm 0.28.0 → 0.30.0
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/package.json +13 -3
- package/src/components/index.js +5 -3
- package/src/composables/index.js +1 -1
- package/src/composables/useSSEBridge.js +118 -0
- package/src/editors/index.js +12 -0
- package/src/gen/FieldMapper.js +116 -0
- package/src/gen/StorageProfileFactory.js +109 -0
- package/src/gen/connectors/BaseConnector.js +142 -0
- package/src/gen/connectors/ManualConnector.js +385 -0
- package/src/gen/connectors/ManualConnector.test.js +499 -0
- package/src/gen/connectors/OpenAPIConnector.js +568 -0
- package/src/gen/connectors/OpenAPIConnector.test.js +737 -0
- package/src/gen/connectors/__fixtures__/sample-openapi.json +311 -0
- package/src/gen/connectors/index.js +11 -0
- package/src/gen/createManagers.js +224 -0
- package/src/gen/decorators.js +129 -0
- package/src/gen/generateManagers.js +266 -0
- package/src/gen/generateManagers.test.js +358 -0
- package/src/gen/index.js +48 -0
- package/src/gen/schema.js +221 -0
- package/src/gen/vite-plugin.js +105 -0
- package/src/generated/managers/testManager.js +45 -0
- package/src/kernel/Kernel.js +228 -3
- package/src/kernel/SSEBridge.js +354 -0
- package/src/kernel/SignalBus.js +8 -0
- package/src/kernel/index.js +5 -0
- package/src/composables/useSSE.js +0 -212
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "qdadm",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.30.0",
|
|
4
4
|
"description": "Vue 3 framework for admin dashboards with PrimeVue",
|
|
5
5
|
"author": "quazardous",
|
|
6
6
|
"license": "MIT",
|
|
@@ -22,10 +22,13 @@
|
|
|
22
22
|
".": "./src/index.js",
|
|
23
23
|
"./composables": "./src/composables/index.js",
|
|
24
24
|
"./components": "./src/components/index.js",
|
|
25
|
+
"./editors": "./src/editors/index.js",
|
|
25
26
|
"./module": "./src/module/index.js",
|
|
26
27
|
"./utils": "./src/utils/index.js",
|
|
27
28
|
"./styles": "./src/styles/index.scss",
|
|
28
|
-
"./styles/breakpoints": "./src/styles/_breakpoints.scss"
|
|
29
|
+
"./styles/breakpoints": "./src/styles/_breakpoints.scss",
|
|
30
|
+
"./gen": "./src/gen/index.js",
|
|
31
|
+
"./gen/vite-plugin": "./src/gen/vite-plugin.js"
|
|
29
32
|
},
|
|
30
33
|
"files": [
|
|
31
34
|
"src",
|
|
@@ -38,10 +41,17 @@
|
|
|
38
41
|
"peerDependencies": {
|
|
39
42
|
"pinia": "^2.0.0",
|
|
40
43
|
"primevue": "^4.0.0",
|
|
41
|
-
"vanilla-jsoneditor": "^0.23.0",
|
|
42
44
|
"vue": "^3.3.0",
|
|
43
45
|
"vue-router": "^4.0.0"
|
|
44
46
|
},
|
|
47
|
+
"peerDependenciesMeta": {
|
|
48
|
+
"vanilla-jsoneditor": {
|
|
49
|
+
"optional": true
|
|
50
|
+
}
|
|
51
|
+
},
|
|
52
|
+
"optionalDependencies": {
|
|
53
|
+
"vanilla-jsoneditor": "^0.23.0"
|
|
54
|
+
},
|
|
45
55
|
"keywords": [
|
|
46
56
|
"vue",
|
|
47
57
|
"vue3",
|
package/src/components/index.js
CHANGED
|
@@ -32,15 +32,17 @@ export { default as ActionButtons } from './lists/ActionButtons.vue'
|
|
|
32
32
|
export { default as ActionColumn } from './lists/ActionColumn.vue'
|
|
33
33
|
export { default as FilterBar } from './lists/FilterBar.vue'
|
|
34
34
|
|
|
35
|
-
// Editors
|
|
35
|
+
// Editors (vanilla-jsoneditor free)
|
|
36
36
|
export { default as KeyValueEditor } from './editors/KeyValueEditor.vue'
|
|
37
37
|
export { default as LanguageEditor } from './editors/LanguageEditor.vue'
|
|
38
38
|
export { default as ScopeEditor } from './editors/ScopeEditor.vue'
|
|
39
|
-
export { default as VanillaJsonEditor } from './editors/VanillaJsonEditor.vue'
|
|
40
39
|
export { default as JsonEditorFoldable } from './editors/JsonEditorFoldable.vue'
|
|
41
|
-
export { default as JsonStructuredField } from './editors/JsonStructuredField.vue'
|
|
42
40
|
export { default as JsonViewer } from './editors/JsonViewer.vue'
|
|
43
41
|
|
|
42
|
+
// NOTE: VanillaJsonEditor and JsonStructuredField require vanilla-jsoneditor
|
|
43
|
+
// Import from 'qdadm/editors' instead:
|
|
44
|
+
// import { VanillaJsonEditor, JsonStructuredField } from 'qdadm/editors'
|
|
45
|
+
|
|
44
46
|
// Dialogs
|
|
45
47
|
export { default as SimpleDialog } from './dialogs/SimpleDialog.vue'
|
|
46
48
|
export { default as MultiStepDialog } from './dialogs/MultiStepDialog.vue'
|
package/src/composables/index.js
CHANGED
|
@@ -20,5 +20,5 @@ export { useSignals } from './useSignals'
|
|
|
20
20
|
export { useZoneRegistry } from './useZoneRegistry'
|
|
21
21
|
export { useHooks } from './useHooks'
|
|
22
22
|
export { useLayoutResolver, createLayoutComponents, layoutMeta, LAYOUT_TYPES } from './useLayoutResolver'
|
|
23
|
-
export {
|
|
23
|
+
export { useSSEBridge } from './useSSEBridge'
|
|
24
24
|
export { useDeferred, useDeferredValue, DEFERRED_INJECTION_KEY } from './useDeferred'
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* useSSEBridge - Composable for SSE connection status
|
|
3
|
+
*
|
|
4
|
+
* Provides reactive connection status for SSEBridge.
|
|
5
|
+
* Components use this instead of managing their own EventSource.
|
|
6
|
+
*
|
|
7
|
+
* Usage:
|
|
8
|
+
* ```js
|
|
9
|
+
* const { connected, reconnecting, error, onEvent } = useSSEBridge()
|
|
10
|
+
*
|
|
11
|
+
* // Reactive connection status
|
|
12
|
+
* watchEffect(() => {
|
|
13
|
+
* if (connected.value) {
|
|
14
|
+
* console.log('SSE connected')
|
|
15
|
+
* }
|
|
16
|
+
* })
|
|
17
|
+
*
|
|
18
|
+
* // Subscribe to specific event (auto-cleanup on unmount)
|
|
19
|
+
* onEvent('task:completed', (payload) => {
|
|
20
|
+
* console.log('Task completed:', payload.data)
|
|
21
|
+
* })
|
|
22
|
+
* ```
|
|
23
|
+
*/
|
|
24
|
+
|
|
25
|
+
import { ref, inject, onUnmounted, onMounted } from 'vue'
|
|
26
|
+
import { SSE_SIGNALS } from '../kernel/SSEBridge.js'
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* @typedef {object} UseSSEBridgeReturn
|
|
30
|
+
* @property {import('vue').Ref<boolean>} connected - Connection status
|
|
31
|
+
* @property {import('vue').Ref<boolean>} reconnecting - Reconnection in progress
|
|
32
|
+
* @property {import('vue').Ref<string|null>} error - Last error message
|
|
33
|
+
* @property {function} onEvent - Subscribe to SSE event (auto-cleanup)
|
|
34
|
+
* @property {function} onAnyEvent - Subscribe to all SSE events (wildcard)
|
|
35
|
+
*/
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Composable for SSEBridge connection status
|
|
39
|
+
* @returns {UseSSEBridgeReturn}
|
|
40
|
+
*/
|
|
41
|
+
export function useSSEBridge() {
|
|
42
|
+
const signals = inject('qdadmSignals')
|
|
43
|
+
const sseBridge = inject('qdadmSSEBridge', null)
|
|
44
|
+
|
|
45
|
+
const connected = ref(sseBridge?.isConnected() ?? false)
|
|
46
|
+
const reconnecting = ref(sseBridge?.isReconnecting() ?? false)
|
|
47
|
+
const error = ref(null)
|
|
48
|
+
|
|
49
|
+
const unbinders = []
|
|
50
|
+
|
|
51
|
+
// Track connection status via signals
|
|
52
|
+
if (signals) {
|
|
53
|
+
const unbindConnected = signals.on(SSE_SIGNALS.CONNECTED, () => {
|
|
54
|
+
connected.value = true
|
|
55
|
+
reconnecting.value = false
|
|
56
|
+
error.value = null
|
|
57
|
+
})
|
|
58
|
+
unbinders.push(unbindConnected)
|
|
59
|
+
|
|
60
|
+
const unbindDisconnected = signals.on(SSE_SIGNALS.DISCONNECTED, () => {
|
|
61
|
+
connected.value = false
|
|
62
|
+
})
|
|
63
|
+
unbinders.push(unbindDisconnected)
|
|
64
|
+
|
|
65
|
+
const unbindError = signals.on(SSE_SIGNALS.ERROR, (payload) => {
|
|
66
|
+
error.value = payload.error
|
|
67
|
+
reconnecting.value = true
|
|
68
|
+
})
|
|
69
|
+
unbinders.push(unbindError)
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* Subscribe to a specific SSE event
|
|
74
|
+
* Automatically cleans up on component unmount.
|
|
75
|
+
*
|
|
76
|
+
* @param {string} eventName - SSE event name (without 'sse:' prefix)
|
|
77
|
+
* @param {function} handler - Event handler (payload) => void
|
|
78
|
+
* @returns {function} Unbind function
|
|
79
|
+
*/
|
|
80
|
+
const onEvent = (eventName, handler) => {
|
|
81
|
+
if (!signals) return () => {}
|
|
82
|
+
|
|
83
|
+
const signal = `sse:${eventName}`
|
|
84
|
+
const unbind = signals.on(signal, handler)
|
|
85
|
+
unbinders.push(unbind)
|
|
86
|
+
|
|
87
|
+
return unbind
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* Subscribe to all SSE events (wildcard)
|
|
92
|
+
* Automatically cleans up on component unmount.
|
|
93
|
+
*
|
|
94
|
+
* @param {function} handler - Event handler (payload) => void
|
|
95
|
+
* @returns {function} Unbind function
|
|
96
|
+
*/
|
|
97
|
+
const onAnyEvent = (handler) => {
|
|
98
|
+
if (!signals) return () => {}
|
|
99
|
+
|
|
100
|
+
const unbind = signals.on('sse:*', handler)
|
|
101
|
+
unbinders.push(unbind)
|
|
102
|
+
|
|
103
|
+
return unbind
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
// Cleanup on unmount
|
|
107
|
+
onUnmounted(() => {
|
|
108
|
+
unbinders.forEach(unbind => unbind())
|
|
109
|
+
})
|
|
110
|
+
|
|
111
|
+
return {
|
|
112
|
+
connected,
|
|
113
|
+
reconnecting,
|
|
114
|
+
error,
|
|
115
|
+
onEvent,
|
|
116
|
+
onAnyEvent
|
|
117
|
+
}
|
|
118
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* qdadm/editors - Optional editors that require vanilla-jsoneditor
|
|
3
|
+
*
|
|
4
|
+
* Import from 'qdadm/editors' only if you need VanillaJsonEditor.
|
|
5
|
+
* This keeps vanilla-jsoneditor out of the main bundle.
|
|
6
|
+
*
|
|
7
|
+
* Usage:
|
|
8
|
+
* import { VanillaJsonEditor, JsonStructuredField } from 'qdadm/editors'
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
export { default as VanillaJsonEditor } from '../components/editors/VanillaJsonEditor.vue'
|
|
12
|
+
export { default as JsonStructuredField } from '../components/editors/JsonStructuredField.vue'
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Field Type Mapper
|
|
3
|
+
*
|
|
4
|
+
* Maps JSON Schema types and formats to UnifiedFieldSchema types.
|
|
5
|
+
* Extracted from Skybot and Faketual to eliminate duplication.
|
|
6
|
+
*
|
|
7
|
+
* Mapping priority:
|
|
8
|
+
* 1. Enum detection (schema.enum present -> 'select')
|
|
9
|
+
* 2. Custom mappings (user-provided)
|
|
10
|
+
* 3. Format-specific mappings (e.g., string + format:email -> email)
|
|
11
|
+
* 4. Base type mappings (e.g., string -> text)
|
|
12
|
+
*
|
|
13
|
+
* @module gen/FieldMapper
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Base type mappings: JSON Schema type -> UnifiedFieldSchema type
|
|
18
|
+
*
|
|
19
|
+
* @type {Readonly<Record<string, import('./schema.js').UnifiedFieldType>>}
|
|
20
|
+
*/
|
|
21
|
+
export const BASE_TYPE_MAPPINGS = Object.freeze({
|
|
22
|
+
string: 'text',
|
|
23
|
+
integer: 'number',
|
|
24
|
+
number: 'number',
|
|
25
|
+
boolean: 'boolean',
|
|
26
|
+
array: 'array',
|
|
27
|
+
object: 'object'
|
|
28
|
+
})
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Format-specific mappings: JSON Schema format -> UnifiedFieldSchema type
|
|
32
|
+
*
|
|
33
|
+
* These take precedence over base type mappings when a format is specified.
|
|
34
|
+
*
|
|
35
|
+
* @type {Readonly<Record<string, import('./schema.js').UnifiedFieldType>>}
|
|
36
|
+
*/
|
|
37
|
+
export const FORMAT_MAPPINGS = Object.freeze({
|
|
38
|
+
email: 'email',
|
|
39
|
+
'date-time': 'datetime',
|
|
40
|
+
date: 'date',
|
|
41
|
+
uri: 'url',
|
|
42
|
+
url: 'url',
|
|
43
|
+
uuid: 'uuid',
|
|
44
|
+
password: 'text'
|
|
45
|
+
})
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Custom mappings override structure
|
|
49
|
+
*
|
|
50
|
+
* @typedef {object} CustomMappings
|
|
51
|
+
* @property {Record<string, string>} [types] - Custom type mappings { jsonSchemaType: unifiedType }
|
|
52
|
+
* @property {Record<string, string>} [formats] - Custom format mappings { jsonSchemaFormat: unifiedType }
|
|
53
|
+
*/
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Minimal JSON Schema property definition for type detection
|
|
57
|
+
*
|
|
58
|
+
* @typedef {object} SchemaProperty
|
|
59
|
+
* @property {string} [type] - JSON Schema type (string, integer, number, boolean, array, object)
|
|
60
|
+
* @property {string} [format] - JSON Schema format (email, date-time, uuid, etc.)
|
|
61
|
+
* @property {Array<*>} [enum] - Enumeration values
|
|
62
|
+
*/
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Get the UnifiedFieldSchema type for a JSON Schema property
|
|
66
|
+
*
|
|
67
|
+
* @param {SchemaProperty} schema - JSON Schema property definition
|
|
68
|
+
* @param {CustomMappings} [customMappings] - Optional custom type/format mappings
|
|
69
|
+
* @returns {import('./schema.js').UnifiedFieldType | 'select'} - Unified field type
|
|
70
|
+
*
|
|
71
|
+
* @example
|
|
72
|
+
* // Basic type mapping
|
|
73
|
+
* getDefaultType({ type: 'string' }) // 'text'
|
|
74
|
+
* getDefaultType({ type: 'integer' }) // 'number'
|
|
75
|
+
*
|
|
76
|
+
* @example
|
|
77
|
+
* // Format takes precedence
|
|
78
|
+
* getDefaultType({ type: 'string', format: 'email' }) // 'email'
|
|
79
|
+
* getDefaultType({ type: 'string', format: 'uuid' }) // 'uuid'
|
|
80
|
+
*
|
|
81
|
+
* @example
|
|
82
|
+
* // Enum detection
|
|
83
|
+
* getDefaultType({ type: 'string', enum: ['a', 'b'] }) // 'select'
|
|
84
|
+
*
|
|
85
|
+
* @example
|
|
86
|
+
* // Custom mappings
|
|
87
|
+
* getDefaultType({ type: 'string', format: 'phone' }, {
|
|
88
|
+
* formats: { phone: 'text' }
|
|
89
|
+
* }) // 'text'
|
|
90
|
+
*/
|
|
91
|
+
export function getDefaultType(schema, customMappings = {}) {
|
|
92
|
+
const { type, format } = schema || {}
|
|
93
|
+
|
|
94
|
+
// Check for enum pattern first (returns 'select')
|
|
95
|
+
if (Array.isArray(schema?.enum) && schema.enum.length > 0) {
|
|
96
|
+
return 'select'
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
// Check custom format mapping
|
|
100
|
+
if (format && customMappings.formats?.[format]) {
|
|
101
|
+
return customMappings.formats[format]
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
// Check default format mapping
|
|
105
|
+
if (format && FORMAT_MAPPINGS[format]) {
|
|
106
|
+
return FORMAT_MAPPINGS[format]
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
// Check custom type mapping
|
|
110
|
+
if (type && customMappings.types?.[type]) {
|
|
111
|
+
return customMappings.types[type]
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
// Fall back to base type mapping, default to 'text'
|
|
115
|
+
return BASE_TYPE_MAPPINGS[type] || 'text'
|
|
116
|
+
}
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Storage Profile Factory Pattern
|
|
3
|
+
*
|
|
4
|
+
* A storage profile factory is a function that creates storage instances for a given endpoint.
|
|
5
|
+
* This pattern enables DRY backend configuration by centralizing storage creation logic
|
|
6
|
+
* (API client, base URL, headers, error handling) in one place.
|
|
7
|
+
*
|
|
8
|
+
* Instead of configuring each entity's storage individually, you define a profile factory
|
|
9
|
+
* once and pass it to createManagers(), which calls it with each entity's endpoint.
|
|
10
|
+
*
|
|
11
|
+
* @module gen/StorageProfileFactory
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Storage Profile Factory Options
|
|
16
|
+
*
|
|
17
|
+
* Per-entity options passed when creating storage from a profile factory.
|
|
18
|
+
* Allows overriding profile defaults for specific entities.
|
|
19
|
+
*
|
|
20
|
+
* @typedef {object} StorageProfileOptions
|
|
21
|
+
* @property {string} [entity] - Entity name (e.g., 'users', 'posts')
|
|
22
|
+
* @property {object} [client] - Override the profile's HTTP client for this entity
|
|
23
|
+
* @property {object} [headers] - Additional headers for this entity
|
|
24
|
+
* @property {boolean} [readOnly] - Force read-only mode (no create/update/delete)
|
|
25
|
+
* @property {Record<string, *>} [extensions] - Custom per-entity options
|
|
26
|
+
*/
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Storage Profile Factory
|
|
30
|
+
*
|
|
31
|
+
* A function that creates storage instances for a given endpoint.
|
|
32
|
+
* The factory encapsulates common storage configuration (API client, auth, base URL)
|
|
33
|
+
* and creates properly-configured storage adapters for each entity.
|
|
34
|
+
*
|
|
35
|
+
* @callback StorageProfileFactory
|
|
36
|
+
* @param {string} endpoint - Entity endpoint path (e.g., '/users', '/api/v1/posts')
|
|
37
|
+
* @param {StorageProfileOptions} [options] - Per-entity options to override profile defaults
|
|
38
|
+
* @returns {import('../entity/storage/IStorage.js').IStorage} Storage instance
|
|
39
|
+
*
|
|
40
|
+
* @example Basic factory using ApiStorage
|
|
41
|
+
* import axios from 'axios'
|
|
42
|
+
* import { ApiStorage } from 'qdadm'
|
|
43
|
+
*
|
|
44
|
+
* // Define profile factory with shared configuration
|
|
45
|
+
* const apiProfile = (endpoint, options = {}) => {
|
|
46
|
+
* return new ApiStorage({
|
|
47
|
+
* endpoint,
|
|
48
|
+
* client: axios.create({
|
|
49
|
+
* baseURL: 'https://api.example.com',
|
|
50
|
+
* headers: { 'Authorization': `Bearer ${getToken()}` }
|
|
51
|
+
* }),
|
|
52
|
+
* ...options
|
|
53
|
+
* })
|
|
54
|
+
* }
|
|
55
|
+
*
|
|
56
|
+
* // Use with createManagers (T00317)
|
|
57
|
+
* const managers = createManagers(schemas, {
|
|
58
|
+
* storageProfile: apiProfile
|
|
59
|
+
* })
|
|
60
|
+
*
|
|
61
|
+
* @example Factory with dynamic client resolution
|
|
62
|
+
* // For Vue/inject pattern - client resolved at call time
|
|
63
|
+
* const lazyApiProfile = (endpoint, options = {}) => {
|
|
64
|
+
* return new ApiStorage({
|
|
65
|
+
* endpoint,
|
|
66
|
+
* getClient: () => inject('apiClient'),
|
|
67
|
+
* ...options
|
|
68
|
+
* })
|
|
69
|
+
* }
|
|
70
|
+
*
|
|
71
|
+
* @example Factory for local development
|
|
72
|
+
* import { MemoryStorage } from 'qdadm'
|
|
73
|
+
*
|
|
74
|
+
* const memoryProfile = (endpoint, options = {}) => {
|
|
75
|
+
* const entity = options.entity || endpoint.replace(/^\//, '')
|
|
76
|
+
* return new MemoryStorage({
|
|
77
|
+
* initialData: mockData[entity] || []
|
|
78
|
+
* })
|
|
79
|
+
* }
|
|
80
|
+
*
|
|
81
|
+
* @example Factory with per-entity overrides
|
|
82
|
+
* const hybridProfile = (endpoint, options = {}) => {
|
|
83
|
+
* // Some entities use SDK, others use REST
|
|
84
|
+
* if (options.useSdk) {
|
|
85
|
+
* return new SdkStorage({
|
|
86
|
+
* sdk: options.sdk,
|
|
87
|
+
* collection: options.entity
|
|
88
|
+
* })
|
|
89
|
+
* }
|
|
90
|
+
* return new ApiStorage({
|
|
91
|
+
* endpoint,
|
|
92
|
+
* client: defaultClient
|
|
93
|
+
* })
|
|
94
|
+
* }
|
|
95
|
+
*
|
|
96
|
+
* @example Factory with environment-based switching
|
|
97
|
+
* const envAwareProfile = (endpoint, options = {}) => {
|
|
98
|
+
* if (import.meta.env.MODE === 'development') {
|
|
99
|
+
* return new MockApiStorage({ endpoint })
|
|
100
|
+
* }
|
|
101
|
+
* return new ApiStorage({
|
|
102
|
+
* endpoint,
|
|
103
|
+
* client: productionClient
|
|
104
|
+
* })
|
|
105
|
+
* }
|
|
106
|
+
*/
|
|
107
|
+
|
|
108
|
+
// Type-only module - no runtime exports
|
|
109
|
+
// Types are available via JSDoc when importing this module
|
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Base Connector Interface
|
|
3
|
+
*
|
|
4
|
+
* Abstract base class for schema connectors. Connectors parse various sources
|
|
5
|
+
* (OpenAPI specs, manual definitions, etc.) into UnifiedEntitySchema format.
|
|
6
|
+
*
|
|
7
|
+
* Subclasses must implement the `parse(source)` method with source-specific logic.
|
|
8
|
+
*
|
|
9
|
+
* @module gen/connectors/BaseConnector
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Connector options for customizing parsing behavior
|
|
14
|
+
*
|
|
15
|
+
* @typedef {object} ConnectorOptions
|
|
16
|
+
* @property {string} [name] - Connector instance name for debugging
|
|
17
|
+
* @property {boolean} [strict] - Throw on validation errors instead of warning (default: false)
|
|
18
|
+
* @property {Record<string, *>} [extensions] - Custom extension data passed to output schemas
|
|
19
|
+
*/
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Parse result with parsed schemas and any encountered issues
|
|
23
|
+
*
|
|
24
|
+
* @typedef {object} ParseResult
|
|
25
|
+
* @property {import('../schema.js').UnifiedEntitySchema[]} schemas - Parsed entity schemas
|
|
26
|
+
* @property {ParseWarning[]} warnings - Non-fatal issues encountered during parsing
|
|
27
|
+
*/
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Warning encountered during parsing
|
|
31
|
+
*
|
|
32
|
+
* @typedef {object} ParseWarning
|
|
33
|
+
* @property {string} path - Location in source where issue occurred
|
|
34
|
+
* @property {string} message - Description of the issue
|
|
35
|
+
* @property {string} [code] - Warning code for programmatic handling
|
|
36
|
+
*/
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Base connector class for parsing schema sources into UnifiedEntitySchema format.
|
|
40
|
+
*
|
|
41
|
+
* @abstract
|
|
42
|
+
* @example
|
|
43
|
+
* // Extending BaseConnector
|
|
44
|
+
* class ManualConnector extends BaseConnector {
|
|
45
|
+
* parse(source) {
|
|
46
|
+
* // Parse inline definitions
|
|
47
|
+
* return source.entities.map(e => this.transformEntity(e))
|
|
48
|
+
* }
|
|
49
|
+
* }
|
|
50
|
+
*
|
|
51
|
+
* @example
|
|
52
|
+
* // Using a connector
|
|
53
|
+
* const connector = new ManualConnector({ strict: true })
|
|
54
|
+
* const schemas = connector.parse(manualDefinitions)
|
|
55
|
+
*/
|
|
56
|
+
export class BaseConnector {
|
|
57
|
+
/**
|
|
58
|
+
* Create a new connector instance
|
|
59
|
+
*
|
|
60
|
+
* @param {ConnectorOptions} [options={}] - Connector configuration options
|
|
61
|
+
*/
|
|
62
|
+
constructor(options = {}) {
|
|
63
|
+
if (new.target === BaseConnector) {
|
|
64
|
+
throw new Error('BaseConnector is abstract and cannot be instantiated directly')
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/** @type {string} */
|
|
68
|
+
this.name = options.name || this.constructor.name
|
|
69
|
+
|
|
70
|
+
/** @type {boolean} */
|
|
71
|
+
this.strict = options.strict ?? false
|
|
72
|
+
|
|
73
|
+
/** @type {Record<string, *>} */
|
|
74
|
+
this.extensions = options.extensions || {}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* Parse a schema source into UnifiedEntitySchema format.
|
|
79
|
+
*
|
|
80
|
+
* This method must be implemented by subclasses to handle source-specific
|
|
81
|
+
* parsing logic. The source parameter format depends on the connector type:
|
|
82
|
+
* - ManualConnector: Object with entity/field definitions
|
|
83
|
+
* - OpenAPIConnector: OpenAPI 3.x specification object
|
|
84
|
+
*
|
|
85
|
+
* @abstract
|
|
86
|
+
* @param {*} source - Source data to parse (format depends on connector type)
|
|
87
|
+
* @returns {import('../schema.js').UnifiedEntitySchema[]} - Parsed entity schemas
|
|
88
|
+
* @throws {Error} - If parsing fails (in strict mode) or source is invalid
|
|
89
|
+
*
|
|
90
|
+
* @example
|
|
91
|
+
* // ManualConnector source format
|
|
92
|
+
* connector.parse({
|
|
93
|
+
* entities: [
|
|
94
|
+
* { name: 'users', endpoint: '/api/users', fields: { ... } }
|
|
95
|
+
* ]
|
|
96
|
+
* })
|
|
97
|
+
*
|
|
98
|
+
* @example
|
|
99
|
+
* // OpenAPIConnector source format
|
|
100
|
+
* connector.parse({
|
|
101
|
+
* openapi: '3.0.0',
|
|
102
|
+
* paths: { '/api/users': { ... } },
|
|
103
|
+
* components: { schemas: { ... } }
|
|
104
|
+
* })
|
|
105
|
+
*/
|
|
106
|
+
parse(source) {
|
|
107
|
+
throw new Error(`${this.name}.parse() must be implemented by subclass`)
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/**
|
|
111
|
+
* Parse with detailed result including warnings
|
|
112
|
+
*
|
|
113
|
+
* Alternative to `parse()` that returns warnings along with schemas.
|
|
114
|
+
* Useful when you want to handle non-fatal issues without strict mode.
|
|
115
|
+
*
|
|
116
|
+
* Default implementation wraps `parse()` with empty warnings array.
|
|
117
|
+
* Subclasses can override for more detailed reporting.
|
|
118
|
+
*
|
|
119
|
+
* @param {*} source - Source data to parse
|
|
120
|
+
* @returns {ParseResult} - Schemas and any warnings encountered
|
|
121
|
+
*/
|
|
122
|
+
parseWithWarnings(source) {
|
|
123
|
+
return {
|
|
124
|
+
schemas: this.parse(source),
|
|
125
|
+
warnings: []
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
/**
|
|
130
|
+
* Validate connector is properly configured before parsing
|
|
131
|
+
*
|
|
132
|
+
* Override in subclasses to add custom validation.
|
|
133
|
+
*
|
|
134
|
+
* @returns {boolean} - True if connector is valid
|
|
135
|
+
* @throws {Error} - If connector configuration is invalid
|
|
136
|
+
*/
|
|
137
|
+
validate() {
|
|
138
|
+
return true
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
export default BaseConnector
|