@yuuko1410/feishu-bitable 0.0.2 → 0.0.3
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 +464 -0
- package/lib/client.d.ts +8 -1
- package/lib/index.cjs +302 -126
- package/lib/index.d.ts +1 -1
- package/lib/index.js +302 -126
- package/lib/types.d.ts +6 -0
- package/package.json +1 -1
package/README.md
ADDED
|
@@ -0,0 +1,464 @@
|
|
|
1
|
+
# @yuuko1410/feishu-bitable
|
|
2
|
+
|
|
3
|
+
基于 Bun + TypeScript + 飞书官方 `@larksuiteoapi/node-sdk` 的多维表格操作库。
|
|
4
|
+
|
|
5
|
+
## 功能总览
|
|
6
|
+
|
|
7
|
+
- 使用 Bun 作为包管理与构建工具
|
|
8
|
+
- 使用 TypeScript 作为源码语言
|
|
9
|
+
- 底层基于飞书官方 Node SDK
|
|
10
|
+
- 支持 ESM、CommonJS、类型声明三种发布产物
|
|
11
|
+
- 支持对象式构造和环境变量构造
|
|
12
|
+
- 支持自动分页获取全部记录
|
|
13
|
+
- 支持批量新增记录
|
|
14
|
+
- 支持官方批量更新 API 的 1:1 封装
|
|
15
|
+
- 支持面向数组的批量更新封装
|
|
16
|
+
- 支持批量删除记录
|
|
17
|
+
- 支持小文件直接上传
|
|
18
|
+
- 支持大文件自动切换分片上传
|
|
19
|
+
- 支持文件下载
|
|
20
|
+
- 支持字段值归一化
|
|
21
|
+
- 支持批量请求自动分片
|
|
22
|
+
- 支持并发控制
|
|
23
|
+
- 支持失败自动重试
|
|
24
|
+
- 导出完整类型和错误类型
|
|
25
|
+
|
|
26
|
+
## 项目结构
|
|
27
|
+
|
|
28
|
+
```text
|
|
29
|
+
packages/feishu-bitable/
|
|
30
|
+
├── package.json
|
|
31
|
+
├── README.md
|
|
32
|
+
├── tsconfig.json
|
|
33
|
+
├── tsconfig.build.json
|
|
34
|
+
├── src/
|
|
35
|
+
│ ├── client.ts
|
|
36
|
+
│ ├── errors.ts
|
|
37
|
+
│ ├── index.ts
|
|
38
|
+
│ ├── types.ts
|
|
39
|
+
│ └── utils.ts
|
|
40
|
+
├── test/
|
|
41
|
+
│ └── bitable.test.ts
|
|
42
|
+
└── lib/ # bun run build 后生成
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
## 安装依赖
|
|
46
|
+
|
|
47
|
+
```bash
|
|
48
|
+
bun install
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
## 构建与开发
|
|
52
|
+
|
|
53
|
+
```bash
|
|
54
|
+
bun run check
|
|
55
|
+
bun test
|
|
56
|
+
bun run build
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
构建完成后输出:
|
|
60
|
+
|
|
61
|
+
- `lib/index.js`:ESM 入口
|
|
62
|
+
- `lib/index.cjs`:CommonJS 入口
|
|
63
|
+
- `lib/index.d.ts`:类型声明入口
|
|
64
|
+
|
|
65
|
+
## 安装到业务项目
|
|
66
|
+
|
|
67
|
+
如果作为 npm 包使用:
|
|
68
|
+
|
|
69
|
+
```bash
|
|
70
|
+
bun add @yuuko1410/feishu-bitable
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
或:
|
|
74
|
+
|
|
75
|
+
```bash
|
|
76
|
+
npm install @yuuko1410/feishu-bitable
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
## 导出内容
|
|
80
|
+
|
|
81
|
+
主导出:
|
|
82
|
+
|
|
83
|
+
- `Bitable`
|
|
84
|
+
- `FeishuBitableError`
|
|
85
|
+
- `AppType`
|
|
86
|
+
- `Domain`
|
|
87
|
+
- `LoggerLevel`
|
|
88
|
+
|
|
89
|
+
类型导出:
|
|
90
|
+
|
|
91
|
+
- `BitableConstructorOptions`
|
|
92
|
+
- `FetchAllRecordsOptions`
|
|
93
|
+
- `BatchOperationOptions`
|
|
94
|
+
- `UpdateRecordsOptions`
|
|
95
|
+
- `UploadFileOptions`
|
|
96
|
+
- `BitableInsertRecord`
|
|
97
|
+
- `BitableUpdateRecord`
|
|
98
|
+
- `BitableRecord`
|
|
99
|
+
- `BitableRecordFields`
|
|
100
|
+
- `BitableFieldValue`
|
|
101
|
+
- `BitableFilterCondition`
|
|
102
|
+
- `BitableFilterGroup`
|
|
103
|
+
- `BitableSort`
|
|
104
|
+
- `BitableBatchUpdatePayload`
|
|
105
|
+
- `BitableBatchUpdateResponse`
|
|
106
|
+
- `MediaParentType`
|
|
107
|
+
- `UploadableFile`
|
|
108
|
+
|
|
109
|
+
## 创建客户端
|
|
110
|
+
|
|
111
|
+
### 方式一:对象式构造
|
|
112
|
+
|
|
113
|
+
```ts
|
|
114
|
+
import { Bitable } from "@yuuko1410/feishu-bitable";
|
|
115
|
+
|
|
116
|
+
const bitable = new Bitable({
|
|
117
|
+
appId: process.env.FEISHU_APP_ID!,
|
|
118
|
+
appSecret: process.env.FEISHU_APP_SECRET!,
|
|
119
|
+
defaultAppToken: process.env.FEISHU_APP_TOKEN,
|
|
120
|
+
});
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
### 方式二:兼容旧式参数构造
|
|
124
|
+
|
|
125
|
+
```ts
|
|
126
|
+
const bitable = new Bitable(
|
|
127
|
+
process.env.FEISHU_APP_TOKEN,
|
|
128
|
+
process.env.FEISHU_APP_ID,
|
|
129
|
+
process.env.FEISHU_APP_SECRET,
|
|
130
|
+
);
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
### 方式三:从环境变量创建
|
|
134
|
+
|
|
135
|
+
```ts
|
|
136
|
+
const bitable = Bitable.fromEnv();
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
默认读取的环境变量:
|
|
140
|
+
|
|
141
|
+
- `FEISHU_APP_ID`
|
|
142
|
+
- `FEISHU_APP_SECRET`
|
|
143
|
+
- `FEISHU_APP_TOKEN`
|
|
144
|
+
|
|
145
|
+
## 构造参数说明
|
|
146
|
+
|
|
147
|
+
`BitableConstructorOptions` 支持:
|
|
148
|
+
|
|
149
|
+
- `appId`:飞书应用 `app_id`
|
|
150
|
+
- `appSecret`:飞书应用 `app_secret`
|
|
151
|
+
- `defaultAppToken`:默认多维表格 `app_token`
|
|
152
|
+
- `appType`:飞书 SDK `AppType`
|
|
153
|
+
- `domain`:飞书 SDK `Domain`
|
|
154
|
+
- `maxRetries`:失败最大重试次数,默认 `5`
|
|
155
|
+
- `retryDelayMs`:重试基础延迟,默认 `1000`
|
|
156
|
+
- `defaultConcurrency`:默认并发数,默认 `1`
|
|
157
|
+
- `sdkClient`:可注入已创建的官方 SDK client,便于测试或复用
|
|
158
|
+
|
|
159
|
+
## 公开 API
|
|
160
|
+
|
|
161
|
+
### 1. `fetchAllRecords`
|
|
162
|
+
|
|
163
|
+
自动分页获取某个数据表的全部记录。
|
|
164
|
+
|
|
165
|
+
```ts
|
|
166
|
+
const records = await bitable.fetchAllRecords("table_id", {
|
|
167
|
+
viewId: "vew_xxx",
|
|
168
|
+
fieldNames: ["名称", "状态"],
|
|
169
|
+
pageSize: 500,
|
|
170
|
+
normalizeFields: true,
|
|
171
|
+
automaticFields: false,
|
|
172
|
+
});
|
|
173
|
+
```
|
|
174
|
+
|
|
175
|
+
功能:
|
|
176
|
+
|
|
177
|
+
- 自动处理分页
|
|
178
|
+
- 默认单页最多 500 条
|
|
179
|
+
- 支持视图筛选
|
|
180
|
+
- 支持字段筛选
|
|
181
|
+
- 支持排序条件
|
|
182
|
+
- 支持飞书过滤条件
|
|
183
|
+
- 支持返回字段自动归一化
|
|
184
|
+
|
|
185
|
+
返回值:
|
|
186
|
+
|
|
187
|
+
- `Promise<BitableRecord[]>`
|
|
188
|
+
|
|
189
|
+
### 2. `insertList`
|
|
190
|
+
|
|
191
|
+
批量新增记录,自动按飞书限制分片。
|
|
192
|
+
|
|
193
|
+
```ts
|
|
194
|
+
await bitable.insertList("table_id", [
|
|
195
|
+
{ 名称: "订单 A", 金额: 100 },
|
|
196
|
+
{ 名称: "订单 B", 金额: 200 },
|
|
197
|
+
], {
|
|
198
|
+
concurrency: 2,
|
|
199
|
+
chunkSize: 500,
|
|
200
|
+
});
|
|
201
|
+
```
|
|
202
|
+
|
|
203
|
+
功能:
|
|
204
|
+
|
|
205
|
+
- 自动分片
|
|
206
|
+
- 支持并发
|
|
207
|
+
- 自动重试
|
|
208
|
+
- 单批最大 500 条
|
|
209
|
+
|
|
210
|
+
### 3. `batchUpdateRecords`
|
|
211
|
+
|
|
212
|
+
对飞书官方 `appTableRecord.batchUpdate` 的直接封装,适合你已经自己组好了官方 payload 的场景。
|
|
213
|
+
|
|
214
|
+
```ts
|
|
215
|
+
await bitable.batchUpdateRecords({
|
|
216
|
+
path: {
|
|
217
|
+
app_token: "app_token",
|
|
218
|
+
table_id: "table_id",
|
|
219
|
+
},
|
|
220
|
+
data: {
|
|
221
|
+
records: [
|
|
222
|
+
{
|
|
223
|
+
record_id: "recxxx",
|
|
224
|
+
fields: {
|
|
225
|
+
状态: "已完成",
|
|
226
|
+
},
|
|
227
|
+
},
|
|
228
|
+
{
|
|
229
|
+
record_id: "recyyy",
|
|
230
|
+
fields: {
|
|
231
|
+
状态: "处理中",
|
|
232
|
+
},
|
|
233
|
+
},
|
|
234
|
+
],
|
|
235
|
+
},
|
|
236
|
+
});
|
|
237
|
+
```
|
|
238
|
+
|
|
239
|
+
功能:
|
|
240
|
+
|
|
241
|
+
- 官方 payload 原样透传
|
|
242
|
+
- 自动重试
|
|
243
|
+
- 自动校验飞书错误码
|
|
244
|
+
|
|
245
|
+
返回值:
|
|
246
|
+
|
|
247
|
+
- `Promise<BitableBatchUpdateResponse>`
|
|
248
|
+
|
|
249
|
+
### 4. `updateRecords`
|
|
250
|
+
|
|
251
|
+
面向业务数组的批量更新封装,内部会转成官方 `batchUpdate` payload。
|
|
252
|
+
|
|
253
|
+
```ts
|
|
254
|
+
await bitable.updateRecords("table_id", [
|
|
255
|
+
{ record_id: "recxxx", 状态: "已完成" },
|
|
256
|
+
{ record_id: "recyyy", 状态: "处理中" },
|
|
257
|
+
], {
|
|
258
|
+
appToken: "app_token",
|
|
259
|
+
concurrency: 2,
|
|
260
|
+
chunkSize: 500,
|
|
261
|
+
userIdType: "open_id",
|
|
262
|
+
ignoreConsistencyCheck: false,
|
|
263
|
+
});
|
|
264
|
+
```
|
|
265
|
+
|
|
266
|
+
功能:
|
|
267
|
+
|
|
268
|
+
- 自动把 `record_id` 从字段中拆出
|
|
269
|
+
- 自动转换成官方 `batchUpdate` 请求结构
|
|
270
|
+
- 自动分片
|
|
271
|
+
- 支持并发
|
|
272
|
+
- 自动重试
|
|
273
|
+
- 支持 `userIdType`
|
|
274
|
+
- 支持 `ignoreConsistencyCheck`
|
|
275
|
+
|
|
276
|
+
返回值:
|
|
277
|
+
|
|
278
|
+
- `Promise<BitableBatchUpdateResponse[]>`
|
|
279
|
+
|
|
280
|
+
### 5. `deleteList`
|
|
281
|
+
|
|
282
|
+
批量删除记录。
|
|
283
|
+
|
|
284
|
+
```ts
|
|
285
|
+
await bitable.deleteList("table_id", ["rec_1", "rec_2"], {
|
|
286
|
+
concurrency: 2,
|
|
287
|
+
chunkSize: 500,
|
|
288
|
+
});
|
|
289
|
+
```
|
|
290
|
+
|
|
291
|
+
功能:
|
|
292
|
+
|
|
293
|
+
- 自动分片
|
|
294
|
+
- 支持并发
|
|
295
|
+
- 自动重试
|
|
296
|
+
- 单批最大 500 条
|
|
297
|
+
|
|
298
|
+
### 6. `uploadFile`
|
|
299
|
+
|
|
300
|
+
上传文件到飞书文档体系。
|
|
301
|
+
|
|
302
|
+
```ts
|
|
303
|
+
const result = await bitable.uploadFile({
|
|
304
|
+
parentType: "bitable_file",
|
|
305
|
+
parentNode: "tbl_xxx",
|
|
306
|
+
fileName: "demo.png",
|
|
307
|
+
file: Bun.file("./demo.png"),
|
|
308
|
+
extra: "{}",
|
|
309
|
+
});
|
|
310
|
+
```
|
|
311
|
+
|
|
312
|
+
支持的文件输入:
|
|
313
|
+
|
|
314
|
+
- base64 字符串
|
|
315
|
+
- data URL 字符串
|
|
316
|
+
- `Buffer`
|
|
317
|
+
- `Uint8Array`
|
|
318
|
+
- `ArrayBuffer`
|
|
319
|
+
- `Blob`
|
|
320
|
+
- `Bun.file()` 返回对象
|
|
321
|
+
|
|
322
|
+
功能:
|
|
323
|
+
|
|
324
|
+
- 自动识别文件输入类型
|
|
325
|
+
- 自动转 `Buffer`
|
|
326
|
+
- 未传 `fileName` 时尝试推断文件名
|
|
327
|
+
- 小于等于 20MB 走 `uploadAll`
|
|
328
|
+
- 大于 20MB 自动走官方分片上传:
|
|
329
|
+
- `uploadPrepare`
|
|
330
|
+
- `uploadPart`
|
|
331
|
+
- `uploadFinish`
|
|
332
|
+
- 自动重试
|
|
333
|
+
|
|
334
|
+
### 7. `downloadFile`
|
|
335
|
+
|
|
336
|
+
下载飞书素材并返回 `Buffer`。
|
|
337
|
+
|
|
338
|
+
```ts
|
|
339
|
+
const buffer = await bitable.downloadFile("file_token");
|
|
340
|
+
```
|
|
341
|
+
|
|
342
|
+
支持:
|
|
343
|
+
|
|
344
|
+
- 传入 `extra`
|
|
345
|
+
- 自动把可读流转为 `Buffer`
|
|
346
|
+
|
|
347
|
+
### 8. `downLoadFile`
|
|
348
|
+
|
|
349
|
+
这是 `downloadFile` 的兼容别名。
|
|
350
|
+
|
|
351
|
+
```ts
|
|
352
|
+
const buffer = await bitable.downLoadFile("file_token");
|
|
353
|
+
```
|
|
354
|
+
|
|
355
|
+
## 自动行为说明
|
|
356
|
+
|
|
357
|
+
### 自动分页
|
|
358
|
+
|
|
359
|
+
`fetchAllRecords` 内部使用官方迭代器接口自动拉取全部页。
|
|
360
|
+
|
|
361
|
+
### 自动分片
|
|
362
|
+
|
|
363
|
+
批量增删改会按飞书上限自动分片,默认和最大都是 500。
|
|
364
|
+
|
|
365
|
+
### 自动并发
|
|
366
|
+
|
|
367
|
+
批量接口支持通过 `concurrency` 控制并发执行数量。
|
|
368
|
+
|
|
369
|
+
### 自动重试
|
|
370
|
+
|
|
371
|
+
以下接口在失败时会自动重试:
|
|
372
|
+
|
|
373
|
+
- `insertList`
|
|
374
|
+
- `batchUpdateRecords`
|
|
375
|
+
- `updateRecords`
|
|
376
|
+
- `deleteList`
|
|
377
|
+
- `uploadFile`
|
|
378
|
+
- `downloadFile`
|
|
379
|
+
|
|
380
|
+
默认配置:
|
|
381
|
+
|
|
382
|
+
- 最大重试次数 `5`
|
|
383
|
+
- 重试延迟基数 `1000ms`
|
|
384
|
+
- 每次重试按尝试次数递增延迟
|
|
385
|
+
|
|
386
|
+
### 字段归一化
|
|
387
|
+
|
|
388
|
+
`fetchAllRecords` 默认会把飞书返回字段结构转成更易用的值:
|
|
389
|
+
|
|
390
|
+
- 单元素文本数组提取为文本
|
|
391
|
+
- `type === 1` 的富文本拼接为字符串
|
|
392
|
+
- `type === 2 | 3 | 1005` 的数组值转成逗号连接字符串
|
|
393
|
+
- 其他复杂字段保持原值
|
|
394
|
+
|
|
395
|
+
## 错误处理
|
|
396
|
+
|
|
397
|
+
库内统一抛出 `FeishuBitableError`:
|
|
398
|
+
|
|
399
|
+
```ts
|
|
400
|
+
import { FeishuBitableError } from "@yuuko1410/feishu-bitable";
|
|
401
|
+
|
|
402
|
+
try {
|
|
403
|
+
await bitable.updateRecords("table_id", records);
|
|
404
|
+
} catch (error) {
|
|
405
|
+
if (error instanceof FeishuBitableError) {
|
|
406
|
+
console.error(error.message);
|
|
407
|
+
console.error(error.code);
|
|
408
|
+
console.error(error.details);
|
|
409
|
+
}
|
|
410
|
+
}
|
|
411
|
+
```
|
|
412
|
+
|
|
413
|
+
错误来源包括:
|
|
414
|
+
|
|
415
|
+
- 凭证缺失
|
|
416
|
+
- `appToken` 缺失
|
|
417
|
+
- 飞书接口返回非 `0` 错误码
|
|
418
|
+
- 上传预处理信息不完整
|
|
419
|
+
- 文件输入类型不支持
|
|
420
|
+
- 多次重试后最终失败
|
|
421
|
+
|
|
422
|
+
## 一个完整示例
|
|
423
|
+
|
|
424
|
+
```ts
|
|
425
|
+
import { Bitable } from "@yuuko1410/feishu-bitable";
|
|
426
|
+
|
|
427
|
+
const bitable = Bitable.fromEnv();
|
|
428
|
+
|
|
429
|
+
const records = await bitable.fetchAllRecords("table_id", {
|
|
430
|
+
fieldNames: ["名称", "状态"],
|
|
431
|
+
});
|
|
432
|
+
|
|
433
|
+
await bitable.insertList("table_id", [
|
|
434
|
+
{ 名称: "新记录", 状态: "待处理" },
|
|
435
|
+
]);
|
|
436
|
+
|
|
437
|
+
await bitable.updateRecords("table_id", [
|
|
438
|
+
{ record_id: "recxxx", 状态: "已完成" },
|
|
439
|
+
]);
|
|
440
|
+
|
|
441
|
+
await bitable.deleteList("table_id", ["recyyy"]);
|
|
442
|
+
```
|
|
443
|
+
|
|
444
|
+
## 测试覆盖的重构能力
|
|
445
|
+
|
|
446
|
+
当前测试已覆盖:
|
|
447
|
+
|
|
448
|
+
- 记录归一化
|
|
449
|
+
- 数组式批量更新到官方 payload 的转换
|
|
450
|
+
- 官方批量更新 payload 透传
|
|
451
|
+
- 文件下载
|
|
452
|
+
- 凭证缺失报错
|
|
453
|
+
|
|
454
|
+
## 发布
|
|
455
|
+
|
|
456
|
+
```bash
|
|
457
|
+
bun run build
|
|
458
|
+
npm publish --access public
|
|
459
|
+
```
|
|
460
|
+
|
|
461
|
+
发布时包含:
|
|
462
|
+
|
|
463
|
+
- `lib/`
|
|
464
|
+
- `README.md`
|
package/lib/client.d.ts
CHANGED
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
import * as lark from "@larksuiteoapi/node-sdk";
|
|
2
|
-
import type { BatchOperationOptions, BitableBatchUpdatePayload, BitableBatchUpdateResponse, BitableConstructorOptions, BitableInsertRecord, BitableRecord, BitableUpdateRecord, FetchAllRecordsOptions, UpdateRecordsOptions, UploadFileOptions } from "./types";
|
|
2
|
+
import type { BatchOperationOptions, BitableBatchUpdatePayload, BitableBatchUpdateResponse, BitableConstructorOptions, BitableInsertRecord, BitableLogger, BitableRecord, BitableUpdateRecord, FetchAllRecordsOptions, UpdateRecordsOptions, UploadFileOptions } from "./types";
|
|
3
3
|
export declare class Bitable {
|
|
4
4
|
readonly client: lark.Client;
|
|
5
5
|
readonly defaultAppToken?: string;
|
|
6
6
|
readonly maxRetries: number;
|
|
7
7
|
readonly retryDelayMs: number;
|
|
8
8
|
readonly defaultConcurrency: number;
|
|
9
|
+
readonly logger: BitableLogger | null;
|
|
9
10
|
constructor(options?: BitableConstructorOptions);
|
|
10
11
|
constructor(defaultAppToken?: string | null, appId?: string | null, appSecret?: string | null);
|
|
11
12
|
static fromEnv(env?: NodeJS.ProcessEnv): Bitable;
|
|
@@ -29,5 +30,11 @@ export declare class Bitable {
|
|
|
29
30
|
downLoadFile(fileToken: string, extra?: string): Promise<Buffer>;
|
|
30
31
|
private resolveConstructorOptions;
|
|
31
32
|
private resolveAppToken;
|
|
33
|
+
private executeBatchUpdateRecords;
|
|
32
34
|
private withRetry;
|
|
35
|
+
private runLogged;
|
|
36
|
+
private logInfo;
|
|
37
|
+
private logWarn;
|
|
38
|
+
private logError;
|
|
39
|
+
private getErrorMeta;
|
|
33
40
|
}
|