@stackql/provider-utils 0.5.7 → 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
CHANGED
package/src/docgen/helpers.js
CHANGED
|
@@ -159,6 +159,7 @@ export function getSqlMethodsWithOrderedFields(resourceData, dereferencedAPI, sq
|
|
|
159
159
|
optionalParams: optionalParams || {},
|
|
160
160
|
requestBody: requestBody || {},
|
|
161
161
|
rawRespProps: respProps,
|
|
162
|
+
methodConfig: methodData.config || null,
|
|
162
163
|
};
|
|
163
164
|
|
|
164
165
|
// Format and sort the properties using our helper functions
|
|
@@ -177,7 +178,7 @@ export function getSqlMethodsWithOrderedFields(resourceData, dereferencedAPI, sq
|
|
|
177
178
|
}
|
|
178
179
|
|
|
179
180
|
for (const thisMethod of resourceData.sqlVerbs[sqlVerb]) {
|
|
180
|
-
const {path, httpVerb, mediaType, openAPIDocKey, objectKey, methodName} = getHttpOperationForSqlVerb(thisMethod.$ref, resourceData);
|
|
181
|
+
const {path, httpVerb, mediaType, openAPIDocKey, objectKey, methodName, methodConfig} = getHttpOperationForSqlVerb(thisMethod.$ref, resourceData);
|
|
181
182
|
const {respProps, respDescription, opDescription, opSummary, requestBody} = getHttpOperationInfo(dereferencedAPI, path, httpVerb, mediaType, openAPIDocKey, objectKey);
|
|
182
183
|
const {requiredParams, optionalParams} = getHttpOperationParams(dereferencedAPI, path, httpVerb);
|
|
183
184
|
|
|
@@ -191,6 +192,7 @@ export function getSqlMethodsWithOrderedFields(resourceData, dereferencedAPI, sq
|
|
|
191
192
|
optionalParams: optionalParams || {},
|
|
192
193
|
requestBody: requestBody || {},
|
|
193
194
|
rawRespProps: respProps,
|
|
195
|
+
methodConfig: methodConfig || null,
|
|
194
196
|
};
|
|
195
197
|
|
|
196
198
|
// Format and sort the properties using our helper functions
|
|
@@ -255,12 +257,19 @@ function formatProperties(respProps) {
|
|
|
255
257
|
// Get the base description
|
|
256
258
|
let fullDescription = propDetails.description || '';
|
|
257
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
|
+
|
|
258
267
|
let additionalDescriptionPaths = [];
|
|
259
268
|
|
|
260
269
|
// Add all other fields to description parts
|
|
261
270
|
for (const [fieldName, fieldValue] of Object.entries(propDetails)) {
|
|
262
271
|
// Skip the fields we're handling separately
|
|
263
|
-
if (fieldName === 'type' || fieldName === 'format' || fieldName === 'description') {
|
|
272
|
+
if (fieldName === 'type' || fieldName === 'format' || fieldName === 'description' || fieldName === 'enum' || fieldName === 'x-enum') {
|
|
264
273
|
continue;
|
|
265
274
|
}
|
|
266
275
|
|
|
@@ -362,13 +371,14 @@ function getHttpOperationForSqlVerb(sqlVerbRef, resourceData){
|
|
|
362
371
|
const httpVerb = operationRef.split('/').pop()
|
|
363
372
|
const path = operationRef.split('/')[0].replaceAll('~1','/');
|
|
364
373
|
|
|
365
|
-
return {
|
|
366
|
-
path,
|
|
367
|
-
httpVerb,
|
|
368
|
-
mediaType: methodObj.response.mediaType,
|
|
374
|
+
return {
|
|
375
|
+
path,
|
|
376
|
+
httpVerb,
|
|
377
|
+
mediaType: methodObj.response.mediaType,
|
|
369
378
|
openAPIDocKey: methodObj.response.openAPIDocKey,
|
|
370
379
|
objectKey: methodObj.response.objectKey || false,
|
|
371
|
-
methodName
|
|
380
|
+
methodName,
|
|
381
|
+
methodConfig: methodObj.config || null
|
|
372
382
|
}
|
|
373
383
|
}
|
|
374
384
|
|
|
@@ -595,6 +605,12 @@ export function generateSchemaJsonFromProps(respProps, depth = 0, maxDepth = 4)
|
|
|
595
605
|
let propType = prop.type || 'object';
|
|
596
606
|
let propDesc = sanitizeHtml(prop.description || '');
|
|
597
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
|
+
|
|
598
614
|
// Add format info to type string if available
|
|
599
615
|
if (prop.format) {
|
|
600
616
|
propType += ` (${prop.format})`;
|
|
@@ -40,16 +40,21 @@ export function createInsertExamples(providerName, serviceName, resourceName, re
|
|
|
40
40
|
// Create SQL example
|
|
41
41
|
content += '\n\n```sql\nINSERT INTO ' + providerName + '.' + serviceName + '.' + resourceName + ' (\n';
|
|
42
42
|
|
|
43
|
-
// Add requestBody fields
|
|
44
|
-
|
|
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
|
|
45
48
|
? Object.entries(methodDetails.requestBody.properties)
|
|
46
49
|
.filter(([_, propDetails]) => propDetails.readOnly !== true)
|
|
47
50
|
.map(([propName]) => propName)
|
|
48
51
|
: [];
|
|
49
52
|
|
|
50
53
|
const requiredBodyProps = methodDetails.requestBody?.required ? methodDetails.requestBody.required : [];
|
|
51
|
-
|
|
52
|
-
const dataProps =
|
|
54
|
+
|
|
55
|
+
const dataProps = hasNaiveTranslate
|
|
56
|
+
? reqBodyProps
|
|
57
|
+
: reqBodyProps.map(prop => 'data__' + prop);
|
|
53
58
|
|
|
54
59
|
// Combine data props with params
|
|
55
60
|
const requiredParams = Object.keys(methodDetails.requiredParams || {});
|
|
@@ -62,11 +67,18 @@ export function createInsertExamples(providerName, serviceName, resourceName, re
|
|
|
62
67
|
// Start SELECT statement
|
|
63
68
|
content += '\n)\nSELECT \n';
|
|
64
69
|
|
|
70
|
+
// Build a set of body prop names for quick lookup
|
|
71
|
+
const bodyPropSet = new Set(reqBodyProps);
|
|
72
|
+
|
|
65
73
|
// Add values placeholders
|
|
66
74
|
const valueLines = allFields.map(field => {
|
|
67
|
-
const isDataField =
|
|
68
|
-
|
|
69
|
-
|
|
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
|
+
|
|
70
82
|
// Check for required body props
|
|
71
83
|
let isRequiredBodyParam = false;
|
|
72
84
|
if(isDataField){
|
|
@@ -78,13 +90,13 @@ export function createInsertExamples(providerName, serviceName, resourceName, re
|
|
|
78
90
|
// Check if it's a number or boolean type
|
|
79
91
|
let isNumber = false;
|
|
80
92
|
let isBoolean = false;
|
|
81
|
-
|
|
93
|
+
|
|
82
94
|
if (isDataField && methodDetails.requestBody?.properties?.[paramName]) {
|
|
83
95
|
const propType = methodDetails.requestBody.properties[paramName].type;
|
|
84
96
|
isNumber = propType === 'number' || propType === 'integer';
|
|
85
97
|
isBoolean = propType === 'boolean';
|
|
86
98
|
}
|
|
87
|
-
|
|
99
|
+
|
|
88
100
|
if (isNumber || isBoolean) {
|
|
89
101
|
return '{{ ' + paramName + ' }}' + (isRequiredBodyParam ? ' /* required */' : '');
|
|
90
102
|
} else {
|
|
@@ -42,95 +42,100 @@ export function createUpdateExamples(providerName, serviceName, resourceName, re
|
|
|
42
42
|
content += '\n\n```sql\n';
|
|
43
43
|
content += (isReplace ? 'REPLACE ' : 'UPDATE ') + providerName + '.' + serviceName + '.' + resourceName;
|
|
44
44
|
|
|
45
|
+
// If requestBodyTranslate.algorithm is 'naive', do not prefix with data__
|
|
46
|
+
const hasNaiveTranslate = methodDetails.methodConfig?.requestBodyTranslate?.algorithm === 'naive';
|
|
47
|
+
|
|
45
48
|
// Add SET clause with requestBody fields (excluding read-only props)
|
|
46
49
|
content += '\nSET \n';
|
|
47
|
-
|
|
50
|
+
|
|
48
51
|
// Get request body fields (excluding read-only props)
|
|
49
|
-
const reqBodyProps = methodDetails.requestBody?.properties
|
|
52
|
+
const reqBodyProps = methodDetails.requestBody?.properties
|
|
50
53
|
? Object.entries(methodDetails.requestBody.properties)
|
|
51
54
|
.filter(([_, propDetails]) => propDetails.readOnly !== true)
|
|
52
55
|
.map(([propName]) => propName)
|
|
53
56
|
: [];
|
|
54
|
-
|
|
55
|
-
// Add
|
|
57
|
+
|
|
58
|
+
// Add fields to SET clause (with or without data__ prefix)
|
|
56
59
|
if (reqBodyProps.length > 0) {
|
|
57
60
|
const setLines = reqBodyProps.map(prop => {
|
|
58
61
|
const propDetails = methodDetails.requestBody.properties[prop];
|
|
59
62
|
const isNumber = propDetails.type === 'number' || propDetails.type === 'integer';
|
|
60
63
|
const isBoolean = propDetails.type === 'boolean';
|
|
61
|
-
|
|
64
|
+
const fieldName = hasNaiveTranslate ? prop : 'data__' + prop;
|
|
65
|
+
|
|
62
66
|
if (isNumber || isBoolean) {
|
|
63
|
-
return
|
|
67
|
+
return fieldName + ' = {{ ' + prop + ' }}';
|
|
64
68
|
} else {
|
|
65
|
-
return
|
|
69
|
+
return fieldName + ' = \'{{ ' + prop + ' }}\'';
|
|
66
70
|
}
|
|
67
71
|
});
|
|
68
|
-
|
|
72
|
+
|
|
69
73
|
content += setLines.join(',\n');
|
|
70
74
|
} else {
|
|
71
75
|
content += '-- No updatable properties';
|
|
72
76
|
}
|
|
73
|
-
|
|
77
|
+
|
|
74
78
|
// Add WHERE clause with parameters
|
|
75
79
|
const requiredParams = Object.keys(methodDetails.requiredParams || {});
|
|
76
80
|
const optionalParams = Object.keys(methodDetails.optionalParams || {});
|
|
77
|
-
|
|
81
|
+
|
|
78
82
|
// Get required body props (excluding read-only props)
|
|
79
|
-
const requiredBodyProps = methodDetails.requestBody?.required
|
|
80
|
-
? methodDetails.requestBody.required.filter(prop =>
|
|
81
|
-
methodDetails.requestBody.properties[prop] &&
|
|
83
|
+
const requiredBodyProps = methodDetails.requestBody?.required
|
|
84
|
+
? methodDetails.requestBody.required.filter(prop =>
|
|
85
|
+
methodDetails.requestBody.properties[prop] &&
|
|
82
86
|
methodDetails.requestBody.properties[prop].readOnly !== true)
|
|
83
87
|
: [];
|
|
84
|
-
|
|
88
|
+
|
|
85
89
|
if (requiredParams.length > 0 || requiredBodyProps.length > 0 || optionalParams.length > 0) {
|
|
86
90
|
|
|
87
91
|
content += '\nWHERE \n';
|
|
88
|
-
|
|
92
|
+
|
|
89
93
|
// Add required parameters
|
|
90
94
|
let clauseCount = 0;
|
|
91
|
-
|
|
95
|
+
|
|
92
96
|
// Add required query/path/header params
|
|
93
97
|
requiredParams.forEach(param => {
|
|
94
98
|
if (clauseCount > 0) content += '\nAND ';
|
|
95
99
|
content += param + ' = \'{{ ' + param + ' }}\' --required';
|
|
96
100
|
clauseCount++;
|
|
97
101
|
});
|
|
98
|
-
|
|
102
|
+
|
|
99
103
|
// Add required body params (only non-readonly ones)
|
|
100
104
|
requiredBodyProps.forEach(prop => {
|
|
101
105
|
if (clauseCount > 0) content += '\nAND ';
|
|
102
|
-
|
|
106
|
+
|
|
103
107
|
const propDetails = methodDetails.requestBody.properties[prop];
|
|
104
108
|
const isBoolean = propDetails.type === 'boolean';
|
|
105
|
-
|
|
109
|
+
const fieldName = hasNaiveTranslate ? prop : 'data__' + prop;
|
|
110
|
+
|
|
106
111
|
if (isBoolean) {
|
|
107
|
-
content +=
|
|
112
|
+
content += fieldName + ' = {{ ' + prop + ' }} --required';
|
|
108
113
|
} else {
|
|
109
|
-
content +=
|
|
114
|
+
content += fieldName + ' = \'{{ ' + prop + ' }}\' --required';
|
|
110
115
|
}
|
|
111
|
-
|
|
116
|
+
|
|
112
117
|
clauseCount++;
|
|
113
118
|
});
|
|
114
|
-
|
|
119
|
+
|
|
115
120
|
// Add optional parameters
|
|
116
121
|
optionalParams.forEach(param => {
|
|
117
122
|
if (clauseCount > 0) content += '\nAND ';
|
|
118
|
-
|
|
123
|
+
|
|
119
124
|
// For boolean parameters, we can add a comment about their default value
|
|
120
125
|
const paramDetails = methodDetails.optionalParams[param];
|
|
121
126
|
const isBoolean = paramDetails.type === 'boolean';
|
|
122
127
|
const defaultValue = paramDetails.default;
|
|
123
|
-
|
|
128
|
+
|
|
124
129
|
if (isBoolean) {
|
|
125
130
|
content += param + ' = {{ ' + param + '}}';
|
|
126
131
|
} else {
|
|
127
132
|
content += param + ' = \'{{ ' + param + '}}\'';
|
|
128
133
|
}
|
|
129
|
-
|
|
134
|
+
|
|
130
135
|
if (isBoolean && defaultValue !== undefined) {
|
|
131
136
|
content += ' -- default: ' + defaultValue;
|
|
132
137
|
}
|
|
133
|
-
|
|
138
|
+
|
|
134
139
|
clauseCount++;
|
|
135
140
|
});
|
|
136
141
|
}
|
|
@@ -9,15 +9,18 @@ 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
|
};
|
|
@@ -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')
|