@modesto_ma/dsh-channels 0.5.0-beta.2 → 0.5.0-beta.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/README.md +72 -15
- package/docs/attachment-pipeline-v2.md +2 -2
- package/docs/feishu-onboarding.md +4 -3
- package/lib/client.js +369 -94
- package/lib/client.js.map +1 -1
- package/lib/index.js +7847 -5293
- package/lib/index.js.map +1 -1
- package/package.json +1 -1
- package/src/access/controller.ts +32 -1
- package/src/analyze/contract.ts +121 -0
- package/src/analyze/launcher.ts +353 -0
- package/src/analyze/server.ts +201 -0
- package/src/cards/analyze.ts +116 -0
- package/src/cards/index.ts +1 -0
- package/src/cards/streaming.ts +58 -0
- package/src/client/api.ts +15 -0
- package/src/client/panel/access-card.tsx +96 -70
- package/src/client/panel/analyze-token-card.tsx +70 -0
- package/src/client/panel/index.tsx +72 -10
- package/src/client/panel/sessions-tab.tsx +7 -4
- package/src/client/panel/shared.tsx +12 -0
- package/src/client/panel/wiki-tab.tsx +62 -0
- package/src/config.ts +52 -14
- package/src/domain.ts +55 -0
- package/src/feishu/scopes.ts +19 -1
- package/src/feishu/tools.ts +3 -1
- package/src/feishu/wiki/a1.ts +97 -0
- package/src/feishu/wiki/api.ts +21 -6
- package/src/feishu/wiki/base-api.ts +267 -0
- package/src/feishu/wiki/base-tools.ts +423 -0
- package/src/feishu/wiki/shared.ts +284 -0
- package/src/feishu/wiki/sheet-api.ts +250 -0
- package/src/feishu/wiki/sheet-tools.ts +493 -0
- package/src/feishu/wiki/tools.ts +102 -189
- package/src/feishu-channel.ts +14 -1
- package/src/health.ts +12 -0
- package/src/host/agent.ts +5 -1
- package/src/index.ts +1 -1
- package/src/interactions/pending.ts +11 -2
- package/src/routing/inbound.ts +57 -14
- package/src/service.ts +161 -28
- package/src/settings/contract.ts +37 -6
- package/src/settings/routes.ts +66 -14
- package/src/settings/surface.ts +69 -20
- package/src/streamer.ts +156 -7
- package/src/workspaces.ts +31 -0
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A1 notation helpers for the wiki sheet tools: column letters, single cells,
|
|
3
|
+
* and rectangle end rows. Ranges never carry a sheet prefix (`Sheet1!`); the
|
|
4
|
+
* sub-sheet is selected separately.
|
|
5
|
+
*
|
|
6
|
+
* @module dsh-channels/feishu/wiki/a1
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
/** Largest column the helpers accept (`ZZZ`). */
|
|
10
|
+
const MAX_COLUMN = 18_278
|
|
11
|
+
|
|
12
|
+
/** Largest row the helpers accept. */
|
|
13
|
+
const MAX_ROW = 10_000_000
|
|
14
|
+
|
|
15
|
+
/** One cell position, 1-based. */
|
|
16
|
+
export interface A1Cell {
|
|
17
|
+
readonly column: number
|
|
18
|
+
readonly row: number
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Column letters to a 1-based index (`A` → 1, `AA` → 27).
|
|
23
|
+
*
|
|
24
|
+
* @param letters Column letters, any case.
|
|
25
|
+
* @returns The index, or undefined when invalid or beyond `ZZZ`.
|
|
26
|
+
*/
|
|
27
|
+
export function columnIndex(letters: string): number | undefined {
|
|
28
|
+
if (!/^[A-Za-z]{1,3}$/u.test(letters)) return undefined
|
|
29
|
+
let index = 0
|
|
30
|
+
for (const char of letters.toUpperCase()) index = index * 26 + (char.charCodeAt(0) - 64)
|
|
31
|
+
return index <= MAX_COLUMN ? index : undefined
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* A 1-based column index to letters (1 → `A`, 27 → `AA`).
|
|
36
|
+
*
|
|
37
|
+
* @param index Column index, at least 1.
|
|
38
|
+
* @returns The letters.
|
|
39
|
+
*/
|
|
40
|
+
export function columnLetters(index: number): string {
|
|
41
|
+
let letters = ''
|
|
42
|
+
let rest = index
|
|
43
|
+
while (rest > 0) {
|
|
44
|
+
const digit = (rest - 1) % 26
|
|
45
|
+
letters = String.fromCharCode(65 + digit) + letters
|
|
46
|
+
rest = Math.floor((rest - 1) / 26)
|
|
47
|
+
}
|
|
48
|
+
return letters
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Parse one cell (`B3`).
|
|
53
|
+
*
|
|
54
|
+
* @param value Cell reference.
|
|
55
|
+
* @returns The position, or undefined when it is not a single valid cell.
|
|
56
|
+
*/
|
|
57
|
+
export function parseCell(value: string): A1Cell | undefined {
|
|
58
|
+
const match = /^([A-Za-z]{1,3})([1-9]\d{0,7})$/u.exec(value.trim())
|
|
59
|
+
if (match === null) return undefined
|
|
60
|
+
const column = columnIndex(match[1] ?? '')
|
|
61
|
+
const row = Number(match[2])
|
|
62
|
+
return column === undefined || row > MAX_ROW ? undefined : { column, row }
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* The rectangle that starts at `start` and spans `rows` × `columns` cells.
|
|
67
|
+
*
|
|
68
|
+
* @param start Top-left cell.
|
|
69
|
+
* @param rows Row count, at least 1.
|
|
70
|
+
* @param columns Column count, at least 1.
|
|
71
|
+
* @returns The range (`B3:D10`).
|
|
72
|
+
*/
|
|
73
|
+
export function rectangle(start: A1Cell, rows: number, columns: number): string {
|
|
74
|
+
return `${columnLetters(start.column)}${start.row}:${columnLetters(start.column + columns - 1)}${start.row + rows - 1}`
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* Last row of a range (`A1:B5` → 5, `C7` → 7); undefined for whole-column ranges (`A:C`).
|
|
79
|
+
*
|
|
80
|
+
* @param range A1 range.
|
|
81
|
+
* @returns The row, or undefined.
|
|
82
|
+
*/
|
|
83
|
+
export function rangeEndRow(range: string): number | undefined {
|
|
84
|
+
const end = range.split(':').at(-1) ?? ''
|
|
85
|
+
const match = /(\d+)$/u.exec(end.trim())
|
|
86
|
+
return match === null ? undefined : Number(match[1])
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* Whether a range is plain A1 (`A1`, `A1:F30`, `A:C`, `3:5`) without a sheet prefix.
|
|
91
|
+
*
|
|
92
|
+
* @param range Candidate range.
|
|
93
|
+
* @returns True when acceptable.
|
|
94
|
+
*/
|
|
95
|
+
export function isA1Range(range: string): boolean {
|
|
96
|
+
return /^(?:[A-Za-z]{1,3}\d{0,8}|\d{1,8})(?::(?:[A-Za-z]{1,3}\d{0,8}|\d{1,8}))?$/u.test(range)
|
|
97
|
+
}
|
package/src/feishu/wiki/api.ts
CHANGED
|
@@ -123,13 +123,14 @@ export interface WikiApi {
|
|
|
123
123
|
*/
|
|
124
124
|
listChildren(spaceId: string, parentNodeToken: string | undefined, pageToken?: string): Promise<WikiNodePage>
|
|
125
125
|
/**
|
|
126
|
-
* Create one empty docx node.
|
|
126
|
+
* Create one empty docx, spreadsheet, or Base node.
|
|
127
127
|
* @param spaceId Wiki space id.
|
|
128
128
|
* @param parentNodeToken Parent node, or undefined for the space top level.
|
|
129
129
|
* @param title Node title.
|
|
130
|
+
* @param objType Object kind of the new node.
|
|
130
131
|
* @returns The new node.
|
|
131
132
|
*/
|
|
132
|
-
|
|
133
|
+
createNode(spaceId: string, parentNodeToken: string | undefined, title: string, objType: 'docx' | 'sheet' | 'bitable'): Promise<WikiNode>
|
|
133
134
|
/**
|
|
134
135
|
* Current revision of one docx document.
|
|
135
136
|
* @param documentId Docx document id (the node's `objToken`).
|
|
@@ -193,7 +194,15 @@ function thrownError(error: unknown): FeishuApiError {
|
|
|
193
194
|
return new FeishuApiError([code === undefined ? undefined : `code=${code}`, msg].filter(Boolean).join(' '), code)
|
|
194
195
|
}
|
|
195
196
|
|
|
196
|
-
|
|
197
|
+
/**
|
|
198
|
+
* One Feishu OpenAPI call: unwraps `data` and maps a transport error or a
|
|
199
|
+
* non-zero `code` to {@link FeishuApiError}.
|
|
200
|
+
*
|
|
201
|
+
* @param request The SDK's generic request.
|
|
202
|
+
* @param input Method, URL, query, and body.
|
|
203
|
+
* @returns The response `data`.
|
|
204
|
+
*/
|
|
205
|
+
export async function call<T>(request: WikiRawRequest, input: Parameters<WikiRawRequest>[0]): Promise<T> {
|
|
197
206
|
let response: FeishuEnvelope<T>
|
|
198
207
|
try {
|
|
199
208
|
response = await request<FeishuEnvelope<T>>(input)
|
|
@@ -226,7 +235,13 @@ function wikiNode(raw: RawNode | undefined, where: string): WikiNode {
|
|
|
226
235
|
}
|
|
227
236
|
}
|
|
228
237
|
|
|
229
|
-
|
|
238
|
+
/**
|
|
239
|
+
* Encode one URL path segment.
|
|
240
|
+
*
|
|
241
|
+
* @param value Raw segment.
|
|
242
|
+
* @returns The encoded segment.
|
|
243
|
+
*/
|
|
244
|
+
export function segment(value: string): string {
|
|
230
245
|
return encodeURIComponent(value)
|
|
231
246
|
}
|
|
232
247
|
|
|
@@ -265,13 +280,13 @@ export function createWikiApi(request: WikiRawRequest): WikiApi {
|
|
|
265
280
|
...(data.has_more === true && data.page_token !== undefined && data.page_token !== '' ? { pageToken: data.page_token } : {}),
|
|
266
281
|
}
|
|
267
282
|
},
|
|
268
|
-
async
|
|
283
|
+
async createNode(spaceId, parentNodeToken, title, objType) {
|
|
269
284
|
const url = `/open-apis/wiki/v2/spaces/${segment(spaceId)}/nodes`
|
|
270
285
|
const data = await call<{ node?: RawNode }>(request, {
|
|
271
286
|
method: 'POST',
|
|
272
287
|
url,
|
|
273
288
|
data: {
|
|
274
|
-
obj_type:
|
|
289
|
+
obj_type: objType,
|
|
275
290
|
node_type: 'origin',
|
|
276
291
|
title,
|
|
277
292
|
...(parentNodeToken === undefined ? {} : { parent_node_token: parentNodeToken }),
|
|
@@ -0,0 +1,267 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Feishu Base (多维表格) calls used by the wiki base tools, made with the app
|
|
3
|
+
* identity (tenant access token).
|
|
4
|
+
*
|
|
5
|
+
* Everything goes through `base/v3/bases/:base_token/...` (the legacy
|
|
6
|
+
* `bitable/v1` API is not used). Record reads return a column matrix that
|
|
7
|
+
* this module turns into one field map per record; record writes take flat
|
|
8
|
+
* maps keyed by field name or field id. Writes carry no precondition, are
|
|
9
|
+
* applied asynchronously (a read straight after a write may not show it), and
|
|
10
|
+
* must not run in parallel on one table.
|
|
11
|
+
*
|
|
12
|
+
* @module dsh-channels/feishu/wiki/base-api
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
import { FeishuApiError } from '../../feishu-channel.ts'
|
|
16
|
+
import { call, segment } from './api.ts'
|
|
17
|
+
import type { WikiRawRequest } from './api.ts'
|
|
18
|
+
|
|
19
|
+
/** Most records one batch write may carry (Feishu limit). */
|
|
20
|
+
export const BASE_BATCH_MAX_RECORDS = 200
|
|
21
|
+
|
|
22
|
+
/** One table of a base. */
|
|
23
|
+
export interface BaseTable {
|
|
24
|
+
readonly tableId: string
|
|
25
|
+
readonly name: string
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/** One field (column) of a table, as Feishu describes it. */
|
|
29
|
+
export interface BaseField {
|
|
30
|
+
readonly fieldId: string
|
|
31
|
+
readonly name: string
|
|
32
|
+
/** `text`, `number`, `select`, `datetime`, `user`, `link`, `checkbox`, … */
|
|
33
|
+
readonly type: string
|
|
34
|
+
/** Remaining Feishu field properties (style, options, multiple, …), passed through. */
|
|
35
|
+
readonly properties: Readonly<Record<string, unknown>>
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/** One cell value as Feishu reads or writes it (JSON). */
|
|
39
|
+
export type BaseCellValue = unknown
|
|
40
|
+
|
|
41
|
+
/** One record: its id and a map from field name to value. */
|
|
42
|
+
export interface BaseRecord {
|
|
43
|
+
readonly recordId: string
|
|
44
|
+
readonly fields: Readonly<Record<string, BaseCellValue>>
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/** Filter, sort, projection, and paging of a record query. */
|
|
48
|
+
export interface BaseRecordQuery {
|
|
49
|
+
/** `{logic, conditions: [[field, operator, value?], …]}`. */
|
|
50
|
+
readonly filter?: object
|
|
51
|
+
/** `[{field, desc}]`. */
|
|
52
|
+
readonly sort?: readonly object[]
|
|
53
|
+
/** Field names or ids to return; all view-visible fields when absent. */
|
|
54
|
+
readonly fields?: readonly string[]
|
|
55
|
+
readonly viewId?: string
|
|
56
|
+
readonly offset: number
|
|
57
|
+
readonly limit: number
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/** A keyword search over some fields. */
|
|
61
|
+
export interface BaseRecordSearch extends BaseRecordQuery {
|
|
62
|
+
readonly keyword: string
|
|
63
|
+
readonly searchFields: readonly string[]
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/** One page of records. */
|
|
67
|
+
export interface BaseRecordPage {
|
|
68
|
+
readonly records: readonly BaseRecord[]
|
|
69
|
+
readonly hasMore: boolean
|
|
70
|
+
/** Table data snapshot version; changes when the data changes. */
|
|
71
|
+
readonly rev?: number
|
|
72
|
+
readonly timezone?: string
|
|
73
|
+
/** Fields Feishu skipped, with its reason. */
|
|
74
|
+
readonly ignoredFields: readonly string[]
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/** Field values of one record write, keyed by field name or id. */
|
|
78
|
+
export type BaseRecordFields = Readonly<Record<string, BaseCellValue>>
|
|
79
|
+
|
|
80
|
+
/** The `base/v3` endpoints the wiki base tools consume. */
|
|
81
|
+
export interface BaseApi {
|
|
82
|
+
/**
|
|
83
|
+
* Tables of one base.
|
|
84
|
+
* @param baseToken Base token (the wiki node's `objToken`).
|
|
85
|
+
* @returns The tables.
|
|
86
|
+
*/
|
|
87
|
+
listTables(baseToken: string): Promise<readonly BaseTable[]>
|
|
88
|
+
/**
|
|
89
|
+
* Fields of one table.
|
|
90
|
+
* @param baseToken Base token.
|
|
91
|
+
* @param tableId Table id.
|
|
92
|
+
* @returns The fields.
|
|
93
|
+
*/
|
|
94
|
+
listFields(baseToken: string, tableId: string): Promise<readonly BaseField[]>
|
|
95
|
+
/**
|
|
96
|
+
* One page of records, filtered and sorted.
|
|
97
|
+
* @param baseToken Base token.
|
|
98
|
+
* @param tableId Table id.
|
|
99
|
+
* @param query Filter, sort, projection, paging.
|
|
100
|
+
* @returns The page.
|
|
101
|
+
*/
|
|
102
|
+
listRecords(baseToken: string, tableId: string, query: BaseRecordQuery): Promise<BaseRecordPage>
|
|
103
|
+
/**
|
|
104
|
+
* One page of records matching a keyword.
|
|
105
|
+
* @param baseToken Base token.
|
|
106
|
+
* @param tableId Table id.
|
|
107
|
+
* @param search Keyword, searched fields, and query options.
|
|
108
|
+
* @returns The page.
|
|
109
|
+
*/
|
|
110
|
+
searchRecords(baseToken: string, tableId: string, search: BaseRecordSearch): Promise<BaseRecordPage>
|
|
111
|
+
/**
|
|
112
|
+
* Create records (at most {@link BASE_BATCH_MAX_RECORDS}).
|
|
113
|
+
* @param baseToken Base token.
|
|
114
|
+
* @param tableId Table id.
|
|
115
|
+
* @param records Field maps.
|
|
116
|
+
* @returns The new record ids, in input order.
|
|
117
|
+
*/
|
|
118
|
+
createRecords(baseToken: string, tableId: string, records: readonly BaseRecordFields[]): Promise<readonly string[]>
|
|
119
|
+
/**
|
|
120
|
+
* Update records (at most {@link BASE_BATCH_MAX_RECORDS}); only the given fields change.
|
|
121
|
+
* @param baseToken Base token.
|
|
122
|
+
* @param tableId Table id.
|
|
123
|
+
* @param updates Record id → changed fields.
|
|
124
|
+
* @returns Fields Feishu ignored (read-only fields), with its reason.
|
|
125
|
+
*/
|
|
126
|
+
updateRecords(baseToken: string, tableId: string, updates: Readonly<Record<string, BaseRecordFields>>): Promise<readonly string[]>
|
|
127
|
+
/**
|
|
128
|
+
* Delete records (at most {@link BASE_BATCH_MAX_RECORDS}).
|
|
129
|
+
* @param baseToken Base token.
|
|
130
|
+
* @param tableId Table id.
|
|
131
|
+
* @param recordIds Records to delete.
|
|
132
|
+
*/
|
|
133
|
+
deleteRecords(baseToken: string, tableId: string, recordIds: readonly string[]): Promise<void>
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
interface RawMatrix {
|
|
137
|
+
fields?: unknown
|
|
138
|
+
record_id_list?: unknown
|
|
139
|
+
data?: unknown
|
|
140
|
+
has_more?: unknown
|
|
141
|
+
rev?: unknown
|
|
142
|
+
timezone?: unknown
|
|
143
|
+
ignored_fields?: unknown
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
function stringArray(value: unknown): string[] | undefined {
|
|
147
|
+
return Array.isArray(value) && value.every(item => typeof item === 'string') ? value as string[] : undefined
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
function ignoredFieldsOf(value: unknown): string[] {
|
|
151
|
+
if (!Array.isArray(value)) return []
|
|
152
|
+
return value.flatMap((item) => {
|
|
153
|
+
if (typeof item !== 'object' || item === null) return []
|
|
154
|
+
const { name, id, reason } = item as { name?: unknown; id?: unknown; reason?: unknown }
|
|
155
|
+
const label = typeof name === 'string' && name !== '' ? name : typeof id === 'string' ? id : '?'
|
|
156
|
+
return [typeof reason === 'string' && reason !== '' ? `${label}: ${reason}` : label]
|
|
157
|
+
})
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
function recordPage(raw: RawMatrix, where: string): BaseRecordPage {
|
|
161
|
+
const names = stringArray(raw.fields) ?? []
|
|
162
|
+
const ids = stringArray(raw.record_id_list) ?? []
|
|
163
|
+
const rows = Array.isArray(raw.data) ? raw.data : []
|
|
164
|
+
if (rows.length !== ids.length) throw new FeishuApiError(`${where}: record ids and rows differ in length`)
|
|
165
|
+
const records = ids.map((recordId, index) => {
|
|
166
|
+
const row: unknown = rows[index]
|
|
167
|
+
if (!Array.isArray(row) || row.length !== names.length) throw new FeishuApiError(`${where}: row ${index} does not match the field list`)
|
|
168
|
+
return { recordId, fields: Object.fromEntries(names.map((name, column) => [name, row[column] ?? null])) }
|
|
169
|
+
})
|
|
170
|
+
return {
|
|
171
|
+
records,
|
|
172
|
+
hasMore: raw.has_more === true,
|
|
173
|
+
...(typeof raw.rev === 'number' ? { rev: raw.rev } : {}),
|
|
174
|
+
...(typeof raw.timezone === 'string' && raw.timezone !== '' ? { timezone: raw.timezone } : {}),
|
|
175
|
+
ignoredFields: ignoredFieldsOf(raw.ignored_fields),
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
/**
|
|
180
|
+
* {@link BaseApi} over the SDK's generic authenticated request.
|
|
181
|
+
*
|
|
182
|
+
* @param request `Client.request` of the connected SDK client.
|
|
183
|
+
* @returns The API.
|
|
184
|
+
*/
|
|
185
|
+
export function createBaseApi(request: WikiRawRequest): BaseApi {
|
|
186
|
+
const tablePath = (baseToken: string, tableId: string): string =>
|
|
187
|
+
`/open-apis/base/v3/bases/${segment(baseToken)}/tables/${segment(tableId)}`
|
|
188
|
+
return {
|
|
189
|
+
async listTables(baseToken) {
|
|
190
|
+
const data = await call<{ tables?: unknown; items?: unknown }>(request, {
|
|
191
|
+
method: 'GET',
|
|
192
|
+
url: `/open-apis/base/v3/bases/${segment(baseToken)}/tables`,
|
|
193
|
+
params: { offset: 0, limit: 300 },
|
|
194
|
+
})
|
|
195
|
+
const raw = Array.isArray(data.tables) ? data.tables : Array.isArray(data.items) ? data.items : []
|
|
196
|
+
return raw.flatMap((item: unknown) => {
|
|
197
|
+
if (typeof item !== 'object' || item === null) return []
|
|
198
|
+
const { id, table_id, name, table_name } = item as Record<string, unknown>
|
|
199
|
+
const tableId = typeof id === 'string' ? id : typeof table_id === 'string' ? table_id : undefined
|
|
200
|
+
if (tableId === undefined) return []
|
|
201
|
+
return [{ tableId, name: typeof name === 'string' ? name : typeof table_name === 'string' ? table_name : '' }]
|
|
202
|
+
})
|
|
203
|
+
},
|
|
204
|
+
async listFields(baseToken, tableId) {
|
|
205
|
+
const data = await call<{ fields?: unknown; items?: unknown }>(request, {
|
|
206
|
+
method: 'GET',
|
|
207
|
+
url: `${tablePath(baseToken, tableId)}/fields`,
|
|
208
|
+
params: { offset: 0, limit: 300 },
|
|
209
|
+
})
|
|
210
|
+
const raw = Array.isArray(data.fields) ? data.fields : Array.isArray(data.items) ? data.items : []
|
|
211
|
+
return raw.flatMap((item: unknown) => {
|
|
212
|
+
if (typeof item !== 'object' || item === null) return []
|
|
213
|
+
const { id, field_id, name, field_name, type, ...properties } = item as Record<string, unknown>
|
|
214
|
+
const fieldId = typeof id === 'string' ? id : typeof field_id === 'string' ? field_id : undefined
|
|
215
|
+
if (fieldId === undefined) return []
|
|
216
|
+
return [{
|
|
217
|
+
fieldId,
|
|
218
|
+
name: typeof name === 'string' ? name : typeof field_name === 'string' ? field_name : '',
|
|
219
|
+
type: typeof type === 'string' ? type : 'not_support',
|
|
220
|
+
properties,
|
|
221
|
+
}]
|
|
222
|
+
})
|
|
223
|
+
},
|
|
224
|
+
async listRecords(baseToken, tableId, query) {
|
|
225
|
+
const url = `${tablePath(baseToken, tableId)}/records`
|
|
226
|
+
const params = new URLSearchParams({ offset: String(query.offset), limit: String(query.limit) })
|
|
227
|
+
for (const field of query.fields ?? []) params.append('field_id', field)
|
|
228
|
+
if (query.viewId !== undefined) params.set('view_id', query.viewId)
|
|
229
|
+
if (query.filter !== undefined) params.set('filter', JSON.stringify(query.filter))
|
|
230
|
+
if (query.sort !== undefined) params.set('sort', JSON.stringify(query.sort))
|
|
231
|
+
const data = await call<RawMatrix>(request, { method: 'GET', url: `${url}?${params.toString()}` })
|
|
232
|
+
return recordPage(data, url)
|
|
233
|
+
},
|
|
234
|
+
async searchRecords(baseToken, tableId, search) {
|
|
235
|
+
const url = `${tablePath(baseToken, tableId)}/records/search`
|
|
236
|
+
const data = await call<RawMatrix>(request, {
|
|
237
|
+
method: 'POST',
|
|
238
|
+
url,
|
|
239
|
+
data: {
|
|
240
|
+
keyword: search.keyword,
|
|
241
|
+
search_fields: search.searchFields,
|
|
242
|
+
...(search.fields === undefined ? {} : { select_fields: search.fields }),
|
|
243
|
+
...(search.viewId === undefined ? {} : { view_id: search.viewId }),
|
|
244
|
+
...(search.filter === undefined ? {} : { filter: search.filter }),
|
|
245
|
+
...(search.sort === undefined ? {} : { sort: search.sort }),
|
|
246
|
+
offset: search.offset,
|
|
247
|
+
limit: search.limit,
|
|
248
|
+
},
|
|
249
|
+
})
|
|
250
|
+
return recordPage(data, url)
|
|
251
|
+
},
|
|
252
|
+
async createRecords(baseToken, tableId, records) {
|
|
253
|
+
const url = `${tablePath(baseToken, tableId)}/records/batch_create`
|
|
254
|
+
const data = await call<{ record_id_list?: unknown }>(request, { method: 'POST', url, data: { create_records: records } })
|
|
255
|
+
return stringArray(data.record_id_list) ?? []
|
|
256
|
+
},
|
|
257
|
+
async updateRecords(baseToken, tableId, updates) {
|
|
258
|
+
const url = `${tablePath(baseToken, tableId)}/records/batch_update`
|
|
259
|
+
const data = await call<{ ignored_fields?: unknown }>(request, { method: 'POST', url, data: { update_records: updates } })
|
|
260
|
+
return ignoredFieldsOf(data.ignored_fields)
|
|
261
|
+
},
|
|
262
|
+
async deleteRecords(baseToken, tableId, recordIds) {
|
|
263
|
+
const url = `${tablePath(baseToken, tableId)}/records/batch_delete`
|
|
264
|
+
await call<unknown>(request, { method: 'POST', url, data: { record_id_list: recordIds } })
|
|
265
|
+
},
|
|
266
|
+
}
|
|
267
|
+
}
|