@stackql/provider-utils 0.1.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.
@@ -0,0 +1,572 @@
1
+ // src/docgen/helpers.js
2
+
3
+ // exported functions for use in other modules
4
+
5
+ export function getIndefiniteArticle(resourceName) {
6
+ // Determine whether to use "a" or "an" based on the first letter's pronunciation
7
+ const firstLetter = resourceName.charAt(0).toLowerCase();
8
+ const vowelSounds = ['a', 'e', 'i', 'o', 'u'];
9
+ // Special case for 'h' when it's silent (like in "hour")
10
+ const specialCaseH = resourceName.toLowerCase().startsWith('hour');
11
+ // Special case for words starting with 'u' but pronounced with 'y' sound (like "user")
12
+ const specialCaseU = firstLetter === 'u' && !resourceName.toLowerCase().startsWith('un');
13
+
14
+ let article = 'a';
15
+ if (vowelSounds.includes(firstLetter) && !specialCaseU) {
16
+ article = 'an';
17
+ }
18
+ // Handle special case for words starting with 'h' where 'h' is silent
19
+ if (firstLetter === 'h' && specialCaseH) {
20
+ article = 'an';
21
+ }
22
+
23
+ return article;
24
+ }
25
+
26
+ export function cleanDescription(description) {
27
+ if (!description) return '';
28
+
29
+ // Replace <a> tags with markdown equivalent
30
+ description = description.replace(/<a\s+(?:[^>]*?\s+)?href="([^"]*)"(?:[^>]*?)>(.*?)<\/a>/gi, '[$2]($1)');
31
+
32
+ // Remove <p> tags and replace them with a single space
33
+ description = description.replace(/<\/?p>/gi, ' ');
34
+
35
+ // Replace <br> tags with a single space
36
+ description = description.replace(/<br\s*\/?>/gi, ' ');
37
+
38
+ // Replace <code> and <pre> tags with markdown code blocks
39
+ description = description.replace(/<(code|pre)>(.*?)<\/\1>/gi, '`$2`');
40
+
41
+ // Convert <ul> and <li> tags into a comma-separated list
42
+ description = description.replace(/<\/?ul>/gi, '');
43
+ description = description.replace(/<li>(.*?)<\/li>/gi, '$1, ');
44
+
45
+ // Remove <name>, <td>, <tr>, and <table> tags
46
+ description = description.replace(/<\/?(name|td|tr|table)>/gi, '');
47
+
48
+ // Replace multiple spaces with a single space
49
+ description = description.replace(/\s+/g, ' ');
50
+
51
+ // Escape pipe characters to prevent breaking markdown tables
52
+ description = description.replace(/\|/g, '\\|');
53
+
54
+ // Remove any trailing commas, spaces, and line breaks
55
+ description = description.replace(/,s*$/, '').trim();
56
+
57
+ description = description.replace(/</g, '{');
58
+ description = description.replace(/>/g, '}');
59
+
60
+ return description;
61
+ }
62
+
63
+ export function getSqlMethodsWithOrderedFields(resourceData, dereferencedAPI, sqlVerb) {
64
+ const methods = {};
65
+
66
+ if (sqlVerb === 'exec') {
67
+ // Get all SQL verb methods
68
+ const allSqlMethodNames = new Set();
69
+ const sqlVerbTypes = ['select', 'insert', 'update', 'delete', 'replace'];
70
+
71
+ for (const verb of sqlVerbTypes) {
72
+ if (resourceData.sqlVerbs[verb] && resourceData.sqlVerbs[verb].length > 0) {
73
+ for (const method of resourceData.sqlVerbs[verb]) {
74
+ const { methodName } = getHttpOperationForSqlVerb(method.$ref, resourceData);
75
+ allSqlMethodNames.add(methodName);
76
+ }
77
+ }
78
+ }
79
+
80
+ // Process each method that's not in any SQL verb
81
+ for (const [methodName, methodData] of Object.entries(resourceData.methods)) {
82
+ if (!allSqlMethodNames.has(methodName)) {
83
+ const { path, httpVerb, mediaType, openAPIDocKey } = methodData.operation;
84
+ let resolvedPath = path;
85
+ let resolvedVerb = httpVerb;
86
+
87
+ // If operation uses $ref, resolve it
88
+ if (methodData.operation.$ref) {
89
+ const refPath = methodData.operation.$ref;
90
+
91
+ // Extract the path and verb from the $ref
92
+ // The path format is typically '#/paths/~1api~1v2~1accounts~1{name}:undrop/post'
93
+ const pathMatch = refPath.match(/#\/paths\/(.+)\/([^/]+)$/);
94
+
95
+ if (pathMatch && pathMatch.length === 3) {
96
+ // Replace the escaped characters in the path
97
+ let path = pathMatch[1]
98
+ .replace(/~1/g, '/') // Replace ~1 with /
99
+ .replace(/~0/g, '~') // Replace ~0 with ~ if needed
100
+
101
+ // Don't modify path parts with special characters like ':undrop'
102
+ resolvedPath = path;
103
+ resolvedVerb = pathMatch[2];
104
+
105
+ console.log(`Resolved path: ${resolvedPath}, verb: ${resolvedVerb}`);
106
+ } else {
107
+ console.warn(`Could not parse $ref path: ${refPath}`);
108
+ // Skip this method if we can't parse the path
109
+ continue;
110
+ }
111
+ }
112
+
113
+ // Get response and params using the same function as for SQL verbs
114
+ const { respProps, respDescription, opDescription, requestBody } = getHttpOperationInfo(
115
+ dereferencedAPI,
116
+ resolvedPath,
117
+ resolvedVerb,
118
+ methodData.response.mediaType || '',
119
+ methodData.response.openAPIDocKey || '200',
120
+ ''
121
+ );
122
+
123
+ const { requiredParams, optionalParams } = getHttpOperationParams(
124
+ dereferencedAPI,
125
+ resolvedPath,
126
+ resolvedVerb
127
+ );
128
+
129
+ // Initialize the method with the same structure as SQL methods
130
+ methods[methodName] = {
131
+ opDescription,
132
+ respDescription,
133
+ properties: {},
134
+ requiredParams: requiredParams || {},
135
+ optionalParams: optionalParams || {},
136
+ requestBody: requestBody || {},
137
+ };
138
+
139
+ // Format and sort the properties using our helper functions
140
+ const allProperties = formatProperties(respProps);
141
+ sortAndAddProperties(methods[methodName], allProperties);
142
+
143
+ console.info(`Processed exec method: ${methodName}`);
144
+ }
145
+ }
146
+
147
+ return methods;
148
+ }
149
+
150
+ if (!resourceData.sqlVerbs[sqlVerb] || resourceData.sqlVerbs[sqlVerb].length === 0) {
151
+ return methods;
152
+ }
153
+
154
+ for (const thisMethod of resourceData.sqlVerbs[sqlVerb]) {
155
+ const {path, httpVerb, mediaType, openAPIDocKey, objectKey, methodName} = getHttpOperationForSqlVerb(thisMethod.$ref, resourceData);
156
+ const {respProps, respDescription, opDescription, requestBody} = getHttpOperationInfo(dereferencedAPI, path, httpVerb, mediaType, openAPIDocKey, objectKey);
157
+ const {requiredParams, optionalParams} = getHttpOperationParams(dereferencedAPI, path, httpVerb);
158
+
159
+ // Initialize the method object with description and params
160
+ methods[methodName] = {
161
+ opDescription,
162
+ respDescription,
163
+ properties: {},
164
+ requiredParams: requiredParams || {},
165
+ optionalParams: optionalParams || {},
166
+ requestBody: requestBody || {},
167
+ };
168
+
169
+ // Format and sort the properties using our helper functions
170
+ const allProperties = formatProperties(respProps);
171
+ sortAndAddProperties(methods[methodName], allProperties);
172
+
173
+ console.info(`Processed method: ${methodName}`);
174
+ }
175
+
176
+ return methods;
177
+ }
178
+
179
+ // internal helper functions for use in this module only
180
+
181
+ // Extract the property sorting logic into a separate function
182
+ function sortAndAddProperties(methodObj, allProperties) {
183
+ // First group: "id" and "name" fields exactly
184
+ const exactIdNameFields = Object.keys(allProperties).filter(
185
+ propName => propName === 'id' || propName === 'name'
186
+ );
187
+
188
+ // Second group: fields ending with "_id"
189
+ const idSuffixFields = Object.keys(allProperties).filter(
190
+ propName => propName !== 'id' && propName.endsWith('_id')
191
+ );
192
+
193
+ // Third group: fields ending with "_name"
194
+ const nameSuffixFields = Object.keys(allProperties).filter(
195
+ propName => propName !== 'name' && propName.endsWith('_name')
196
+ );
197
+
198
+ // Fourth group: all other fields
199
+ const otherFields = Object.keys(allProperties).filter(
200
+ propName => !exactIdNameFields.includes(propName) &&
201
+ !idSuffixFields.includes(propName) &&
202
+ !nameSuffixFields.includes(propName)
203
+ );
204
+
205
+ // Create a sorted array of all field names according to priority
206
+ const sortedFieldNames = [
207
+ ...exactIdNameFields.sort(), // Sort alphabetically within group
208
+ ...idSuffixFields.sort(), // Sort alphabetically within group
209
+ ...nameSuffixFields.sort(), // Sort alphabetically within group
210
+ ...otherFields.sort() // Sort alphabetically within group
211
+ ];
212
+
213
+ // Add properties to the method in the sorted order
214
+ for (const propName of sortedFieldNames) {
215
+ methodObj.properties[propName] = allProperties[propName];
216
+ }
217
+ }
218
+
219
+ // And also extract the property formatting logic
220
+ function formatProperties(respProps) {
221
+ const allProperties = {};
222
+ for (const [propName, propDetails] of Object.entries(respProps)) {
223
+ let typeString = propDetails.type || '';
224
+ if (propDetails.format) {
225
+ typeString += ` (${propDetails.format})`;
226
+ }
227
+
228
+ // Get the base description
229
+ let fullDescription = propDetails.description || '';
230
+ fullDescription = fullDescription.replace(/\n/g, ' ');
231
+ let additionalDescriptionPaths = [];
232
+
233
+ // Add all other fields to description parts
234
+ for (const [fieldName, fieldValue] of Object.entries(propDetails)) {
235
+ // Skip the fields we're handling separately
236
+ if (fieldName === 'type' || fieldName === 'format' || fieldName === 'description') {
237
+ continue;
238
+ }
239
+
240
+ if (typeof fieldValue != 'string') {
241
+ continue;
242
+ } else {
243
+ additionalDescriptionPaths.push(`${fieldName}: ${String(fieldValue)}`);
244
+ }
245
+ }
246
+
247
+ fullDescription += additionalDescriptionPaths.length > 0 ? ` (${additionalDescriptionPaths.join(', ')})` : '';
248
+
249
+ // Store formatted property details
250
+ allProperties[propName] = {
251
+ type: typeString,
252
+ description: escapeHtml(fullDescription),
253
+ };
254
+ }
255
+ return allProperties;
256
+ }
257
+
258
+ function escapeHtml(text) {
259
+ if (!text) return '';
260
+ return text
261
+ .replace(/&/g, '&amp;')
262
+ .replace(/</g, '&lt;')
263
+ .replace(/>/g, '&gt;')
264
+ .replace(/"/g, '&quot;')
265
+ .replace(/'/g, '&#039;');
266
+ }
267
+
268
+ export function sanitizeHtml(text) {
269
+ return text
270
+ // Replace "<" unless it's followed by "a", "/a", "b", "/b", "strong", or "/strong"
271
+ .replace(/<(?!\/?(?:a|b|strong)\b)/gi, '&lt;')
272
+ // Replace ">" unless it's preceded by "</a", "<a ...>", "</b", "<b ...>", "</strong", or "<strong ...>"
273
+ .replace(/(?<!<\/?(?:a|b|strong)[^>]*)>/gi, '&gt;')
274
+ // Add quotes around unquoted href values (within <a ...>)
275
+ .replace(/(<a\b[^>]*?)href=([^"' \t\r\n>]+)/gi, '$1href="$2"')
276
+ // Wrap backticked text with <code>
277
+ .replace(/`([^`]+)`/g, '<code>$1</code>')
278
+ // Replace { and }
279
+ .replace(/{/g, '&#123;')
280
+ .replace(/}/g, '&#125;')
281
+ // Escape backslash
282
+ .replace(/\\/g, '\\\\')
283
+ // Replace " with &quot; UNLESS inside <code>...</code> OR inside an href="...".
284
+ // The alternation matches either a whole <code>...</code> block, an href="...",
285
+ // or a bare " to replace.
286
+ .replace(/(<code>[\s\S]*?<\/code>)|(\bhref="[^"]*")|"/gi, (m, code, href) => {
287
+ if (code) return code; // keep code blocks untouched
288
+ if (href) return href; // keep href="..." quotes untouched
289
+ return '&quot;'; // everything else: replace "
290
+ });
291
+ }
292
+
293
+ function getRequiredServerVars(dereferencedAPI) {
294
+ const serverVars = {};
295
+
296
+ // Check if servers and variables exist in the API spec
297
+ if (!dereferencedAPI.servers ||
298
+ !dereferencedAPI.servers[0] ||
299
+ !dereferencedAPI.servers[0].variables) {
300
+ return serverVars;
301
+ }
302
+
303
+ // Process each server variable
304
+ for (const [varName, varDetails] of Object.entries(dereferencedAPI.servers[0].variables)) {
305
+ // Start with base type and description
306
+ let typeString = 'string'; // Base type for server variables
307
+ let description = varDetails.description || '';
308
+
309
+ // Replace newlines with spaces
310
+ description = description.replace(/\n/g, ' ');
311
+
312
+ // If format exists, add it to type
313
+ if (varDetails.format) {
314
+ typeString += ` (${varDetails.format})`;
315
+ }
316
+
317
+ // Collect additional fields for description
318
+ let additionalFields = [];
319
+
320
+ for (const [fieldName, fieldValue] of Object.entries(varDetails)) {
321
+ // Skip description and format which are already handled
322
+ if (fieldName === 'description' || fieldName === 'format') {
323
+ continue;
324
+ }
325
+
326
+ // Format the field value appropriately
327
+ let formattedValue;
328
+ if (Array.isArray(fieldValue)) {
329
+ formattedValue = `[${fieldValue.join(', ')}]`;
330
+ } else if (typeof fieldValue === 'object' && fieldValue !== null) {
331
+ formattedValue = JSON.stringify(fieldValue);
332
+ } else {
333
+ formattedValue = String(fieldValue);
334
+ }
335
+
336
+ additionalFields.push(`${fieldName}: ${formattedValue}`);
337
+ }
338
+
339
+ // Add the additional fields to description if any exist
340
+ if (additionalFields.length > 0) {
341
+ if (description) {
342
+ description += ' ';
343
+ }
344
+ description += `(${additionalFields.join(', ')})`;
345
+ }
346
+
347
+ // Create the server variable entry
348
+ serverVars[varName] = {
349
+ type: typeString,
350
+ description: description
351
+ };
352
+ }
353
+
354
+ return serverVars;
355
+ }
356
+
357
+ function getHttpOperationForSqlVerb(sqlVerbRef, resourceData){
358
+
359
+ console.log(`Getting http operation for sql verb...`);
360
+
361
+ // get path and verb
362
+ const methodName = sqlVerbRef.split('/').pop();
363
+ const methodObj = resourceData.methods[methodName]
364
+ const operationRef = methodObj.operation.$ref.split('#/paths/').pop();
365
+ const httpVerb = operationRef.split('/').pop()
366
+ const path = operationRef.split('/')[0].replaceAll('~1','/');
367
+
368
+ return {
369
+ path,
370
+ httpVerb,
371
+ mediaType: methodObj.response.mediaType,
372
+ openAPIDocKey: methodObj.response.openAPIDocKey,
373
+ objectKey: methodObj.response.objectKey || false,
374
+ methodName
375
+ }
376
+ }
377
+
378
+ function getHttpOperationInfo(dereferencedAPI, path, httpVerb, mediaType, openAPIDocKey, objectKey) {
379
+ console.log(`Getting response for ${path}/${httpVerb}...`);
380
+
381
+ // Check if the path exists in the dereferencedAPI
382
+ if (!dereferencedAPI.paths[path]) {
383
+ throw new Error(`Path '${path}' not found in dereferencedAPI.paths`);
384
+ }
385
+
386
+ // Check if the HTTP verb exists for this path
387
+ if (!dereferencedAPI.paths[path][httpVerb]) {
388
+ throw new Error(`HTTP verb '${httpVerb}' not found for path '${path}'`);
389
+ }
390
+
391
+ // get op description
392
+ const opDescription = dereferencedAPI.paths[path][httpVerb].description || '';
393
+
394
+ // Extract request body if it exists
395
+ let requestBody = {};
396
+ if (dereferencedAPI.paths[path][httpVerb].requestBody &&
397
+ dereferencedAPI.paths[path][httpVerb].requestBody.content) {
398
+
399
+ // Get first content type available in requestBody
400
+ const contentTypes = Object.keys(dereferencedAPI.paths[path][httpVerb].requestBody.content);
401
+ if (contentTypes.length > 0) {
402
+ const firstContentType = contentTypes[0];
403
+ const reqBodySchema = dereferencedAPI.paths[path][httpVerb].requestBody.content[firstContentType].schema;
404
+ if (reqBodySchema) {
405
+ // If schema is a reference, use it directly
406
+ if (reqBodySchema.$ref) {
407
+ requestBody = reqBodySchema;
408
+ }
409
+ // If schema is an object with properties, get them
410
+ else if (reqBodySchema.properties) {
411
+ requestBody = {
412
+ properties: reqBodySchema.properties,
413
+ required: reqBodySchema.required || []
414
+ };
415
+ }
416
+ // If schema is something else, use it as is
417
+ else {
418
+ requestBody = reqBodySchema;
419
+ }
420
+ }
421
+ }
422
+ }
423
+
424
+ // Check if the response exists
425
+ if (!dereferencedAPI.paths[path][httpVerb].responses ||
426
+ !dereferencedAPI.paths[path][httpVerb].responses[openAPIDocKey]) {
427
+ console.warn(`Response '${openAPIDocKey}' not found for ${path}/${httpVerb}`);
428
+ return {
429
+ respProps: {},
430
+ respDescription: '',
431
+ opDescription,
432
+ requestBody
433
+ };
434
+ }
435
+
436
+ // Check if there's a content section with the mediaType
437
+ const responseObj = dereferencedAPI.paths[path][httpVerb].responses[openAPIDocKey];
438
+
439
+ // If no content or no mediaType in the response, return empty properties
440
+ if (!responseObj.content || !mediaType || !responseObj.content[mediaType] || !responseObj.content[mediaType].schema) {
441
+ return {
442
+ respProps: {},
443
+ respDescription: responseObj.description || '',
444
+ opDescription,
445
+ requestBody
446
+ };
447
+ }
448
+
449
+ const schema = responseObj.content[mediaType].schema;
450
+
451
+ if (schema.type === 'array') {
452
+ return {
453
+ respProps: schema.items.properties || {},
454
+ respDescription: schema.items.description || responseObj.description || '',
455
+ opDescription,
456
+ requestBody
457
+ };
458
+ } else if (schema.type === 'object') {
459
+ return {
460
+ respProps: schema.properties || {},
461
+ respDescription: schema.description || responseObj.description || '',
462
+ opDescription,
463
+ requestBody
464
+ };
465
+ } else {
466
+ // For primitive types or when schema structure is unexpected
467
+ return {
468
+ respProps: {},
469
+ respDescription: schema.description || responseObj.description || '',
470
+ opDescription,
471
+ requestBody
472
+ };
473
+ }
474
+ }
475
+
476
+ function getHttpOperationParams(dereferencedAPI, path, httpVerb) {
477
+ const requiredParams = {};
478
+ const optionalParams = {};
479
+
480
+ // Get the parameters array from the operation
481
+ const params = dereferencedAPI.paths[path][httpVerb].parameters || [];
482
+
483
+ // Process each parameter
484
+ for (const param of params) {
485
+ // Skip parameters without a name or schema
486
+ if (!param.name || !param.schema) continue;
487
+
488
+ // Format the type string
489
+ let typeString = param.schema.type || '';
490
+ if (param.schema.format) {
491
+ typeString += ` (${param.schema.format})`;
492
+ }
493
+
494
+ // Get the base description and clean it up
495
+ let description = param.description || '';
496
+ // Replace newlines with spaces to avoid string concatenation in output
497
+ description = description.replace(/\n/g, ' ');
498
+
499
+ let additionalDescriptionParts = [];
500
+
501
+ for (const [fieldName, fieldValue] of Object.entries(param.schema)) {
502
+ if (fieldName === 'type' || fieldName === 'format' || fieldName === 'description' || fieldName === 'pattern') {
503
+ continue;
504
+ }
505
+
506
+ let formattedValue;
507
+ if (Array.isArray(fieldValue)) {
508
+ formattedValue = `[${fieldValue.join(', ')}]`;
509
+ } else if (typeof fieldValue === 'object' && fieldValue !== null) {
510
+ formattedValue = JSON.stringify(fieldValue);
511
+ } else {
512
+ formattedValue = String(fieldValue);
513
+ }
514
+
515
+ // if (fieldName === 'pattern') {
516
+ // additionalDescriptionParts.push(`pattern: <code>${formattedValue}</code>`);
517
+ // } else {
518
+ // additionalDescriptionParts.push(`${fieldName}: ${formattedValue}`);
519
+ // }
520
+ }
521
+
522
+ // Add any fields from the parameter itself that might contain metadata
523
+ for (const [fieldName, fieldValue] of Object.entries(param)) {
524
+ // Skip fields we've already processed or don't need
525
+ if (fieldName === 'name' || fieldName === 'schema' ||
526
+ fieldName === 'required' || fieldName === 'in' ||
527
+ fieldName === 'description') {
528
+ continue;
529
+ }
530
+
531
+ // Handle example field specifically (it might exist at param level not schema level)
532
+ if (fieldName === 'example' && !param.schema.example) {
533
+ let formattedValue;
534
+ if (Array.isArray(fieldValue)) {
535
+ formattedValue = `[${fieldValue.join(', ')}]`;
536
+ } else if (typeof fieldValue === 'object' && fieldValue !== null) {
537
+ formattedValue = JSON.stringify(fieldValue);
538
+ } else {
539
+ formattedValue = String(fieldValue);
540
+ }
541
+
542
+ additionalDescriptionParts.push(`example: ${formattedValue}`);
543
+ }
544
+ }
545
+
546
+ // Add additional description parts in parentheses if there are any
547
+ if (additionalDescriptionParts.length > 0) {
548
+ description += ` (${additionalDescriptionParts.join(', ')})`;
549
+ }
550
+
551
+ // Create the parameter details object
552
+ const paramDetails = {
553
+ type: typeString,
554
+ description: description // Apply escapeHtml here if needed
555
+ };
556
+
557
+ // Add to the appropriate category based on required flag
558
+ if (param.required === true) {
559
+ requiredParams[param.name] = paramDetails;
560
+ } else {
561
+ optionalParams[param.name] = paramDetails;
562
+ }
563
+ }
564
+
565
+ // Get server variables and merge them into requiredParams
566
+ const serverVars = getRequiredServerVars(dereferencedAPI);
567
+
568
+ // Merge server variables into requiredParams
569
+ Object.assign(requiredParams, serverVars);
570
+
571
+ return { requiredParams, optionalParams };
572
+ }
@@ -0,0 +1,5 @@
1
+ // src/docgen/index.js
2
+
3
+ // Export all documentation generation functions
4
+ export { generateDocs } from './generator.js';
5
+ export { createResourceIndexContent } from './resource-content.js';
@@ -0,0 +1,74 @@
1
+ // src/docgen/resource/examples/delete-example.js
2
+ import {
3
+ getSqlMethodsWithOrderedFields,
4
+ } from '../../helpers.js';
5
+
6
+ export function createDeleteExamples(providerName, serviceName, resourceName, resourceData, dereferencedAPI) {
7
+ const deleteMethods = getSqlMethodsWithOrderedFields(resourceData, dereferencedAPI, 'delete');
8
+
9
+ // if there are no delete methods, return empty content
10
+ if (Object.keys(deleteMethods).length === 0) {
11
+ return '';
12
+ }
13
+
14
+ let content = '\n\n## `DELETE` examples\n\n';
15
+
16
+ // Create tab structure with values array
17
+ content += '<Tabs\n defaultValue="' + Object.keys(deleteMethods)[0] + '"\n values={[\n';
18
+
19
+ // Add each method as a tab option
20
+ Object.keys(deleteMethods).forEach((methodName, index, arr) => {
21
+ content += ' { label: \'' + methodName + '\', value: \'' + methodName + '\' }';
22
+ content += index < arr.length - 1 ? ',\n' : '\n';
23
+ });
24
+
25
+ content += ' ]}\n>\n';
26
+
27
+ // Create each method tab content
28
+ Object.entries(deleteMethods).forEach(([methodName, methodDetails]) => {
29
+ content += '<TabItem value="' + methodName + '">\n\n';
30
+
31
+ // Add method description
32
+ content += methodDetails.opDescription || methodDetails.respDescription || 'No description available.';
33
+
34
+ // Create SQL example
35
+ content += '\n\n```sql\nDELETE FROM ' + providerName + '.' + serviceName + '.' + resourceName;
36
+
37
+ // Add WHERE clause with parameters
38
+ const requiredParams = Object.keys(methodDetails.requiredParams || {});
39
+ const optionalParams = Object.keys(methodDetails.optionalParams || {});
40
+
41
+ if (requiredParams.length > 0 || optionalParams.length > 0) {
42
+ content += '\nWHERE ';
43
+
44
+ // Add required parameters
45
+ requiredParams.forEach((param, index) => {
46
+ content += param + ' = \'{{ ' + param + ' }}\' --required';
47
+ content += index < requiredParams.length - 1 || optionalParams.length > 0 ? '\nAND ' : '';
48
+ });
49
+
50
+ // Add optional parameters
51
+ optionalParams.forEach((param, index) => {
52
+ // For boolean parameters, we can add a comment about their default value
53
+ const paramDetails = methodDetails.optionalParams[param];
54
+ const isBoolean = paramDetails.type === 'boolean';
55
+ const defaultValue = paramDetails.default;
56
+
57
+ content += param + ' = \'{{ ' + param + ' }}\'';
58
+
59
+ if (isBoolean && defaultValue !== undefined) {
60
+ content += ' -- default: ' + defaultValue;
61
+ }
62
+
63
+ content += index < optionalParams.length - 1 ? '\nAND ' : '';
64
+ });
65
+ }
66
+
67
+ content += ';\n```\n</TabItem>\n';
68
+ });
69
+
70
+ // Close tabs
71
+ content += '</Tabs>\n';
72
+
73
+ return content;
74
+ }