mobx-lark 2.8.0 → 2.9.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -13,7 +13,7 @@ import {
13
13
  TableRecordFields,
14
14
  TableView
15
15
  } from './type';
16
- import { makeSimpleFilter } from './utility';
16
+ import { makeSimpleFilter, mapKeys } from './utility';
17
17
 
18
18
  export * from './type';
19
19
  export * from './utility';
@@ -31,13 +31,13 @@ export interface BiDataQueryOptions {
31
31
  /**
32
32
  * @see {@link https://open.feishu.cn/document/ukTMukTMukTM/uUDN04SN0QjL1QDN/bitable-overview}
33
33
  */
34
- export function BiDataTable<T extends DataObject, F extends Filter<T> = Filter<T>>(
34
+ export function BiDataTable<D extends DataObject, F extends Filter<D> = Filter<D>>(
35
35
  Base = ListModel
36
36
  ) {
37
- abstract class BiDataTableModel extends Stream<T, F>(Base) {
38
- requiredKeys: readonly (keyof T)[] = [];
37
+ abstract class BiDataTableModel extends Stream<D, F>(Base) {
38
+ requiredKeys: readonly (keyof D)[] = [];
39
39
 
40
- sort: Partial<Record<keyof T, 'ASC' | 'DESC'>> = {};
40
+ sort: Partial<Record<keyof D, 'ASC' | 'DESC'>> = {};
41
41
 
42
42
  queryOptions: BiDataQueryOptions = {
43
43
  text_field_as_array: true,
@@ -50,7 +50,15 @@ export function BiDataTable<T extends DataObject, F extends Filter<T> = Filter<T
50
50
  this.baseURI = `bitable/v1/apps/${appId}/tables/${tableId}/records`;
51
51
  }
52
52
 
53
- extractFields({ fields, ...meta }: TableRecord<T>): T {
53
+ keyMap?: Partial<Record<keyof D, string>>;
54
+
55
+ get nameMap() {
56
+ return this.keyMap
57
+ ? Object.fromEntries(Object.entries(this.keyMap).map(([key, name]) => [name, key]))
58
+ : {};
59
+ }
60
+
61
+ extractFields({ fields, ...meta }: TableRecord<D>): D {
54
62
  return { ...meta, ...fields };
55
63
  }
56
64
 
@@ -59,8 +67,19 @@ export function BiDataTable<T extends DataObject, F extends Filter<T> = Filter<T
59
67
  */
60
68
  normalize = this.extractFields;
61
69
 
70
+ /**
71
+ * @protected
72
+ */
73
+ mapFields({ fields, ...meta }: TableRecord<DataObject>) {
74
+ const { nameMap } = this;
75
+
76
+ const mappedData = isEmpty(nameMap) ? (fields as D) : (mapKeys(fields, nameMap) as D);
77
+
78
+ return this.normalize({ fields: mappedData, ...meta });
79
+ }
80
+
62
81
  wrapFields(fields: F) {
63
- return fields as unknown as T;
82
+ return fields as unknown as D;
64
83
  }
65
84
 
66
85
  /**
@@ -68,10 +87,10 @@ export function BiDataTable<T extends DataObject, F extends Filter<T> = Filter<T
68
87
  */
69
88
  @toggle('downloading')
70
89
  async getOne(id: string) {
71
- const { body } = await this.client.get<TableRecordData<T>>(
90
+ const { body } = await this.client.get<TableRecordData<D>>(
72
91
  `${this.baseURI}/${id}?${buildURLData(this.queryOptions)}`
73
92
  );
74
- return (this.currentOne = this.normalize(body!.data!.record));
93
+ return (this.currentOne = this.mapFields(body!.data!.record));
75
94
  }
76
95
 
77
96
  /**
@@ -80,29 +99,31 @@ export function BiDataTable<T extends DataObject, F extends Filter<T> = Filter<T
80
99
  */
81
100
  @toggle('uploading')
82
101
  async updateOne(data: F, id?: string) {
83
- const fields = this.wrapFields(data);
102
+ const rawData = this.wrapFields(data);
103
+
104
+ const fields = isEmpty(this.keyMap) ? rawData : (mapKeys(rawData, this.keyMap) as D);
84
105
 
85
106
  const { body } = await (id
86
- ? this.client.put<TableRecordData<T>>(`${this.baseURI}/${id}`, {
87
- fields
88
- })
89
- : this.client.post<TableRecordData<T>>(this.baseURI, {
90
- fields
91
- }));
92
- return (this.currentOne = this.normalize(body!.data!.record));
107
+ ? this.client.put<TableRecordData<D>>(`${this.baseURI}/${id}`, { fields })
108
+ : this.client.post<TableRecordData<D>>(this.baseURI, { fields }));
109
+
110
+ return (this.currentOne = this.mapFields(body!.data!.record));
111
+ }
112
+
113
+ mapFilter(filter: DataObject) {
114
+ return isEmpty(this.keyMap) ? (filter as F) : (mapKeys(filter, this.keyMap) as F);
93
115
  }
94
116
 
95
117
  makeFilter(filter: F) {
96
- return [
118
+ const requiredFilter =
97
119
  this.requiredKeys[0] &&
98
- makeSimpleFilter(
99
- Object.fromEntries(this.requiredKeys.map(key => [key, ''])),
100
- '!='
101
- ),
102
- !isEmpty(filter) && makeSimpleFilter(filter)
103
- ]
104
- .filter(Boolean)
105
- .join('&&');
120
+ makeSimpleFilter(
121
+ this.mapFilter(Object.fromEntries(this.requiredKeys.map(key => [key, '']))),
122
+ '!='
123
+ );
124
+ const customFilter = !isEmpty(filter) && makeSimpleFilter(this.mapFilter(filter));
125
+
126
+ return [requiredFilter, customFilter].filter(Boolean).join('&&');
106
127
  }
107
128
 
108
129
  /**
@@ -118,13 +139,13 @@ export function BiDataTable<T extends DataObject, F extends Filter<T> = Filter<T
118
139
  Object.entries(this.sort).map(([key, order]) => `${key} ${order}`)
119
140
  )
120
141
  };
121
- const stream = createPageStream<TableRecord<T>>(
142
+ const stream = createPageStream<TableRecord<DataObject>>(
122
143
  this.client,
123
144
  this.baseURI,
124
145
  total => (this.totalCount = total),
125
146
  { ...searchParams, ...this.queryOptions }
126
147
  );
127
- for await (const item of stream) yield this.normalize(item);
148
+ for await (const item of stream) yield this.mapFields(item);
128
149
  }
129
150
 
130
151
  async getViewList(
@@ -156,13 +177,14 @@ export type BiSearchFilter<D extends DataObject> = Filter<D> & {
156
177
  keywords?: string;
157
178
  };
158
179
 
159
- export function BiSearch<D extends DataObject, F extends BiSearchFilter<D> = BiSearchFilter<D>>(
160
- Model: Constructor<ListModel<D, F>>
161
- ) {
162
- abstract class BiSearchModel extends Model {
180
+ export function BiSearch<
181
+ D extends DataObject,
182
+ F extends BiSearchFilter<D> = BiSearchFilter<D>,
183
+ M extends ReturnType<typeof BiDataTable<D, F>> = ReturnType<typeof BiDataTable<D, F>>
184
+ >(Model: M) {
185
+ abstract class BiSearchModel extends (Model as ReturnType<typeof BiDataTable<D, F>>) {
163
186
  declare baseURI: string;
164
187
  declare client: RESTClient;
165
- declare loadPage: (pageIndex: number, pageSize: number, filter: F) => Promise<PageData<D>>;
166
188
 
167
189
  abstract searchKeys: readonly (keyof TableRecordFields)[];
168
190
 
@@ -170,7 +192,9 @@ export function BiSearch<D extends DataObject, F extends BiSearchFilter<D> = BiS
170
192
  accessor keywords = '';
171
193
 
172
194
  makeFilter(filter: F) {
173
- return isEmpty(filter) ? '' : makeSimpleFilter(filter, 'contains', 'OR');
195
+ return isEmpty(filter)
196
+ ? ''
197
+ : makeSimpleFilter(this.mapFilter(filter), 'contains', 'OR');
174
198
  }
175
199
 
176
200
  async getList(
@@ -1,10 +1,13 @@
1
1
  import { DataObject } from 'mobx-restful';
2
- import {
3
- TableCellLink,
4
- TableCellLocation,
5
- TableCellRelation,
6
- TableCellText
7
- } from './type';
2
+ import { TableCellLink, TableCellLocation, TableCellRelation, TableCellText } from './type';
3
+
4
+ export const mapKeys = <I extends DataObject, O extends DataObject>(
5
+ data: I,
6
+ map: Partial<Record<keyof I, PropertyKey>>
7
+ ) =>
8
+ Object.fromEntries(
9
+ Object.entries(data).map(([key, value]) => [map[key as keyof I] || key, value])
10
+ ) as O;
8
11
 
9
12
  export type FilterOperator = '<' | '<=' | '=' | '!=' | '=>' | '>' | 'contains';
10
13
 
@@ -34,9 +37,7 @@ export function makeSimpleFilter(
34
37
  return list[1] ? `${relation}(${list})` : list[0];
35
38
  }
36
39
 
37
- export const normalizeText = (
38
- value: TableCellText | TableCellLink | TableCellRelation
39
- ) =>
40
+ export const normalizeText = (value: TableCellText | TableCellLink | TableCellRelation) =>
40
41
  (value && typeof value === 'object' && 'text' in value && value.text) || '';
41
42
 
42
43
  export const normalizeTextArray = (list: TableCellText[]) =>
@@ -50,8 +51,7 @@ export const normalizeTextArray = (list: TableCellText[]) =>
50
51
  ['']
51
52
  );
52
53
  export function coordinateOf(location: TableCellLocation): [number, number] {
53
- const [longitude, latitude] =
54
- (location as TableCellLocation)?.location.split(',') || [];
54
+ const [longitude, latitude] = (location as TableCellLocation)?.location.split(',') || [];
55
55
 
56
56
  return [+latitude, +longitude];
57
57
  }
@@ -1,8 +1,16 @@
1
- import { makeFormData } from 'koajax';
1
+ import { makeFormData, readAs } from 'koajax';
2
2
  import { BaseModel, RESTClient, toggle } from 'mobx-restful';
3
3
 
4
4
  import { LarkData } from '../../type';
5
- import { TaxiInvoice, TrainInvoice, VatInvoice, VehicleInvoice } from './type';
5
+ import {
6
+ BankCardEntity,
7
+ Contract,
8
+ Resume,
9
+ TaxiInvoice,
10
+ TrainInvoice,
11
+ VatInvoice,
12
+ VehicleInvoice
13
+ } from './type';
6
14
 
7
15
  export * from './type';
8
16
 
@@ -58,4 +66,60 @@ export abstract class DocumentAIModel extends BaseModel {
58
66
 
59
67
  return body!.data!.vehicle_invoice;
60
68
  }
69
+
70
+ /**
71
+ * @see {@link https://open.feishu.cn/document/server-docs/ai/optical_char_recognition-v1/basic_recognize}
72
+ */
73
+ @toggle('uploading')
74
+ async recognizeText(image: File) {
75
+ const URI = (await readAs(image, 'dataURL').result) as string;
76
+
77
+ const [, base64] = URI.split(',');
78
+
79
+ const { body } = await this.client.post<LarkData<{ text_list: string[] }>>(
80
+ 'optical_char_recognition/v1/image/basic_recognize',
81
+ { image: base64 }
82
+ );
83
+ return body!.data!.text_list;
84
+ }
85
+
86
+ /**
87
+ * @see {@link https://open.feishu.cn/document/ai/document_ai-v1/bank_card/recognize}
88
+ */
89
+ @toggle('uploading')
90
+ async recognizeBankCard(file: File) {
91
+ const { body } = await this.client.post<
92
+ LarkData<{ bank_card: { entities: BankCardEntity[] } }>
93
+ >(`${this.baseURI}/bank_card/recognize`, makeFormData({ file }));
94
+
95
+ return body!.data!.bank_card.entities;
96
+ }
97
+
98
+ /**
99
+ * @see {@link https://open.feishu.cn/document/ai/document_ai-v1/resume/parse}
100
+ */
101
+ @toggle('uploading')
102
+ async parseResume(file: File) {
103
+ const { body } = await this.client.post<LarkData<{ resumes: Resume[] }>>(
104
+ `${this.baseURI}/resume/parse`,
105
+ makeFormData({ file })
106
+ );
107
+ return body!.data!.resumes;
108
+ }
109
+
110
+ /**
111
+ * @see {@link https://open.feishu.cn/document/server-docs/ai/document_ai-v1/contract/field_extraction}
112
+ */
113
+ @toggle('uploading')
114
+ async extractContract(
115
+ file: File,
116
+ ocr_mode: 'unused' | 'force' | 'auto' = 'auto',
117
+ pdf_page_limit = 100
118
+ ) {
119
+ const { body } = await this.client.post<LarkData<Contract>>(
120
+ `${this.baseURI}/contract/field_extraction`,
121
+ makeFormData({ file, ocr_mode, pdf_page_limit })
122
+ );
123
+ return body!.data!;
124
+ }
61
125
  }
@@ -1,3 +1,5 @@
1
+ import { Gender } from '../User/type';
2
+
1
3
  export type InvoiceEntityType =
2
4
  | `invoice_${'code' | 'no' | 'special_seal'}`
3
5
  | `seller_${'name' | 'taxpayer_no'}_in_seal`;
@@ -67,3 +69,130 @@ export interface VehicleInvoice {
67
69
  | `total_price${'' | '_little'}`;
68
70
  value: string;
69
71
  }
72
+
73
+ export interface BankCardEntity {
74
+ type: 'card_number' | 'date_of_expiry';
75
+ value: string;
76
+ }
77
+
78
+ export type ResumePeriod = Record<`${'start' | 'end'}_${'date' | 'time'}`, string>;
79
+
80
+ export enum EducationQualification {
81
+ PrimarySchool = 1,
82
+ JuniorHighSchool = 2,
83
+ VocationalHighSchool = 3,
84
+ HighSchool = 4,
85
+ AssociateDegree = 5,
86
+ BachelorDegree = 6,
87
+ MasterDegree = 7,
88
+ Doctorate = 8,
89
+ Other = 9
90
+ }
91
+
92
+ export interface ResumeEducation
93
+ extends ResumePeriod, Record<'school' | 'major' | 'degree', string> {
94
+ qualification: EducationQualification;
95
+ }
96
+
97
+ export enum CareerType {
98
+ Internship = 1,
99
+ FullTime = 2
100
+ }
101
+
102
+ export interface ResumeCareer
103
+ extends ResumePeriod, Record<'company' | 'title' | 'type_str' | 'job_description', string> {
104
+ type: CareerType;
105
+ }
106
+
107
+ export type ResumeProject = ResumePeriod & Record<'name' | 'title' | 'description', string>;
108
+
109
+ export interface ResumeLanguage {
110
+ level: number;
111
+ description: string;
112
+ }
113
+
114
+ export type ResumeAward = Record<'award' | 'date' | 'description', string>;
115
+
116
+ export type ResumeCertificate = Record<'name' | 'desc', string>;
117
+
118
+ export type ResumeCompetition = ResumeCertificate;
119
+
120
+ export interface Resume
121
+ extends
122
+ Record<
123
+ | 'file_md5'
124
+ | `${'' | 'new_'}content`
125
+ | 'name'
126
+ | 'email'
127
+ | 'mobile'
128
+ | 'country_code'
129
+ | 'date_of_birth'
130
+ | `${'current' | 'home'}_location`
131
+ | 'self_evaluation',
132
+ string
133
+ >,
134
+ Record<`willing_${'positions' | 'locations'}` | 'urls' | 'social_links', string[]> {
135
+ mobile_is_virtual: boolean;
136
+ educations: ResumeEducation[];
137
+ careers: ResumeCareer[];
138
+ projects: ResumeProject[];
139
+ work_year: number | null;
140
+ gender: Gender;
141
+ languages: ResumeLanguage[];
142
+ awards: ResumeAward[];
143
+ certificates: ResumeCertificate[];
144
+ competitions: ResumeCompetition[];
145
+ }
146
+
147
+ export interface ContractPrice {
148
+ contract_price: number;
149
+ contract_price_original: string;
150
+ text: string;
151
+ }
152
+
153
+ export type ContractInitialTerm = Record<`initial_${'time' | 'unit'}`, string>;
154
+
155
+ export interface ContractTime extends Record<
156
+ `${'' | 'original_'}time_${'start' | 'end'}` | `text_${'start' | 'end' | 'initial_term'}`,
157
+ string
158
+ > {
159
+ initial_term: ContractInitialTerm;
160
+ }
161
+
162
+ export interface ContractCopy extends Record<'original_copy' | 'key' | 'text', string> {
163
+ copy_num: number;
164
+ }
165
+
166
+ export type ContractCurrency = Record<`currency_${'name' | 'text'}`, string>;
167
+
168
+ export type ContractBodyType = 'buy' | 'sell' | 'third';
169
+
170
+ export type ContractContact = Record<
171
+ 'contacts' | 'id_number' | 'phone' | 'email' | 'address',
172
+ string
173
+ >;
174
+ export type ContractBodyEntity = ContractContact & Record<'legal_representative' | 'party', string>;
175
+
176
+ export interface ContractBodyInfo {
177
+ body_type: ContractBodyType;
178
+ value: ContractBodyEntity;
179
+ }
180
+
181
+ export type ContractBankType = `${'buy' | 'sell' | 'third' | 'uncertain'}_bank`;
182
+
183
+ export type ContractBankEntity = ContractContact &
184
+ Record<'bank_name' | `account_${'name' | 'number'}` | 'tax_number', string>;
185
+
186
+ export interface ContractBankInfo {
187
+ bank_type: ContractBankType;
188
+ value: ContractBankEntity;
189
+ }
190
+
191
+ export interface Contract extends Record<'file_id' | 'header', string> {
192
+ price: ContractPrice;
193
+ time: ContractTime;
194
+ copy: ContractCopy;
195
+ currency: ContractCurrency;
196
+ body_info: ContractBodyInfo[];
197
+ bank_info: ContractBankInfo[];
198
+ }