@yuuko1410/feishu-bitable 0.0.3 → 0.0.4

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/lib/client.d.ts CHANGED
@@ -37,4 +37,5 @@ export declare class Bitable {
37
37
  private logWarn;
38
38
  private logError;
39
39
  private getErrorMeta;
40
+ private logRequest;
40
41
  }
package/lib/index.cjs CHANGED
@@ -285,7 +285,7 @@ class Bitable {
285
285
  }, async () => {
286
286
  const token = this.resolveAppToken(appToken);
287
287
  const pageSize = Math.max(1, Math.min(options.pageSize ?? FEISHU_BATCH_LIMIT, FEISHU_BATCH_LIMIT));
288
- const iterator = await this.client.bitable.v1.appTableRecord.searchWithIterator({
288
+ const request = {
289
289
  path: {
290
290
  app_token: token,
291
291
  table_id: tableId
@@ -301,7 +301,8 @@ class Bitable {
301
301
  sort: options.sort,
302
302
  automatic_fields: options.automaticFields
303
303
  }
304
- });
304
+ };
305
+ const iterator = await this.client.bitable.v1.appTableRecord.searchWithIterator(this.logRequest("fetchAllRecords request", request));
305
306
  const allRecords = [];
306
307
  for await (const page of iterator) {
307
308
  const items = page?.items ?? [];
@@ -326,12 +327,15 @@ class Bitable {
326
327
  }
327
328
  const token = this.resolveAppToken(options.appToken);
328
329
  const chunks = chunkArray(records, options.chunkSize ?? FEISHU_BATCH_LIMIT);
329
- const responses = await runWithConcurrency(chunks, options.concurrency ?? this.defaultConcurrency, async (chunk) => this.withRetry("insert records", async () => assertFeishuResponse(await this.client.bitable.v1.appTableRecord.batchCreate({
330
- path: { app_token: token, table_id: tableId },
331
- data: {
332
- records: chunk.map((fields) => ({ fields }))
333
- }
334
- }), "insert records")));
330
+ const responses = await runWithConcurrency(chunks, options.concurrency ?? this.defaultConcurrency, async (chunk) => this.withRetry("insert records", async () => {
331
+ const request = {
332
+ path: { app_token: token, table_id: tableId },
333
+ data: {
334
+ records: chunk.map((fields) => ({ fields }))
335
+ }
336
+ };
337
+ return assertFeishuResponse(await this.client.bitable.v1.appTableRecord.batchCreate(this.logRequest("insertList request", request)), "insert records");
338
+ }));
335
339
  this.logInfo("insertList completed", {
336
340
  tableId,
337
341
  chunkCount: chunks.length
@@ -358,8 +362,18 @@ class Bitable {
358
362
  const token = this.resolveAppToken(options.appToken);
359
363
  const chunks = chunkArray(records, options.chunkSize ?? FEISHU_BATCH_LIMIT);
360
364
  const responses = await runWithConcurrency(chunks, options.concurrency ?? this.defaultConcurrency, async (chunk, index) => {
361
- const batchRecords = chunk.map((record) => {
365
+ const batchRecords = chunk.map((record, recordIndex) => {
362
366
  const { recordId, fields } = splitUpdateRecord(record);
367
+ if (!recordId || !recordId.trim()) {
368
+ throw new FeishuBitableError(`updateRecords failed: record_id is required for chunk ${index}, item ${recordIndex}`, {
369
+ details: {
370
+ tableId,
371
+ chunkIndex: index,
372
+ recordIndex,
373
+ record
374
+ }
375
+ });
376
+ }
363
377
  if (Object.keys(fields).length === 0) {
364
378
  return null;
365
379
  }
@@ -418,12 +432,15 @@ class Bitable {
418
432
  }
419
433
  const token = this.resolveAppToken(options.appToken);
420
434
  const chunks = chunkArray(recordIds, options.chunkSize ?? FEISHU_BATCH_LIMIT);
421
- const responses = await runWithConcurrency(chunks, options.concurrency ?? this.defaultConcurrency, async (chunk) => this.withRetry("delete records", async () => assertFeishuResponse(await this.client.bitable.v1.appTableRecord.batchDelete({
422
- path: { app_token: token, table_id: tableId },
423
- data: {
424
- records: chunk
425
- }
426
- }), "delete records")));
435
+ const responses = await runWithConcurrency(chunks, options.concurrency ?? this.defaultConcurrency, async (chunk) => this.withRetry("delete records", async () => {
436
+ const request = {
437
+ path: { app_token: token, table_id: tableId },
438
+ data: {
439
+ records: chunk
440
+ }
441
+ };
442
+ return assertFeishuResponse(await this.client.bitable.v1.appTableRecord.batchDelete(this.logRequest("deleteList request", request)), "delete records");
443
+ }));
427
444
  this.logInfo("deleteList completed", {
428
445
  tableId,
429
446
  chunkCount: chunks.length
@@ -445,25 +462,37 @@ class Bitable {
445
462
  mode: buffer.byteLength <= FEISHU_SIMPLE_UPLOAD_LIMIT ? "simple" : "multipart"
446
463
  });
447
464
  if (buffer.byteLength <= FEISHU_SIMPLE_UPLOAD_LIMIT) {
448
- return await this.withRetry("upload file", async () => this.client.drive.v1.media.uploadAll({
465
+ return await this.withRetry("upload file", async () => {
466
+ const request = {
467
+ data: {
468
+ file_name: fileName,
469
+ parent_type: options.parentType,
470
+ parent_node: options.parentNode,
471
+ size: buffer.byteLength,
472
+ extra: options.extra,
473
+ file: buffer
474
+ }
475
+ };
476
+ this.logRequest("uploadFile request", {
477
+ data: {
478
+ ...request.data,
479
+ file: `[Buffer ${buffer.byteLength} bytes]`
480
+ }
481
+ });
482
+ return this.client.drive.v1.media.uploadAll(request);
483
+ }) ?? {};
484
+ }
485
+ const prepare = assertFeishuResponse(await this.withRetry("prepare multipart upload", async () => {
486
+ const request = {
449
487
  data: {
450
488
  file_name: fileName,
451
489
  parent_type: options.parentType,
452
490
  parent_node: options.parentNode,
453
- size: buffer.byteLength,
454
- extra: options.extra,
455
- file: buffer
491
+ size: buffer.byteLength
456
492
  }
457
- })) ?? {};
458
- }
459
- const prepare = assertFeishuResponse(await this.withRetry("prepare multipart upload", async () => this.client.drive.v1.media.uploadPrepare({
460
- data: {
461
- file_name: fileName,
462
- parent_type: options.parentType,
463
- parent_node: options.parentNode,
464
- size: buffer.byteLength
465
- }
466
- })), "prepare multipart upload");
493
+ };
494
+ return this.client.drive.v1.media.uploadPrepare(this.logRequest("uploadPrepare request", request));
495
+ }), "prepare multipart upload");
467
496
  const uploadId = prepare.data?.upload_id;
468
497
  const blockSize = prepare.data?.block_size;
469
498
  const blockNum = prepare.data?.block_num;
@@ -476,21 +505,33 @@ class Bitable {
476
505
  const start = index * blockSize;
477
506
  const end = Math.min(start + blockSize, buffer.byteLength);
478
507
  const chunk = buffer.subarray(start, end);
479
- await this.withRetry(`upload file chunk ${index + 1}/${blockNum}`, async () => this.client.drive.v1.media.uploadPart({
508
+ await this.withRetry(`upload file chunk ${index + 1}/${blockNum}`, async () => {
509
+ const request = {
510
+ data: {
511
+ upload_id: uploadId,
512
+ seq: index,
513
+ size: chunk.byteLength,
514
+ file: chunk
515
+ }
516
+ };
517
+ this.logRequest("uploadPart request", {
518
+ data: {
519
+ ...request.data,
520
+ file: `[Buffer ${chunk.byteLength} bytes]`
521
+ }
522
+ });
523
+ return this.client.drive.v1.media.uploadPart(request);
524
+ });
525
+ }
526
+ const finish = assertFeishuResponse(await this.withRetry("finish multipart upload", async () => {
527
+ const request = {
480
528
  data: {
481
529
  upload_id: uploadId,
482
- seq: index,
483
- size: chunk.byteLength,
484
- file: chunk
530
+ block_num: blockNum
485
531
  }
486
- }));
487
- }
488
- const finish = assertFeishuResponse(await this.withRetry("finish multipart upload", async () => this.client.drive.v1.media.uploadFinish({
489
- data: {
490
- upload_id: uploadId,
491
- block_num: blockNum
492
- }
493
- })), "finish multipart upload");
532
+ };
533
+ return this.client.drive.v1.media.uploadFinish(this.logRequest("uploadFinish request", request));
534
+ }), "finish multipart upload");
494
535
  return {
495
536
  file_token: finish.data?.file_token
496
537
  };
@@ -501,14 +542,17 @@ class Bitable {
501
542
  fileToken,
502
543
  hasExtra: Boolean(extra)
503
544
  }, async () => {
504
- const response = await this.withRetry("download file", async () => this.client.drive.v1.media.download({
505
- path: {
506
- file_token: fileToken
507
- },
508
- params: {
509
- extra
510
- }
511
- }));
545
+ const response = await this.withRetry("download file", async () => {
546
+ const request = {
547
+ path: {
548
+ file_token: fileToken
549
+ },
550
+ params: {
551
+ extra
552
+ }
553
+ };
554
+ return this.client.drive.v1.media.download(this.logRequest("downloadFile request", request));
555
+ });
512
556
  const buffer = await readableToBuffer(response.getReadableStream());
513
557
  this.logInfo("downloadFile completed", {
514
558
  fileToken,
@@ -550,7 +594,7 @@ class Bitable {
550
594
  return token;
551
595
  }
552
596
  async executeBatchUpdateRecords(payload) {
553
- return this.withRetry("batch update records", async () => assertFeishuResponse(await this.client.bitable.v1.appTableRecord.batchUpdate(payload), "batch update records"));
597
+ return this.withRetry("batch update records", async () => assertFeishuResponse(await this.client.bitable.v1.appTableRecord.batchUpdate(this.logRequest("batchUpdateRecords request", payload)), "batch update records"));
554
598
  }
555
599
  async withRetry(label, task) {
556
600
  let lastError;
@@ -623,6 +667,12 @@ class Bitable {
623
667
  errorMessage: String(error)
624
668
  };
625
669
  }
670
+ logRequest(message, payload) {
671
+ this.logInfo(message, {
672
+ payload
673
+ });
674
+ return payload;
675
+ }
626
676
  }
627
677
 
628
678
  // src/index.ts
package/lib/index.js CHANGED
@@ -211,7 +211,7 @@ class Bitable {
211
211
  }, async () => {
212
212
  const token = this.resolveAppToken(appToken);
213
213
  const pageSize = Math.max(1, Math.min(options.pageSize ?? FEISHU_BATCH_LIMIT, FEISHU_BATCH_LIMIT));
214
- const iterator = await this.client.bitable.v1.appTableRecord.searchWithIterator({
214
+ const request = {
215
215
  path: {
216
216
  app_token: token,
217
217
  table_id: tableId
@@ -227,7 +227,8 @@ class Bitable {
227
227
  sort: options.sort,
228
228
  automatic_fields: options.automaticFields
229
229
  }
230
- });
230
+ };
231
+ const iterator = await this.client.bitable.v1.appTableRecord.searchWithIterator(this.logRequest("fetchAllRecords request", request));
231
232
  const allRecords = [];
232
233
  for await (const page of iterator) {
233
234
  const items = page?.items ?? [];
@@ -252,12 +253,15 @@ class Bitable {
252
253
  }
253
254
  const token = this.resolveAppToken(options.appToken);
254
255
  const chunks = chunkArray(records, options.chunkSize ?? FEISHU_BATCH_LIMIT);
255
- const responses = await runWithConcurrency(chunks, options.concurrency ?? this.defaultConcurrency, async (chunk) => this.withRetry("insert records", async () => assertFeishuResponse(await this.client.bitable.v1.appTableRecord.batchCreate({
256
- path: { app_token: token, table_id: tableId },
257
- data: {
258
- records: chunk.map((fields) => ({ fields }))
259
- }
260
- }), "insert records")));
256
+ const responses = await runWithConcurrency(chunks, options.concurrency ?? this.defaultConcurrency, async (chunk) => this.withRetry("insert records", async () => {
257
+ const request = {
258
+ path: { app_token: token, table_id: tableId },
259
+ data: {
260
+ records: chunk.map((fields) => ({ fields }))
261
+ }
262
+ };
263
+ return assertFeishuResponse(await this.client.bitable.v1.appTableRecord.batchCreate(this.logRequest("insertList request", request)), "insert records");
264
+ }));
261
265
  this.logInfo("insertList completed", {
262
266
  tableId,
263
267
  chunkCount: chunks.length
@@ -284,8 +288,18 @@ class Bitable {
284
288
  const token = this.resolveAppToken(options.appToken);
285
289
  const chunks = chunkArray(records, options.chunkSize ?? FEISHU_BATCH_LIMIT);
286
290
  const responses = await runWithConcurrency(chunks, options.concurrency ?? this.defaultConcurrency, async (chunk, index) => {
287
- const batchRecords = chunk.map((record) => {
291
+ const batchRecords = chunk.map((record, recordIndex) => {
288
292
  const { recordId, fields } = splitUpdateRecord(record);
293
+ if (!recordId || !recordId.trim()) {
294
+ throw new FeishuBitableError(`updateRecords failed: record_id is required for chunk ${index}, item ${recordIndex}`, {
295
+ details: {
296
+ tableId,
297
+ chunkIndex: index,
298
+ recordIndex,
299
+ record
300
+ }
301
+ });
302
+ }
289
303
  if (Object.keys(fields).length === 0) {
290
304
  return null;
291
305
  }
@@ -344,12 +358,15 @@ class Bitable {
344
358
  }
345
359
  const token = this.resolveAppToken(options.appToken);
346
360
  const chunks = chunkArray(recordIds, options.chunkSize ?? FEISHU_BATCH_LIMIT);
347
- const responses = await runWithConcurrency(chunks, options.concurrency ?? this.defaultConcurrency, async (chunk) => this.withRetry("delete records", async () => assertFeishuResponse(await this.client.bitable.v1.appTableRecord.batchDelete({
348
- path: { app_token: token, table_id: tableId },
349
- data: {
350
- records: chunk
351
- }
352
- }), "delete records")));
361
+ const responses = await runWithConcurrency(chunks, options.concurrency ?? this.defaultConcurrency, async (chunk) => this.withRetry("delete records", async () => {
362
+ const request = {
363
+ path: { app_token: token, table_id: tableId },
364
+ data: {
365
+ records: chunk
366
+ }
367
+ };
368
+ return assertFeishuResponse(await this.client.bitable.v1.appTableRecord.batchDelete(this.logRequest("deleteList request", request)), "delete records");
369
+ }));
353
370
  this.logInfo("deleteList completed", {
354
371
  tableId,
355
372
  chunkCount: chunks.length
@@ -371,25 +388,37 @@ class Bitable {
371
388
  mode: buffer.byteLength <= FEISHU_SIMPLE_UPLOAD_LIMIT ? "simple" : "multipart"
372
389
  });
373
390
  if (buffer.byteLength <= FEISHU_SIMPLE_UPLOAD_LIMIT) {
374
- return await this.withRetry("upload file", async () => this.client.drive.v1.media.uploadAll({
391
+ return await this.withRetry("upload file", async () => {
392
+ const request = {
393
+ data: {
394
+ file_name: fileName,
395
+ parent_type: options.parentType,
396
+ parent_node: options.parentNode,
397
+ size: buffer.byteLength,
398
+ extra: options.extra,
399
+ file: buffer
400
+ }
401
+ };
402
+ this.logRequest("uploadFile request", {
403
+ data: {
404
+ ...request.data,
405
+ file: `[Buffer ${buffer.byteLength} bytes]`
406
+ }
407
+ });
408
+ return this.client.drive.v1.media.uploadAll(request);
409
+ }) ?? {};
410
+ }
411
+ const prepare = assertFeishuResponse(await this.withRetry("prepare multipart upload", async () => {
412
+ const request = {
375
413
  data: {
376
414
  file_name: fileName,
377
415
  parent_type: options.parentType,
378
416
  parent_node: options.parentNode,
379
- size: buffer.byteLength,
380
- extra: options.extra,
381
- file: buffer
417
+ size: buffer.byteLength
382
418
  }
383
- })) ?? {};
384
- }
385
- const prepare = assertFeishuResponse(await this.withRetry("prepare multipart upload", async () => this.client.drive.v1.media.uploadPrepare({
386
- data: {
387
- file_name: fileName,
388
- parent_type: options.parentType,
389
- parent_node: options.parentNode,
390
- size: buffer.byteLength
391
- }
392
- })), "prepare multipart upload");
419
+ };
420
+ return this.client.drive.v1.media.uploadPrepare(this.logRequest("uploadPrepare request", request));
421
+ }), "prepare multipart upload");
393
422
  const uploadId = prepare.data?.upload_id;
394
423
  const blockSize = prepare.data?.block_size;
395
424
  const blockNum = prepare.data?.block_num;
@@ -402,21 +431,33 @@ class Bitable {
402
431
  const start = index * blockSize;
403
432
  const end = Math.min(start + blockSize, buffer.byteLength);
404
433
  const chunk = buffer.subarray(start, end);
405
- await this.withRetry(`upload file chunk ${index + 1}/${blockNum}`, async () => this.client.drive.v1.media.uploadPart({
434
+ await this.withRetry(`upload file chunk ${index + 1}/${blockNum}`, async () => {
435
+ const request = {
436
+ data: {
437
+ upload_id: uploadId,
438
+ seq: index,
439
+ size: chunk.byteLength,
440
+ file: chunk
441
+ }
442
+ };
443
+ this.logRequest("uploadPart request", {
444
+ data: {
445
+ ...request.data,
446
+ file: `[Buffer ${chunk.byteLength} bytes]`
447
+ }
448
+ });
449
+ return this.client.drive.v1.media.uploadPart(request);
450
+ });
451
+ }
452
+ const finish = assertFeishuResponse(await this.withRetry("finish multipart upload", async () => {
453
+ const request = {
406
454
  data: {
407
455
  upload_id: uploadId,
408
- seq: index,
409
- size: chunk.byteLength,
410
- file: chunk
456
+ block_num: blockNum
411
457
  }
412
- }));
413
- }
414
- const finish = assertFeishuResponse(await this.withRetry("finish multipart upload", async () => this.client.drive.v1.media.uploadFinish({
415
- data: {
416
- upload_id: uploadId,
417
- block_num: blockNum
418
- }
419
- })), "finish multipart upload");
458
+ };
459
+ return this.client.drive.v1.media.uploadFinish(this.logRequest("uploadFinish request", request));
460
+ }), "finish multipart upload");
420
461
  return {
421
462
  file_token: finish.data?.file_token
422
463
  };
@@ -427,14 +468,17 @@ class Bitable {
427
468
  fileToken,
428
469
  hasExtra: Boolean(extra)
429
470
  }, async () => {
430
- const response = await this.withRetry("download file", async () => this.client.drive.v1.media.download({
431
- path: {
432
- file_token: fileToken
433
- },
434
- params: {
435
- extra
436
- }
437
- }));
471
+ const response = await this.withRetry("download file", async () => {
472
+ const request = {
473
+ path: {
474
+ file_token: fileToken
475
+ },
476
+ params: {
477
+ extra
478
+ }
479
+ };
480
+ return this.client.drive.v1.media.download(this.logRequest("downloadFile request", request));
481
+ });
438
482
  const buffer = await readableToBuffer(response.getReadableStream());
439
483
  this.logInfo("downloadFile completed", {
440
484
  fileToken,
@@ -476,7 +520,7 @@ class Bitable {
476
520
  return token;
477
521
  }
478
522
  async executeBatchUpdateRecords(payload) {
479
- return this.withRetry("batch update records", async () => assertFeishuResponse(await this.client.bitable.v1.appTableRecord.batchUpdate(payload), "batch update records"));
523
+ return this.withRetry("batch update records", async () => assertFeishuResponse(await this.client.bitable.v1.appTableRecord.batchUpdate(this.logRequest("batchUpdateRecords request", payload)), "batch update records"));
480
524
  }
481
525
  async withRetry(label, task) {
482
526
  let lastError;
@@ -549,6 +593,12 @@ class Bitable {
549
593
  errorMessage: String(error)
550
594
  };
551
595
  }
596
+ logRequest(message, payload) {
597
+ this.logInfo(message, {
598
+ payload
599
+ });
600
+ return payload;
601
+ }
552
602
  }
553
603
 
554
604
  // src/index.ts
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@yuuko1410/feishu-bitable",
3
- "version": "0.0.3",
3
+ "version": "0.0.4",
4
4
  "description": "基于 Bun + TypeScript + 飞书官方 SDK 的多维表格操作库",
5
5
  "type": "module",
6
6
  "main": "./lib/index.cjs",