airtable-ts 1.5.0 → 1.6.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.
- package/dist/AirtableTs.d.ts +3 -3
- package/dist/AirtableTs.js +2 -2
- package/dist/AirtableTsError.js +0 -2
- package/dist/assertMatchesSchema.d.ts +1 -1
- package/dist/assertMatchesSchema.js +1 -2
- package/dist/getAirtableTsTable.d.ts +3 -3
- package/dist/getAirtableTsTable.js +18 -9
- package/dist/getFields.d.ts +1 -1
- package/dist/mapping/fieldMappers.d.ts +1 -1
- package/dist/mapping/fieldMappers.js +48 -26
- package/dist/mapping/nameMapper.d.ts +1 -1
- package/dist/mapping/nameMapper.js +4 -3
- package/dist/mapping/recordMapper.d.ts +7 -11
- package/dist/mapping/recordMapper.js +0 -4
- package/dist/mapping/typeUtils.d.ts +17 -17
- package/dist/mapping/typeUtils.js +1 -1
- package/dist/types.d.ts +4 -4
- package/dist/wrapToCatchAirtableErrors.js +0 -2
- package/package.json +12 -15
package/dist/AirtableTs.d.ts
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import Airtable from 'airtable';
|
|
2
|
-
import { Item, Table } from './mapping/typeUtils';
|
|
3
|
-
import { AirtableTsTable, AirtableTsOptions, ScanParams } from './types';
|
|
2
|
+
import { type Item, type Table } from './mapping/typeUtils';
|
|
3
|
+
import { type AirtableTsTable, type AirtableTsOptions, type ScanParams } from './types';
|
|
4
4
|
export declare class AirtableTs {
|
|
5
5
|
airtable: Airtable;
|
|
6
|
-
private options;
|
|
6
|
+
private readonly options;
|
|
7
7
|
constructor(options: AirtableTsOptions);
|
|
8
8
|
get<T extends Item>(table: Table<T>, id: string): Promise<T>;
|
|
9
9
|
scan<T extends Item>(table: Table<T>, params?: ScanParams): Promise<T[]>;
|
package/dist/AirtableTs.js
CHANGED
|
@@ -19,7 +19,7 @@ class AirtableTs {
|
|
|
19
19
|
this.options = {
|
|
20
20
|
...airtable_1.default.default_config(),
|
|
21
21
|
...options,
|
|
22
|
-
baseSchemaCacheDurationMs: options.baseSchemaCacheDurationMs ??
|
|
22
|
+
baseSchemaCacheDurationMs: options.baseSchemaCacheDurationMs ?? 120_000,
|
|
23
23
|
};
|
|
24
24
|
}
|
|
25
25
|
async get(table, id) {
|
|
@@ -59,7 +59,7 @@ class AirtableTs {
|
|
|
59
59
|
(0, assertMatchesSchema_1.assertMatchesSchema)(table, { ...data });
|
|
60
60
|
const { id, ...withoutId } = data;
|
|
61
61
|
const airtableTsTable = await (0, getAirtableTsTable_1.getAirtableTsTable)(this.airtable, table, this.options);
|
|
62
|
-
const record = await airtableTsTable.update(
|
|
62
|
+
const record = await airtableTsTable.update(id, (0, recordMapper_1.mapRecordToAirtable)(table, withoutId, airtableTsTable));
|
|
63
63
|
return (0, recordMapper_1.mapRecordFromAirtable)(table, record);
|
|
64
64
|
}
|
|
65
65
|
async remove(table, id) {
|
package/dist/AirtableTsError.js
CHANGED
|
@@ -27,9 +27,7 @@ class AirtableTsError extends Error {
|
|
|
27
27
|
exports.AirtableTsError = AirtableTsError;
|
|
28
28
|
const prependError = (error, prefix) => {
|
|
29
29
|
if (error instanceof AirtableTsError) {
|
|
30
|
-
// eslint-disable-next-line no-param-reassign
|
|
31
30
|
error.message = `${prefix}: ${error.message}`;
|
|
32
|
-
// eslint-disable-next-line no-param-reassign
|
|
33
31
|
error.stack = `Error: ${prefix}: ${error.stack?.startsWith('Error: ') ? error.stack.slice('Error: '.length) : error.stack}`;
|
|
34
32
|
}
|
|
35
33
|
return error;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.assertMatchesSchema =
|
|
3
|
+
exports.assertMatchesSchema = assertMatchesSchema;
|
|
4
4
|
const typeUtils_1 = require("./mapping/typeUtils");
|
|
5
5
|
const AirtableTsError_1 = require("./AirtableTsError");
|
|
6
6
|
/**
|
|
@@ -39,4 +39,3 @@ function assertMatchesSchema(table, data, mode = 'partial') {
|
|
|
39
39
|
}
|
|
40
40
|
});
|
|
41
41
|
}
|
|
42
|
-
exports.assertMatchesSchema = assertMatchesSchema;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import Airtable from 'airtable';
|
|
2
|
-
import { Item, Table } from './mapping/typeUtils';
|
|
3
|
-
import { AirtableTsTable, CompleteAirtableTsOptions } from './types';
|
|
1
|
+
import type Airtable from 'airtable';
|
|
2
|
+
import { type Item, type Table } from './mapping/typeUtils';
|
|
3
|
+
import { type AirtableTsTable, type CompleteAirtableTsOptions } from './types';
|
|
4
4
|
export declare const getAirtableTsTable: <T extends Item>(airtable: Airtable, table: Table<T>, options: CompleteAirtableTsOptions) => Promise<AirtableTsTable<T>>;
|
|
@@ -15,13 +15,23 @@ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (
|
|
|
15
15
|
}) : function(o, v) {
|
|
16
16
|
o["default"] = v;
|
|
17
17
|
});
|
|
18
|
-
var __importStar = (this && this.__importStar) || function (
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
};
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
25
35
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
26
36
|
exports.getAirtableTsTable = void 0;
|
|
27
37
|
const axios_1 = __importStar(require("axios"));
|
|
@@ -73,14 +83,13 @@ const getAirtableBaseSchema = async (baseId, options) => {
|
|
|
73
83
|
url: `/v0/meta/bases/${baseId}/tables`,
|
|
74
84
|
...(options.requestTimeout ? { timeout: options.requestTimeout } : {}),
|
|
75
85
|
headers: {
|
|
76
|
-
// eslint-disable-next-line no-underscore-dangle
|
|
77
86
|
Authorization: `Bearer ${options.apiKey}`,
|
|
78
87
|
...options.customHeaders,
|
|
79
88
|
},
|
|
80
89
|
}).catch((err) => {
|
|
81
90
|
const normalizedErrorMessage = err instanceof axios_1.AxiosError
|
|
82
91
|
? `${err.message}. Status: ${err.status}. Data: ${JSON.stringify(err.response?.data)}`
|
|
83
|
-
: err;
|
|
92
|
+
: String(err);
|
|
84
93
|
throw new AirtableTsError_1.AirtableTsError({
|
|
85
94
|
message: `Failed to get base schema: ${normalizedErrorMessage}`,
|
|
86
95
|
type: AirtableTsError_1.ErrorType.API_ERROR,
|
package/dist/getFields.d.ts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import { Item, Table } from './mapping/typeUtils';
|
|
1
|
+
import { type Item, type Table } from './mapping/typeUtils';
|
|
2
2
|
export declare const getFields: (table: Table<Item>) => string[];
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { AirtableTypeString, FromAirtableTypeString, FromTsTypeString, TsTypeString } from './typeUtils';
|
|
1
|
+
import { type AirtableTypeString, type FromAirtableTypeString, type FromTsTypeString, type TsTypeString } from './typeUtils';
|
|
2
2
|
type Mapper = {
|
|
3
3
|
[T in TsTypeString]?: {
|
|
4
4
|
[A in AirtableTypeString | 'unknown']?: {
|
|
@@ -30,6 +30,18 @@ const coerce = (airtableType, tsType) => (value) => {
|
|
|
30
30
|
if (!parsedType.array && Array.isArray(value) && value.length === 1 && typeof value[0] === parsedType.single) {
|
|
31
31
|
return value[0];
|
|
32
32
|
}
|
|
33
|
+
// { specialValue: 'NaN' }
|
|
34
|
+
if (parsedType.nullable && typeof value === 'object' && value !== null && 'specialValue' in value && value.specialValue === 'NaN') {
|
|
35
|
+
return null;
|
|
36
|
+
}
|
|
37
|
+
// { error: '#ERROR!' }
|
|
38
|
+
if (parsedType.nullable && typeof value === 'object' && value !== null && 'error' in value && value.error === '#ERROR!') {
|
|
39
|
+
return null;
|
|
40
|
+
}
|
|
41
|
+
// [{ error: '#ERROR!' }]
|
|
42
|
+
if (parsedType.nullable && Array.isArray(value) && value.length === 1 && typeof value[0] === 'object' && value[0] !== null && 'error' in value[0] && value[0].error === '#ERROR!') {
|
|
43
|
+
return null;
|
|
44
|
+
}
|
|
33
45
|
if (!parsedType.array && Array.isArray(value) && value.length !== 1) {
|
|
34
46
|
throw new AirtableTsError_1.AirtableTsError({
|
|
35
47
|
message: `Cannot convert array with ${value.length} entries from airtable type '${airtableType} to TypeScript type '${tsType}'.`,
|
|
@@ -45,9 +57,10 @@ const coerce = (airtableType, tsType) => (value) => {
|
|
|
45
57
|
};
|
|
46
58
|
const dateTimeMapperPair = {
|
|
47
59
|
// Number assumed to be unix time in seconds
|
|
48
|
-
toAirtable
|
|
49
|
-
if (value === null)
|
|
60
|
+
toAirtable(value) {
|
|
61
|
+
if (value === null) {
|
|
50
62
|
return null;
|
|
63
|
+
}
|
|
51
64
|
const date = new Date(typeof value === 'number' ? value * 1000 : value);
|
|
52
65
|
if (Number.isNaN(date.getTime())) {
|
|
53
66
|
throw new AirtableTsError_1.AirtableTsError({
|
|
@@ -57,9 +70,10 @@ const dateTimeMapperPair = {
|
|
|
57
70
|
}
|
|
58
71
|
return date.toJSON();
|
|
59
72
|
},
|
|
60
|
-
fromAirtable
|
|
61
|
-
if (value === null || value === undefined)
|
|
73
|
+
fromAirtable(value) {
|
|
74
|
+
if (value === null || value === undefined) {
|
|
62
75
|
return null;
|
|
76
|
+
}
|
|
63
77
|
const date = new Date(value);
|
|
64
78
|
if (Number.isNaN(date.getTime())) {
|
|
65
79
|
throw new AirtableTsError_1.AirtableTsError({
|
|
@@ -72,45 +86,49 @@ const dateTimeMapperPair = {
|
|
|
72
86
|
};
|
|
73
87
|
const aiTextMapperPair = {
|
|
74
88
|
toAirtable: readonly('aiText'),
|
|
75
|
-
fromAirtable
|
|
76
|
-
if (!obj || typeof obj !== 'object' || !('value' in obj) || typeof obj.value !== 'string')
|
|
89
|
+
fromAirtable(obj) {
|
|
90
|
+
if (!obj || typeof obj !== 'object' || !('value' in obj) || typeof obj.value !== 'string') {
|
|
77
91
|
return null;
|
|
92
|
+
}
|
|
78
93
|
return obj.value;
|
|
79
94
|
},
|
|
80
95
|
};
|
|
81
96
|
const barcodeMapperPair = {
|
|
82
97
|
toAirtable: (value) => ({ text: value }),
|
|
83
|
-
fromAirtable
|
|
84
|
-
if (!obj || typeof obj !== 'object' || !('text' in obj) || typeof obj.text !== 'string')
|
|
98
|
+
fromAirtable(obj) {
|
|
99
|
+
if (!obj || typeof obj !== 'object' || !('text' in obj) || typeof obj.text !== 'string') {
|
|
85
100
|
return null;
|
|
101
|
+
}
|
|
86
102
|
return obj.text;
|
|
87
103
|
},
|
|
88
104
|
};
|
|
89
105
|
const collaboratorMapperPair = {
|
|
90
106
|
toAirtable: (value) => ({ id: value }),
|
|
91
|
-
fromAirtable
|
|
92
|
-
if (!obj || typeof obj !== 'object' || !('id' in obj) || typeof obj.id !== 'string')
|
|
107
|
+
fromAirtable(obj) {
|
|
108
|
+
if (!obj || typeof obj !== 'object' || !('id' in obj) || typeof obj.id !== 'string') {
|
|
93
109
|
return null;
|
|
110
|
+
}
|
|
94
111
|
return obj.id;
|
|
95
112
|
},
|
|
96
113
|
};
|
|
97
114
|
const multipleCollaboratorsMapperPair = {
|
|
98
115
|
toAirtable: (value) => value,
|
|
99
|
-
fromAirtable
|
|
116
|
+
fromAirtable(obj) {
|
|
100
117
|
return obj?.map((v) => ('id' in v && typeof v.id === 'string' ? v.id : null)).filter(Boolean) ?? null;
|
|
101
118
|
},
|
|
102
119
|
};
|
|
103
120
|
const multipleAttachmentsMapperPair = {
|
|
104
121
|
toAirtable: readonly('multipleAttachments'),
|
|
105
|
-
fromAirtable
|
|
122
|
+
fromAirtable(obj) {
|
|
106
123
|
return obj?.map((v) => ('url' in v && typeof v.url === 'string' ? v.url : null)).filter(Boolean) ?? null;
|
|
107
124
|
},
|
|
108
125
|
};
|
|
109
126
|
const buttonMapperPair = {
|
|
110
127
|
toAirtable: readonly('button'),
|
|
111
|
-
fromAirtable
|
|
112
|
-
if (!obj || typeof obj !== 'object' || !('label' in obj) || typeof obj.label !== 'string')
|
|
128
|
+
fromAirtable(obj) {
|
|
129
|
+
if (!obj || typeof obj !== 'object' || !('label' in obj) || typeof obj.label !== 'string') {
|
|
113
130
|
return null;
|
|
131
|
+
}
|
|
114
132
|
return obj.label;
|
|
115
133
|
},
|
|
116
134
|
};
|
|
@@ -210,10 +228,11 @@ const numberOrNull = {
|
|
|
210
228
|
},
|
|
211
229
|
date: {
|
|
212
230
|
toAirtable: (value) => dateTimeMapperPair.toAirtable(value)?.slice(0, 10) ?? null,
|
|
213
|
-
fromAirtable
|
|
231
|
+
fromAirtable(value) {
|
|
214
232
|
const nullableValue = dateTimeMapperPair.fromAirtable(value);
|
|
215
|
-
if (nullableValue === null)
|
|
233
|
+
if (nullableValue === null) {
|
|
216
234
|
return null;
|
|
235
|
+
}
|
|
217
236
|
const date = new Date(nullableValue);
|
|
218
237
|
if (Number.isNaN(date.getTime())) {
|
|
219
238
|
throw new AirtableTsError_1.AirtableTsError({
|
|
@@ -226,10 +245,11 @@ const numberOrNull = {
|
|
|
226
245
|
},
|
|
227
246
|
dateTime: {
|
|
228
247
|
toAirtable: dateTimeMapperPair.toAirtable,
|
|
229
|
-
fromAirtable
|
|
248
|
+
fromAirtable(value) {
|
|
230
249
|
const nullableValue = dateTimeMapperPair.fromAirtable(value);
|
|
231
|
-
if (nullableValue === null)
|
|
250
|
+
if (nullableValue === null) {
|
|
232
251
|
return null;
|
|
252
|
+
}
|
|
233
253
|
const date = new Date(nullableValue);
|
|
234
254
|
if (Number.isNaN(date.getTime())) {
|
|
235
255
|
throw new AirtableTsError_1.AirtableTsError({
|
|
@@ -242,10 +262,11 @@ const numberOrNull = {
|
|
|
242
262
|
},
|
|
243
263
|
createdTime: {
|
|
244
264
|
toAirtable: dateTimeMapperPair.toAirtable,
|
|
245
|
-
fromAirtable
|
|
265
|
+
fromAirtable(value) {
|
|
246
266
|
const nullableValue = dateTimeMapperPair.fromAirtable(value);
|
|
247
|
-
if (nullableValue === null)
|
|
267
|
+
if (nullableValue === null) {
|
|
248
268
|
return null;
|
|
269
|
+
}
|
|
249
270
|
const date = new Date(nullableValue);
|
|
250
271
|
if (Number.isNaN(date.getTime())) {
|
|
251
272
|
throw new AirtableTsError_1.AirtableTsError({
|
|
@@ -258,10 +279,11 @@ const numberOrNull = {
|
|
|
258
279
|
},
|
|
259
280
|
lastModifiedTime: {
|
|
260
281
|
toAirtable: dateTimeMapperPair.toAirtable,
|
|
261
|
-
fromAirtable
|
|
282
|
+
fromAirtable(value) {
|
|
262
283
|
const nullableValue = dateTimeMapperPair.fromAirtable(value);
|
|
263
|
-
if (nullableValue === null)
|
|
284
|
+
if (nullableValue === null) {
|
|
264
285
|
return null;
|
|
286
|
+
}
|
|
265
287
|
const date = new Date(nullableValue);
|
|
266
288
|
if (Number.isNaN(date.getTime())) {
|
|
267
289
|
throw new AirtableTsError_1.AirtableTsError({
|
|
@@ -297,7 +319,7 @@ const stringArrayOrNull = {
|
|
|
297
319
|
multipleCollaborators: multipleCollaboratorsMapperPair,
|
|
298
320
|
multipleAttachments: multipleAttachmentsMapperPair,
|
|
299
321
|
multipleLookupValues: {
|
|
300
|
-
toAirtable
|
|
322
|
+
toAirtable() {
|
|
301
323
|
throw new AirtableTsError_1.AirtableTsError({
|
|
302
324
|
message: 'Lookup fields are read-only and cannot be modified.',
|
|
303
325
|
type: AirtableTsError_1.ErrorType.SCHEMA_VALIDATION,
|
|
@@ -306,7 +328,7 @@ const stringArrayOrNull = {
|
|
|
306
328
|
fromAirtable: coerce('multipleLookupValues', 'string[] | null'),
|
|
307
329
|
},
|
|
308
330
|
formula: {
|
|
309
|
-
toAirtable
|
|
331
|
+
toAirtable() {
|
|
310
332
|
throw new AirtableTsError_1.AirtableTsError({
|
|
311
333
|
message: 'Formula fields are read-only and cannot be modified.',
|
|
312
334
|
type: AirtableTsError_1.ErrorType.SCHEMA_VALIDATION,
|
|
@@ -326,7 +348,7 @@ exports.fieldMappers = {
|
|
|
326
348
|
...Object.fromEntries(Object.entries(stringOrNull['string | null']).map(([airtableType, nullablePair]) => {
|
|
327
349
|
return [airtableType, {
|
|
328
350
|
toAirtable: nullablePair.toAirtable,
|
|
329
|
-
fromAirtable
|
|
351
|
+
fromAirtable(value) {
|
|
330
352
|
const nullableValue = nullablePair.fromAirtable(value);
|
|
331
353
|
if (nullableValue === null && ['multipleRecordLinks', 'dateTime', 'createdTime', 'lastModifiedTime'].includes(airtableType)) {
|
|
332
354
|
throw new AirtableTsError_1.AirtableTsError({
|
|
@@ -354,7 +376,7 @@ exports.fieldMappers = {
|
|
|
354
376
|
...Object.fromEntries(Object.entries(numberOrNull['number | null']).map(([airtableType, nullablePair]) => {
|
|
355
377
|
return [airtableType, {
|
|
356
378
|
toAirtable: nullablePair.toAirtable,
|
|
357
|
-
fromAirtable
|
|
379
|
+
fromAirtable(value) {
|
|
358
380
|
const nullableValue = nullablePair.fromAirtable(value);
|
|
359
381
|
if (nullableValue === null) {
|
|
360
382
|
throw new AirtableTsError_1.AirtableTsError({
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { Item, Table, FromTsTypeString, TsTypeString } from './typeUtils';
|
|
1
|
+
import { type Item, type Table, type FromTsTypeString, type TsTypeString } from './typeUtils';
|
|
2
2
|
/**
|
|
3
3
|
* Maps a TS object (matching table.mappings) to another TS object (matching table.schema),
|
|
4
4
|
* mapping columns based on the table definition.
|
|
@@ -38,9 +38,10 @@ const mapRecordFieldNamesAirtableToTs = (table, tsRecord) => {
|
|
|
38
38
|
if (Array.isArray(mappingToAirtable)) {
|
|
39
39
|
return [outputFieldName, mappingToAirtable.map((airtableFieldName) => tsRecord[airtableFieldName])];
|
|
40
40
|
}
|
|
41
|
+
// eslint-disable-next-line @typescript-eslint/no-unnecessary-type-assertion
|
|
41
42
|
return [outputFieldName, tsRecord[mappingToAirtable]];
|
|
42
43
|
}));
|
|
43
|
-
return Object.assign(item, { id: tsRecord
|
|
44
|
+
return Object.assign(item, { id: tsRecord.id });
|
|
44
45
|
};
|
|
45
46
|
exports.mapRecordFieldNamesAirtableToTs = mapRecordFieldNamesAirtableToTs;
|
|
46
47
|
/**
|
|
@@ -88,13 +89,13 @@ const mapRecordFieldNamesTsToAirtable = (table, item) => {
|
|
|
88
89
|
}
|
|
89
90
|
// This should be unreachable because of our types
|
|
90
91
|
throw new AirtableTsError_1.AirtableTsError({
|
|
91
|
-
message: `Received null for non-nullable field '${outputFieldName}' (${mappingToAirtable}) with type '${tsType}' in table '${table.name}' (${table.tableId}). This should never happen in normal operation as it should be caught before this point.`,
|
|
92
|
+
message: `Received null for non-nullable field '${outputFieldName}' (${JSON.stringify(mappingToAirtable)}) with type '${tsType}' in table '${table.name}' (${table.tableId}). This should never happen in normal operation as it should be caught before this point.`,
|
|
92
93
|
type: AirtableTsError_1.ErrorType.SCHEMA_VALIDATION,
|
|
93
94
|
});
|
|
94
95
|
}
|
|
95
96
|
if (!Array.isArray(value)) {
|
|
96
97
|
throw new AirtableTsError_1.AirtableTsError({
|
|
97
|
-
message: `Expected an array for field '${outputFieldName}' (${mappingToAirtable}) in table '${table.name}' (${table.tableId}), but received ${typeof value}.`,
|
|
98
|
+
message: `Expected an array for field '${outputFieldName}' (${JSON.stringify(mappingToAirtable)}) in table '${table.name}' (${table.tableId}), but received ${typeof value}.`,
|
|
98
99
|
type: AirtableTsError_1.ErrorType.SCHEMA_VALIDATION,
|
|
99
100
|
});
|
|
100
101
|
}
|
|
@@ -1,17 +1,13 @@
|
|
|
1
|
-
import { FieldSet } from 'airtable';
|
|
2
|
-
import { AirtableRecord, AirtableTsTable } from '../types';
|
|
3
|
-
import { FromTsTypeString, Item, Table, TsTypeString } from './typeUtils';
|
|
1
|
+
import { type FieldSet } from 'airtable';
|
|
2
|
+
import { type AirtableRecord, type AirtableTsTable } from '../types';
|
|
3
|
+
import { type FromTsTypeString, type Item, type Table, type TsTypeString } from './typeUtils';
|
|
4
4
|
export declare const mapRecordFromAirtable: <T extends Item>(table: Table<T>, record: AirtableRecord) => T;
|
|
5
5
|
export declare const mapRecordToAirtable: <T extends Item>(table: Table<T>, item: Partial<T>, airtableTsTable: AirtableTsTable) => FieldSet;
|
|
6
6
|
export declare const visibleForTesting: {
|
|
7
|
-
mapRecordTypeAirtableToTs: <T extends {
|
|
8
|
-
[fieldNameOrId: string]: TsTypeString;
|
|
9
|
-
}>(table: Table<Item>, tsTypes: T, record: AirtableRecord) => { [F in keyof T]: FromTsTypeString<T[F]>; } & {
|
|
7
|
+
mapRecordTypeAirtableToTs: <T extends Record<string, TsTypeString>>(table: Table<Item>, tsTypes: T, record: AirtableRecord) => ({ [F in keyof T]: FromTsTypeString<T[F]>; } & {
|
|
10
8
|
id: string;
|
|
11
|
-
};
|
|
12
|
-
mapRecordTypeTsToAirtable: <
|
|
13
|
-
[fieldNameOrId: string]: TsTypeString;
|
|
14
|
-
}, R extends { [K in keyof T_1]?: FromTsTypeString<T_1[K]>; } & {
|
|
9
|
+
});
|
|
10
|
+
mapRecordTypeTsToAirtable: <T extends Record<string, TsTypeString>, R extends { [K in keyof T]?: FromTsTypeString<T[K]>; } & {
|
|
15
11
|
id?: string;
|
|
16
|
-
}>(table: Table<Item>, tsTypes:
|
|
12
|
+
}>(table: Table<Item>, tsTypes: T, tsRecord: R, airtableTsTable: AirtableTsTable) => FieldSet;
|
|
17
13
|
};
|
|
@@ -45,7 +45,6 @@ const getMapper = (tsType, airtableType) => {
|
|
|
45
45
|
const mapRecordTypeAirtableToTs = (table, tsTypes, record) => {
|
|
46
46
|
const item = {};
|
|
47
47
|
Object.entries(tsTypes).forEach(([fieldNameOrId, tsType]) => {
|
|
48
|
-
// eslint-disable-next-line no-underscore-dangle
|
|
49
48
|
const fieldDefinition = record._table.fields.find((f) => f.id === fieldNameOrId || f.name === fieldNameOrId);
|
|
50
49
|
if (!fieldDefinition) {
|
|
51
50
|
// This should not happen normally, as we should only be trying to map fields that are in the table definition
|
|
@@ -57,7 +56,6 @@ const mapRecordTypeAirtableToTs = (table, tsTypes, record) => {
|
|
|
57
56
|
const value = record.fields[fieldDefinition.name];
|
|
58
57
|
try {
|
|
59
58
|
const { fromAirtable } = getMapper(tsType, fieldDefinition.type);
|
|
60
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
61
59
|
item[fieldNameOrId] = fromAirtable(value);
|
|
62
60
|
}
|
|
63
61
|
catch (error) {
|
|
@@ -101,7 +99,6 @@ const mapRecordTypeTsToAirtable = (table, tsTypes, tsRecord, airtableTsTable) =>
|
|
|
101
99
|
suggestion: 'Ensure the value matches the expected type in your schema definition.',
|
|
102
100
|
});
|
|
103
101
|
}
|
|
104
|
-
// eslint-disable-next-line no-underscore-dangle
|
|
105
102
|
const fieldDefinition = airtableTsTable.fields.find((f) => f.id === fieldNameOrId || f.name === fieldNameOrId);
|
|
106
103
|
if (!fieldDefinition) {
|
|
107
104
|
const tsName = table.mappings ? Object.entries(table.mappings).find((e) => e[1] === fieldNameOrId)?.[0] : undefined;
|
|
@@ -113,7 +110,6 @@ const mapRecordTypeTsToAirtable = (table, tsTypes, tsRecord, airtableTsTable) =>
|
|
|
113
110
|
}
|
|
114
111
|
try {
|
|
115
112
|
const { toAirtable } = getMapper(tsType, fieldDefinition.type);
|
|
116
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
117
113
|
item[fieldNameOrId] = toAirtable(value);
|
|
118
114
|
}
|
|
119
115
|
catch (error) {
|
|
@@ -4,11 +4,11 @@ export type ToTsTypeString<T> = null extends T ? `${NonNullToString<T>} | null`
|
|
|
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
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 'aiText' | 'barcode' | 'singleCollaborator' | 'createdBy' | 'modifiedBy' | 'button' ? object : T extends 'multipleCollaborators' | 'multipleAttachments' ? object[] : T extends 'unknown' ? unknown : never);
|
|
7
|
-
|
|
7
|
+
type TypeDef = {
|
|
8
8
|
single: 'string' | 'number' | 'boolean';
|
|
9
9
|
array: boolean;
|
|
10
10
|
nullable: boolean;
|
|
11
|
-
}
|
|
11
|
+
};
|
|
12
12
|
export declare const parseType: (t: TsTypeString) => TypeDef;
|
|
13
13
|
/**
|
|
14
14
|
* Verifies whether the given value is assignable to the given type
|
|
@@ -43,11 +43,11 @@ export declare const matchesType: (value: unknown, tsType: TsTypeString) => bool
|
|
|
43
43
|
*/
|
|
44
44
|
export declare const airtableFieldNameTsTypes: <T extends Item>(table: Table<T>) => Record<string, TsTypeString>;
|
|
45
45
|
export type MappingValue<T> = T extends unknown[] ? string | string[] : string;
|
|
46
|
-
export
|
|
46
|
+
export type Item = {
|
|
47
47
|
/** Represents the Airtable record id, @example "rec1234" */
|
|
48
48
|
id: string;
|
|
49
|
-
}
|
|
50
|
-
export
|
|
49
|
+
};
|
|
50
|
+
export type Table<T extends Item> = {
|
|
51
51
|
/** A simple name for the entities in this table, to be used in error messages @example "person" */
|
|
52
52
|
name: string;
|
|
53
53
|
/** The base id for this table. You can get this from the URL when accessing the table in the web UI. @example "app1234" */
|
|
@@ -59,19 +59,19 @@ export interface Table<T extends Item> {
|
|
|
59
59
|
[k in keyof Omit<T, 'id'>]: ToTsTypeString<T[k]>;
|
|
60
60
|
};
|
|
61
61
|
/**
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
62
|
+
* Optional name mappings. This allows you to detach the schema names from the names you want to use in your code.
|
|
63
|
+
* @example
|
|
64
|
+
* export const personTable: Table<{ id: string, firstName: string }> = {
|
|
65
|
+
* name: 'person', baseId: 'app1234', tableId: 'tbl1234',
|
|
66
|
+
* schema: { firstName: 'string' },
|
|
67
|
+
* // The field is named '[core] First Name' in the base. If this ever changes, we just need to update it here.
|
|
68
|
+
* mappings: { firstName: '[core] First Name' },
|
|
69
|
+
* };
|
|
70
|
+
* const people = await db.scan(studentTable);
|
|
71
|
+
* const firstPersonsFirstName = people[0].firstName;
|
|
72
|
+
* */
|
|
73
73
|
mappings?: {
|
|
74
74
|
[k in keyof Omit<T, 'id'>]: MappingValue<T[k]>;
|
|
75
75
|
};
|
|
76
|
-
}
|
|
76
|
+
};
|
|
77
77
|
export {};
|
|
@@ -112,7 +112,7 @@ const airtableFieldNameTsTypes = (table) => {
|
|
|
112
112
|
return [[mappingToAirtable, tsType]];
|
|
113
113
|
}
|
|
114
114
|
catch (error) {
|
|
115
|
-
throw (0, AirtableTsError_1.prependError)(error, `Error with field ${
|
|
115
|
+
throw (0, AirtableTsError_1.prependError)(error, `Error with field ${String(outputFieldName)} (${JSON.stringify(mappingToAirtable)})`);
|
|
116
116
|
}
|
|
117
117
|
}).flat(1));
|
|
118
118
|
};
|
package/dist/types.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { FieldSet, Table as AirtableSdkTable, Record as AirtableSdkRecord, AirtableOptions } from 'airtable';
|
|
2
|
-
import { QueryParams } from 'airtable/lib/query_params';
|
|
3
|
-
import { Item, Table } from './mapping/typeUtils';
|
|
2
|
+
import { type QueryParams } from 'airtable/lib/query_params';
|
|
3
|
+
import { type Item, type Table } from './mapping/typeUtils';
|
|
4
4
|
export type AirtableRecord = Omit<AirtableSdkRecord<FieldSet>, '_table'> & {
|
|
5
5
|
_table: AirtableTsTable;
|
|
6
6
|
};
|
|
@@ -13,10 +13,10 @@ export type AirtableTsTable<T extends Item = Item> = AirtableSdkTable<FieldSet>
|
|
|
13
13
|
tsDefinition: Table<T>;
|
|
14
14
|
__brand?: T;
|
|
15
15
|
};
|
|
16
|
-
|
|
16
|
+
type AirtableTsSpecificOptions = {
|
|
17
17
|
/** The Airtable base schema is used to determine the appropriate type mapper for the field type (for example converting a number to a string representing a date is different to converting a number to a singleLineText). For performance reasons, airtable-ts caches base schemas so we don't refetch it for every request. Note that we always still do validation against the expected type at runtime so the library is always type-safe. @default 120_000 */
|
|
18
18
|
baseSchemaCacheDurationMs?: number;
|
|
19
|
-
}
|
|
19
|
+
};
|
|
20
20
|
export type AirtableTsOptions = AirtableOptions & AirtableTsSpecificOptions;
|
|
21
21
|
export type CompleteAirtableTsOptions = AirtableTsOptions & Required<AirtableTsSpecificOptions>;
|
|
22
22
|
export type ScanParams = Omit<QueryParams<unknown>, 'fields' | 'cellFormat' | 'method' | 'returnFieldsByFieldId' | 'pageSize' | 'offset'>;
|
|
@@ -36,7 +36,6 @@ function wrapAirtableError(error) {
|
|
|
36
36
|
}
|
|
37
37
|
const wrapToCatchAirtableErrors = (c) => {
|
|
38
38
|
// Cast to any to bypass TypeScript's type checking, as unfortunately this is too funky for TypeScript
|
|
39
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
40
39
|
const prototype = c.prototype;
|
|
41
40
|
const methods = Object.getOwnPropertyNames(prototype).filter((prop) => {
|
|
42
41
|
return prop !== 'constructor' && typeof prototype[prop] === 'function';
|
|
@@ -44,7 +43,6 @@ const wrapToCatchAirtableErrors = (c) => {
|
|
|
44
43
|
methods.forEach((method) => {
|
|
45
44
|
const original = prototype[method];
|
|
46
45
|
if (typeof original === 'function') {
|
|
47
|
-
// eslint-disable-next-line func-names
|
|
48
46
|
prototype[method] = function (...args) {
|
|
49
47
|
try {
|
|
50
48
|
const result = original.apply(this, args);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "airtable-ts",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.6.0",
|
|
4
4
|
"description": "A type-safe Airtable SDK",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "Adam Jones (domdomegg)",
|
|
@@ -16,26 +16,23 @@
|
|
|
16
16
|
"scripts": {
|
|
17
17
|
"test": "vitest run",
|
|
18
18
|
"test:watch": "vitest --watch",
|
|
19
|
-
"lint": "eslint
|
|
19
|
+
"lint": "eslint",
|
|
20
20
|
"clean": "rm -rf dist",
|
|
21
21
|
"build": "tsc --project tsconfig.build.json",
|
|
22
22
|
"prepublishOnly": "npm run clean && npm run build"
|
|
23
23
|
},
|
|
24
|
-
"devDependencies": {
|
|
25
|
-
"@tsconfig/node-lts": "^20.1.0",
|
|
26
|
-
"@tsconfig/strictest": "^2.0.2",
|
|
27
|
-
"eslint": "^8.56.0",
|
|
28
|
-
"eslint-config-domdomegg": "^1.2.3",
|
|
29
|
-
"typescript": "^5.3.3",
|
|
30
|
-
"vitest": "^1.0.4"
|
|
31
|
-
},
|
|
32
|
-
"eslintConfig": {
|
|
33
|
-
"extends": [
|
|
34
|
-
"eslint-config-domdomegg"
|
|
35
|
-
]
|
|
36
|
-
},
|
|
37
24
|
"dependencies": {
|
|
38
25
|
"airtable": "^0.12.2",
|
|
39
26
|
"axios": "^1.6.8"
|
|
27
|
+
},
|
|
28
|
+
"devDependencies": {
|
|
29
|
+
"@tsconfig/node-lts": "^22.0.1",
|
|
30
|
+
"@tsconfig/strictest": "^2.0.2",
|
|
31
|
+
"@types/node": "^22.15.3",
|
|
32
|
+
"eslint": "^9.25.1",
|
|
33
|
+
"eslint-config-domdomegg": "^2.0.8",
|
|
34
|
+
"tsconfig-domdomegg": "^1.0.0",
|
|
35
|
+
"typescript": "^5.8.3",
|
|
36
|
+
"vitest": "^3.1.2"
|
|
40
37
|
}
|
|
41
38
|
}
|