@stackql/provider-utils 0.3.8 → 0.4.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 +1 -1
- package/src/docgen/helpers.js +2 -0
- package/src/providerdev/split.js +142 -105
package/package.json
CHANGED
package/src/docgen/helpers.js
CHANGED
|
@@ -31,8 +31,10 @@ export function sanitizeHtml(text) {
|
|
|
31
31
|
.replace(/</g, '<')
|
|
32
32
|
// edge case
|
|
33
33
|
.replace(/}_{/g, '}_{')
|
|
34
|
+
.replace(/\n/g, '<br />')
|
|
34
35
|
}
|
|
35
36
|
|
|
37
|
+
|
|
36
38
|
export function getSqlMethodsWithOrderedFields(resourceData, dereferencedAPI, sqlVerb) {
|
|
37
39
|
const methods = {};
|
|
38
40
|
|
package/src/providerdev/split.js
CHANGED
|
@@ -154,35 +154,37 @@ function getAllRefs(obj) {
|
|
|
154
154
|
}
|
|
155
155
|
|
|
156
156
|
/**
|
|
157
|
-
* Extract all $ref values from path-level parameters
|
|
157
|
+
* Extract all $ref values from path-level parameters and non-operation elements
|
|
158
158
|
* @param {Object} pathItem - Path item from OpenAPI doc
|
|
159
159
|
* @returns {Set<string>} - Set of refs
|
|
160
160
|
*/
|
|
161
161
|
function getPathLevelRefs(pathItem) {
|
|
162
162
|
const refs = new Set();
|
|
163
|
-
const relevantVerbs = ["get", "put", "post", "patch", "delete"];
|
|
164
163
|
|
|
165
164
|
// Check for path-level parameters
|
|
166
165
|
if (pathItem.parameters) {
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
if (
|
|
171
|
-
|
|
172
|
-
|
|
166
|
+
for (const param of pathItem.parameters) {
|
|
167
|
+
if (param.$ref) {
|
|
168
|
+
refs.add(param.$ref);
|
|
169
|
+
} else if (typeof param === 'object') {
|
|
170
|
+
// Extract refs from schema if present
|
|
171
|
+
if (param.schema && param.schema.$ref) {
|
|
172
|
+
refs.add(param.schema.$ref);
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
// Also get all nested refs in the parameter object
|
|
176
|
+
for (const ref of getAllRefs(param)) {
|
|
177
|
+
refs.add(ref);
|
|
178
|
+
}
|
|
173
179
|
}
|
|
174
180
|
}
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
for (const ref of getAllRefs(param)) {
|
|
183
|
-
refs.add(ref);
|
|
184
|
-
}
|
|
185
|
-
}
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
// Also check other non-operation properties for refs
|
|
184
|
+
for (const key in pathItem) {
|
|
185
|
+
if (!OPERATIONS.includes(key)) {
|
|
186
|
+
for (const ref of getAllRefs(pathItem[key])) {
|
|
187
|
+
refs.add(ref);
|
|
186
188
|
}
|
|
187
189
|
}
|
|
188
190
|
}
|
|
@@ -256,6 +258,73 @@ function addMissingObjectTypes(obj) {
|
|
|
256
258
|
return obj;
|
|
257
259
|
}
|
|
258
260
|
|
|
261
|
+
/**
|
|
262
|
+
* Recursively resolve and add all references to service components
|
|
263
|
+
* @param {Set<string>} refs - Set of references to resolve
|
|
264
|
+
* @param {Object} service - Service object to add components to
|
|
265
|
+
* @param {Object} components - Source components from API doc
|
|
266
|
+
* @param {boolean} debug - Debug flag
|
|
267
|
+
* @param {Set<string>} processed - Set of already processed refs (to prevent infinite recursion)
|
|
268
|
+
*/
|
|
269
|
+
function resolveReferences(refs, service, components, debug, processed = new Set()) {
|
|
270
|
+
let newRefs = new Set();
|
|
271
|
+
|
|
272
|
+
for (const ref of refs) {
|
|
273
|
+
// Skip if already processed
|
|
274
|
+
if (processed.has(ref)) {
|
|
275
|
+
continue;
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
processed.add(ref);
|
|
279
|
+
|
|
280
|
+
const parts = ref.split('/');
|
|
281
|
+
|
|
282
|
+
// Only process refs that point to components
|
|
283
|
+
if (parts.length >= 4 && parts[1] === "components") {
|
|
284
|
+
const componentType = parts[2];
|
|
285
|
+
const componentName = parts[3];
|
|
286
|
+
|
|
287
|
+
// Check if component type exists in service
|
|
288
|
+
if (!service.components[componentType]) {
|
|
289
|
+
service.components[componentType] = {};
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
// Skip if component already added
|
|
293
|
+
if (service.components[componentType][componentName]) {
|
|
294
|
+
continue;
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
// Add component if it exists in source document
|
|
298
|
+
if (components[componentType] && components[componentType][componentName]) {
|
|
299
|
+
service.components[componentType][componentName] =
|
|
300
|
+
JSON.parse(JSON.stringify(components[componentType][componentName]));
|
|
301
|
+
|
|
302
|
+
if (debug) {
|
|
303
|
+
logger.debug(`Added component ${componentType}/${componentName}`);
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
// Find all refs in the newly added component
|
|
307
|
+
const componentRefs = getAllRefs(service.components[componentType][componentName]);
|
|
308
|
+
for (const cRef of componentRefs) {
|
|
309
|
+
if (!processed.has(cRef)) {
|
|
310
|
+
newRefs.add(cRef);
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
} else if (debug) {
|
|
314
|
+
logger.debug(`WARNING: Could not find component ${componentType}/${componentName}`);
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
// If we found new refs, resolve them too (recursively)
|
|
320
|
+
if (newRefs.size > 0) {
|
|
321
|
+
if (debug) {
|
|
322
|
+
logger.debug(`Found ${newRefs.size} additional refs to resolve`);
|
|
323
|
+
}
|
|
324
|
+
resolveReferences(newRefs, service, components, debug, processed);
|
|
325
|
+
}
|
|
326
|
+
}
|
|
327
|
+
|
|
259
328
|
/**
|
|
260
329
|
* Split OpenAPI document into service-specific files
|
|
261
330
|
* @param {Object} options - Options for splitting
|
|
@@ -307,7 +376,7 @@ export async function split(options) {
|
|
|
307
376
|
const services = {};
|
|
308
377
|
let opCounter = 0;
|
|
309
378
|
|
|
310
|
-
//
|
|
379
|
+
// First pass: identify all services and collect operations
|
|
311
380
|
for (const [pathKey, pathItem] of Object.entries(apiPaths)) {
|
|
312
381
|
if (verbose) {
|
|
313
382
|
logger.debug(`Processing path ${pathKey}`);
|
|
@@ -317,6 +386,9 @@ export async function split(options) {
|
|
|
317
386
|
continue;
|
|
318
387
|
}
|
|
319
388
|
|
|
389
|
+
// Collect all services that use this path
|
|
390
|
+
const pathServices = new Set();
|
|
391
|
+
|
|
320
392
|
// Process each operation (HTTP verb)
|
|
321
393
|
for (const [verbKey, opItem] of Object.entries(pathItem)) {
|
|
322
394
|
if (!OPERATIONS.includes(verbKey) || !opItem) {
|
|
@@ -333,7 +405,7 @@ export async function split(options) {
|
|
|
333
405
|
}
|
|
334
406
|
|
|
335
407
|
// Skip excluded operations
|
|
336
|
-
if (isOperationExcluded(excludeList, opItem
|
|
408
|
+
if (isOperationExcluded(excludeList, opItem)) {
|
|
337
409
|
continue;
|
|
338
410
|
}
|
|
339
411
|
|
|
@@ -349,6 +421,8 @@ export async function split(options) {
|
|
|
349
421
|
continue;
|
|
350
422
|
}
|
|
351
423
|
|
|
424
|
+
pathServices.add(service);
|
|
425
|
+
|
|
352
426
|
if (verbose) {
|
|
353
427
|
logger.debug(`Service name: ${service}`);
|
|
354
428
|
logger.debug(`Service desc: ${serviceDesc}`);
|
|
@@ -380,104 +454,58 @@ export async function split(options) {
|
|
|
380
454
|
opItem['x-github'].subcategory
|
|
381
455
|
);
|
|
382
456
|
}
|
|
383
|
-
|
|
384
|
-
// Get all refs for operation
|
|
385
|
-
const opRefs = getAllRefs(opItem);
|
|
386
|
-
|
|
387
|
-
if (verbose) {
|
|
388
|
-
logger.debug(`Found ${opRefs.size} refs for ${service}`);
|
|
389
|
-
}
|
|
390
|
-
|
|
391
|
-
// Add refs to components
|
|
392
|
-
addRefsToComponents(opRefs, services[service], apiDocObj.components || {}, verbose);
|
|
393
|
-
|
|
394
|
-
// Get internal refs
|
|
395
|
-
for (let i = 0; i < 3; i++) { // Internal ref depth
|
|
396
|
-
const intRefs = getAllRefs(services[service].components);
|
|
397
|
-
if (verbose) {
|
|
398
|
-
logger.debug(`Found ${intRefs.size} INTERNAL refs for service ${service}`);
|
|
399
|
-
}
|
|
400
|
-
addRefsToComponents(intRefs, services[service], apiDocObj.components || {}, verbose);
|
|
401
|
-
}
|
|
402
|
-
|
|
403
|
-
// Get deeply nested schema refs
|
|
404
|
-
for (let i = 0; i < 10; i++) { // Schema max ref depth
|
|
405
|
-
const intRefs = getAllRefs(services[service].components);
|
|
406
|
-
// Filter refs that are already in service components
|
|
407
|
-
const filteredRefs = new Set();
|
|
408
|
-
for (const ref of intRefs) {
|
|
409
|
-
const parts = ref.split('/');
|
|
410
|
-
if (parts.length >= 4 && parts[1] === "components" && parts[2] === "schemas" &&
|
|
411
|
-
!services[service].components.schemas[parts[3]]) {
|
|
412
|
-
filteredRefs.add(ref);
|
|
413
|
-
}
|
|
414
|
-
}
|
|
415
|
-
|
|
416
|
-
if (verbose) {
|
|
417
|
-
logger.debug(`Found ${filteredRefs.size} INTERNAL schema refs for service ${service}`);
|
|
418
|
-
}
|
|
419
|
-
|
|
420
|
-
if (filteredRefs.size > 0) {
|
|
421
|
-
if (verbose) {
|
|
422
|
-
logger.debug(`Adding ${filteredRefs.size} INTERNAL schema refs for service ${service}`);
|
|
423
|
-
}
|
|
424
|
-
addRefsToComponents(filteredRefs, services[service], apiDocObj.components || {}, verbose);
|
|
425
|
-
} else {
|
|
426
|
-
if (verbose) {
|
|
427
|
-
logger.debug(`Exiting INTERNAL schema refs for ${service}`);
|
|
428
|
-
}
|
|
429
|
-
break;
|
|
430
|
-
}
|
|
431
|
-
}
|
|
432
457
|
}
|
|
433
458
|
|
|
434
|
-
//
|
|
435
|
-
const
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
// Copy path-level parameters if they exist
|
|
445
|
-
if (pathItem.parameters) {
|
|
446
|
-
services[svcName].paths[pathKey].parameters = pathItem.parameters;
|
|
459
|
+
// For each service that uses this path, add path-level parameters
|
|
460
|
+
for (const service of pathServices) {
|
|
461
|
+
// Copy non-operation elements (like parameters) to service paths
|
|
462
|
+
for (const key in pathItem) {
|
|
463
|
+
if (!OPERATIONS.includes(key)) {
|
|
464
|
+
if (!services[service].paths[pathKey]) {
|
|
465
|
+
services[service].paths[pathKey] = {};
|
|
466
|
+
}
|
|
467
|
+
services[service].paths[pathKey][key] = pathItem[key];
|
|
447
468
|
}
|
|
448
|
-
|
|
449
|
-
// Add references from path-level parameters
|
|
450
|
-
addRefsToComponents(pathRefs, services[svcName], apiDocObj.components || {}, verbose);
|
|
451
469
|
}
|
|
452
470
|
}
|
|
453
471
|
}
|
|
454
472
|
|
|
455
|
-
//
|
|
473
|
+
// Second pass: collect all references for each service
|
|
456
474
|
for (const service in services) {
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
475
|
+
if (verbose) {
|
|
476
|
+
logger.debug(`Collecting references for service ${service}`);
|
|
477
|
+
}
|
|
478
|
+
|
|
479
|
+
// Get all refs from all operations in this service
|
|
480
|
+
const allRefs = new Set();
|
|
481
|
+
|
|
482
|
+
// Collect refs from paths
|
|
483
|
+
for (const pathKey in services[service].paths) {
|
|
484
|
+
const pathItem = services[service].paths[pathKey];
|
|
485
|
+
|
|
486
|
+
// Get refs from path-level parameters
|
|
487
|
+
const pathRefs = getPathLevelRefs(pathItem);
|
|
488
|
+
for (const ref of pathRefs) {
|
|
489
|
+
allRefs.add(ref);
|
|
460
490
|
}
|
|
461
491
|
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
if (verbose) {
|
|
469
|
-
logger.debug(`Adding ${nonOp} to ${service} for path ${pathKey}`);
|
|
470
|
-
}
|
|
471
|
-
|
|
472
|
-
// Special case for parameters
|
|
473
|
-
if (nonOp === 'parameters') {
|
|
474
|
-
for (const verbKey in services[service].paths[pathKey]) {
|
|
475
|
-
services[service].paths[pathKey][verbKey].parameters = apiPaths[pathKey].parameters;
|
|
476
|
-
}
|
|
492
|
+
// Get refs from operations
|
|
493
|
+
for (const verbKey in pathItem) {
|
|
494
|
+
if (OPERATIONS.includes(verbKey)) {
|
|
495
|
+
const opRefs = getAllRefs(pathItem[verbKey]);
|
|
496
|
+
for (const ref of opRefs) {
|
|
497
|
+
allRefs.add(ref);
|
|
477
498
|
}
|
|
478
499
|
}
|
|
479
500
|
}
|
|
480
501
|
}
|
|
502
|
+
|
|
503
|
+
if (verbose) {
|
|
504
|
+
logger.debug(`Found ${allRefs.size} total refs for service ${service}`);
|
|
505
|
+
}
|
|
506
|
+
|
|
507
|
+
// Resolve all references recursively
|
|
508
|
+
resolveReferences(allRefs, services[service], apiDocObj.components || {}, verbose);
|
|
481
509
|
}
|
|
482
510
|
|
|
483
511
|
// Update path param names (replace hyphens with underscores)
|
|
@@ -529,6 +557,15 @@ export async function split(options) {
|
|
|
529
557
|
services[service].components = addMissingObjectTypes(services[service].components);
|
|
530
558
|
}
|
|
531
559
|
|
|
560
|
+
// Cleanup empty components
|
|
561
|
+
for (const service in services) {
|
|
562
|
+
for (const componentType in services[service].components) {
|
|
563
|
+
if (Object.keys(services[service].components[componentType]).length === 0) {
|
|
564
|
+
delete services[service].components[componentType];
|
|
565
|
+
}
|
|
566
|
+
}
|
|
567
|
+
}
|
|
568
|
+
|
|
532
569
|
// Write out service docs
|
|
533
570
|
for (const service in services) {
|
|
534
571
|
logger.info(`✅ Writing out OpenAPI doc for [${service}]`);
|