@stackql/provider-utils 0.5.6 → 0.5.8
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/generator.js +7 -5
- package/src/docgen/helpers.js +34 -12
- package/src/docgen/resource/examples/delete-example.js +2 -2
- package/src/docgen/resource/examples/exec-example.js +2 -2
- package/src/docgen/resource/examples/insert-example.js +23 -12
- package/src/docgen/resource/examples/select-example.js +2 -2
- package/src/docgen/resource/examples/update-example.js +34 -31
- package/src/docgen/resource/examples.js +10 -10
- package/src/docgen/resource/methods.js +10 -7
- package/src/docgen/resource-content.js +3 -2
- package/src/providerdev/generate.js +36 -6
package/package.json
CHANGED
package/src/docgen/generator.js
CHANGED
|
@@ -291,6 +291,7 @@ export async function generateDocsv2(options) {
|
|
|
291
291
|
outputDir, // e.g., 'website'
|
|
292
292
|
providerDataDir, // e.g., 'config/provider-data'
|
|
293
293
|
dereferenced = false,
|
|
294
|
+
succinct = false, // use summary instead of description for method/example descriptions
|
|
294
295
|
} = options;
|
|
295
296
|
|
|
296
297
|
console.log(`documenting ${providerName} (v2)...`);
|
|
@@ -338,7 +339,7 @@ export async function generateDocsv2(options) {
|
|
|
338
339
|
const filePath = path.join(serviceDir, file);
|
|
339
340
|
totalServicesCount++;
|
|
340
341
|
const serviceFolder = `${servicesDir}/${serviceName}`;
|
|
341
|
-
await createDocsForServicev2(filePath, providerName, serviceName, serviceFolder, dereferenced);
|
|
342
|
+
await createDocsForServicev2(filePath, providerName, serviceName, serviceFolder, dereferenced, succinct);
|
|
342
343
|
}
|
|
343
344
|
|
|
344
345
|
console.log(`Processed ${totalServicesCount} services`);
|
|
@@ -394,7 +395,7 @@ ${servicesToMarkdown(providerName, secondColumnServices)}
|
|
|
394
395
|
}
|
|
395
396
|
|
|
396
397
|
// v2 service processing - uses SchemaTable for collapsible nested fields
|
|
397
|
-
async function createDocsForServicev2(yamlFilePath, providerName, serviceName, serviceFolder, dereferenced = false) {
|
|
398
|
+
async function createDocsForServicev2(yamlFilePath, providerName, serviceName, serviceFolder, dereferenced = false, succinct = false) {
|
|
398
399
|
|
|
399
400
|
const data = yaml.load(fs.readFileSync(yamlFilePath, 'utf8'));
|
|
400
401
|
|
|
@@ -467,18 +468,18 @@ async function createDocsForServicev2(yamlFilePath, providerName, serviceName, s
|
|
|
467
468
|
|
|
468
469
|
// Process each resource in first column
|
|
469
470
|
for (const resource of firstColumn) {
|
|
470
|
-
await processResourcev2(providerName, serviceFolder, serviceName, resource);
|
|
471
|
+
await processResourcev2(providerName, serviceFolder, serviceName, resource, succinct);
|
|
471
472
|
}
|
|
472
473
|
|
|
473
474
|
// Process each resource in second column
|
|
474
475
|
for (const resource of secondColumn) {
|
|
475
|
-
await processResourcev2(providerName, serviceFolder, serviceName, resource);
|
|
476
|
+
await processResourcev2(providerName, serviceFolder, serviceName, resource, succinct);
|
|
476
477
|
}
|
|
477
478
|
|
|
478
479
|
console.log(`Generated documentation (v2) for ${serviceName}`);
|
|
479
480
|
}
|
|
480
481
|
|
|
481
|
-
async function processResourcev2(providerName, serviceFolder, serviceName, resource) {
|
|
482
|
+
async function processResourcev2(providerName, serviceFolder, serviceName, resource, succinct = false) {
|
|
482
483
|
console.log(`Processing resource (v2): ${resource.name}`);
|
|
483
484
|
|
|
484
485
|
const resourceFolder = path.join(serviceFolder, resource.name);
|
|
@@ -491,6 +492,7 @@ async function processResourcev2(providerName, serviceFolder, serviceName, resou
|
|
|
491
492
|
providerName,
|
|
492
493
|
serviceName,
|
|
493
494
|
resource,
|
|
495
|
+
succinct,
|
|
494
496
|
);
|
|
495
497
|
fs.writeFileSync(resourceIndexPath, resourceIndexContent);
|
|
496
498
|
|
package/src/docgen/helpers.js
CHANGED
|
@@ -134,7 +134,7 @@ export function getSqlMethodsWithOrderedFields(resourceData, dereferencedAPI, sq
|
|
|
134
134
|
}
|
|
135
135
|
|
|
136
136
|
// Get response and params using the same function as for SQL verbs
|
|
137
|
-
const { respProps, respDescription, opDescription, requestBody } = getHttpOperationInfo(
|
|
137
|
+
const { respProps, respDescription, opDescription, opSummary, requestBody } = getHttpOperationInfo(
|
|
138
138
|
dereferencedAPI,
|
|
139
139
|
resolvedPath,
|
|
140
140
|
resolvedVerb,
|
|
@@ -152,12 +152,14 @@ export function getSqlMethodsWithOrderedFields(resourceData, dereferencedAPI, sq
|
|
|
152
152
|
// Initialize the method with the same structure as SQL methods
|
|
153
153
|
methods[methodName] = {
|
|
154
154
|
opDescription,
|
|
155
|
+
opSummary,
|
|
155
156
|
respDescription,
|
|
156
157
|
properties: {},
|
|
157
158
|
requiredParams: requiredParams || {},
|
|
158
159
|
optionalParams: optionalParams || {},
|
|
159
160
|
requestBody: requestBody || {},
|
|
160
161
|
rawRespProps: respProps,
|
|
162
|
+
methodConfig: methodData.config || null,
|
|
161
163
|
};
|
|
162
164
|
|
|
163
165
|
// Format and sort the properties using our helper functions
|
|
@@ -176,19 +178,21 @@ export function getSqlMethodsWithOrderedFields(resourceData, dereferencedAPI, sq
|
|
|
176
178
|
}
|
|
177
179
|
|
|
178
180
|
for (const thisMethod of resourceData.sqlVerbs[sqlVerb]) {
|
|
179
|
-
const {path, httpVerb, mediaType, openAPIDocKey, objectKey, methodName} = getHttpOperationForSqlVerb(thisMethod.$ref, resourceData);
|
|
180
|
-
const {respProps, respDescription, opDescription, requestBody} = getHttpOperationInfo(dereferencedAPI, path, httpVerb, mediaType, openAPIDocKey, objectKey);
|
|
181
|
+
const {path, httpVerb, mediaType, openAPIDocKey, objectKey, methodName, methodConfig} = getHttpOperationForSqlVerb(thisMethod.$ref, resourceData);
|
|
182
|
+
const {respProps, respDescription, opDescription, opSummary, requestBody} = getHttpOperationInfo(dereferencedAPI, path, httpVerb, mediaType, openAPIDocKey, objectKey);
|
|
181
183
|
const {requiredParams, optionalParams} = getHttpOperationParams(dereferencedAPI, path, httpVerb);
|
|
182
184
|
|
|
183
185
|
// Initialize the method object with description and params
|
|
184
186
|
methods[methodName] = {
|
|
185
187
|
opDescription,
|
|
188
|
+
opSummary,
|
|
186
189
|
respDescription,
|
|
187
190
|
properties: {},
|
|
188
191
|
requiredParams: requiredParams || {},
|
|
189
192
|
optionalParams: optionalParams || {},
|
|
190
193
|
requestBody: requestBody || {},
|
|
191
194
|
rawRespProps: respProps,
|
|
195
|
+
methodConfig: methodConfig || null,
|
|
192
196
|
};
|
|
193
197
|
|
|
194
198
|
// Format and sort the properties using our helper functions
|
|
@@ -253,12 +257,19 @@ function formatProperties(respProps) {
|
|
|
253
257
|
// Get the base description
|
|
254
258
|
let fullDescription = propDetails.description || '';
|
|
255
259
|
fullDescription = fullDescription.replace(/\n/g, ' ');
|
|
260
|
+
|
|
261
|
+
// Collect enum values from enum or x-enum keys
|
|
262
|
+
const enumValues = propDetails['enum'] || propDetails['x-enum'];
|
|
263
|
+
if (Array.isArray(enumValues) && enumValues.length > 0) {
|
|
264
|
+
fullDescription += ` (${enumValues.join(', ')})`;
|
|
265
|
+
}
|
|
266
|
+
|
|
256
267
|
let additionalDescriptionPaths = [];
|
|
257
268
|
|
|
258
269
|
// Add all other fields to description parts
|
|
259
270
|
for (const [fieldName, fieldValue] of Object.entries(propDetails)) {
|
|
260
271
|
// Skip the fields we're handling separately
|
|
261
|
-
if (fieldName === 'type' || fieldName === 'format' || fieldName === 'description') {
|
|
272
|
+
if (fieldName === 'type' || fieldName === 'format' || fieldName === 'description' || fieldName === 'enum' || fieldName === 'x-enum') {
|
|
262
273
|
continue;
|
|
263
274
|
}
|
|
264
275
|
|
|
@@ -360,13 +371,14 @@ function getHttpOperationForSqlVerb(sqlVerbRef, resourceData){
|
|
|
360
371
|
const httpVerb = operationRef.split('/').pop()
|
|
361
372
|
const path = operationRef.split('/')[0].replaceAll('~1','/');
|
|
362
373
|
|
|
363
|
-
return {
|
|
364
|
-
path,
|
|
365
|
-
httpVerb,
|
|
366
|
-
mediaType: methodObj.response.mediaType,
|
|
374
|
+
return {
|
|
375
|
+
path,
|
|
376
|
+
httpVerb,
|
|
377
|
+
mediaType: methodObj.response.mediaType,
|
|
367
378
|
openAPIDocKey: methodObj.response.openAPIDocKey,
|
|
368
379
|
objectKey: methodObj.response.objectKey || false,
|
|
369
|
-
methodName
|
|
380
|
+
methodName,
|
|
381
|
+
methodConfig: methodObj.config || null
|
|
370
382
|
}
|
|
371
383
|
}
|
|
372
384
|
|
|
@@ -383,8 +395,9 @@ function getHttpOperationInfo(dereferencedAPI, path, httpVerb, mediaType, openAP
|
|
|
383
395
|
throw new Error(`HTTP verb '${httpVerb}' not found for path '${path}'`);
|
|
384
396
|
}
|
|
385
397
|
|
|
386
|
-
// Get operation description
|
|
398
|
+
// Get operation description and summary
|
|
387
399
|
const opDescription = (dereferencedAPI.paths[path][httpVerb].description || '');
|
|
400
|
+
const opSummary = (dereferencedAPI.paths[path][httpVerb].summary || '');
|
|
388
401
|
|
|
389
402
|
// Extract request body if it exists
|
|
390
403
|
let requestBody = {};
|
|
@@ -474,19 +487,21 @@ function getHttpOperationInfo(dereferencedAPI, path, httpVerb, mediaType, openAP
|
|
|
474
487
|
respProps: {},
|
|
475
488
|
respDescription: '',
|
|
476
489
|
opDescription,
|
|
490
|
+
opSummary,
|
|
477
491
|
requestBody
|
|
478
492
|
};
|
|
479
493
|
}
|
|
480
|
-
|
|
494
|
+
|
|
481
495
|
// Check if there's a content section with the mediaType
|
|
482
496
|
const responseObj = dereferencedAPI.paths[path][httpVerb].responses[openAPIDocKey];
|
|
483
|
-
|
|
497
|
+
|
|
484
498
|
// If no content or no mediaType in the response, return empty properties
|
|
485
499
|
if (!responseObj.content || !mediaType || !responseObj.content[mediaType] || !responseObj.content[mediaType].schema) {
|
|
486
500
|
return {
|
|
487
501
|
respProps: {},
|
|
488
502
|
respDescription: responseObj.description || '',
|
|
489
503
|
opDescription,
|
|
504
|
+
opSummary,
|
|
490
505
|
requestBody
|
|
491
506
|
};
|
|
492
507
|
}
|
|
@@ -499,6 +514,7 @@ function getHttpOperationInfo(dereferencedAPI, path, httpVerb, mediaType, openAP
|
|
|
499
514
|
respProps,
|
|
500
515
|
respDescription: responseObj.description ? responseObj.description : respDescription,
|
|
501
516
|
opDescription,
|
|
517
|
+
opSummary,
|
|
502
518
|
requestBody
|
|
503
519
|
};
|
|
504
520
|
}
|
|
@@ -589,6 +605,12 @@ export function generateSchemaJsonFromProps(respProps, depth = 0, maxDepth = 4)
|
|
|
589
605
|
let propType = prop.type || 'object';
|
|
590
606
|
let propDesc = sanitizeHtml(prop.description || '');
|
|
591
607
|
|
|
608
|
+
// Collect enum values from enum or x-enum keys
|
|
609
|
+
const enumValues = prop['enum'] || prop['x-enum'];
|
|
610
|
+
if (Array.isArray(enumValues) && enumValues.length > 0) {
|
|
611
|
+
propDesc += ` (${enumValues.join(', ')})`;
|
|
612
|
+
}
|
|
613
|
+
|
|
592
614
|
// Add format info to type string if available
|
|
593
615
|
if (prop.format) {
|
|
594
616
|
propType += ` (${prop.format})`;
|
|
@@ -4,7 +4,7 @@ import {
|
|
|
4
4
|
sanitizeHtml,
|
|
5
5
|
} from '../../helpers.js';
|
|
6
6
|
|
|
7
|
-
export function createDeleteExamples(providerName, serviceName, resourceName, resourceData, dereferencedAPI) {
|
|
7
|
+
export function createDeleteExamples(providerName, serviceName, resourceName, resourceData, dereferencedAPI, succinct = false) {
|
|
8
8
|
const deleteMethods = getSqlMethodsWithOrderedFields(resourceData, dereferencedAPI, 'delete');
|
|
9
9
|
|
|
10
10
|
// if there are no delete methods, return empty content
|
|
@@ -30,7 +30,7 @@ export function createDeleteExamples(providerName, serviceName, resourceName, re
|
|
|
30
30
|
content += '<TabItem value="' + methodName + '">\n\n';
|
|
31
31
|
|
|
32
32
|
// Add method description
|
|
33
|
-
const opDescription = methodDetails.opDescription || 'No description available.';
|
|
33
|
+
const opDescription = (succinct && methodDetails.opSummary) ? methodDetails.opSummary : (methodDetails.opDescription || 'No description available.');
|
|
34
34
|
content += sanitizeHtml(opDescription);
|
|
35
35
|
|
|
36
36
|
// Create SQL example
|
|
@@ -4,7 +4,7 @@ import {
|
|
|
4
4
|
sanitizeHtml
|
|
5
5
|
} from '../../helpers.js';
|
|
6
6
|
|
|
7
|
-
export function createExecExamples(providerName, serviceName, resourceName, resourceData, dereferencedAPI) {
|
|
7
|
+
export function createExecExamples(providerName, serviceName, resourceName, resourceData, dereferencedAPI, succinct = false) {
|
|
8
8
|
const execMethods = getSqlMethodsWithOrderedFields(resourceData, dereferencedAPI, 'exec');
|
|
9
9
|
|
|
10
10
|
// if there are no exec methods, return empty content
|
|
@@ -30,7 +30,7 @@ export function createExecExamples(providerName, serviceName, resourceName, reso
|
|
|
30
30
|
content += '<TabItem value="' + methodName + '">\n\n';
|
|
31
31
|
|
|
32
32
|
// Add method description
|
|
33
|
-
const opDescription = methodDetails.opDescription || methodDetails.respDescription || 'No description available.';
|
|
33
|
+
const opDescription = (succinct && methodDetails.opSummary) ? methodDetails.opSummary : (methodDetails.opDescription || methodDetails.respDescription || 'No description available.');
|
|
34
34
|
content += sanitizeHtml(opDescription);
|
|
35
35
|
|
|
36
36
|
// Create SQL example
|
|
@@ -4,7 +4,7 @@ import {
|
|
|
4
4
|
sanitizeHtml
|
|
5
5
|
} from '../../helpers.js';
|
|
6
6
|
|
|
7
|
-
export function createInsertExamples(providerName, serviceName, resourceName, resourceData, dereferencedAPI) {
|
|
7
|
+
export function createInsertExamples(providerName, serviceName, resourceName, resourceData, dereferencedAPI, succinct = false) {
|
|
8
8
|
const insertMethods = getSqlMethodsWithOrderedFields(resourceData, dereferencedAPI, 'insert');
|
|
9
9
|
|
|
10
10
|
// if there are no insert methods, return empty content
|
|
@@ -34,23 +34,27 @@ export function createInsertExamples(providerName, serviceName, resourceName, re
|
|
|
34
34
|
content += '<TabItem value="' + methodName + '">\n\n';
|
|
35
35
|
|
|
36
36
|
// Add method description
|
|
37
|
-
const opDescription = methodDetails.opDescription || 'No description available.';
|
|
37
|
+
const opDescription = (succinct && methodDetails.opSummary) ? methodDetails.opSummary : (methodDetails.opDescription || 'No description available.');
|
|
38
38
|
content += sanitizeHtml(opDescription);
|
|
39
|
-
// content += methodDetails.opDescription || 'No description available.';
|
|
40
39
|
|
|
41
40
|
// Create SQL example
|
|
42
41
|
content += '\n\n```sql\nINSERT INTO ' + providerName + '.' + serviceName + '.' + resourceName + ' (\n';
|
|
43
42
|
|
|
44
|
-
// Add requestBody fields
|
|
45
|
-
|
|
43
|
+
// Add requestBody fields (excluding read-only props)
|
|
44
|
+
// If requestBodyTranslate.algorithm is 'naive', do not prefix with data__
|
|
45
|
+
const hasNaiveTranslate = methodDetails.methodConfig?.requestBodyTranslate?.algorithm === 'naive';
|
|
46
|
+
|
|
47
|
+
const reqBodyProps = methodDetails.requestBody?.properties
|
|
46
48
|
? Object.entries(methodDetails.requestBody.properties)
|
|
47
49
|
.filter(([_, propDetails]) => propDetails.readOnly !== true)
|
|
48
50
|
.map(([propName]) => propName)
|
|
49
51
|
: [];
|
|
50
52
|
|
|
51
53
|
const requiredBodyProps = methodDetails.requestBody?.required ? methodDetails.requestBody.required : [];
|
|
52
|
-
|
|
53
|
-
const dataProps =
|
|
54
|
+
|
|
55
|
+
const dataProps = hasNaiveTranslate
|
|
56
|
+
? reqBodyProps
|
|
57
|
+
: reqBodyProps.map(prop => 'data__' + prop);
|
|
54
58
|
|
|
55
59
|
// Combine data props with params
|
|
56
60
|
const requiredParams = Object.keys(methodDetails.requiredParams || {});
|
|
@@ -63,11 +67,18 @@ export function createInsertExamples(providerName, serviceName, resourceName, re
|
|
|
63
67
|
// Start SELECT statement
|
|
64
68
|
content += '\n)\nSELECT \n';
|
|
65
69
|
|
|
70
|
+
// Build a set of body prop names for quick lookup
|
|
71
|
+
const bodyPropSet = new Set(reqBodyProps);
|
|
72
|
+
|
|
66
73
|
// Add values placeholders
|
|
67
74
|
const valueLines = allFields.map(field => {
|
|
68
|
-
const isDataField =
|
|
69
|
-
|
|
70
|
-
|
|
75
|
+
const isDataField = hasNaiveTranslate
|
|
76
|
+
? bodyPropSet.has(field)
|
|
77
|
+
: field.startsWith('data__');
|
|
78
|
+
const paramName = isDataField
|
|
79
|
+
? (hasNaiveTranslate ? field : field.substring(6))
|
|
80
|
+
: field;
|
|
81
|
+
|
|
71
82
|
// Check for required body props
|
|
72
83
|
let isRequiredBodyParam = false;
|
|
73
84
|
if(isDataField){
|
|
@@ -79,13 +90,13 @@ export function createInsertExamples(providerName, serviceName, resourceName, re
|
|
|
79
90
|
// Check if it's a number or boolean type
|
|
80
91
|
let isNumber = false;
|
|
81
92
|
let isBoolean = false;
|
|
82
|
-
|
|
93
|
+
|
|
83
94
|
if (isDataField && methodDetails.requestBody?.properties?.[paramName]) {
|
|
84
95
|
const propType = methodDetails.requestBody.properties[paramName].type;
|
|
85
96
|
isNumber = propType === 'number' || propType === 'integer';
|
|
86
97
|
isBoolean = propType === 'boolean';
|
|
87
98
|
}
|
|
88
|
-
|
|
99
|
+
|
|
89
100
|
if (isNumber || isBoolean) {
|
|
90
101
|
return '{{ ' + paramName + ' }}' + (isRequiredBodyParam ? ' /* required */' : '');
|
|
91
102
|
} else {
|
|
@@ -4,7 +4,7 @@ import {
|
|
|
4
4
|
sanitizeHtml
|
|
5
5
|
} from '../../helpers.js';
|
|
6
6
|
|
|
7
|
-
export function createSelectExamples(providerName, serviceName, resourceName, resourceData, dereferencedAPI) {
|
|
7
|
+
export function createSelectExamples(providerName, serviceName, resourceName, resourceData, dereferencedAPI, succinct = false) {
|
|
8
8
|
const selectMethods = getSqlMethodsWithOrderedFields(resourceData, dereferencedAPI, 'select');
|
|
9
9
|
|
|
10
10
|
// if there are no select methods, return empty content
|
|
@@ -30,7 +30,7 @@ export function createSelectExamples(providerName, serviceName, resourceName, re
|
|
|
30
30
|
content += '<TabItem value="' + methodName + '">\n\n';
|
|
31
31
|
// content += methodDetails.opDescription || 'No description available.';
|
|
32
32
|
// Add method description
|
|
33
|
-
const opDescription = methodDetails.opDescription || methodDetails.respDescription || 'No description available.';
|
|
33
|
+
const opDescription = (succinct && methodDetails.opSummary) ? methodDetails.opSummary : (methodDetails.opDescription || methodDetails.respDescription || 'No description available.');
|
|
34
34
|
content += sanitizeHtml(opDescription);
|
|
35
35
|
|
|
36
36
|
// Create SQL example
|
|
@@ -4,7 +4,7 @@ import {
|
|
|
4
4
|
sanitizeHtml
|
|
5
5
|
} from '../../helpers.js';
|
|
6
6
|
|
|
7
|
-
export function createUpdateExamples(providerName, serviceName, resourceName, resourceData, dereferencedAPI, isReplace = false) {
|
|
7
|
+
export function createUpdateExamples(providerName, serviceName, resourceName, resourceData, dereferencedAPI, isReplace = false, succinct = false) {
|
|
8
8
|
const updateMethods = getSqlMethodsWithOrderedFields(resourceData, dereferencedAPI, isReplace ? 'replace' : 'update');
|
|
9
9
|
|
|
10
10
|
// if there are no update methods, return empty content
|
|
@@ -34,105 +34,108 @@ export function createUpdateExamples(providerName, serviceName, resourceName, re
|
|
|
34
34
|
Object.entries(updateMethods).forEach(([methodName, methodDetails]) => {
|
|
35
35
|
content += '<TabItem value="' + methodName + '">\n\n';
|
|
36
36
|
|
|
37
|
-
// // Add method description
|
|
38
|
-
// content += methodDetails.opDescription || methodDetails.respDescription || 'No description available.';
|
|
39
37
|
// Add method description
|
|
40
|
-
const opDescription = methodDetails.opDescription || 'No description available.';
|
|
38
|
+
const opDescription = (succinct && methodDetails.opSummary) ? methodDetails.opSummary : (methodDetails.opDescription || 'No description available.');
|
|
41
39
|
content += sanitizeHtml(opDescription);
|
|
42
40
|
|
|
43
41
|
// Create SQL example
|
|
44
42
|
content += '\n\n```sql\n';
|
|
45
43
|
content += (isReplace ? 'REPLACE ' : 'UPDATE ') + providerName + '.' + serviceName + '.' + resourceName;
|
|
46
44
|
|
|
45
|
+
// If requestBodyTranslate.algorithm is 'naive', do not prefix with data__
|
|
46
|
+
const hasNaiveTranslate = methodDetails.methodConfig?.requestBodyTranslate?.algorithm === 'naive';
|
|
47
|
+
|
|
47
48
|
// Add SET clause with requestBody fields (excluding read-only props)
|
|
48
49
|
content += '\nSET \n';
|
|
49
|
-
|
|
50
|
+
|
|
50
51
|
// Get request body fields (excluding read-only props)
|
|
51
|
-
const reqBodyProps = methodDetails.requestBody?.properties
|
|
52
|
+
const reqBodyProps = methodDetails.requestBody?.properties
|
|
52
53
|
? Object.entries(methodDetails.requestBody.properties)
|
|
53
54
|
.filter(([_, propDetails]) => propDetails.readOnly !== true)
|
|
54
55
|
.map(([propName]) => propName)
|
|
55
56
|
: [];
|
|
56
|
-
|
|
57
|
-
// Add
|
|
57
|
+
|
|
58
|
+
// Add fields to SET clause (with or without data__ prefix)
|
|
58
59
|
if (reqBodyProps.length > 0) {
|
|
59
60
|
const setLines = reqBodyProps.map(prop => {
|
|
60
61
|
const propDetails = methodDetails.requestBody.properties[prop];
|
|
61
62
|
const isNumber = propDetails.type === 'number' || propDetails.type === 'integer';
|
|
62
63
|
const isBoolean = propDetails.type === 'boolean';
|
|
63
|
-
|
|
64
|
+
const fieldName = hasNaiveTranslate ? prop : 'data__' + prop;
|
|
65
|
+
|
|
64
66
|
if (isNumber || isBoolean) {
|
|
65
|
-
return
|
|
67
|
+
return fieldName + ' = {{ ' + prop + ' }}';
|
|
66
68
|
} else {
|
|
67
|
-
return
|
|
69
|
+
return fieldName + ' = \'{{ ' + prop + ' }}\'';
|
|
68
70
|
}
|
|
69
71
|
});
|
|
70
|
-
|
|
72
|
+
|
|
71
73
|
content += setLines.join(',\n');
|
|
72
74
|
} else {
|
|
73
75
|
content += '-- No updatable properties';
|
|
74
76
|
}
|
|
75
|
-
|
|
77
|
+
|
|
76
78
|
// Add WHERE clause with parameters
|
|
77
79
|
const requiredParams = Object.keys(methodDetails.requiredParams || {});
|
|
78
80
|
const optionalParams = Object.keys(methodDetails.optionalParams || {});
|
|
79
|
-
|
|
81
|
+
|
|
80
82
|
// Get required body props (excluding read-only props)
|
|
81
|
-
const requiredBodyProps = methodDetails.requestBody?.required
|
|
82
|
-
? methodDetails.requestBody.required.filter(prop =>
|
|
83
|
-
methodDetails.requestBody.properties[prop] &&
|
|
83
|
+
const requiredBodyProps = methodDetails.requestBody?.required
|
|
84
|
+
? methodDetails.requestBody.required.filter(prop =>
|
|
85
|
+
methodDetails.requestBody.properties[prop] &&
|
|
84
86
|
methodDetails.requestBody.properties[prop].readOnly !== true)
|
|
85
87
|
: [];
|
|
86
|
-
|
|
88
|
+
|
|
87
89
|
if (requiredParams.length > 0 || requiredBodyProps.length > 0 || optionalParams.length > 0) {
|
|
88
90
|
|
|
89
91
|
content += '\nWHERE \n';
|
|
90
|
-
|
|
92
|
+
|
|
91
93
|
// Add required parameters
|
|
92
94
|
let clauseCount = 0;
|
|
93
|
-
|
|
95
|
+
|
|
94
96
|
// Add required query/path/header params
|
|
95
97
|
requiredParams.forEach(param => {
|
|
96
98
|
if (clauseCount > 0) content += '\nAND ';
|
|
97
99
|
content += param + ' = \'{{ ' + param + ' }}\' --required';
|
|
98
100
|
clauseCount++;
|
|
99
101
|
});
|
|
100
|
-
|
|
102
|
+
|
|
101
103
|
// Add required body params (only non-readonly ones)
|
|
102
104
|
requiredBodyProps.forEach(prop => {
|
|
103
105
|
if (clauseCount > 0) content += '\nAND ';
|
|
104
|
-
|
|
106
|
+
|
|
105
107
|
const propDetails = methodDetails.requestBody.properties[prop];
|
|
106
108
|
const isBoolean = propDetails.type === 'boolean';
|
|
107
|
-
|
|
109
|
+
const fieldName = hasNaiveTranslate ? prop : 'data__' + prop;
|
|
110
|
+
|
|
108
111
|
if (isBoolean) {
|
|
109
|
-
content +=
|
|
112
|
+
content += fieldName + ' = {{ ' + prop + ' }} --required';
|
|
110
113
|
} else {
|
|
111
|
-
content +=
|
|
114
|
+
content += fieldName + ' = \'{{ ' + prop + ' }}\' --required';
|
|
112
115
|
}
|
|
113
|
-
|
|
116
|
+
|
|
114
117
|
clauseCount++;
|
|
115
118
|
});
|
|
116
|
-
|
|
119
|
+
|
|
117
120
|
// Add optional parameters
|
|
118
121
|
optionalParams.forEach(param => {
|
|
119
122
|
if (clauseCount > 0) content += '\nAND ';
|
|
120
|
-
|
|
123
|
+
|
|
121
124
|
// For boolean parameters, we can add a comment about their default value
|
|
122
125
|
const paramDetails = methodDetails.optionalParams[param];
|
|
123
126
|
const isBoolean = paramDetails.type === 'boolean';
|
|
124
127
|
const defaultValue = paramDetails.default;
|
|
125
|
-
|
|
128
|
+
|
|
126
129
|
if (isBoolean) {
|
|
127
130
|
content += param + ' = {{ ' + param + '}}';
|
|
128
131
|
} else {
|
|
129
132
|
content += param + ' = \'{{ ' + param + '}}\'';
|
|
130
133
|
}
|
|
131
|
-
|
|
134
|
+
|
|
132
135
|
if (isBoolean && defaultValue !== undefined) {
|
|
133
136
|
content += ' -- default: ' + defaultValue;
|
|
134
137
|
}
|
|
135
|
-
|
|
138
|
+
|
|
136
139
|
clauseCount++;
|
|
137
140
|
});
|
|
138
141
|
}
|
|
@@ -6,26 +6,26 @@ import { createUpdateExamples } from './examples/update-example.js';
|
|
|
6
6
|
import { createDeleteExamples } from './examples/delete-example.js';
|
|
7
7
|
import { createExecExamples } from './examples/exec-example.js';
|
|
8
8
|
|
|
9
|
-
export function createExamplesSection(providerName, serviceName, resourceName, resourceData, dereferencedAPI) {
|
|
9
|
+
export function createExamplesSection(providerName, serviceName, resourceName, resourceData, dereferencedAPI, succinct = false) {
|
|
10
10
|
let content = '';
|
|
11
|
-
|
|
11
|
+
|
|
12
12
|
// Add SELECT examples
|
|
13
|
-
content += createSelectExamples(providerName, serviceName, resourceName, resourceData, dereferencedAPI);
|
|
13
|
+
content += createSelectExamples(providerName, serviceName, resourceName, resourceData, dereferencedAPI, succinct);
|
|
14
14
|
|
|
15
15
|
// Add INSERT examples
|
|
16
|
-
content += createInsertExamples(providerName, serviceName, resourceName, resourceData, dereferencedAPI);
|
|
17
|
-
|
|
16
|
+
content += createInsertExamples(providerName, serviceName, resourceName, resourceData, dereferencedAPI, succinct);
|
|
17
|
+
|
|
18
18
|
// Add UPDATE examples
|
|
19
|
-
content += createUpdateExamples(providerName, serviceName, resourceName, resourceData, dereferencedAPI, false);
|
|
20
|
-
|
|
19
|
+
content += createUpdateExamples(providerName, serviceName, resourceName, resourceData, dereferencedAPI, false, succinct);
|
|
20
|
+
|
|
21
21
|
// Add REPLACE examples
|
|
22
|
-
content += createUpdateExamples(providerName, serviceName, resourceName, resourceData, dereferencedAPI, true);
|
|
22
|
+
content += createUpdateExamples(providerName, serviceName, resourceName, resourceData, dereferencedAPI, true, succinct);
|
|
23
23
|
|
|
24
24
|
// Add DELETE examples
|
|
25
|
-
content += createDeleteExamples(providerName, serviceName, resourceName, resourceData, dereferencedAPI);
|
|
25
|
+
content += createDeleteExamples(providerName, serviceName, resourceName, resourceData, dereferencedAPI, succinct);
|
|
26
26
|
|
|
27
27
|
// Add EXEC examples
|
|
28
|
-
content += createExecExamples(providerName, serviceName, resourceName, resourceData, dereferencedAPI);
|
|
28
|
+
content += createExecExamples(providerName, serviceName, resourceName, resourceData, dereferencedAPI, succinct);
|
|
29
29
|
|
|
30
30
|
return content;
|
|
31
31
|
}
|
|
@@ -9,20 +9,23 @@ const getRequiredBodyParams = (methodDetails, accessType) => {
|
|
|
9
9
|
if (!['insert', 'update', 'replace', 'exec'].includes(accessType)) {
|
|
10
10
|
return [];
|
|
11
11
|
}
|
|
12
|
-
|
|
12
|
+
|
|
13
13
|
// Get required body params if they exist
|
|
14
14
|
const requiredBodyProps = methodDetails.requestBody?.required ? methodDetails.requestBody.required : [];
|
|
15
|
-
|
|
16
|
-
//
|
|
17
|
-
|
|
15
|
+
|
|
16
|
+
// Check if requestBodyTranslate.algorithm is 'naive' - if so, don't prefix with data__
|
|
17
|
+
const hasNaiveTranslate = methodDetails.methodConfig?.requestBodyTranslate?.algorithm === 'naive';
|
|
18
|
+
|
|
19
|
+
// For insert, update, and replace, prefix with data__ (unless naive translate is set)
|
|
20
|
+
if (['insert', 'update', 'replace'].includes(accessType) && !hasNaiveTranslate) {
|
|
18
21
|
return requiredBodyProps.map(prop => `data__${prop}`);
|
|
19
22
|
} else {
|
|
20
|
-
// For exec, don't prefix
|
|
23
|
+
// For exec or naive translate, don't prefix
|
|
21
24
|
return requiredBodyProps;
|
|
22
25
|
}
|
|
23
26
|
};
|
|
24
27
|
|
|
25
|
-
export function createMethodsSection(resourceData, dereferencedAPI) {
|
|
28
|
+
export function createMethodsSection(resourceData, dereferencedAPI, succinct = false) {
|
|
26
29
|
|
|
27
30
|
let content = `\n## Methods\n\n`;
|
|
28
31
|
|
|
@@ -80,7 +83,7 @@ export function createMethodsSection(resourceData, dereferencedAPI) {
|
|
|
80
83
|
<td><CopyableCode code="${accessType}" /></td>
|
|
81
84
|
<td>${requiredParamsStr}</td>
|
|
82
85
|
<td>${optionalParamsStr}</td>
|
|
83
|
-
<td>${sanitizeHtml(methodDetails.opDescription)}</td>
|
|
86
|
+
<td>${sanitizeHtml(succinct && methodDetails.opSummary ? methodDetails.opSummary : methodDetails.opDescription)}</td>
|
|
84
87
|
</tr>`;
|
|
85
88
|
}
|
|
86
89
|
};
|
|
@@ -26,13 +26,14 @@ export async function createResourceIndexContentv2(
|
|
|
26
26
|
providerName,
|
|
27
27
|
serviceName,
|
|
28
28
|
resource,
|
|
29
|
+
succinct = false,
|
|
29
30
|
) {
|
|
30
31
|
// Generate each section of the documentation (v2 uses SchemaTable for fields)
|
|
31
32
|
const overviewContent = createOverviewSectionv2(resource.name, resource.type, resource.description, providerName, serviceName);
|
|
32
33
|
const fieldsContent = createFieldsSectionv2(resource.type, resource.resourceData, resource.dereferencedAPI);
|
|
33
|
-
const methodsContent = resource.type === 'Resource' ? createMethodsSection(resource.resourceData, resource.dereferencedAPI) : '';
|
|
34
|
+
const methodsContent = resource.type === 'Resource' ? createMethodsSection(resource.resourceData, resource.dereferencedAPI, succinct) : '';
|
|
34
35
|
const paramsContent = resource.type === 'Resource' ? createParamsSection(resource.resourceData, resource.dereferencedAPI) : '';
|
|
35
|
-
const examplesContent = resource.type === 'Resource' ? createExamplesSection(providerName, serviceName, resource.name, resource.resourceData, resource.dereferencedAPI) : '';
|
|
36
|
+
const examplesContent = resource.type === 'Resource' ? createExamplesSection(providerName, serviceName, resource.name, resource.resourceData, resource.dereferencedAPI, succinct) : '';
|
|
36
37
|
|
|
37
38
|
// Combine all sections into the final content
|
|
38
39
|
return `${overviewContent}${fieldsContent}${methodsContent}${paramsContent}${examplesContent}`;
|
|
@@ -177,6 +177,8 @@ export async function generate(options) {
|
|
|
177
177
|
providerId,
|
|
178
178
|
servers = null,
|
|
179
179
|
providerConfig = null,
|
|
180
|
+
serviceConfig = null,
|
|
181
|
+
naiveReqBodyTranslate = false,
|
|
180
182
|
skipFiles = []
|
|
181
183
|
} = options;
|
|
182
184
|
|
|
@@ -217,8 +219,19 @@ export async function generate(options) {
|
|
|
217
219
|
return false;
|
|
218
220
|
}
|
|
219
221
|
|
|
222
|
+
// Parse serviceConfig if provided
|
|
223
|
+
let serviceConfigJson = null;
|
|
224
|
+
if (serviceConfig) {
|
|
225
|
+
try {
|
|
226
|
+
serviceConfigJson = JSON.parse(serviceConfig);
|
|
227
|
+
} catch (error) {
|
|
228
|
+
logger.error(`❌ Failed to parse service config JSON: ${error.message}`);
|
|
229
|
+
return false;
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
|
|
220
233
|
const providerServices = {};
|
|
221
|
-
|
|
234
|
+
|
|
222
235
|
try {
|
|
223
236
|
const files = fs.readdirSync(inputDir);
|
|
224
237
|
|
|
@@ -302,10 +315,22 @@ export async function generate(options) {
|
|
|
302
315
|
const pathRef = encodeRefPath(pathKey, verb);
|
|
303
316
|
const responseInfo = getSuccessResponseInfo(operation);
|
|
304
317
|
|
|
305
|
-
const methodEntry = {
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
318
|
+
const methodEntry = {};
|
|
319
|
+
|
|
320
|
+
// Add requestBodyTranslate config for methods with request bodies
|
|
321
|
+
if (naiveReqBodyTranslate && ['post', 'put', 'patch'].includes(verb)) {
|
|
322
|
+
// Check if the operation actually has a request body
|
|
323
|
+
if (operation.requestBody) {
|
|
324
|
+
methodEntry.config = {
|
|
325
|
+
requestBodyTranslate: {
|
|
326
|
+
algorithm: 'naive'
|
|
327
|
+
}
|
|
328
|
+
};
|
|
329
|
+
}
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
methodEntry.operation = { $ref: pathRef };
|
|
333
|
+
methodEntry.response = responseInfo;
|
|
309
334
|
|
|
310
335
|
// Add objectKey to the response info if it exists in the manifest and is for a GET operation
|
|
311
336
|
if (entry.stackql_object_key && verb === 'get') {
|
|
@@ -344,7 +369,12 @@ export async function generate(options) {
|
|
|
344
369
|
return false;
|
|
345
370
|
}
|
|
346
371
|
}
|
|
347
|
-
|
|
372
|
+
|
|
373
|
+
// Inject x-stackQL-config if serviceConfig is provided
|
|
374
|
+
if (serviceConfigJson) {
|
|
375
|
+
spec['x-stackQL-config'] = serviceConfigJson;
|
|
376
|
+
}
|
|
377
|
+
|
|
348
378
|
// Write enriched spec (always as YAML, ensuring .yaml extension)
|
|
349
379
|
const outputFilename = filename.endsWith('.json')
|
|
350
380
|
? filename.replace(/\.json$/, '.yaml')
|