apaas-oapi-client 0.1.33 → 0.1.35

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/README.md CHANGED
@@ -16,6 +16,7 @@
16
16
  - ✅ record 单条查询
17
17
  - ✅ record 单条更新、批量更新
18
18
  - ✅ record 单条删除、批量删除
19
+ - ✅ **导出数据对象文档为 Markdown**
19
20
  - ✅ 内置 Bottleneck 限流器
20
21
  - ✅ 自定义日志等级
21
22
 
package/UserManual.md CHANGED
@@ -332,6 +332,49 @@ const res = await client.object.metadata.fields({
332
332
  console.log(res);
333
333
  ```
334
334
 
335
+ ### **导出数据对象文档为 Markdown**
336
+
337
+ 将数据对象的元数据导出为详细的 Markdown 文档,包含完整的字段信息、类型、配置等。
338
+
339
+ ```JavaScript
340
+ const fs = require('fs');
341
+
342
+ // 方式一:导出所有对象(推荐,无需参数)
343
+ const markdown = await client.object.metadata.export2markdown();
344
+ fs.writeFileSync('all_objects.md', markdown, 'utf-8');
345
+
346
+ // 方式二:只导出指定的对象
347
+ const markdown2 = await client.object.metadata.export2markdown({
348
+ object_names: ['object_store', 'object_order', '_user']
349
+ });
350
+ fs.writeFileSync('specific_objects.md', markdown2, 'utf-8');
351
+
352
+ // 方式三:结合 listWithIterator 灵活筛选
353
+ const allObjects = await client.object.listWithIterator();
354
+ const customObjects = allObjects.items
355
+ .filter(obj => !obj.apiName.startsWith('_')) // 只要自定义对象
356
+ .map(obj => obj.apiName);
357
+
358
+ const markdown3 = await client.object.metadata.export2markdown({
359
+ object_names: customObjects
360
+ });
361
+ fs.writeFileSync('custom_objects.md', markdown3, 'utf-8');
362
+
363
+ console.log('✅ 文档导出成功!');
364
+ ```
365
+
366
+ **生成的 Markdown 文档包含:**
367
+ - 📋 自动生成的目录(带锚点链接)
368
+ - 📊 每个对象的详细信息(中英文名称、创建时间、字段数量)
369
+ - 📝 字段列表(中文名称、API名称、类型、必填、唯一性)
370
+ - ⚙️ 字段配置详情:
371
+ - 选项字段:展示所有选项值
372
+ - 公式字段:显示公式表达式和返回类型
373
+ - 引用字段:显示引用来源和字段
374
+ - lookup 字段:显示关联对象
375
+ - 其他配置:最大长度、小数位、显示样式等
376
+ - 🎯 字段智能排序(系统字段、业务字段、公式字段分类展示)
377
+
335
378
  ***
336
379
 
337
380
 
package/dist/index.js CHANGED
@@ -120,10 +120,10 @@ class Client {
120
120
  /**
121
121
  * 列出所有对象(数据表)
122
122
  * @param params 请求参数 { offset?, filter?, limit? }
123
- * @returns 接口返回结果,包含 has_more 字段表示是否还有更多数据
123
+ * @returns 接口返回结果 { code, items, total, msg, has_more }
124
124
  */
125
125
  list: async (params) => {
126
- var _a, _b;
126
+ var _a, _b, _c, _d, _e, _f;
127
127
  const offset = (_a = params === null || params === void 0 ? void 0 : params.offset) !== null && _a !== void 0 ? _a : 0;
128
128
  const limit = (_b = params === null || params === void 0 ? void 0 : params.limit) !== null && _b !== void 0 ? _b : 50;
129
129
  const filter = params === null || params === void 0 ? void 0 : params.filter;
@@ -139,23 +139,28 @@ class Client {
139
139
  });
140
140
  this.log(LoggerLevel.debug, `[object.list] Objects list fetched successfully: code=${res.data.code}`);
141
141
  this.log(LoggerLevel.trace, `[object.list] Response: ${JSON.stringify(res.data)}`);
142
- // 添加 has_more 字段判断是否还有更多数据
143
- if (res.data && res.data.data) {
144
- const total = res.data.data.total || 0;
145
- const currentEnd = offset + limit;
146
- res.data.has_more = currentEnd < total;
147
- this.log(LoggerLevel.debug, `[object.list] has_more=${res.data.has_more}, total=${total}, currentEnd=${currentEnd}`);
148
- }
149
- return res.data;
142
+ // 扁平化返回结构并添加 has_more 字段
143
+ const items = ((_d = (_c = res.data) === null || _c === void 0 ? void 0 : _c.data) === null || _d === void 0 ? void 0 : _d.items) || [];
144
+ const total = ((_f = (_e = res.data) === null || _e === void 0 ? void 0 : _e.data) === null || _f === void 0 ? void 0 : _f.total) || 0;
145
+ const currentEnd = offset + limit;
146
+ const has_more = currentEnd < total;
147
+ this.log(LoggerLevel.debug, `[object.list] has_more=${has_more}, total=${total}, currentEnd=${currentEnd}`);
148
+ return {
149
+ code: res.data.code,
150
+ items,
151
+ total,
152
+ msg: res.data.msg,
153
+ has_more
154
+ };
150
155
  },
151
156
  /**
152
157
  * 列出所有对象(数据表)- 支持自动分页查询
153
158
  * @description 该方法会自动处理分页,直到没有更多数据为止
154
159
  * @param params 请求参数 { filter?, limit? }
155
- * @returns { total, items }
160
+ * @returns { code, msg, items, total, failed? } - code 表示失败的分页数量
156
161
  */
157
162
  listWithIterator: async (params) => {
158
- var _a, _b, _c, _d;
163
+ var _a, _b;
159
164
  const filter = params === null || params === void 0 ? void 0 : params.filter;
160
165
  const limit = (_a = params === null || params === void 0 ? void 0 : params.limit) !== null && _a !== void 0 ? _a : 50;
161
166
  let results = [];
@@ -164,40 +169,87 @@ class Client {
164
169
  let hasMore = true;
165
170
  let page = 0;
166
171
  let totalPages = 0;
172
+ let failed = [];
173
+ let allSuccess = true;
167
174
  this.log(LoggerLevel.info, `[object.listWithIterator] Starting paginated query with limit=${limit}`);
168
175
  while (hasMore) {
169
- const res = await this.object.list({
170
- offset,
171
- limit,
172
- filter
173
- });
174
- if (res.code !== '0') {
175
- this.log(LoggerLevel.error, `[object.listWithIterator] Error querying objects: code=${res.code}, msg=${res.msg}`);
176
- throw new Error(res.msg || `Query failed with code ${res.code}`);
177
- }
178
- page += 1;
179
- if (res.data && Array.isArray(res.data.items)) {
180
- results = results.concat(res.data.items);
181
- }
182
- if (res.data && (res.data.total !== undefined && res.data.total !== null)) {
183
- total = res.data.total;
176
+ try {
177
+ const res = await this.object.list({
178
+ offset,
179
+ limit,
180
+ filter
181
+ });
182
+ if (res.code !== '0') {
183
+ this.log(LoggerLevel.error, `[object.listWithIterator] Error querying objects: code=${res.code}, msg=${res.msg}, offset=${offset}`);
184
+ allSuccess = false;
185
+ failed.push({
186
+ offset,
187
+ limit,
188
+ code: res.code,
189
+ msg: res.msg || `Query failed with code ${res.code}`
190
+ });
191
+ // 继续尝试下一页,而不是直接退出
192
+ offset += limit;
193
+ page += 1;
194
+ continue;
195
+ }
196
+ page += 1;
197
+ if (Array.isArray(res.items)) {
198
+ results = results.concat(res.items);
199
+ }
200
+ if (res.total !== undefined && res.total !== null) {
201
+ total = res.total;
202
+ }
203
+ if (page === 1) {
204
+ totalPages = Math.ceil(total / limit);
205
+ this.log(LoggerLevel.info, `[object.listWithIterator] Total objects: ${total}, pages: ${totalPages}`);
206
+ }
207
+ // 判断是否还有更多数据
208
+ hasMore = res.has_more === true;
209
+ offset += limit;
210
+ const padLength = totalPages.toString().length;
211
+ const pageStr = page.toString().padStart(padLength, '0');
212
+ const totalPagesStr = totalPages.toString().padStart(padLength, '0');
213
+ this.log(LoggerLevel.info, `[object.listWithIterator] Page completed: [${pageStr}/${totalPagesStr}]`);
214
+ this.log(LoggerLevel.debug, `[object.listWithIterator] Page ${page} details: items=${(_b = res.items) === null || _b === void 0 ? void 0 : _b.length}, hasMore=${hasMore}`);
215
+ this.log(LoggerLevel.trace, `[object.listWithIterator] Page ${page} data: ${JSON.stringify(res.items)}`);
184
216
  }
185
- if (page === 1) {
186
- totalPages = Math.ceil(total / limit);
187
- this.log(LoggerLevel.info, `[object.listWithIterator] Total objects: ${total}, pages: ${totalPages}`);
217
+ catch (error) {
218
+ this.log(LoggerLevel.error, `[object.listWithIterator] Exception occurred: ${error}, offset=${offset}`);
219
+ allSuccess = false;
220
+ failed.push({
221
+ offset,
222
+ limit,
223
+ code: '1',
224
+ msg: error instanceof Error ? error.message : String(error)
225
+ });
226
+ // 继续尝试下一页
227
+ offset += limit;
228
+ page += 1;
229
+ // 如果没有获取到 total,可能需要退出循环
230
+ if (total === 0) {
231
+ hasMore = false;
232
+ }
233
+ else {
234
+ hasMore = offset < total;
235
+ }
188
236
  }
189
- // 判断是否还有更多数据
190
- hasMore = res.has_more === true;
191
- offset += limit;
192
- const padLength = totalPages.toString().length;
193
- const pageStr = page.toString().padStart(padLength, '0');
194
- const totalPagesStr = totalPages.toString().padStart(padLength, '0');
195
- this.log(LoggerLevel.info, `[object.listWithIterator] Page completed: [${pageStr}/${totalPagesStr}]`);
196
- this.log(LoggerLevel.debug, `[object.listWithIterator] Page ${page} details: items=${(_c = (_b = res.data) === null || _b === void 0 ? void 0 : _b.items) === null || _c === void 0 ? void 0 : _c.length}, hasMore=${hasMore}`);
197
- this.log(LoggerLevel.trace, `[object.listWithIterator] Page ${page} data: ${JSON.stringify((_d = res.data) === null || _d === void 0 ? void 0 : _d.items)}`);
198
237
  }
199
- this.log(LoggerLevel.info, `[object.listWithIterator] Completed: total=${total}, fetched=${results.length}`);
200
- return { total, items: results };
238
+ const resultCode = failed.length.toString();
239
+ const resultMsg = failed.length === 0
240
+ ? 'Success'
241
+ : `Completed with ${failed.length} failed page(s)`;
242
+ this.log(LoggerLevel.info, `[object.listWithIterator] Completed: code=${resultCode}, total=${total}, fetched=${results.length}, failed=${failed.length}`);
243
+ const result = {
244
+ code: resultCode,
245
+ msg: resultMsg,
246
+ items: results,
247
+ total
248
+ };
249
+ if (failed.length > 0) {
250
+ result.failed = failed;
251
+ }
252
+ return result;
201
253
  },
202
254
  metadata: {
203
255
  /**
@@ -235,6 +287,242 @@ class Client {
235
287
  this.log(LoggerLevel.debug, `[object.metadata.fields] All fields metadata fetched: ${object_name}, code=${res.data.code}`);
236
288
  this.log(LoggerLevel.trace, `[object.metadata.fields] Response: ${JSON.stringify(res.data)}`);
237
289
  return res.data;
290
+ },
291
+ /**
292
+ * 导出数据对象元数据为 Markdown 文档
293
+ * @description 将数据对象的字段信息导出为详细的 Markdown 文档,包含字段类型、配置、选项等完整信息
294
+ * @param options 导出配置
295
+ * @param options.object_names 可选,要导出的对象名称数组。如果不传,则导出所有对象
296
+ * @returns Markdown 文档字符串
297
+ * @example
298
+ * ```typescript
299
+ * // 导出所有对象
300
+ * const markdown = await client.object.metadata.export2markdown();
301
+ *
302
+ * // 只导出指定对象
303
+ * const markdown = await client.object.metadata.export2markdown({
304
+ * object_names: ['object_store', 'object_order', '_user']
305
+ * });
306
+ *
307
+ * // 结合 listWithIterator 使用
308
+ * const allObjects = await client.object.listWithIterator();
309
+ * const objectNames = allObjects.items.map(obj => obj.apiName);
310
+ * const markdown = await client.object.metadata.export2markdown({
311
+ * object_names: objectNames
312
+ * });
313
+ *
314
+ * // 保存到文件
315
+ * fs.writeFileSync('objects_doc.md', markdown, 'utf-8');
316
+ * ```
317
+ */
318
+ export2markdown: async (options) => {
319
+ var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o, _p, _q, _r, _s, _t, _u, _v, _w;
320
+ const objectNames = options === null || options === void 0 ? void 0 : options.object_names;
321
+ this.log(LoggerLevel.info, `[object.metadata.export2markdown] Starting markdown export${objectNames && objectNames.length > 0 ? ` for ${objectNames.length} objects` : ' for all objects'}`);
322
+ let items = [];
323
+ if (objectNames && objectNames.length > 0) {
324
+ // 如果指定了对象名称,只获取这些对象
325
+ this.log(LoggerLevel.debug, `[object.metadata.export2markdown] Fetching specified objects: ${objectNames.join(', ')}`);
326
+ // 先获取所有对象列表
327
+ const allObjects = await this.object.listWithIterator();
328
+ // 过滤出指定的对象
329
+ items = allObjects.items.filter((obj) => objectNames.includes(obj.apiName));
330
+ // 检查是否有不存在的对象
331
+ if (items.length < objectNames.length) {
332
+ const foundNames = items.map((obj) => obj.apiName);
333
+ const notFound = objectNames.filter(name => !foundNames.includes(name));
334
+ this.log(LoggerLevel.warn, `[object.metadata.export2markdown] Objects not found: ${notFound.join(', ')}`);
335
+ }
336
+ this.log(LoggerLevel.debug, `[object.metadata.export2markdown] Found ${items.length}/${objectNames.length} matching objects`);
337
+ if (items.length === 0) {
338
+ this.log(LoggerLevel.warn, `[object.metadata.export2markdown] No matching objects found`);
339
+ return '# 数据对象字段文档\n\n> 未找到匹配的对象\n';
340
+ }
341
+ }
342
+ else {
343
+ // 获取所有对象
344
+ this.log(LoggerLevel.debug, `[object.metadata.export2markdown] Fetching all objects`);
345
+ const allObjects = await this.object.listWithIterator();
346
+ items = allObjects.items || [];
347
+ }
348
+ this.log(LoggerLevel.debug, `[object.metadata.export2markdown] Fetched ${items.length} objects`);
349
+ // 生成 Markdown 文档
350
+ let markdown = '# 数据对象字段文档\n\n';
351
+ markdown += `> 生成时间: ${dayjs().format('YYYY-MM-DD HH:mm:ss')}\n\n`;
352
+ markdown += `> 对象总数: ${items.length}\n\n`;
353
+ markdown += '---\n\n';
354
+ // 目录
355
+ markdown += '## 目录\n\n';
356
+ items.forEach((obj, index) => {
357
+ var _a, _b;
358
+ const chineseName = ((_a = obj.label) === null || _a === void 0 ? void 0 : _a.zh_CN) || ((_b = obj.label) === null || _b === void 0 ? void 0 : _b.en_US) || obj.apiName;
359
+ markdown += `${index + 1}. [${chineseName} (${obj.apiName})](#${obj.apiName.replace(/_/g, '')})\n`;
360
+ });
361
+ markdown += '\n---\n\n';
362
+ // 遍历每个对象
363
+ for (const obj of items) {
364
+ const chineseName = ((_a = obj.label) === null || _a === void 0 ? void 0 : _a.zh_CN) || ((_b = obj.label) === null || _b === void 0 ? void 0 : _b.en_US) || obj.apiName;
365
+ const englishName = ((_c = obj.label) === null || _c === void 0 ? void 0 : _c.en_US) || '';
366
+ markdown += `## ${chineseName} \`${obj.apiName}\`\n\n`;
367
+ if (englishName && englishName !== chineseName) {
368
+ markdown += `**英文名称:** ${englishName}\n\n`;
369
+ }
370
+ markdown += `**创建时间:** ${dayjs(obj.createdAt).format('YYYY-MM-DD HH:mm:ss')}\n\n`;
371
+ markdown += `**字段数量:** ${((_d = obj.fields) === null || _d === void 0 ? void 0 : _d.length) || 0}\n\n`;
372
+ // 字段表格
373
+ if (obj.fields && obj.fields.length > 0) {
374
+ // 对字段进行分类和排序
375
+ const systemFieldOrder = ['_name', '_createdBy', '_createdAt', '_updatedBy', '_updatedAt'];
376
+ const specialFieldTypes = ['formula', 'referenceField'];
377
+ let idField = null;
378
+ const normalFields = [];
379
+ const specialFields = [];
380
+ const systemFields = [];
381
+ obj.fields.forEach((field) => {
382
+ var _a;
383
+ if (field.apiName === '_id') {
384
+ idField = field;
385
+ }
386
+ else if (systemFieldOrder.includes(field.apiName)) {
387
+ systemFields.push(field);
388
+ }
389
+ else if (specialFieldTypes.includes((_a = field.type) === null || _a === void 0 ? void 0 : _a.name)) {
390
+ specialFields.push(field);
391
+ }
392
+ else {
393
+ normalFields.push(field);
394
+ }
395
+ });
396
+ // 对系统字段按指定顺序排序
397
+ systemFields.sort((a, b) => {
398
+ return systemFieldOrder.indexOf(a.apiName) - systemFieldOrder.indexOf(b.apiName);
399
+ });
400
+ // 组合所有字段:_id + 正常字段 + 特殊字段 + 系统字段
401
+ const sortedFields = [];
402
+ if (idField)
403
+ sortedFields.push(idField);
404
+ sortedFields.push(...normalFields);
405
+ sortedFields.push(...specialFields);
406
+ sortedFields.push(...systemFields);
407
+ markdown += '### 字段列表\n\n';
408
+ markdown += '| 中文名称 | API名称 | 类型 | 必填 | 唯一 | 其他设置 |\n';
409
+ markdown += '|---------|---------|------|------|------|----------|\n';
410
+ for (const field of sortedFields) {
411
+ // 转义 Markdown 表格中的特殊字符
412
+ const escapeMarkdown = (text) => {
413
+ return text.replace(/\|/g, '\\|').replace(/\n/g, ' ');
414
+ };
415
+ const label = escapeMarkdown(((_e = field.label) === null || _e === void 0 ? void 0 : _e.zh_CN) || ((_f = field.label) === null || _f === void 0 ? void 0 : _f.en_US) || '-');
416
+ const apiName = field.apiName || '-';
417
+ const typeName = ((_g = field.type) === null || _g === void 0 ? void 0 : _g.name) || '-';
418
+ const required = ((_j = (_h = field.type) === null || _h === void 0 ? void 0 : _h.settings) === null || _j === void 0 ? void 0 : _j.required) ? '✓' : '';
419
+ const unique = ((_l = (_k = field.type) === null || _k === void 0 ? void 0 : _k.settings) === null || _l === void 0 ? void 0 : _l.unique) ? '✓' : '';
420
+ // 构建其他设置信息
421
+ const otherSettings = [];
422
+ const settings = ((_m = field.type) === null || _m === void 0 ? void 0 : _m.settings) || {};
423
+ // lookup 类型:标注关联的对象(外键)
424
+ if (((_o = field.type) === null || _o === void 0 ? void 0 : _o.name) === 'lookup' && settings.objectAPIName) {
425
+ otherSettings.push(`🔗 关联对象: \`${settings.objectAPIName}\``);
426
+ }
427
+ // referenceField 类型:引用字段
428
+ if (((_p = field.type) === null || _p === void 0 ? void 0 : _p.name) === 'referenceField') {
429
+ otherSettings.push(`⚙️ 系统自动维护,不需要写/更新`);
430
+ if (settings.guideFieldAPIName) {
431
+ otherSettings.push(`📎 引用自: \`${settings.guideFieldAPIName}\``);
432
+ }
433
+ if (settings.fieldAPIName) {
434
+ otherSettings.push(`📋 引用字段: \`${settings.fieldAPIName}\``);
435
+ }
436
+ }
437
+ // formula 类型:系统自动维护
438
+ if (((_q = field.type) === null || _q === void 0 ? void 0 : _q.name) === 'formula') {
439
+ otherSettings.push(`⚙️ 系统自动维护,不需要写/更新`);
440
+ if (settings.formula && Array.isArray(settings.formula)) {
441
+ // 优先显示中文公式,否则显示第一个
442
+ const zhFormula = settings.formula.find((f) => f.language_code === 2052);
443
+ const formulaText = (zhFormula === null || zhFormula === void 0 ? void 0 : zhFormula.text) || ((_r = settings.formula[0]) === null || _r === void 0 ? void 0 : _r.text);
444
+ if (formulaText) {
445
+ // 转义公式中的特殊字符
446
+ const escapedFormula = formulaText.replace(/\|/g, '\\|').replace(/\n/g, ' ');
447
+ otherSettings.push(`公式: ${escapedFormula}`);
448
+ }
449
+ }
450
+ if (settings.returnType) {
451
+ otherSettings.push(`返回类型: ${settings.returnType}`);
452
+ }
453
+ }
454
+ // 根据不同类型展示不同的设置
455
+ if (settings.maxLength) {
456
+ otherSettings.push(`最大长度:${settings.maxLength}`);
457
+ }
458
+ if (settings.decimalPlacesNumber !== undefined) {
459
+ otherSettings.push(`小数位:${settings.decimalPlacesNumber}`);
460
+ }
461
+ if (settings.displayAsPercentage) {
462
+ otherSettings.push('百分比显示');
463
+ }
464
+ if (settings.multiline) {
465
+ otherSettings.push('多行');
466
+ }
467
+ if (settings.multiple) {
468
+ otherSettings.push('多选');
469
+ }
470
+ if (settings.hierarchy) {
471
+ otherSettings.push('层级');
472
+ }
473
+ if (settings.displayStyle && ((_s = field.type) === null || _s === void 0 ? void 0 : _s.name) !== 'lookup') {
474
+ otherSettings.push(`显示样式:${settings.displayStyle}`);
475
+ }
476
+ if (settings.referenceObjectApiName) {
477
+ otherSettings.push(`关联:${settings.referenceObjectApiName}`);
478
+ }
479
+ if (settings.rollUpType) {
480
+ otherSettings.push(`汇总:${settings.rollUpType}`);
481
+ }
482
+ // 如果是 option 类型,获取选项列表
483
+ if (((_t = field.type) === null || _t === void 0 ? void 0 : _t.name) === 'option') {
484
+ // 优先使用已有的 optionList,避免额外 API 请求
485
+ const options = settings.optionList;
486
+ if (options && options.length > 0) {
487
+ const optionTexts = options.map((opt) => {
488
+ var _a, _b;
489
+ const zhLabel = ((_b = (_a = opt.label) === null || _a === void 0 ? void 0 : _a.find((l) => l.language_code === 2052)) === null || _b === void 0 ? void 0 : _b.text) || '-';
490
+ return `${zhLabel}(\`${opt.apiName}\`)`;
491
+ });
492
+ otherSettings.push(`选项: ${optionTexts.join(', ')}`);
493
+ }
494
+ else {
495
+ // 如果没有 optionList,再尝试单独请求(但这可能很慢)
496
+ try {
497
+ this.log(LoggerLevel.debug, `[object.metadata.export2markdown] Fetching option details for ${obj.apiName}.${field.apiName}`);
498
+ const optionsResult = await this.object.metadata.field({
499
+ object_name: obj.apiName,
500
+ field_name: field.apiName
501
+ });
502
+ const fetchedOptions = (_w = (_v = (_u = optionsResult === null || optionsResult === void 0 ? void 0 : optionsResult.data) === null || _u === void 0 ? void 0 : _u.type) === null || _v === void 0 ? void 0 : _v.settings) === null || _w === void 0 ? void 0 : _w.optionList;
503
+ if (fetchedOptions && fetchedOptions.length > 0) {
504
+ const optionTexts = fetchedOptions.map((opt) => {
505
+ var _a, _b;
506
+ const zhLabel = ((_b = (_a = opt.label) === null || _a === void 0 ? void 0 : _a.find((l) => l.language_code === 2052)) === null || _b === void 0 ? void 0 : _b.text) || '-';
507
+ return `${zhLabel}(\`${opt.apiName}\`)`;
508
+ });
509
+ otherSettings.push(`选项: ${optionTexts.join(', ')}`);
510
+ }
511
+ }
512
+ catch (error) {
513
+ this.log(LoggerLevel.warn, `[object.metadata.export2markdown] Failed to fetch option field details for ${obj.apiName}.${field.apiName}:`, error);
514
+ }
515
+ }
516
+ }
517
+ const otherSettingsStr = otherSettings.length > 0 ? otherSettings.join('<br>') : '-';
518
+ markdown += `| ${label} | \`${apiName}\` | ${typeName} | ${required} | ${unique} | ${otherSettingsStr} |\n`;
519
+ }
520
+ markdown += '\n';
521
+ }
522
+ markdown += '---\n\n';
523
+ }
524
+ this.log(LoggerLevel.info, `[object.metadata.export2markdown] Markdown export completed`);
525
+ return markdown;
238
526
  }
239
527
  },
240
528
  search: {
@@ -103,7 +103,7 @@ declare class Client {
103
103
  /**
104
104
  * 列出所有对象(数据表)
105
105
  * @param params 请求参数 { offset?, filter?, limit? }
106
- * @returns 接口返回结果,包含 has_more 字段表示是否还有更多数据
106
+ * @returns 接口返回结果 { code, items, total, msg, has_more }
107
107
  */
108
108
  list: (params?: {
109
109
  offset?: number;
@@ -117,7 +117,7 @@ declare class Client {
117
117
  * 列出所有对象(数据表)- 支持自动分页查询
118
118
  * @description 该方法会自动处理分页,直到没有更多数据为止
119
119
  * @param params 请求参数 { filter?, limit? }
120
- * @returns { total, items }
120
+ * @returns { code, msg, items, total, failed? } - code 表示失败的分页数量
121
121
  */
122
122
  listWithIterator: (params?: {
123
123
  filter?: {
@@ -126,8 +126,16 @@ declare class Client {
126
126
  };
127
127
  limit?: number;
128
128
  }) => Promise<{
129
- total: number;
129
+ code: string;
130
+ msg: string;
130
131
  items: any[];
132
+ total: number;
133
+ failed?: Array<{
134
+ offset: number;
135
+ limit: number;
136
+ code: string;
137
+ msg: string;
138
+ }>;
131
139
  }>;
132
140
  metadata: {
133
141
  /**
@@ -149,6 +157,36 @@ declare class Client {
149
157
  fields: (params: {
150
158
  object_name: string;
151
159
  }) => Promise<any>;
160
+ /**
161
+ * 导出数据对象元数据为 Markdown 文档
162
+ * @description 将数据对象的字段信息导出为详细的 Markdown 文档,包含字段类型、配置、选项等完整信息
163
+ * @param options 导出配置
164
+ * @param options.object_names 可选,要导出的对象名称数组。如果不传,则导出所有对象
165
+ * @returns Markdown 文档字符串
166
+ * @example
167
+ * ```typescript
168
+ * // 导出所有对象
169
+ * const markdown = await client.object.metadata.export2markdown();
170
+ *
171
+ * // 只导出指定对象
172
+ * const markdown = await client.object.metadata.export2markdown({
173
+ * object_names: ['object_store', 'object_order', '_user']
174
+ * });
175
+ *
176
+ * // 结合 listWithIterator 使用
177
+ * const allObjects = await client.object.listWithIterator();
178
+ * const objectNames = allObjects.items.map(obj => obj.apiName);
179
+ * const markdown = await client.object.metadata.export2markdown({
180
+ * object_names: objectNames
181
+ * });
182
+ *
183
+ * // 保存到文件
184
+ * fs.writeFileSync('objects_doc.md', markdown, 'utf-8');
185
+ * ```
186
+ */
187
+ export2markdown: (options?: {
188
+ object_names?: string[];
189
+ }) => Promise<string>;
152
190
  };
153
191
  search: {
154
192
  /**
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "apaas-oapi-client",
3
- "version": "0.1.33",
3
+ "version": "0.1.35",
4
4
  "main": "dist/index.js",
5
5
  "exports": {
6
6
  ".": "./dist/index.js",
package/src/index.ts CHANGED
@@ -291,7 +291,7 @@ class Client {
291
291
  /**
292
292
  * 列出所有对象(数据表)
293
293
  * @param params 请求参数 { offset?, filter?, limit? }
294
- * @returns 接口返回结果,包含 has_more 字段表示是否还有更多数据
294
+ * @returns 接口返回结果 { code, items, total, msg, has_more }
295
295
  */
296
296
  list: async (params?: { offset?: number; filter?: { type?: string; quickQuery?: string }; limit?: number }): Promise<any> => {
297
297
  const offset = params?.offset ?? 0;
@@ -314,24 +314,36 @@ class Client {
314
314
  this.log(LoggerLevel.debug, `[object.list] Objects list fetched successfully: code=${res.data.code}`);
315
315
  this.log(LoggerLevel.trace, `[object.list] Response: ${JSON.stringify(res.data)}`);
316
316
 
317
- // 添加 has_more 字段判断是否还有更多数据
318
- if (res.data && res.data.data) {
319
- const total = res.data.data.total || 0;
320
- const currentEnd = offset + limit;
321
- res.data.has_more = currentEnd < total;
322
- this.log(LoggerLevel.debug, `[object.list] has_more=${res.data.has_more}, total=${total}, currentEnd=${currentEnd}`);
323
- }
317
+ // 扁平化返回结构并添加 has_more 字段
318
+ const items = res.data?.data?.items || [];
319
+ const total = res.data?.data?.total || 0;
320
+ const currentEnd = offset + limit;
321
+ const has_more = currentEnd < total;
324
322
 
325
- return res.data;
323
+ this.log(LoggerLevel.debug, `[object.list] has_more=${has_more}, total=${total}, currentEnd=${currentEnd}`);
324
+
325
+ return {
326
+ code: res.data.code,
327
+ items,
328
+ total,
329
+ msg: res.data.msg,
330
+ has_more
331
+ };
326
332
  },
327
333
 
328
334
  /**
329
335
  * 列出所有对象(数据表)- 支持自动分页查询
330
336
  * @description 该方法会自动处理分页,直到没有更多数据为止
331
337
  * @param params 请求参数 { filter?, limit? }
332
- * @returns { total, items }
338
+ * @returns { code, msg, items, total, failed? } - code 表示失败的分页数量
333
339
  */
334
- listWithIterator: async (params?: { filter?: { type?: string; quickQuery?: string }; limit?: number }): Promise<{ total: number; items: any[] }> => {
340
+ listWithIterator: async (params?: { filter?: { type?: string; quickQuery?: string }; limit?: number }): Promise<{
341
+ code: string;
342
+ msg: string;
343
+ items: any[];
344
+ total: number;
345
+ failed?: Array<{ offset: number; limit: number; code: string; msg: string }>
346
+ }> => {
335
347
  const filter = params?.filter;
336
348
  const limit = params?.limit ?? 50;
337
349
 
@@ -341,51 +353,101 @@ class Client {
341
353
  let hasMore = true;
342
354
  let page = 0;
343
355
  let totalPages = 0;
356
+ let failed: Array<{ offset: number; limit: number; code: string; msg: string }> = [];
357
+ let allSuccess = true;
344
358
 
345
359
  this.log(LoggerLevel.info, `[object.listWithIterator] Starting paginated query with limit=${limit}`);
346
360
 
347
361
  while (hasMore) {
348
- const res = await this.object.list({
349
- offset,
350
- limit,
351
- filter
352
- });
362
+ try {
363
+ const res = await this.object.list({
364
+ offset,
365
+ limit,
366
+ filter
367
+ });
353
368
 
354
- if (res.code !== '0') {
355
- this.log(LoggerLevel.error, `[object.listWithIterator] Error querying objects: code=${res.code}, msg=${res.msg}`);
356
- throw new Error(res.msg || `Query failed with code ${res.code}`);
357
- }
369
+ if (res.code !== '0') {
370
+ this.log(LoggerLevel.error, `[object.listWithIterator] Error querying objects: code=${res.code}, msg=${res.msg}, offset=${offset}`);
371
+ allSuccess = false;
372
+ failed.push({
373
+ offset,
374
+ limit,
375
+ code: res.code,
376
+ msg: res.msg || `Query failed with code ${res.code}`
377
+ });
378
+ // 继续尝试下一页,而不是直接退出
379
+ offset += limit;
380
+ page += 1;
381
+ continue;
382
+ }
358
383
 
359
- page += 1;
384
+ page += 1;
360
385
 
361
- if (res.data && Array.isArray(res.data.items)) {
362
- results = results.concat(res.data.items);
363
- }
386
+ if (Array.isArray(res.items)) {
387
+ results = results.concat(res.items);
388
+ }
364
389
 
365
- if (res.data && (res.data.total !== undefined && res.data.total !== null)) {
366
- total = res.data.total;
367
- }
390
+ if (res.total !== undefined && res.total !== null) {
391
+ total = res.total;
392
+ }
368
393
 
369
- if (page === 1) {
370
- totalPages = Math.ceil(total / limit);
371
- this.log(LoggerLevel.info, `[object.listWithIterator] Total objects: ${total}, pages: ${totalPages}`);
372
- }
394
+ if (page === 1) {
395
+ totalPages = Math.ceil(total / limit);
396
+ this.log(LoggerLevel.info, `[object.listWithIterator] Total objects: ${total}, pages: ${totalPages}`);
397
+ }
373
398
 
374
- // 判断是否还有更多数据
375
- hasMore = res.has_more === true;
376
- offset += limit;
399
+ // 判断是否还有更多数据
400
+ hasMore = res.has_more === true;
401
+ offset += limit;
377
402
 
378
- const padLength = totalPages.toString().length;
379
- const pageStr = page.toString().padStart(padLength, '0');
380
- const totalPagesStr = totalPages.toString().padStart(padLength, '0');
403
+ const padLength = totalPages.toString().length;
404
+ const pageStr = page.toString().padStart(padLength, '0');
405
+ const totalPagesStr = totalPages.toString().padStart(padLength, '0');
381
406
 
382
- this.log(LoggerLevel.info, `[object.listWithIterator] Page completed: [${pageStr}/${totalPagesStr}]`);
383
- this.log(LoggerLevel.debug, `[object.listWithIterator] Page ${page} details: items=${res.data?.items?.length}, hasMore=${hasMore}`);
384
- this.log(LoggerLevel.trace, `[object.listWithIterator] Page ${page} data: ${JSON.stringify(res.data?.items)}`);
407
+ this.log(LoggerLevel.info, `[object.listWithIterator] Page completed: [${pageStr}/${totalPagesStr}]`);
408
+ this.log(LoggerLevel.debug, `[object.listWithIterator] Page ${page} details: items=${res.items?.length}, hasMore=${hasMore}`);
409
+ this.log(LoggerLevel.trace, `[object.listWithIterator] Page ${page} data: ${JSON.stringify(res.items)}`);
410
+ } catch (error) {
411
+ this.log(LoggerLevel.error, `[object.listWithIterator] Exception occurred: ${error}, offset=${offset}`);
412
+ allSuccess = false;
413
+ failed.push({
414
+ offset,
415
+ limit,
416
+ code: '1',
417
+ msg: error instanceof Error ? error.message : String(error)
418
+ });
419
+ // 继续尝试下一页
420
+ offset += limit;
421
+ page += 1;
422
+
423
+ // 如果没有获取到 total,可能需要退出循环
424
+ if (total === 0) {
425
+ hasMore = false;
426
+ } else {
427
+ hasMore = offset < total;
428
+ }
429
+ }
385
430
  }
386
431
 
387
- this.log(LoggerLevel.info, `[object.listWithIterator] Completed: total=${total}, fetched=${results.length}`);
388
- return { total, items: results };
432
+ const resultCode = failed.length.toString();
433
+ const resultMsg = failed.length === 0
434
+ ? 'Success'
435
+ : `Completed with ${failed.length} failed page(s)`;
436
+
437
+ this.log(LoggerLevel.info, `[object.listWithIterator] Completed: code=${resultCode}, total=${total}, fetched=${results.length}, failed=${failed.length}`);
438
+
439
+ const result: any = {
440
+ code: resultCode,
441
+ msg: resultMsg,
442
+ items: results,
443
+ total
444
+ };
445
+
446
+ if (failed.length > 0) {
447
+ result.failed = failed;
448
+ }
449
+
450
+ return result;
389
451
  },
390
452
 
391
453
  metadata: {
@@ -431,6 +493,267 @@ class Client {
431
493
  this.log(LoggerLevel.debug, `[object.metadata.fields] All fields metadata fetched: ${object_name}, code=${res.data.code}`);
432
494
  this.log(LoggerLevel.trace, `[object.metadata.fields] Response: ${JSON.stringify(res.data)}`);
433
495
  return res.data;
496
+ },
497
+
498
+ /**
499
+ * 导出数据对象元数据为 Markdown 文档
500
+ * @description 将数据对象的字段信息导出为详细的 Markdown 文档,包含字段类型、配置、选项等完整信息
501
+ * @param options 导出配置
502
+ * @param options.object_names 可选,要导出的对象名称数组。如果不传,则导出所有对象
503
+ * @returns Markdown 文档字符串
504
+ * @example
505
+ * ```typescript
506
+ * // 导出所有对象
507
+ * const markdown = await client.object.metadata.export2markdown();
508
+ *
509
+ * // 只导出指定对象
510
+ * const markdown = await client.object.metadata.export2markdown({
511
+ * object_names: ['object_store', 'object_order', '_user']
512
+ * });
513
+ *
514
+ * // 结合 listWithIterator 使用
515
+ * const allObjects = await client.object.listWithIterator();
516
+ * const objectNames = allObjects.items.map(obj => obj.apiName);
517
+ * const markdown = await client.object.metadata.export2markdown({
518
+ * object_names: objectNames
519
+ * });
520
+ *
521
+ * // 保存到文件
522
+ * fs.writeFileSync('objects_doc.md', markdown, 'utf-8');
523
+ * ```
524
+ */
525
+ export2markdown: async (options?: { object_names?: string[] }): Promise<string> => {
526
+ const objectNames = options?.object_names;
527
+
528
+ this.log(LoggerLevel.info, `[object.metadata.export2markdown] Starting markdown export${objectNames && objectNames.length > 0 ? ` for ${objectNames.length} objects` : ' for all objects'}`);
529
+
530
+ let items: any[] = [];
531
+
532
+ if (objectNames && objectNames.length > 0) {
533
+ // 如果指定了对象名称,只获取这些对象
534
+ this.log(LoggerLevel.debug, `[object.metadata.export2markdown] Fetching specified objects: ${objectNames.join(', ')}`);
535
+
536
+ // 先获取所有对象列表
537
+ const allObjects = await this.object.listWithIterator();
538
+
539
+ // 过滤出指定的对象
540
+ items = allObjects.items.filter((obj: any) => objectNames.includes(obj.apiName));
541
+
542
+ // 检查是否有不存在的对象
543
+ if (items.length < objectNames.length) {
544
+ const foundNames = items.map((obj: any) => obj.apiName);
545
+ const notFound = objectNames.filter(name => !foundNames.includes(name));
546
+ this.log(LoggerLevel.warn, `[object.metadata.export2markdown] Objects not found: ${notFound.join(', ')}`);
547
+ }
548
+
549
+ this.log(LoggerLevel.debug, `[object.metadata.export2markdown] Found ${items.length}/${objectNames.length} matching objects`);
550
+
551
+ if (items.length === 0) {
552
+ this.log(LoggerLevel.warn, `[object.metadata.export2markdown] No matching objects found`);
553
+ return '# 数据对象字段文档\n\n> 未找到匹配的对象\n';
554
+ }
555
+ } else {
556
+ // 获取所有对象
557
+ this.log(LoggerLevel.debug, `[object.metadata.export2markdown] Fetching all objects`);
558
+ const allObjects = await this.object.listWithIterator();
559
+ items = allObjects.items || [];
560
+ }
561
+
562
+ this.log(LoggerLevel.debug, `[object.metadata.export2markdown] Fetched ${items.length} objects`);
563
+
564
+ // 生成 Markdown 文档
565
+ let markdown = '# 数据对象字段文档\n\n';
566
+ markdown += `> 生成时间: ${dayjs().format('YYYY-MM-DD HH:mm:ss')}\n\n`;
567
+ markdown += `> 对象总数: ${items.length}\n\n`;
568
+ markdown += '---\n\n';
569
+
570
+ // 目录
571
+ markdown += '## 目录\n\n';
572
+ items.forEach((obj: any, index: number) => {
573
+ const chineseName = obj.label?.zh_CN || obj.label?.en_US || obj.apiName;
574
+ markdown += `${index + 1}. [${chineseName} (${obj.apiName})](#${obj.apiName.replace(/_/g, '')})\n`;
575
+ });
576
+ markdown += '\n---\n\n';
577
+
578
+ // 遍历每个对象
579
+ for (const obj of items) {
580
+ const chineseName = obj.label?.zh_CN || obj.label?.en_US || obj.apiName;
581
+ const englishName = obj.label?.en_US || '';
582
+
583
+ markdown += `## ${chineseName} \`${obj.apiName}\`\n\n`;
584
+
585
+ if (englishName && englishName !== chineseName) {
586
+ markdown += `**英文名称:** ${englishName}\n\n`;
587
+ }
588
+
589
+ markdown += `**创建时间:** ${dayjs(obj.createdAt).format('YYYY-MM-DD HH:mm:ss')}\n\n`;
590
+ markdown += `**字段数量:** ${obj.fields?.length || 0}\n\n`;
591
+
592
+ // 字段表格
593
+ if (obj.fields && obj.fields.length > 0) {
594
+ // 对字段进行分类和排序
595
+ const systemFieldOrder = ['_name', '_createdBy', '_createdAt', '_updatedBy', '_updatedAt'];
596
+ const specialFieldTypes = ['formula', 'referenceField'];
597
+
598
+ let idField: any = null;
599
+ const normalFields: any[] = [];
600
+ const specialFields: any[] = [];
601
+ const systemFields: any[] = [];
602
+
603
+ obj.fields.forEach((field: any) => {
604
+ if (field.apiName === '_id') {
605
+ idField = field;
606
+ } else if (systemFieldOrder.includes(field.apiName)) {
607
+ systemFields.push(field);
608
+ } else if (specialFieldTypes.includes(field.type?.name)) {
609
+ specialFields.push(field);
610
+ } else {
611
+ normalFields.push(field);
612
+ }
613
+ });
614
+
615
+ // 对系统字段按指定顺序排序
616
+ systemFields.sort((a, b) => {
617
+ return systemFieldOrder.indexOf(a.apiName) - systemFieldOrder.indexOf(b.apiName);
618
+ });
619
+
620
+ // 组合所有字段:_id + 正常字段 + 特殊字段 + 系统字段
621
+ const sortedFields: any[] = [];
622
+ if (idField) sortedFields.push(idField);
623
+ sortedFields.push(...normalFields);
624
+ sortedFields.push(...specialFields);
625
+ sortedFields.push(...systemFields);
626
+
627
+ markdown += '### 字段列表\n\n';
628
+ markdown += '| 中文名称 | API名称 | 类型 | 必填 | 唯一 | 其他设置 |\n';
629
+ markdown += '|---------|---------|------|------|------|----------|\n';
630
+
631
+ for (const field of sortedFields) {
632
+ // 转义 Markdown 表格中的特殊字符
633
+ const escapeMarkdown = (text: string): string => {
634
+ return text.replace(/\|/g, '\\|').replace(/\n/g, ' ');
635
+ };
636
+
637
+ const label = escapeMarkdown(field.label?.zh_CN || field.label?.en_US || '-');
638
+ const apiName = field.apiName || '-';
639
+ const typeName = field.type?.name || '-';
640
+ const required = field.type?.settings?.required ? '✓' : '';
641
+ const unique = field.type?.settings?.unique ? '✓' : '';
642
+
643
+ // 构建其他设置信息
644
+ const otherSettings: string[] = [];
645
+ const settings = field.type?.settings || {};
646
+
647
+ // lookup 类型:标注关联的对象(外键)
648
+ if (field.type?.name === 'lookup' && settings.objectAPIName) {
649
+ otherSettings.push(`🔗 关联对象: \`${settings.objectAPIName}\``);
650
+ }
651
+
652
+ // referenceField 类型:引用字段
653
+ if (field.type?.name === 'referenceField') {
654
+ otherSettings.push(`⚙️ 系统自动维护,不需要写/更新`);
655
+ if (settings.guideFieldAPIName) {
656
+ otherSettings.push(`📎 引用自: \`${settings.guideFieldAPIName}\``);
657
+ }
658
+ if (settings.fieldAPIName) {
659
+ otherSettings.push(`📋 引用字段: \`${settings.fieldAPIName}\``);
660
+ }
661
+ }
662
+
663
+ // formula 类型:系统自动维护
664
+ if (field.type?.name === 'formula') {
665
+ otherSettings.push(`⚙️ 系统自动维护,不需要写/更新`);
666
+ if (settings.formula && Array.isArray(settings.formula)) {
667
+ // 优先显示中文公式,否则显示第一个
668
+ const zhFormula = settings.formula.find((f: any) => f.language_code === 2052);
669
+ const formulaText = zhFormula?.text || settings.formula[0]?.text;
670
+ if (formulaText) {
671
+ // 转义公式中的特殊字符
672
+ const escapedFormula = formulaText.replace(/\|/g, '\\|').replace(/\n/g, ' ');
673
+ otherSettings.push(`公式: ${escapedFormula}`);
674
+ }
675
+ }
676
+ if (settings.returnType) {
677
+ otherSettings.push(`返回类型: ${settings.returnType}`);
678
+ }
679
+ }
680
+
681
+ // 根据不同类型展示不同的设置
682
+ if (settings.maxLength) {
683
+ otherSettings.push(`最大长度:${settings.maxLength}`);
684
+ }
685
+ if (settings.decimalPlacesNumber !== undefined) {
686
+ otherSettings.push(`小数位:${settings.decimalPlacesNumber}`);
687
+ }
688
+ if (settings.displayAsPercentage) {
689
+ otherSettings.push('百分比显示');
690
+ }
691
+ if (settings.multiline) {
692
+ otherSettings.push('多行');
693
+ }
694
+ if (settings.multiple) {
695
+ otherSettings.push('多选');
696
+ }
697
+ if (settings.hierarchy) {
698
+ otherSettings.push('层级');
699
+ }
700
+ if (settings.displayStyle && field.type?.name !== 'lookup') {
701
+ otherSettings.push(`显示样式:${settings.displayStyle}`);
702
+ }
703
+ if (settings.referenceObjectApiName) {
704
+ otherSettings.push(`关联:${settings.referenceObjectApiName}`);
705
+ }
706
+ if (settings.rollUpType) {
707
+ otherSettings.push(`汇总:${settings.rollUpType}`);
708
+ }
709
+
710
+ // 如果是 option 类型,获取选项列表
711
+ if (field.type?.name === 'option') {
712
+ // 优先使用已有的 optionList,避免额外 API 请求
713
+ const options = settings.optionList;
714
+ if (options && options.length > 0) {
715
+ const optionTexts = options.map((opt: any) => {
716
+ const zhLabel = opt.label?.find((l: any) => l.language_code === 2052)?.text || '-';
717
+ return `${zhLabel}(\`${opt.apiName}\`)`;
718
+ });
719
+ otherSettings.push(`选项: ${optionTexts.join(', ')}`);
720
+ } else {
721
+ // 如果没有 optionList,再尝试单独请求(但这可能很慢)
722
+ try {
723
+ this.log(LoggerLevel.debug, `[object.metadata.export2markdown] Fetching option details for ${obj.apiName}.${field.apiName}`);
724
+ const optionsResult = await this.object.metadata.field({
725
+ object_name: obj.apiName,
726
+ field_name: field.apiName
727
+ });
728
+
729
+ const fetchedOptions = optionsResult?.data?.type?.settings?.optionList;
730
+ if (fetchedOptions && fetchedOptions.length > 0) {
731
+ const optionTexts = fetchedOptions.map((opt: any) => {
732
+ const zhLabel = opt.label?.find((l: any) => l.language_code === 2052)?.text || '-';
733
+ return `${zhLabel}(\`${opt.apiName}\`)`;
734
+ });
735
+ otherSettings.push(`选项: ${optionTexts.join(', ')}`);
736
+ }
737
+ } catch (error) {
738
+ this.log(LoggerLevel.warn, `[object.metadata.export2markdown] Failed to fetch option field details for ${obj.apiName}.${field.apiName}:`, error);
739
+ }
740
+ }
741
+ }
742
+
743
+ const otherSettingsStr = otherSettings.length > 0 ? otherSettings.join('<br>') : '-';
744
+
745
+ markdown += `| ${label} | \`${apiName}\` | ${typeName} | ${required} | ${unique} | ${otherSettingsStr} |\n`;
746
+ }
747
+
748
+ markdown += '\n';
749
+ }
750
+
751
+ markdown += '---\n\n';
752
+ }
753
+
754
+ this.log(LoggerLevel.info, `[object.metadata.export2markdown] Markdown export completed`);
755
+
756
+ return markdown;
434
757
  }
435
758
  },
436
759