airtable-ts-codegen 2.1.0 → 2.2.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/README.md CHANGED
@@ -22,6 +22,18 @@ AIRTABLE_API_KEY=pat1234.abcd AIRTABLE_BASE_ID=app1234 AIRTABLE_VIEW_IDS=viw1234
22
22
 
23
23
  This will output a file `app1234.ts` that exports table definitions with only the fields visible in the specified views.
24
24
 
25
+ ### Attachment type (unstable)
26
+
27
+ By default, `multipleAttachments` fields are typed as `string[]` (array of URLs). To get full attachment metadata (id, filename, size, type, etc.), set `UNSTABLE_AIRTABLE_ATTACHMENT_TYPE=Attachment`:
28
+
29
+ ```sh
30
+ AIRTABLE_API_KEY=pat1234.abcd AIRTABLE_BASE_ID=app1234 UNSTABLE_AIRTABLE_ATTACHMENT_TYPE=Attachment npx airtable-ts-codegen
31
+ ```
32
+
33
+ This will generate `Attachment[]` instead of `string[]` for attachment fields, giving you access to the full attachment object with properties like `id`, `url`, `filename`, `size`, `type`, `width`, `height`, and `thumbnails`.
34
+
35
+ > **Note:** This option is unstable and may change or be removed in future versions.
36
+
25
37
  <details>
26
38
  <summary>Example generated file</summary>
27
39
 
@@ -35,6 +35,8 @@ export declare const tablesMeta: {
35
35
  isValid?: never;
36
36
  formula?: never;
37
37
  result?: never;
38
+ recordLinkFieldId?: never;
39
+ fieldIdInLinkedTable?: never;
38
40
  };
39
41
  id: string;
40
42
  name: string;
@@ -58,6 +60,8 @@ export declare const tablesMeta: {
58
60
  isValid?: never;
59
61
  formula?: never;
60
62
  result?: never;
63
+ recordLinkFieldId?: never;
64
+ fieldIdInLinkedTable?: never;
61
65
  };
62
66
  id: string;
63
67
  name: string;
@@ -85,6 +89,8 @@ export declare const tablesMeta: {
85
89
  isValid?: never;
86
90
  formula?: never;
87
91
  result?: never;
92
+ recordLinkFieldId?: never;
93
+ fieldIdInLinkedTable?: never;
88
94
  };
89
95
  id: string;
90
96
  name: string;
@@ -108,6 +114,8 @@ export declare const tablesMeta: {
108
114
  isValid?: never;
109
115
  formula?: never;
110
116
  result?: never;
117
+ recordLinkFieldId?: never;
118
+ fieldIdInLinkedTable?: never;
111
119
  };
112
120
  id: string;
113
121
  name: string;
@@ -134,6 +142,8 @@ export declare const tablesMeta: {
134
142
  isValid?: never;
135
143
  formula?: never;
136
144
  result?: never;
145
+ recordLinkFieldId?: never;
146
+ fieldIdInLinkedTable?: never;
137
147
  };
138
148
  id: string;
139
149
  name: string;
@@ -163,6 +173,8 @@ export declare const tablesMeta: {
163
173
  isValid?: never;
164
174
  formula?: never;
165
175
  result?: never;
176
+ recordLinkFieldId?: never;
177
+ fieldIdInLinkedTable?: never;
166
178
  };
167
179
  id: string;
168
180
  name: string;
@@ -186,6 +198,8 @@ export declare const tablesMeta: {
186
198
  isValid?: never;
187
199
  formula?: never;
188
200
  result?: never;
201
+ recordLinkFieldId?: never;
202
+ fieldIdInLinkedTable?: never;
189
203
  };
190
204
  id: string;
191
205
  name: string;
@@ -209,6 +223,8 @@ export declare const tablesMeta: {
209
223
  isValid?: never;
210
224
  formula?: never;
211
225
  result?: never;
226
+ recordLinkFieldId?: never;
227
+ fieldIdInLinkedTable?: never;
212
228
  };
213
229
  id: string;
214
230
  name: string;
@@ -232,6 +248,8 @@ export declare const tablesMeta: {
232
248
  isValid?: never;
233
249
  formula?: never;
234
250
  result?: never;
251
+ recordLinkFieldId?: never;
252
+ fieldIdInLinkedTable?: never;
235
253
  };
236
254
  id: string;
237
255
  name: string;
@@ -255,6 +273,8 @@ export declare const tablesMeta: {
255
273
  isValid?: never;
256
274
  formula?: never;
257
275
  result?: never;
276
+ recordLinkFieldId?: never;
277
+ fieldIdInLinkedTable?: never;
258
278
  };
259
279
  id: string;
260
280
  name: string;
@@ -281,6 +301,8 @@ export declare const tablesMeta: {
281
301
  symbol?: never;
282
302
  durationFormat?: never;
283
303
  max?: never;
304
+ recordLinkFieldId?: never;
305
+ fieldIdInLinkedTable?: never;
284
306
  };
285
307
  id: string;
286
308
  name: string;
@@ -300,6 +322,8 @@ export declare const tablesMeta: {
300
322
  format: string;
301
323
  };
302
324
  timeZone: string;
325
+ precision?: never;
326
+ choices?: never;
303
327
  };
304
328
  };
305
329
  choices?: never;
@@ -317,6 +341,8 @@ export declare const tablesMeta: {
317
341
  max?: never;
318
342
  isValid?: never;
319
343
  formula?: never;
344
+ recordLinkFieldId?: never;
345
+ fieldIdInLinkedTable?: never;
320
346
  };
321
347
  id: string;
322
348
  name: string;
@@ -338,6 +364,8 @@ export declare const tablesMeta: {
338
364
  format: string;
339
365
  };
340
366
  timeZone: string;
367
+ precision?: never;
368
+ choices?: never;
341
369
  };
342
370
  };
343
371
  choices?: never;
@@ -353,6 +381,104 @@ export declare const tablesMeta: {
353
381
  durationFormat?: never;
354
382
  max?: never;
355
383
  formula?: never;
384
+ recordLinkFieldId?: never;
385
+ fieldIdInLinkedTable?: never;
386
+ };
387
+ id: string;
388
+ name: string;
389
+ description?: never;
390
+ } | {
391
+ type: string;
392
+ options: {
393
+ isValid: boolean;
394
+ recordLinkFieldId: string;
395
+ fieldIdInLinkedTable: string;
396
+ result: {
397
+ type: string;
398
+ options?: never;
399
+ };
400
+ choices?: never;
401
+ isReversed?: never;
402
+ referencedFieldIds?: never;
403
+ prompt?: never;
404
+ icon?: never;
405
+ color?: never;
406
+ dateFormat?: never;
407
+ timeFormat?: never;
408
+ timeZone?: never;
409
+ precision?: never;
410
+ symbol?: never;
411
+ durationFormat?: never;
412
+ max?: never;
413
+ formula?: never;
414
+ };
415
+ id: string;
416
+ name: string;
417
+ description?: never;
418
+ } | {
419
+ type: string;
420
+ options: {
421
+ isValid: boolean;
422
+ recordLinkFieldId: string;
423
+ fieldIdInLinkedTable: string;
424
+ result: {
425
+ type: string;
426
+ options: {
427
+ precision: number;
428
+ dateFormat?: never;
429
+ timeFormat?: never;
430
+ timeZone?: never;
431
+ choices?: never;
432
+ };
433
+ };
434
+ choices?: never;
435
+ isReversed?: never;
436
+ referencedFieldIds?: never;
437
+ prompt?: never;
438
+ icon?: never;
439
+ color?: never;
440
+ dateFormat?: never;
441
+ timeFormat?: never;
442
+ timeZone?: never;
443
+ precision?: never;
444
+ symbol?: never;
445
+ durationFormat?: never;
446
+ max?: never;
447
+ formula?: never;
448
+ };
449
+ id: string;
450
+ name: string;
451
+ description?: never;
452
+ } | {
453
+ type: string;
454
+ options: {
455
+ isValid: boolean;
456
+ recordLinkFieldId: string;
457
+ fieldIdInLinkedTable: string;
458
+ result: {
459
+ type: string;
460
+ options: {
461
+ choices: never[];
462
+ dateFormat?: never;
463
+ timeFormat?: never;
464
+ timeZone?: never;
465
+ precision?: never;
466
+ };
467
+ };
468
+ choices?: never;
469
+ isReversed?: never;
470
+ referencedFieldIds?: never;
471
+ prompt?: never;
472
+ icon?: never;
473
+ color?: never;
474
+ dateFormat?: never;
475
+ timeFormat?: never;
476
+ timeZone?: never;
477
+ precision?: never;
478
+ symbol?: never;
479
+ durationFormat?: never;
480
+ max?: never;
481
+ formula?: never;
356
482
  };
357
483
  id: string;
358
484
  name: string;
@@ -63,7 +63,22 @@ exports.tablesMeta = {
63
63
  { type: 'createdBy', id: 'fldDcRoVGJXIhVSAi', name: 'Created By' },
64
64
  { type: 'autoNumber', id: 'fldGM8wIYk3G8WMiE', name: 'ID' },
65
65
  { type: 'barcode', id: 'fldMPs1eLH8X5bWeS', name: 'Barcode' },
66
- { type: 'button', id: 'fldAG3LTbJRwQVEvS', name: 'Button' }],
66
+ { type: 'button', id: 'fldAG3LTbJRwQVEvS', name: 'Button' },
67
+ {
68
+ type: 'multipleLookupValues', options: {
69
+ isValid: true, recordLinkFieldId: 'fldLinked', fieldIdInLinkedTable: 'fldName', result: { type: 'singleLineText' },
70
+ }, id: 'fldLookupText', name: 'Lookup Text',
71
+ },
72
+ {
73
+ type: 'multipleLookupValues', options: {
74
+ isValid: true, recordLinkFieldId: 'fldLinked', fieldIdInLinkedTable: 'fldNumber', result: { type: 'number', options: { precision: 0 } },
75
+ }, id: 'fldLookupNumber', name: 'Lookup Number',
76
+ },
77
+ {
78
+ type: 'multipleLookupValues', options: {
79
+ isValid: true, recordLinkFieldId: 'fldLinked', fieldIdInLinkedTable: 'fldTags', result: { type: 'multipleSelects', options: { choices: [] } },
80
+ }, id: 'fldLookupTags', name: 'Lookup Tags',
81
+ }],
67
82
  views: [],
68
83
  }],
69
84
  };
package/dist/cli.js CHANGED
@@ -13,7 +13,11 @@ if (!baseId) {
13
13
  throw new Error('No Airtable base id set. Make sure the AIRTABLE_BASE_ID environment variable is set.');
14
14
  }
15
15
  const viewIds = process.env.AIRTABLE_VIEW_IDS;
16
- const config = { apiKey, baseId, ...(viewIds && { viewIds: viewIds.split(',') }) };
16
+ const unstableAttachmentTypeEnv = process.env.UNSTABLE_AIRTABLE_ATTACHMENT_TYPE;
17
+ const unstable_attachmentType = unstableAttachmentTypeEnv === 'Attachment' ? 'Attachment' : 'string';
18
+ const config = {
19
+ apiKey, baseId, ...(viewIds && { viewIds: viewIds.split(',') }), unstable_attachmentType,
20
+ };
17
21
  const generateCode = async () => {
18
22
  console.log(`Generating TypeScript definitions for base ${baseId}${viewIds ? ` with views ${viewIds}` : ''}...`);
19
23
  return (0, _1.main)(config);
package/dist/index.d.ts CHANGED
@@ -5,5 +5,13 @@ export type Config = {
5
5
  endpointUrl?: string;
6
6
  requestTimeout?: number;
7
7
  customHeaders?: Record<string, string | number | boolean>;
8
+ /**
9
+ * Type to use for multipleAttachments fields.
10
+ * - 'string': generates `string[]` (array of URLs only)
11
+ * - 'Attachment': generates `Attachment[]` (full metadata including id, filename, size, etc.)
12
+ * @default 'string'
13
+ * @unstable This option may change or be removed in future versions.
14
+ */
15
+ unstable_attachmentType?: 'string' | 'Attachment';
8
16
  };
9
17
  export declare const main: (config: Config) => Promise<string>;
package/dist/index.js CHANGED
@@ -11,10 +11,15 @@ const view_1 = require("./view");
11
11
  const main = async (config) => {
12
12
  const baseSchema = await (0, getBaseSchema_1.getBaseSchema)(config.baseId, config);
13
13
  const filteredBaseSchema = await (0, view_1.filterBaseSchemaByView)(baseSchema, config);
14
+ // Determine if we need to import Attachment type
15
+ const useAttachmentType = config.unstable_attachmentType === 'Attachment';
16
+ const importTypes = useAttachmentType
17
+ ? 'import type { Attachment, Item, Table } from \'airtable-ts\';'
18
+ : 'import type { Item, Table } from \'airtable-ts\';';
14
19
  return [
15
20
  '/* DO NOT EDIT: this file was automatically generated by airtable-ts-codegen */',
16
21
  '/* eslint-disable */',
17
- 'import type { Item, Table } from \'airtable-ts\';',
22
+ importTypes,
18
23
  '',
19
24
  filteredBaseSchema.map((tableSchema) => generateCode(config, tableSchema)).join('\n\n'),
20
25
  ].join('\n');
@@ -45,11 +50,12 @@ const generateCode = (config, tableSchema) => {
45
50
  const itemNameRaw = (0, escapeIdentifier_1.escapeIdentifier)((0, recase_1.recase)(null, 'pascal', tableSchema.name));
46
51
  const itemName = /.s$/.test(itemNameRaw) ? itemNameRaw.slice(0, itemNameRaw.length - 1) : itemNameRaw;
47
52
  const tableName = (0, escapeIdentifier_1.escapeIdentifier)(`${(0, recase_1.recase)(null, 'camel', tableSchema.name)}Table`);
53
+ const jsTypeOptions = { attachmentType: config.unstable_attachmentType };
48
54
  const fields = tableSchema.fields.map((f) => ({
49
55
  ...f,
50
56
  originalName: f.name,
51
57
  jsName: (0, escapeIdentifier_1.escapeIdentifier)((0, recase_1.recase)(null, 'camel', (0, escapeIdentifier_1.escapeIdentifier)(f.name))),
52
- jsType: (0, jsTypeForAirtableType_1.jsTypeForAirtableType)(f),
58
+ jsType: (0, jsTypeForAirtableType_1.jsTypeForAirtableType)(f, jsTypeOptions),
53
59
  }));
54
60
  return `export interface ${itemName} extends Item {
55
61
  id: string,${fields.map(generateInterfaceEntry).join('')}
@@ -1,7 +1,11 @@
1
1
  import { type FieldSchema } from './getBaseSchema';
2
+ export type JsTypeOptions = {
3
+ /** Type to use for multipleAttachments fields */
4
+ attachmentType?: 'string' | 'Attachment' | undefined;
5
+ };
2
6
  /**
3
7
  * Returns the corresponding Typescript type for the given Airtable field type.
4
8
  *
5
9
  * Unsupported fields return `null` and will be filtered out by the caller.
6
10
  */
7
- export declare const jsTypeForAirtableType: (field: FieldSchema) => string | null;
11
+ export declare const jsTypeForAirtableType: (field: FieldSchema, options?: JsTypeOptions) => string | null;
@@ -6,7 +6,7 @@ exports.jsTypeForAirtableType = void 0;
6
6
  *
7
7
  * Unsupported fields return `null` and will be filtered out by the caller.
8
8
  */
9
- const jsTypeForAirtableType = (field) => {
9
+ const jsTypeForAirtableType = (field, options = {}) => {
10
10
  switch (field.type) {
11
11
  case 'url':
12
12
  case 'email':
@@ -25,6 +25,7 @@ const jsTypeForAirtableType = (field) => {
25
25
  case 'createdBy':
26
26
  return 'string';
27
27
  case 'multipleAttachments':
28
+ return options.attachmentType === 'Attachment' ? 'Attachment[]' : 'string[]';
28
29
  case 'multipleCollaborators':
29
30
  case 'multipleRecordLinks':
30
31
  case 'multipleSelects':
@@ -46,15 +47,33 @@ const jsTypeForAirtableType = (field) => {
46
47
  return 'number'; // Unix timestamp in seconds
47
48
  case 'checkbox':
48
49
  return 'boolean';
49
- case 'lookup':
50
50
  case 'multipleLookupValues':
51
+ if (field.options
52
+ && 'result' in field.options
53
+ && typeof field.options.result === 'object'
54
+ && field.options.result !== null) {
55
+ const innerType = (0, exports.jsTypeForAirtableType)(field.options.result, options);
56
+ if (innerType === null) {
57
+ return null;
58
+ }
59
+ // multipleLookupValues always returns an array
60
+ // Strip nullability from inner type since the array itself represents empty state
61
+ const baseType = innerType.replace(/ \| null$/, '').replace(/^null \| /, '');
62
+ // If inner type is already an array, Airtable flattens lookup results
63
+ if (baseType.endsWith('[]')) {
64
+ return baseType;
65
+ }
66
+ return `${baseType}[]`;
67
+ }
68
+ throw new Error(`Invalid ${field.type} field (no options.result): ${field.id}`);
69
+ case 'lookup':
51
70
  case 'rollup':
52
71
  case 'formula':
53
72
  if (field.options
54
73
  && 'result' in field.options
55
74
  && typeof field.options.result === 'object'
56
75
  && field.options.result !== null) {
57
- const innerType = (0, exports.jsTypeForAirtableType)(field.options.result);
76
+ const innerType = (0, exports.jsTypeForAirtableType)(field.options.result, options);
58
77
  if (innerType === null) {
59
78
  return null;
60
79
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "airtable-ts-codegen",
3
- "version": "2.1.0",
3
+ "version": "2.2.0",
4
4
  "description": "Autogenerate TypeScript definitions for your Airtable base",
5
5
  "license": "MIT",
6
6
  "author": "Adam Jones (domdomegg)",