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