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.
@@ -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[]>;
@@ -19,7 +19,7 @@ class AirtableTs {
19
19
  this.options = {
20
20
  ...airtable_1.default.default_config(),
21
21
  ...options,
22
- baseSchemaCacheDurationMs: options.baseSchemaCacheDurationMs ?? 120000,
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(data.id, (0, recordMapper_1.mapRecordToAirtable)(table, withoutId, airtableTsTable));
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) {
@@ -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,4 +1,4 @@
1
- import { Item, Table } from './mapping/typeUtils';
1
+ import { type Item, type Table } from './mapping/typeUtils';
2
2
  /**
3
3
  * In theory, this should never catch stuff because our type mapping logic should
4
4
  * verify the types are compatible.
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.assertMatchesSchema = void 0;
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 (mod) {
19
- if (mod && mod.__esModule) return mod;
20
- var result = {};
21
- if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
22
- __setModuleDefault(result, mod);
23
- return result;
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,
@@ -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: (value) => {
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: (value) => {
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: (obj) => {
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: (obj) => {
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: (obj) => {
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: (obj) => {
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: (obj) => {
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: (obj) => {
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: (value) => {
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: (value) => {
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: (value) => {
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: (value) => {
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: (value) => {
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: (value) => {
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['id'] });
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: <T_1 extends {
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: T_1, tsRecord: R, airtableTsTable: AirtableTsTable) => FieldSet;
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
- interface TypeDef {
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 interface Item {
46
+ export type Item = {
47
47
  /** Represents the Airtable record id, @example "rec1234" */
48
48
  id: string;
49
- }
50
- export interface Table<T extends Item> {
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
- * 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
- * */
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 ${JSON.stringify(outputFieldName)} (${mappingToAirtable})`);
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
- interface AirtableTsSpecificOptions {
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.5.0",
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 --ext .js,.jsx,.ts,.tsx .",
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
  }