airtable-ts 1.2.0 → 1.3.1
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 +10 -9
- package/dist/assertMatchesSchema.js +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/dist/mapping/fieldMappers.d.ts +1 -1
- package/dist/mapping/fieldMappers.js +173 -422
- package/dist/mapping/recordMapper.js +28 -18
- package/dist/mapping/typeUtils.d.ts +7 -1
- package/dist/mapping/typeUtils.js +3 -2
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -27,21 +27,22 @@ const db = new AirtableTs({
|
|
|
27
27
|
apiKey: 'pat1234.abcdef',
|
|
28
28
|
});
|
|
29
29
|
|
|
30
|
-
|
|
30
|
+
// Tip: use airtable-ts-codegen to autogenerate these from your Airtable base
|
|
31
|
+
export const studentTable: Table<{ id: string, firstName: string, classes: string[] }> = {
|
|
31
32
|
name: 'student',
|
|
32
33
|
baseId: 'app1234',
|
|
33
34
|
tableId: 'tbl1234',
|
|
34
|
-
schema: {
|
|
35
|
+
schema: { firstName: 'string', classes: 'string[]' },
|
|
35
36
|
// optional: use mappings with field ids to prevent renamings breaking your app,
|
|
36
37
|
// or with field names to make handling renamings easy
|
|
37
|
-
mappings: {
|
|
38
|
+
mappings: { firstName: 'fld1234', classes: 'Classes student is enrolled in' },
|
|
38
39
|
};
|
|
39
40
|
|
|
40
|
-
export const classTable: Table<{ id: string,
|
|
41
|
+
export const classTable: Table<{ id: string, title: string }> = {
|
|
41
42
|
name: 'class',
|
|
42
43
|
baseId: 'app1234',
|
|
43
44
|
tableId: 'tbl4567',
|
|
44
|
-
schema: {
|
|
45
|
+
schema: { title: 'string' },
|
|
45
46
|
};
|
|
46
47
|
|
|
47
48
|
// Now we can get all the records in a table (a scan)
|
|
@@ -49,18 +50,18 @@ const classes = await db.scan(classTable);
|
|
|
49
50
|
|
|
50
51
|
// Get, update and delete specific records:
|
|
51
52
|
const student = await db.get(studentTable, 'rec1234');
|
|
52
|
-
await db.update(studentTable, { id: 'rec1234',
|
|
53
|
+
await db.update(studentTable, { id: 'rec1234', firstName: 'Adam' });
|
|
53
54
|
await db.remove(studentTable, 'rec5678');
|
|
54
55
|
|
|
55
56
|
// Or for a more involved example:
|
|
56
|
-
async function
|
|
57
|
+
async function prefixTitleOfFirstClassOfFirstStudent(prefix: string) {
|
|
57
58
|
const students = await db.scan(studentTable);
|
|
58
59
|
if (!students[0]) throw new Error('There are no students');
|
|
59
60
|
if (!students[0].classes[0]) throw new Error('First student does not have a class');
|
|
60
61
|
|
|
61
62
|
const currentClass = await db.get(classTable, students[0].classes[0]);
|
|
62
|
-
const
|
|
63
|
-
await db.update(classTable, { id: currentClass.id,
|
|
63
|
+
const newTitle = prefix + currentClass.title;
|
|
64
|
+
await db.update(classTable, { id: currentClass.id, title: newTitle });
|
|
64
65
|
}
|
|
65
66
|
|
|
66
67
|
// And should you ever need it, access to the raw Airtable JS SDK
|
|
@@ -12,7 +12,7 @@ const typeUtils_1 = require("./mapping/typeUtils");
|
|
|
12
12
|
* @param table
|
|
13
13
|
* @param data
|
|
14
14
|
*/
|
|
15
|
-
function assertMatchesSchema(table, data, mode = '
|
|
15
|
+
function assertMatchesSchema(table, data, mode = 'partial') {
|
|
16
16
|
if (typeof data !== 'object' || data === null) {
|
|
17
17
|
throw new Error(`[airtable-ts] Item for ${table.name} is not an object`);
|
|
18
18
|
}
|
package/dist/index.d.ts
CHANGED
|
@@ -8,7 +8,7 @@ export declare class AirtableTs {
|
|
|
8
8
|
constructor(options: AirtableTsOptions);
|
|
9
9
|
get<T extends Item>(table: Table<T>, id: string): Promise<T>;
|
|
10
10
|
scan<T extends Item>(table: Table<T>, params?: ScanParams): Promise<T[]>;
|
|
11
|
-
insert<T extends Item>(table: Table<T>, data: Omit<T, 'id'
|
|
11
|
+
insert<T extends Item>(table: Table<T>, data: Partial<Omit<T, 'id'>>): Promise<T>;
|
|
12
12
|
update<T extends Item>(table: Table<T>, data: Partial<T> & {
|
|
13
13
|
id: string;
|
|
14
14
|
}): Promise<T>;
|
package/dist/index.js
CHANGED
|
@@ -46,7 +46,7 @@ class AirtableTs {
|
|
|
46
46
|
return (0, recordMapper_1.mapRecordFromAirtable)(table, record);
|
|
47
47
|
}
|
|
48
48
|
async update(table, data) {
|
|
49
|
-
(0, assertMatchesSchema_1.assertMatchesSchema)(table, { ...data }
|
|
49
|
+
(0, assertMatchesSchema_1.assertMatchesSchema)(table, { ...data });
|
|
50
50
|
const { id, ...withoutId } = data;
|
|
51
51
|
const airtableTable = await (0, getAirtableTable_1.getAirtableTable)(this.airtable, table, this.options);
|
|
52
52
|
const record = await airtableTable.update(data.id, (0, recordMapper_1.mapRecordToAirtable)(table, withoutId, airtableTable));
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { AirtableTypeString, FromAirtableTypeString, FromTsTypeString, TsTypeString } from './typeUtils';
|
|
2
2
|
type Mapper = {
|
|
3
3
|
[T in TsTypeString]?: {
|
|
4
|
-
[A in AirtableTypeString]?: {
|
|
4
|
+
[A in AirtableTypeString | 'unknown']?: {
|
|
5
5
|
toAirtable: (value: FromTsTypeString<T>) => FromAirtableTypeString<A>;
|
|
6
6
|
fromAirtable: (value: FromAirtableTypeString<A> | null | undefined) => FromTsTypeString<T>;
|
|
7
7
|
};
|
|
@@ -1,129 +1,56 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.fieldMappers = void 0;
|
|
4
|
-
const
|
|
5
|
-
if (value === null || value === undefined) {
|
|
6
|
-
throw new Error('[airtable-ts] Missing required value');
|
|
7
|
-
}
|
|
8
|
-
return value;
|
|
9
|
-
};
|
|
4
|
+
const typeUtils_1 = require("./typeUtils");
|
|
10
5
|
const fallbackMapperPair = (toFallback, fromFallback) => ({
|
|
11
6
|
toAirtable: (value) => value ?? toFallback,
|
|
12
7
|
fromAirtable: (value) => value ?? fromFallback,
|
|
13
8
|
});
|
|
14
|
-
const
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
return [value];
|
|
34
|
-
},
|
|
35
|
-
fromAirtable: (value) => {
|
|
36
|
-
if (!value) {
|
|
37
|
-
throw new Error('[airtable-ts] Failed to coerce multipleSelects type field to a single string, as it was blank');
|
|
38
|
-
}
|
|
39
|
-
if (value.length !== 1) {
|
|
40
|
-
throw new Error(`[airtable-ts] Can't coerce multipleSelects to a single string, as there were ${value?.length} entries`);
|
|
41
|
-
}
|
|
42
|
-
return value[0];
|
|
43
|
-
},
|
|
44
|
-
},
|
|
45
|
-
multipleRecordLinks: {
|
|
46
|
-
toAirtable: (value) => {
|
|
47
|
-
return [value];
|
|
48
|
-
},
|
|
49
|
-
fromAirtable: (value) => {
|
|
50
|
-
if (!value) {
|
|
51
|
-
throw new Error('[airtable-ts] Failed to coerce multipleRecordLinks type field to a single string, as it was blank');
|
|
52
|
-
}
|
|
53
|
-
if (value.length !== 1) {
|
|
54
|
-
throw new Error(`[airtable-ts] Can't coerce multipleRecordLinks to a single string, as there were ${value?.length} entries`);
|
|
55
|
-
}
|
|
56
|
-
return value[0];
|
|
57
|
-
},
|
|
58
|
-
},
|
|
59
|
-
date: {
|
|
60
|
-
toAirtable: (value) => {
|
|
61
|
-
const date = new Date(value);
|
|
62
|
-
if (Number.isNaN(date.getTime())) {
|
|
63
|
-
throw new Error('[airtable-ts] Invalid date string');
|
|
64
|
-
}
|
|
65
|
-
return date.toJSON().slice(0, 10);
|
|
66
|
-
},
|
|
67
|
-
fromAirtable: (value) => {
|
|
68
|
-
const date = new Date(value ?? '');
|
|
69
|
-
if (Number.isNaN(date.getTime())) {
|
|
70
|
-
throw new Error('[airtable-ts] Invalid date string');
|
|
71
|
-
}
|
|
72
|
-
return date.toJSON();
|
|
73
|
-
},
|
|
74
|
-
},
|
|
75
|
-
dateTime: {
|
|
76
|
-
toAirtable: (value) => {
|
|
77
|
-
const date = new Date(value);
|
|
78
|
-
if (Number.isNaN(date.getTime())) {
|
|
79
|
-
throw new Error('[airtable-ts] Invalid dateTime string');
|
|
80
|
-
}
|
|
81
|
-
return date.toJSON();
|
|
82
|
-
},
|
|
83
|
-
fromAirtable: (value) => {
|
|
84
|
-
const date = new Date(value ?? '');
|
|
85
|
-
if (Number.isNaN(date.getTime())) {
|
|
86
|
-
throw new Error('[airtable-ts] Invalid dateTime string');
|
|
87
|
-
}
|
|
88
|
-
return date.toJSON();
|
|
89
|
-
},
|
|
90
|
-
},
|
|
91
|
-
multipleLookupValues: {
|
|
92
|
-
toAirtable: () => { throw new Error('[airtable-ts] lookup type field is readonly'); },
|
|
93
|
-
fromAirtable: (value) => {
|
|
94
|
-
if (!value) {
|
|
95
|
-
throw new Error('[airtable-ts] Failed to coerce lookup type field to a single string, as it was blank');
|
|
96
|
-
}
|
|
97
|
-
if (value.length !== 1) {
|
|
98
|
-
throw new Error(`[airtable-ts] Can't coerce lookup to a single string, as there were ${value?.length} entries`);
|
|
99
|
-
}
|
|
100
|
-
if (typeof value[0] !== 'string') {
|
|
101
|
-
throw new Error(`[airtable-ts] Can't coerce singular lookup to a single string, as it was of type ${typeof value[0]}`);
|
|
102
|
-
}
|
|
103
|
-
return value[0];
|
|
104
|
-
},
|
|
105
|
-
},
|
|
106
|
-
rollup: {
|
|
107
|
-
toAirtable: () => { throw new Error('[airtable-ts] rollup type field is readonly'); },
|
|
108
|
-
fromAirtable: (value) => {
|
|
109
|
-
if (typeof value === 'string')
|
|
110
|
-
return value;
|
|
111
|
-
if (value === undefined || value === null)
|
|
112
|
-
return '';
|
|
113
|
-
throw new Error(`[airtable-ts] Can't coerce rollup to a string, as it was of type ${typeof value}`);
|
|
114
|
-
},
|
|
115
|
-
},
|
|
116
|
-
formula: {
|
|
117
|
-
toAirtable: () => { throw new Error('[airtable-ts] formula type field is readonly'); },
|
|
118
|
-
fromAirtable: (value) => {
|
|
119
|
-
if (typeof value === 'string')
|
|
120
|
-
return value;
|
|
121
|
-
if (value === undefined || value === null)
|
|
122
|
-
return '';
|
|
123
|
-
throw new Error(`[airtable-ts] Can't coerce formula to a string, as it was of type ${typeof value}`);
|
|
124
|
-
},
|
|
125
|
-
},
|
|
9
|
+
const dateTimeMapperPair = {
|
|
10
|
+
// Number assumed to be unix time in seconds
|
|
11
|
+
toAirtable: (value) => {
|
|
12
|
+
if (value === null)
|
|
13
|
+
return null;
|
|
14
|
+
const date = new Date(typeof value === 'number' ? value * 1000 : value);
|
|
15
|
+
if (Number.isNaN(date.getTime())) {
|
|
16
|
+
throw new Error('[airtable-ts] Invalid dateTime');
|
|
17
|
+
}
|
|
18
|
+
return date.toJSON();
|
|
19
|
+
},
|
|
20
|
+
fromAirtable: (value) => {
|
|
21
|
+
if (value === null || value === undefined)
|
|
22
|
+
return null;
|
|
23
|
+
const date = new Date(value);
|
|
24
|
+
if (Number.isNaN(date.getTime())) {
|
|
25
|
+
throw new Error('[airtable-ts] Invalid dateTime');
|
|
26
|
+
}
|
|
27
|
+
return date.toJSON();
|
|
126
28
|
},
|
|
29
|
+
};
|
|
30
|
+
const readonly = (airtableType) => () => { throw new Error(`[airtable-ts] ${airtableType} type field is readonly`); };
|
|
31
|
+
const coerce = (airtableType, tsType) => (value) => {
|
|
32
|
+
const parsedType = (0, typeUtils_1.parseType)(tsType);
|
|
33
|
+
if (!parsedType.array && typeof value === parsedType.single) {
|
|
34
|
+
return value;
|
|
35
|
+
}
|
|
36
|
+
if (parsedType.array && Array.isArray(value) && value.every((v) => typeof v === parsedType.single)) {
|
|
37
|
+
return value;
|
|
38
|
+
}
|
|
39
|
+
if (parsedType.nullable && (value === undefined || value === null || (Array.isArray(value) && value.length === 0))) {
|
|
40
|
+
return null;
|
|
41
|
+
}
|
|
42
|
+
if (parsedType.array && typeof value === parsedType.single) {
|
|
43
|
+
return [value];
|
|
44
|
+
}
|
|
45
|
+
if (!parsedType.array && Array.isArray(value) && value.length === 1 && typeof value[0] === parsedType.single) {
|
|
46
|
+
return value[0];
|
|
47
|
+
}
|
|
48
|
+
if (!parsedType.array && Array.isArray(value) && value.length !== 1) {
|
|
49
|
+
throw new Error(`[airtable-ts] Can't coerce ${airtableType} to a ${tsType}, as there were ${value.length} array entries`);
|
|
50
|
+
}
|
|
51
|
+
throw new Error(`[airtable-ts] Can't coerce ${airtableType} to a ${tsType}, as it was of type ${typeof value}`);
|
|
52
|
+
};
|
|
53
|
+
const stringOrNull = {
|
|
127
54
|
'string | null': {
|
|
128
55
|
url: fallbackMapperPair(null, null),
|
|
129
56
|
email: fallbackMapperPair(null, null),
|
|
@@ -132,230 +59,57 @@ exports.fieldMappers = {
|
|
|
132
59
|
multilineText: fallbackMapperPair(null, null),
|
|
133
60
|
richText: fallbackMapperPair(null, null),
|
|
134
61
|
singleSelect: fallbackMapperPair(null, null),
|
|
135
|
-
externalSyncSource: {
|
|
136
|
-
toAirtable: () => { throw new Error('[airtable-ts] externalSyncSource type field is readonly'); },
|
|
137
|
-
fromAirtable: (value) => value ?? null,
|
|
138
|
-
},
|
|
139
62
|
multipleSelects: {
|
|
140
|
-
toAirtable: (value) =>
|
|
141
|
-
|
|
142
|
-
},
|
|
143
|
-
fromAirtable: (value) => {
|
|
144
|
-
if (!value || value.length === 0) {
|
|
145
|
-
return null;
|
|
146
|
-
}
|
|
147
|
-
if (value.length !== 1) {
|
|
148
|
-
throw new Error(`[airtable-ts] Can't coerce multipleSelects to a single string, as there were ${value?.length} entries`);
|
|
149
|
-
}
|
|
150
|
-
return value[0];
|
|
151
|
-
},
|
|
63
|
+
toAirtable: (value) => (value ? [value] : []),
|
|
64
|
+
fromAirtable: coerce('multipleSelects', 'string | null'),
|
|
152
65
|
},
|
|
153
66
|
multipleRecordLinks: {
|
|
154
|
-
toAirtable: (value) =>
|
|
155
|
-
|
|
156
|
-
},
|
|
157
|
-
fromAirtable: (value) => {
|
|
158
|
-
if (!value || value.length === 0) {
|
|
159
|
-
return null;
|
|
160
|
-
}
|
|
161
|
-
if (value.length !== 1) {
|
|
162
|
-
throw new Error(`[airtable-ts] Can't coerce multipleRecordLinks to a single string, as there were ${value?.length} entries`);
|
|
163
|
-
}
|
|
164
|
-
return value[0];
|
|
165
|
-
},
|
|
67
|
+
toAirtable: (value) => (value ? [value] : []),
|
|
68
|
+
fromAirtable: coerce('multipleRecordLinks', 'string | null'),
|
|
166
69
|
},
|
|
167
70
|
date: {
|
|
168
|
-
toAirtable: (value) =>
|
|
169
|
-
|
|
170
|
-
return null;
|
|
171
|
-
const date = new Date(value);
|
|
172
|
-
if (Number.isNaN(date.getTime())) {
|
|
173
|
-
throw new Error('[airtable-ts] Invalid date');
|
|
174
|
-
}
|
|
175
|
-
return date.toJSON().slice(0, 10);
|
|
176
|
-
},
|
|
177
|
-
fromAirtable: (value) => {
|
|
178
|
-
if (value === null || value === undefined)
|
|
179
|
-
return null;
|
|
180
|
-
const date = new Date(value);
|
|
181
|
-
if (Number.isNaN(date.getTime())) {
|
|
182
|
-
throw new Error('[airtable-ts] Invalid date');
|
|
183
|
-
}
|
|
184
|
-
return date.toJSON();
|
|
185
|
-
},
|
|
186
|
-
},
|
|
187
|
-
dateTime: {
|
|
188
|
-
toAirtable: (value) => {
|
|
189
|
-
if (value === null)
|
|
190
|
-
return null;
|
|
191
|
-
const date = new Date(value);
|
|
192
|
-
if (Number.isNaN(date.getTime())) {
|
|
193
|
-
throw new Error('[airtable-ts] Invalid dateTime');
|
|
194
|
-
}
|
|
195
|
-
return date.toJSON();
|
|
196
|
-
},
|
|
197
|
-
fromAirtable: (value) => {
|
|
198
|
-
if (value === null || value === undefined)
|
|
199
|
-
return null;
|
|
200
|
-
const date = new Date(value);
|
|
201
|
-
if (Number.isNaN(date.getTime())) {
|
|
202
|
-
throw new Error('[airtable-ts] Invalid dateTime');
|
|
203
|
-
}
|
|
204
|
-
return date.toJSON();
|
|
205
|
-
},
|
|
71
|
+
toAirtable: (value) => dateTimeMapperPair.toAirtable(value)?.slice(0, 10) ?? null,
|
|
72
|
+
fromAirtable: dateTimeMapperPair.fromAirtable,
|
|
206
73
|
},
|
|
74
|
+
dateTime: dateTimeMapperPair,
|
|
75
|
+
createdTime: dateTimeMapperPair,
|
|
76
|
+
lastModifiedTime: dateTimeMapperPair,
|
|
207
77
|
multipleLookupValues: {
|
|
208
|
-
toAirtable: (
|
|
209
|
-
fromAirtable: (
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
throw new Error(`[airtable-ts] Can't coerce lookup to a single string, as there were ${value?.length} entries`);
|
|
215
|
-
}
|
|
216
|
-
if (typeof value[0] !== 'string') {
|
|
217
|
-
throw new Error(`[airtable-ts] Can't coerce singular lookup to a single string, as it was of type ${typeof value[0]}`);
|
|
218
|
-
}
|
|
219
|
-
return value[0];
|
|
220
|
-
},
|
|
78
|
+
toAirtable: readonly('multipleLookupValues'),
|
|
79
|
+
fromAirtable: coerce('multipleLookupValues', 'string | null'),
|
|
80
|
+
},
|
|
81
|
+
externalSyncSource: {
|
|
82
|
+
toAirtable: readonly('externalSyncSource'),
|
|
83
|
+
fromAirtable: coerce('externalSyncSource', 'string | null'),
|
|
221
84
|
},
|
|
222
85
|
rollup: {
|
|
223
|
-
toAirtable: (
|
|
224
|
-
fromAirtable: (
|
|
225
|
-
if (typeof value === 'string')
|
|
226
|
-
return value;
|
|
227
|
-
if (value === undefined || value === null)
|
|
228
|
-
return null;
|
|
229
|
-
throw new Error(`[airtable-ts] Can't coerce rollup to a string, as it was of type ${typeof value}`);
|
|
230
|
-
},
|
|
86
|
+
toAirtable: readonly('rollup'),
|
|
87
|
+
fromAirtable: coerce('rollup', 'string | null'),
|
|
231
88
|
},
|
|
232
89
|
formula: {
|
|
233
|
-
toAirtable: (
|
|
234
|
-
fromAirtable: (
|
|
235
|
-
if (typeof value === 'string')
|
|
236
|
-
return value;
|
|
237
|
-
if (value === undefined || value === null)
|
|
238
|
-
return null;
|
|
239
|
-
throw new Error(`[airtable-ts] Can't coerce formula to a string, as it was of type ${typeof value}`);
|
|
240
|
-
},
|
|
90
|
+
toAirtable: readonly('formula'),
|
|
91
|
+
fromAirtable: coerce('formula', 'string | null'),
|
|
241
92
|
},
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
multipleLookupValues: {
|
|
246
|
-
toAirtable: () => { throw new Error('[airtable-ts] lookup type field is readonly'); },
|
|
247
|
-
fromAirtable: (value) => {
|
|
248
|
-
if (!value) {
|
|
249
|
-
throw new Error('[airtable-ts] Failed to coerce lookup type field to a single boolean, as it was blank');
|
|
250
|
-
}
|
|
251
|
-
if (value.length !== 1) {
|
|
252
|
-
throw new Error(`[airtable-ts] Can't coerce lookup to a single boolean, as there were ${value?.length} entries`);
|
|
253
|
-
}
|
|
254
|
-
if (typeof value[0] !== 'boolean') {
|
|
255
|
-
throw new Error(`[airtable-ts] Can't coerce singular lookup to a single boolean, as it was of type ${typeof value[0]}`);
|
|
256
|
-
}
|
|
257
|
-
return value[0];
|
|
258
|
-
},
|
|
93
|
+
unknown: {
|
|
94
|
+
toAirtable: (value) => value,
|
|
95
|
+
fromAirtable: coerce('unknown', 'string | null'),
|
|
259
96
|
},
|
|
260
97
|
},
|
|
98
|
+
};
|
|
99
|
+
const booleanOrNull = {
|
|
261
100
|
'boolean | null': {
|
|
262
101
|
checkbox: fallbackMapperPair(null, null),
|
|
263
102
|
multipleLookupValues: {
|
|
264
|
-
toAirtable: (
|
|
265
|
-
fromAirtable: (
|
|
266
|
-
if (!value || value.length === 0) {
|
|
267
|
-
return null;
|
|
268
|
-
}
|
|
269
|
-
if (value.length !== 1) {
|
|
270
|
-
throw new Error(`[airtable-ts] Can't coerce lookup to a single boolean, as there were ${value?.length} entries`);
|
|
271
|
-
}
|
|
272
|
-
if (typeof value[0] !== 'boolean') {
|
|
273
|
-
throw new Error(`[airtable-ts] Can't coerce singular lookup to a single boolean, as it was of type ${typeof value[0]}`);
|
|
274
|
-
}
|
|
275
|
-
return value[0];
|
|
276
|
-
},
|
|
103
|
+
toAirtable: readonly('multipleLookupValues'),
|
|
104
|
+
fromAirtable: coerce('multipleLookupValues', 'boolean | null'),
|
|
277
105
|
},
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
rating: requiredMapperPair,
|
|
282
|
-
duration: requiredMapperPair,
|
|
283
|
-
currency: requiredMapperPair,
|
|
284
|
-
percent: requiredMapperPair,
|
|
285
|
-
count: {
|
|
286
|
-
toAirtable: () => { throw new Error('[airtable-ts] count type field is readonly'); },
|
|
287
|
-
fromAirtable: (value) => required(value),
|
|
288
|
-
},
|
|
289
|
-
autoNumber: {
|
|
290
|
-
toAirtable: () => { throw new Error('[airtable-ts] autoNumber type field is readonly'); },
|
|
291
|
-
fromAirtable: (value) => required(value),
|
|
292
|
-
},
|
|
293
|
-
// Number assumed to be unix time in seconds
|
|
294
|
-
date: {
|
|
295
|
-
toAirtable: (value) => {
|
|
296
|
-
const date = new Date(value * 1000);
|
|
297
|
-
if (Number.isNaN(date.getTime())) {
|
|
298
|
-
throw new Error('[airtable-ts] Invalid date');
|
|
299
|
-
}
|
|
300
|
-
return date.toJSON().slice(0, 10);
|
|
301
|
-
},
|
|
302
|
-
fromAirtable: (value) => {
|
|
303
|
-
const date = new Date(value ?? '');
|
|
304
|
-
if (Number.isNaN(date.getTime())) {
|
|
305
|
-
throw new Error('[airtable-ts] Invalid date');
|
|
306
|
-
}
|
|
307
|
-
return Math.floor(date.getTime() / 1000);
|
|
308
|
-
},
|
|
309
|
-
},
|
|
310
|
-
// Number assumed to be unix time in seconds
|
|
311
|
-
dateTime: {
|
|
312
|
-
toAirtable: (value) => {
|
|
313
|
-
const date = new Date(value * 1000);
|
|
314
|
-
if (Number.isNaN(date.getTime())) {
|
|
315
|
-
throw new Error('[airtable-ts] Invalid dateTime');
|
|
316
|
-
}
|
|
317
|
-
return date.toJSON();
|
|
318
|
-
},
|
|
319
|
-
fromAirtable: (value) => {
|
|
320
|
-
const date = new Date(value ?? '');
|
|
321
|
-
if (Number.isNaN(date.getTime())) {
|
|
322
|
-
throw new Error('[airtable-ts] Invalid dateTime');
|
|
323
|
-
}
|
|
324
|
-
return Math.floor(date.getTime() / 1000);
|
|
325
|
-
},
|
|
326
|
-
},
|
|
327
|
-
multipleLookupValues: {
|
|
328
|
-
toAirtable: () => { throw new Error('[airtable-ts] lookup type field is readonly'); },
|
|
329
|
-
fromAirtable: (value) => {
|
|
330
|
-
if (!value) {
|
|
331
|
-
throw new Error('[airtable-ts] Failed to coerce lookup type field to a single number, as it was blank');
|
|
332
|
-
}
|
|
333
|
-
if (value.length !== 1) {
|
|
334
|
-
throw new Error(`[airtable-ts] Can't coerce lookup to a single number, as there were ${value?.length} entries`);
|
|
335
|
-
}
|
|
336
|
-
if (typeof value[0] !== 'number') {
|
|
337
|
-
throw new Error(`[airtable-ts] Can't coerce singular lookup to a single number, as it was of type ${typeof value[0]}`);
|
|
338
|
-
}
|
|
339
|
-
return value[0];
|
|
340
|
-
},
|
|
341
|
-
},
|
|
342
|
-
rollup: {
|
|
343
|
-
toAirtable: () => { throw new Error('[airtable-ts] rollup type field is readonly'); },
|
|
344
|
-
fromAirtable: (value) => {
|
|
345
|
-
if (typeof value === 'number')
|
|
346
|
-
return value;
|
|
347
|
-
throw new Error(`[airtable-ts] Can't coerce rollup to a number, as it was of type ${typeof value}`);
|
|
348
|
-
},
|
|
349
|
-
},
|
|
350
|
-
formula: {
|
|
351
|
-
toAirtable: () => { throw new Error('[airtable-ts] formula type field is readonly'); },
|
|
352
|
-
fromAirtable: (value) => {
|
|
353
|
-
if (typeof value === 'number')
|
|
354
|
-
return value;
|
|
355
|
-
throw new Error(`[airtable-ts] Can't coerce formula to a number, as it was of type ${typeof value}`);
|
|
356
|
-
},
|
|
106
|
+
unknown: {
|
|
107
|
+
toAirtable: (value) => value,
|
|
108
|
+
fromAirtable: coerce('unknown', 'boolean | null'),
|
|
357
109
|
},
|
|
358
110
|
},
|
|
111
|
+
};
|
|
112
|
+
const numberOrNull = {
|
|
359
113
|
'number | null': {
|
|
360
114
|
number: fallbackMapperPair(null, null),
|
|
361
115
|
rating: fallbackMapperPair(null, null),
|
|
@@ -364,150 +118,147 @@ exports.fieldMappers = {
|
|
|
364
118
|
percent: fallbackMapperPair(null, null),
|
|
365
119
|
count: {
|
|
366
120
|
fromAirtable: (value) => value ?? null,
|
|
367
|
-
toAirtable: (
|
|
121
|
+
toAirtable: readonly('count'),
|
|
368
122
|
},
|
|
369
123
|
autoNumber: {
|
|
370
124
|
fromAirtable: (value) => value ?? null,
|
|
371
|
-
toAirtable: (
|
|
125
|
+
toAirtable: readonly('autoNumber'),
|
|
372
126
|
},
|
|
373
|
-
// Number assumed to be unix time in seconds
|
|
374
127
|
date: {
|
|
375
|
-
toAirtable: (value) =>
|
|
376
|
-
|
|
128
|
+
toAirtable: (value) => dateTimeMapperPair.toAirtable(value)?.slice(0, 10) ?? null,
|
|
129
|
+
fromAirtable: (value) => {
|
|
130
|
+
const nullableValue = dateTimeMapperPair.fromAirtable(value);
|
|
131
|
+
if (nullableValue === null)
|
|
377
132
|
return null;
|
|
378
|
-
const date = new Date(
|
|
133
|
+
const date = new Date(nullableValue);
|
|
379
134
|
if (Number.isNaN(date.getTime())) {
|
|
380
135
|
throw new Error('[airtable-ts] Invalid date');
|
|
381
136
|
}
|
|
382
|
-
return date.
|
|
137
|
+
return Math.floor(date.getTime() / 1000);
|
|
383
138
|
},
|
|
139
|
+
},
|
|
140
|
+
dateTime: {
|
|
141
|
+
toAirtable: dateTimeMapperPair.toAirtable,
|
|
384
142
|
fromAirtable: (value) => {
|
|
385
|
-
|
|
143
|
+
const nullableValue = dateTimeMapperPair.fromAirtable(value);
|
|
144
|
+
if (nullableValue === null)
|
|
386
145
|
return null;
|
|
387
|
-
const date = new Date(
|
|
146
|
+
const date = new Date(nullableValue);
|
|
388
147
|
if (Number.isNaN(date.getTime())) {
|
|
389
148
|
throw new Error('[airtable-ts] Invalid date');
|
|
390
149
|
}
|
|
391
150
|
return Math.floor(date.getTime() / 1000);
|
|
392
151
|
},
|
|
393
152
|
},
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
153
|
+
createdTime: {
|
|
154
|
+
toAirtable: dateTimeMapperPair.toAirtable,
|
|
155
|
+
fromAirtable: (value) => {
|
|
156
|
+
const nullableValue = dateTimeMapperPair.fromAirtable(value);
|
|
157
|
+
if (nullableValue === null)
|
|
398
158
|
return null;
|
|
399
|
-
const date = new Date(
|
|
159
|
+
const date = new Date(nullableValue);
|
|
400
160
|
if (Number.isNaN(date.getTime())) {
|
|
401
|
-
throw new Error('[airtable-ts] Invalid
|
|
161
|
+
throw new Error('[airtable-ts] Invalid date');
|
|
402
162
|
}
|
|
403
|
-
return date.
|
|
163
|
+
return Math.floor(date.getTime() / 1000);
|
|
404
164
|
},
|
|
165
|
+
},
|
|
166
|
+
lastModifiedTime: {
|
|
167
|
+
toAirtable: dateTimeMapperPair.toAirtable,
|
|
405
168
|
fromAirtable: (value) => {
|
|
406
|
-
|
|
169
|
+
const nullableValue = dateTimeMapperPair.fromAirtable(value);
|
|
170
|
+
if (nullableValue === null)
|
|
407
171
|
return null;
|
|
408
|
-
const date = new Date(
|
|
172
|
+
const date = new Date(nullableValue);
|
|
409
173
|
if (Number.isNaN(date.getTime())) {
|
|
410
|
-
throw new Error('[airtable-ts] Invalid
|
|
174
|
+
throw new Error('[airtable-ts] Invalid date');
|
|
411
175
|
}
|
|
412
176
|
return Math.floor(date.getTime() / 1000);
|
|
413
177
|
},
|
|
414
178
|
},
|
|
415
179
|
multipleLookupValues: {
|
|
416
|
-
toAirtable: (
|
|
417
|
-
fromAirtable: (
|
|
418
|
-
if (!value || value.length === 0) {
|
|
419
|
-
return null;
|
|
420
|
-
}
|
|
421
|
-
if (value.length !== 1) {
|
|
422
|
-
throw new Error(`[airtable-ts] Can't coerce lookup to a single number, as there were ${value?.length} entries`);
|
|
423
|
-
}
|
|
424
|
-
if (typeof value[0] !== 'number') {
|
|
425
|
-
throw new Error(`[airtable-ts] Can't coerce singular lookup to a single number, as it was of type ${typeof value[0]}`);
|
|
426
|
-
}
|
|
427
|
-
return value[0];
|
|
428
|
-
},
|
|
180
|
+
toAirtable: readonly('multipleLookupValues'),
|
|
181
|
+
fromAirtable: coerce('multipleLookupValues', 'number | null'),
|
|
429
182
|
},
|
|
430
183
|
rollup: {
|
|
431
|
-
toAirtable: (
|
|
432
|
-
fromAirtable: (
|
|
433
|
-
if (typeof value === 'number')
|
|
434
|
-
return value;
|
|
435
|
-
if (value === null || value === undefined)
|
|
436
|
-
return null;
|
|
437
|
-
throw new Error(`[airtable-ts] Can't coerce rollup to a number, as it was of type ${typeof value}`);
|
|
438
|
-
},
|
|
184
|
+
toAirtable: readonly('rollup'),
|
|
185
|
+
fromAirtable: coerce('rollup', 'number | null'),
|
|
439
186
|
},
|
|
440
187
|
formula: {
|
|
441
|
-
toAirtable: (
|
|
442
|
-
fromAirtable: (
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
throw new Error(`[airtable-ts] Can't coerce formula to a number, as it was of type ${typeof value}`);
|
|
448
|
-
},
|
|
188
|
+
toAirtable: readonly('formula'),
|
|
189
|
+
fromAirtable: coerce('formula', 'number | null'),
|
|
190
|
+
},
|
|
191
|
+
unknown: {
|
|
192
|
+
toAirtable: (value) => value,
|
|
193
|
+
fromAirtable: coerce('unknown', 'number | null'),
|
|
449
194
|
},
|
|
450
195
|
},
|
|
451
|
-
|
|
196
|
+
};
|
|
197
|
+
const stringArrayOrNull = {
|
|
198
|
+
'string[] | null': {
|
|
452
199
|
multipleSelects: fallbackMapperPair([], []),
|
|
453
200
|
multipleRecordLinks: fallbackMapperPair([], []),
|
|
454
201
|
multipleLookupValues: {
|
|
455
202
|
toAirtable: () => { throw new Error('[airtable-ts] lookup type field is readonly'); },
|
|
456
|
-
fromAirtable: (
|
|
457
|
-
if (!Array.isArray(value)) {
|
|
458
|
-
throw new Error('[airtable-ts] Failed to coerce lookup type field to a string array, as it was not an array');
|
|
459
|
-
}
|
|
460
|
-
if (value.some((v) => typeof v !== 'string')) {
|
|
461
|
-
throw new Error('[airtable-ts] Can\'t coerce lookup to a string array, as it had non string type');
|
|
462
|
-
}
|
|
463
|
-
return value;
|
|
464
|
-
},
|
|
203
|
+
fromAirtable: coerce('multipleLookupValues', 'string[] | null'),
|
|
465
204
|
},
|
|
466
205
|
formula: {
|
|
467
206
|
toAirtable: () => { throw new Error('[airtable-ts] formula type field is readonly'); },
|
|
468
|
-
fromAirtable: (
|
|
469
|
-
if (!Array.isArray(value)) {
|
|
470
|
-
throw new Error('[airtable-ts] Failed to coerce formula type field to a string array, as it was not an array');
|
|
471
|
-
}
|
|
472
|
-
if (value.some((v) => typeof v !== 'string')) {
|
|
473
|
-
throw new Error('[airtable-ts] Can\'t coerce formula to a string array, as it had non string type');
|
|
474
|
-
}
|
|
475
|
-
return value;
|
|
476
|
-
},
|
|
207
|
+
fromAirtable: coerce('multipleLookupValues', 'string[] | null'),
|
|
477
208
|
},
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
multipleRecordLinks: fallbackMapperPair(null, null),
|
|
482
|
-
multipleLookupValues: {
|
|
483
|
-
toAirtable: () => { throw new Error('[airtable-ts] lookup type field is readonly'); },
|
|
484
|
-
fromAirtable: (value) => {
|
|
485
|
-
if (!value && !Array.isArray(value)) {
|
|
486
|
-
return null;
|
|
487
|
-
}
|
|
488
|
-
if (!Array.isArray(value)) {
|
|
489
|
-
throw new Error('[airtable-ts] Failed to coerce lookup type field to a string array, as it was not an array');
|
|
490
|
-
}
|
|
491
|
-
if (value.some((v) => typeof v !== 'string')) {
|
|
492
|
-
throw new Error('[airtable-ts] Can\'t coerce lookup to a string array, as it had non string type');
|
|
493
|
-
}
|
|
494
|
-
return value;
|
|
495
|
-
},
|
|
496
|
-
},
|
|
497
|
-
formula: {
|
|
498
|
-
toAirtable: () => { throw new Error('[airtable-ts] formula type field is readonly'); },
|
|
499
|
-
fromAirtable: (value) => {
|
|
500
|
-
if (!value && !Array.isArray(value)) {
|
|
501
|
-
return null;
|
|
502
|
-
}
|
|
503
|
-
if (!Array.isArray(value)) {
|
|
504
|
-
throw new Error('[airtable-ts] Failed to coerce formula type field to a string array, as it was not an array');
|
|
505
|
-
}
|
|
506
|
-
if (value.some((v) => typeof v !== 'string')) {
|
|
507
|
-
throw new Error('[airtable-ts] Can\'t coerce formula to a string array, as it had non string type');
|
|
508
|
-
}
|
|
509
|
-
return value;
|
|
510
|
-
},
|
|
209
|
+
unknown: {
|
|
210
|
+
toAirtable: (value) => value,
|
|
211
|
+
fromAirtable: coerce('unknown', 'string[] | null'),
|
|
511
212
|
},
|
|
512
213
|
},
|
|
513
214
|
};
|
|
215
|
+
exports.fieldMappers = {
|
|
216
|
+
...stringOrNull,
|
|
217
|
+
string: {
|
|
218
|
+
...Object.fromEntries(Object.entries(stringOrNull['string | null']).map(([airtableType, nullablePair]) => {
|
|
219
|
+
return [airtableType, {
|
|
220
|
+
toAirtable: nullablePair.toAirtable,
|
|
221
|
+
fromAirtable: (value) => {
|
|
222
|
+
const nullableValue = nullablePair.fromAirtable(value);
|
|
223
|
+
if (nullableValue === null && ['multipleRecordLinks', 'dateTime', 'createdTime', 'lastModifiedTime'].includes(airtableType)) {
|
|
224
|
+
throw new Error(`[airtable-ts] Expected non-null or non-empty value to map to string for field type ${airtableType}`);
|
|
225
|
+
}
|
|
226
|
+
return nullableValue ?? '';
|
|
227
|
+
},
|
|
228
|
+
}];
|
|
229
|
+
})),
|
|
230
|
+
},
|
|
231
|
+
...booleanOrNull,
|
|
232
|
+
boolean: {
|
|
233
|
+
...Object.fromEntries(Object.entries(booleanOrNull['boolean | null']).map(([airtableType, nullablePair]) => {
|
|
234
|
+
return [airtableType, {
|
|
235
|
+
toAirtable: nullablePair.toAirtable,
|
|
236
|
+
fromAirtable: (value) => nullablePair.fromAirtable(value) ?? false,
|
|
237
|
+
}];
|
|
238
|
+
})),
|
|
239
|
+
},
|
|
240
|
+
...numberOrNull,
|
|
241
|
+
number: {
|
|
242
|
+
...Object.fromEntries(Object.entries(numberOrNull['number | null']).map(([airtableType, nullablePair]) => {
|
|
243
|
+
return [airtableType, {
|
|
244
|
+
toAirtable: nullablePair.toAirtable,
|
|
245
|
+
fromAirtable: (value) => {
|
|
246
|
+
const nullableValue = nullablePair.fromAirtable(value);
|
|
247
|
+
if (nullableValue === null) {
|
|
248
|
+
throw new Error(`[airtable-ts] Expected non-null or non-empty value to map to number for field type ${airtableType}`);
|
|
249
|
+
}
|
|
250
|
+
return nullableValue;
|
|
251
|
+
},
|
|
252
|
+
}];
|
|
253
|
+
})),
|
|
254
|
+
},
|
|
255
|
+
...stringArrayOrNull,
|
|
256
|
+
'string[]': {
|
|
257
|
+
...Object.fromEntries(Object.entries(stringArrayOrNull['string[] | null']).map(([airtableType, nullablePair]) => {
|
|
258
|
+
return [airtableType, {
|
|
259
|
+
toAirtable: nullablePair.toAirtable,
|
|
260
|
+
fromAirtable: (value) => nullablePair.fromAirtable(value) ?? [],
|
|
261
|
+
}];
|
|
262
|
+
})),
|
|
263
|
+
},
|
|
264
|
+
};
|
|
@@ -4,6 +4,20 @@ exports.visibleForTesting = exports.mapRecordToAirtable = exports.mapRecordFromA
|
|
|
4
4
|
const fieldMappers_1 = require("./fieldMappers");
|
|
5
5
|
const nameMapper_1 = require("./nameMapper");
|
|
6
6
|
const typeUtils_1 = require("./typeUtils");
|
|
7
|
+
const getMapper = (tsType, airtableType) => {
|
|
8
|
+
const tsMapper = fieldMappers_1.fieldMappers[tsType];
|
|
9
|
+
if (!tsMapper) {
|
|
10
|
+
throw new Error(`[airtable-ts] No mappers for ts type ${tsType}`);
|
|
11
|
+
}
|
|
12
|
+
if (tsMapper[airtableType]) {
|
|
13
|
+
return tsMapper[airtableType];
|
|
14
|
+
}
|
|
15
|
+
if (tsMapper.unknown) {
|
|
16
|
+
console.warn(`[airtable-ts] Unknown airtable type ${airtableType}. This is not fully supported and exact mapping behaviour may change in a future release.`);
|
|
17
|
+
return tsMapper.unknown;
|
|
18
|
+
}
|
|
19
|
+
throw new Error(`[airtable-ts] Expected to be able to map to ts type ${tsType}, but got airtable type ${airtableType} which can't.`);
|
|
20
|
+
};
|
|
7
21
|
/**
|
|
8
22
|
* This function coerces an Airtable record to a TypeScript object, given an
|
|
9
23
|
* object type definition. It will do this using the field mappers on each
|
|
@@ -28,18 +42,10 @@ const mapRecordTypeAirtableToTs = (tsTypes, record) => {
|
|
|
28
42
|
throw new Error(`[airtable-ts] Failed to get Airtable field ${fieldNameOrId}`);
|
|
29
43
|
}
|
|
30
44
|
const value = record.fields[fieldDefinition.name];
|
|
31
|
-
const tsMapper = fieldMappers_1.fieldMappers[tsType];
|
|
32
|
-
if (!tsMapper) {
|
|
33
|
-
throw new Error(`[airtable-ts] No mappers for ts type ${tsType}`);
|
|
34
|
-
}
|
|
35
|
-
const specificMapper = tsMapper[fieldDefinition.type]?.fromAirtable;
|
|
36
|
-
if (!specificMapper) {
|
|
37
|
-
// eslint-disable-next-line no-underscore-dangle
|
|
38
|
-
throw new Error(`[airtable-ts] Expected field ${record._table.name}.${fieldNameOrId} to be able to map to ts type ${tsType}, but got airtable type ${fieldDefinition.type} which can't.`);
|
|
39
|
-
}
|
|
40
45
|
try {
|
|
46
|
+
const { fromAirtable } = getMapper(tsType, fieldDefinition.type);
|
|
41
47
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
42
|
-
item[fieldNameOrId] =
|
|
48
|
+
item[fieldNameOrId] = fromAirtable(value);
|
|
43
49
|
}
|
|
44
50
|
catch (error) {
|
|
45
51
|
if (error instanceof Error) {
|
|
@@ -88,16 +94,20 @@ const mapRecordTypeTsToAirtable = (tsTypes, tsRecord, airtableTable) => {
|
|
|
88
94
|
if (!fieldDefinition) {
|
|
89
95
|
throw new Error(`[airtable-ts] Failed to get Airtable field ${fieldNameOrId}`);
|
|
90
96
|
}
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
97
|
+
try {
|
|
98
|
+
const { toAirtable } = getMapper(tsType, fieldDefinition.type);
|
|
99
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
100
|
+
item[fieldNameOrId] = toAirtable(value);
|
|
94
101
|
}
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
102
|
+
catch (error) {
|
|
103
|
+
if (error instanceof Error) {
|
|
104
|
+
// eslint-disable-next-line no-underscore-dangle
|
|
105
|
+
error.message = `Failed to map field ${airtableTable.name}.${fieldNameOrId}: ${error.message}`;
|
|
106
|
+
// eslint-disable-next-line no-underscore-dangle
|
|
107
|
+
error.stack = `Error: Failed to map field ${airtableTable.name}.${fieldNameOrId}: ${error.stack?.startsWith('Error: ') ? error.stack.slice('Error: '.length) : error.stack}`;
|
|
108
|
+
}
|
|
109
|
+
throw error;
|
|
98
110
|
}
|
|
99
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
100
|
-
item[fieldNameOrId] = specificMapper(value);
|
|
101
111
|
});
|
|
102
112
|
return Object.assign(item, { id: tsRecord.id });
|
|
103
113
|
};
|
|
@@ -3,7 +3,13 @@ type NonNullToString<T> = T extends string ? 'string' : T extends number ? 'numb
|
|
|
3
3
|
export type ToTsTypeString<T> = null extends T ? `${NonNullToString<T>} | null` : NonNullToString<T>;
|
|
4
4
|
export type FromTsTypeString<T> = T extends 'string' ? string : T extends 'string | null' ? string | null : T extends 'number' ? number : T extends 'number | null' ? number | null : T extends 'boolean' ? boolean : T extends 'boolean | null' ? boolean | null : T extends 'string[]' ? string[] : T extends 'string[] | null' ? string[] | null : T extends 'number[]' ? number[] : T extends 'number[] | null' ? number[] | null : T extends 'boolean[]' ? boolean[] : T extends 'boolean[] | null' ? boolean[] | null : never;
|
|
5
5
|
export type AirtableTypeString = 'aiText' | 'autoNumber' | 'barcode' | 'button' | 'checkbox' | 'count' | 'createdBy' | 'createdTime' | 'currency' | 'date' | 'dateTime' | 'duration' | 'email' | 'externalSyncSource' | 'formula' | 'lastModifiedBy' | 'lastModifiedTime' | 'lookup' | 'multipleLookupValues' | 'multilineText' | 'multipleAttachments' | 'multipleCollaborators' | 'multipleRecordLinks' | 'multipleSelects' | 'number' | 'percent' | 'phoneNumber' | 'rating' | 'richText' | 'rollup' | 'singleCollaborator' | 'singleLineText' | 'singleSelect' | 'url';
|
|
6
|
-
export type FromAirtableTypeString<T extends AirtableTypeString> = null | (T extends 'url' | 'email' | 'phoneNumber' | 'singleLineText' | 'multilineText' | 'richText' | 'singleSelect' | 'externalSyncSource' | 'date' | 'dateTime' | 'createdTime' | 'lastModifiedTime' ? string : T extends 'multipleRecordLinks' | 'multipleSelects' ? string[] : T extends 'number' | 'rating' | 'duration' | 'currency' | 'percent' | 'count' | 'autoNumber' ? number : T extends 'checkbox' ? boolean : T extends 'lookup' | 'multipleLookupValues' | 'rollup' | 'formula' ? FromAirtableTypeString<any>[] : never);
|
|
6
|
+
export type FromAirtableTypeString<T extends AirtableTypeString | 'unknown'> = null | (T extends 'url' | 'email' | 'phoneNumber' | 'singleLineText' | 'multilineText' | 'richText' | 'singleSelect' | 'externalSyncSource' | 'date' | 'dateTime' | 'createdTime' | 'lastModifiedTime' ? string : T extends 'multipleRecordLinks' | 'multipleSelects' ? string[] : T extends 'number' | 'rating' | 'duration' | 'currency' | 'percent' | 'count' | 'autoNumber' ? number : T extends 'checkbox' ? boolean : T extends 'lookup' | 'multipleLookupValues' | 'rollup' | 'formula' ? FromAirtableTypeString<any>[] : T extends 'unknown' ? unknown : never);
|
|
7
|
+
interface TypeDef {
|
|
8
|
+
single: 'string' | 'number' | 'boolean';
|
|
9
|
+
array: boolean;
|
|
10
|
+
nullable: boolean;
|
|
11
|
+
}
|
|
12
|
+
export declare const parseType: (t: TsTypeString) => TypeDef;
|
|
7
13
|
/**
|
|
8
14
|
* Verifies whether the given value is assignable to the given type
|
|
9
15
|
*
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.airtableFieldNameTsTypes = exports.matchesType = void 0;
|
|
3
|
+
exports.airtableFieldNameTsTypes = exports.matchesType = exports.parseType = void 0;
|
|
4
4
|
const parseType = (t) => {
|
|
5
5
|
if (t.endsWith('[] | null')) {
|
|
6
6
|
return {
|
|
@@ -29,6 +29,7 @@ const parseType = (t) => {
|
|
|
29
29
|
nullable: false,
|
|
30
30
|
};
|
|
31
31
|
};
|
|
32
|
+
exports.parseType = parseType;
|
|
32
33
|
/**
|
|
33
34
|
* Verifies whether the given value is assignable to the given type
|
|
34
35
|
*
|
|
@@ -42,7 +43,7 @@ const parseType = (t) => {
|
|
|
42
43
|
* @example true
|
|
43
44
|
*/
|
|
44
45
|
const matchesType = (value, tsType) => {
|
|
45
|
-
const expectedType = parseType(tsType);
|
|
46
|
+
const expectedType = (0, exports.parseType)(tsType);
|
|
46
47
|
if (expectedType.nullable && value === null) {
|
|
47
48
|
return true;
|
|
48
49
|
}
|