airtable-ts-codegen 1.3.0 → 2.0.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.
@@ -0,0 +1,362 @@
1
+ /**
2
+ * Dump of an airtable table containing all field types.
3
+ */
4
+ export declare const tablesMeta: {
5
+ tables: {
6
+ id: string;
7
+ name: string;
8
+ primaryFieldId: string;
9
+ fields: ({
10
+ type: string;
11
+ id: string;
12
+ name: string;
13
+ options?: never;
14
+ description?: never;
15
+ } | {
16
+ type: string;
17
+ options: {
18
+ choices: {
19
+ id: string;
20
+ name: string;
21
+ color: string;
22
+ }[];
23
+ isReversed?: never;
24
+ referencedFieldIds?: never;
25
+ prompt?: never;
26
+ icon?: never;
27
+ color?: never;
28
+ dateFormat?: never;
29
+ timeFormat?: never;
30
+ timeZone?: never;
31
+ precision?: never;
32
+ symbol?: never;
33
+ durationFormat?: never;
34
+ max?: never;
35
+ isValid?: never;
36
+ formula?: never;
37
+ result?: never;
38
+ };
39
+ id: string;
40
+ name: string;
41
+ description?: never;
42
+ } | {
43
+ type: string;
44
+ options: {
45
+ isReversed: boolean;
46
+ choices?: never;
47
+ referencedFieldIds?: never;
48
+ prompt?: never;
49
+ icon?: never;
50
+ color?: never;
51
+ dateFormat?: never;
52
+ timeFormat?: never;
53
+ timeZone?: never;
54
+ precision?: never;
55
+ symbol?: never;
56
+ durationFormat?: never;
57
+ max?: never;
58
+ isValid?: never;
59
+ formula?: never;
60
+ result?: never;
61
+ };
62
+ id: string;
63
+ name: string;
64
+ description?: never;
65
+ } | {
66
+ type: string;
67
+ options: {
68
+ referencedFieldIds: string[];
69
+ prompt: (string | {
70
+ field: {
71
+ fieldId: string;
72
+ };
73
+ })[];
74
+ choices?: never;
75
+ isReversed?: never;
76
+ icon?: never;
77
+ color?: never;
78
+ dateFormat?: never;
79
+ timeFormat?: never;
80
+ timeZone?: never;
81
+ precision?: never;
82
+ symbol?: never;
83
+ durationFormat?: never;
84
+ max?: never;
85
+ isValid?: never;
86
+ formula?: never;
87
+ result?: never;
88
+ };
89
+ id: string;
90
+ name: string;
91
+ description: string;
92
+ } | {
93
+ type: string;
94
+ options: {
95
+ icon: string;
96
+ color: string;
97
+ choices?: never;
98
+ isReversed?: never;
99
+ referencedFieldIds?: never;
100
+ prompt?: never;
101
+ dateFormat?: never;
102
+ timeFormat?: never;
103
+ timeZone?: never;
104
+ precision?: never;
105
+ symbol?: never;
106
+ durationFormat?: never;
107
+ max?: never;
108
+ isValid?: never;
109
+ formula?: never;
110
+ result?: never;
111
+ };
112
+ id: string;
113
+ name: string;
114
+ description?: never;
115
+ } | {
116
+ type: string;
117
+ options: {
118
+ dateFormat: {
119
+ name: string;
120
+ format: string;
121
+ };
122
+ choices?: never;
123
+ isReversed?: never;
124
+ referencedFieldIds?: never;
125
+ prompt?: never;
126
+ icon?: never;
127
+ color?: never;
128
+ timeFormat?: never;
129
+ timeZone?: never;
130
+ precision?: never;
131
+ symbol?: never;
132
+ durationFormat?: never;
133
+ max?: never;
134
+ isValid?: never;
135
+ formula?: never;
136
+ result?: never;
137
+ };
138
+ id: string;
139
+ name: string;
140
+ description?: never;
141
+ } | {
142
+ type: string;
143
+ options: {
144
+ dateFormat: {
145
+ name: string;
146
+ format: string;
147
+ };
148
+ timeFormat: {
149
+ name: string;
150
+ format: string;
151
+ };
152
+ timeZone: string;
153
+ choices?: never;
154
+ isReversed?: never;
155
+ referencedFieldIds?: never;
156
+ prompt?: never;
157
+ icon?: never;
158
+ color?: never;
159
+ precision?: never;
160
+ symbol?: never;
161
+ durationFormat?: never;
162
+ max?: never;
163
+ isValid?: never;
164
+ formula?: never;
165
+ result?: never;
166
+ };
167
+ id: string;
168
+ name: string;
169
+ description?: never;
170
+ } | {
171
+ type: string;
172
+ options: {
173
+ precision: number;
174
+ choices?: never;
175
+ isReversed?: never;
176
+ referencedFieldIds?: never;
177
+ prompt?: never;
178
+ icon?: never;
179
+ color?: never;
180
+ dateFormat?: never;
181
+ timeFormat?: never;
182
+ timeZone?: never;
183
+ symbol?: never;
184
+ durationFormat?: never;
185
+ max?: never;
186
+ isValid?: never;
187
+ formula?: never;
188
+ result?: never;
189
+ };
190
+ id: string;
191
+ name: string;
192
+ description?: never;
193
+ } | {
194
+ type: string;
195
+ options: {
196
+ precision: number;
197
+ symbol: string;
198
+ choices?: never;
199
+ isReversed?: never;
200
+ referencedFieldIds?: never;
201
+ prompt?: never;
202
+ icon?: never;
203
+ color?: never;
204
+ dateFormat?: never;
205
+ timeFormat?: never;
206
+ timeZone?: never;
207
+ durationFormat?: never;
208
+ max?: never;
209
+ isValid?: never;
210
+ formula?: never;
211
+ result?: never;
212
+ };
213
+ id: string;
214
+ name: string;
215
+ description?: never;
216
+ } | {
217
+ type: string;
218
+ options: {
219
+ durationFormat: string;
220
+ choices?: never;
221
+ isReversed?: never;
222
+ referencedFieldIds?: never;
223
+ prompt?: never;
224
+ icon?: never;
225
+ color?: never;
226
+ dateFormat?: never;
227
+ timeFormat?: never;
228
+ timeZone?: never;
229
+ precision?: never;
230
+ symbol?: never;
231
+ max?: never;
232
+ isValid?: never;
233
+ formula?: never;
234
+ result?: never;
235
+ };
236
+ id: string;
237
+ name: string;
238
+ description?: never;
239
+ } | {
240
+ type: string;
241
+ options: {
242
+ icon: string;
243
+ max: number;
244
+ color: string;
245
+ choices?: never;
246
+ isReversed?: never;
247
+ referencedFieldIds?: never;
248
+ prompt?: never;
249
+ dateFormat?: never;
250
+ timeFormat?: never;
251
+ timeZone?: never;
252
+ precision?: never;
253
+ symbol?: never;
254
+ durationFormat?: never;
255
+ isValid?: never;
256
+ formula?: never;
257
+ result?: never;
258
+ };
259
+ id: string;
260
+ name: string;
261
+ description?: never;
262
+ } | {
263
+ type: string;
264
+ options: {
265
+ isValid: boolean;
266
+ formula: string;
267
+ referencedFieldIds: string[];
268
+ result: {
269
+ type: string;
270
+ options?: never;
271
+ };
272
+ choices?: never;
273
+ isReversed?: never;
274
+ prompt?: never;
275
+ icon?: never;
276
+ color?: never;
277
+ dateFormat?: never;
278
+ timeFormat?: never;
279
+ timeZone?: never;
280
+ precision?: never;
281
+ symbol?: never;
282
+ durationFormat?: never;
283
+ max?: never;
284
+ };
285
+ id: string;
286
+ name: string;
287
+ description?: never;
288
+ } | {
289
+ type: string;
290
+ options: {
291
+ result: {
292
+ type: string;
293
+ options: {
294
+ dateFormat: {
295
+ name: string;
296
+ format: string;
297
+ };
298
+ timeFormat: {
299
+ name: string;
300
+ format: string;
301
+ };
302
+ timeZone: string;
303
+ };
304
+ };
305
+ choices?: never;
306
+ isReversed?: never;
307
+ referencedFieldIds?: never;
308
+ prompt?: never;
309
+ icon?: never;
310
+ color?: never;
311
+ dateFormat?: never;
312
+ timeFormat?: never;
313
+ timeZone?: never;
314
+ precision?: never;
315
+ symbol?: never;
316
+ durationFormat?: never;
317
+ max?: never;
318
+ isValid?: never;
319
+ formula?: never;
320
+ };
321
+ id: string;
322
+ name: string;
323
+ description?: never;
324
+ } | {
325
+ type: string;
326
+ options: {
327
+ isValid: boolean;
328
+ referencedFieldIds: never[];
329
+ result: {
330
+ type: string;
331
+ options: {
332
+ dateFormat: {
333
+ name: string;
334
+ format: string;
335
+ };
336
+ timeFormat: {
337
+ name: string;
338
+ format: string;
339
+ };
340
+ timeZone: string;
341
+ };
342
+ };
343
+ choices?: never;
344
+ isReversed?: never;
345
+ prompt?: never;
346
+ icon?: never;
347
+ color?: never;
348
+ dateFormat?: never;
349
+ timeFormat?: never;
350
+ timeZone?: never;
351
+ precision?: never;
352
+ symbol?: never;
353
+ durationFormat?: never;
354
+ max?: never;
355
+ formula?: never;
356
+ };
357
+ id: string;
358
+ name: string;
359
+ description?: never;
360
+ })[];
361
+ }[];
362
+ };
@@ -0,0 +1,68 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.tablesMeta = void 0;
4
+ /**
5
+ * Dump of an airtable table containing all field types.
6
+ */
7
+ exports.tablesMeta = {
8
+ tables: [{
9
+ id: 'tbluntrYUZ5bTz7YP', name: 'Table 1', primaryFieldId: 'fld7fNzf6ElsnsiXP', fields: [{ type: 'singleLineText', id: 'fld7fNzf6ElsnsiXP', name: 'Single Line Text' },
10
+ { type: 'singleCollaborator', id: 'fldzS7AY8z7fnPA2k', name: 'User' },
11
+ {
12
+ type: 'singleSelect', options: { choices: [{ id: 'sel8rlr1irDiJymSd', name: 'Todo', color: 'redLight2' }, { id: 'sel7fRrgENSSM1Kwx', name: 'In progress', color: 'yellowLight2' }, { id: 'selMu3Gxu6YDW89st', name: 'Done', color: 'greenLight2' }] }, id: 'fldtZ94euYOEgLIKx', name: 'Single Select',
13
+ },
14
+ {
15
+ type: 'multipleAttachments', options: { isReversed: false }, id: 'fldZXvZHFB1kQ8aO4', name: 'Attachments',
16
+ },
17
+ {
18
+ type: 'aiText', options: { referencedFieldIds: ['fldZXvZHFB1kQ8aO4'], prompt: ['', { field: { fieldId: 'fldZXvZHFB1kQ8aO4' } }] }, id: 'fldIs4nyExgOYguEi', name: 'AI Text', description: '',
19
+ },
20
+ { type: 'multilineText', id: 'fldFtEQouCSxqW6sL', name: 'Long Text' },
21
+ {
22
+ type: 'checkbox', options: { icon: 'check', color: 'greenBright' }, id: 'flddE4RG6Mlugqo8X', name: 'Checkbox',
23
+ },
24
+ {
25
+ type: 'multipleSelects', options: { choices: [{ id: 'selcufYWsGwXvoe5K', name: 'A', color: 'blueLight2' }, { id: 'seldJM3V2vfY1MwId', name: 'B', color: 'cyanLight2' }] }, id: 'fldFWelqOO9koF8fM', name: 'Multiple Select',
26
+ },
27
+ {
28
+ type: 'date', options: { dateFormat: { name: 'local', format: 'l' } }, id: 'fldAUPoAJ67TUwaVS', name: 'Date',
29
+ },
30
+ {
31
+ type: 'dateTime', options: { dateFormat: { name: 'local', format: 'l' }, timeFormat: { name: '12hour', format: 'h:mma' }, timeZone: 'client' }, id: 'fldXCnBVRZ4HO4KIh', name: 'Datetime',
32
+ },
33
+ { type: 'phoneNumber', id: 'fldo5kOL07o835nyt', name: 'Phone Number' },
34
+ { type: 'email', id: 'fldkmeNd7ZAGTS0nA', name: 'Email' },
35
+ { type: 'url', id: 'fldAyWIcU6mOwCZl2', name: 'URL' },
36
+ {
37
+ type: 'number', options: { precision: 1 }, id: 'fldN9DNkpCQ6IXH5C', name: 'Number',
38
+ },
39
+ {
40
+ type: 'currency', options: { precision: 2, symbol: '$' }, id: 'fldcaKq3Hq6Rr1R53', name: 'Currency',
41
+ },
42
+ {
43
+ type: 'percent', options: { precision: 0 }, id: 'fldsKAqUJjKv5hlH1', name: 'Percent',
44
+ },
45
+ {
46
+ type: 'duration', options: { durationFormat: 'h:mm' }, id: 'fldMH1inCgmp6KD8q', name: 'Duration',
47
+ },
48
+ {
49
+ type: 'rating', options: { icon: 'star', max: 5, color: 'yellowBright' }, id: 'fldFZtTmcTm1Fxu4B', name: 'Rating',
50
+ },
51
+ {
52
+ type: 'formula', options: {
53
+ isValid: true, formula: '{fld7fNzf6ElsnsiXP}', referencedFieldIds: ['fld7fNzf6ElsnsiXP'], result: { type: 'singleLineText' },
54
+ }, id: 'fldmYyrXlqiUpBu0C', name: 'Formula',
55
+ },
56
+ {
57
+ type: 'createdTime', options: { result: { type: 'dateTime', options: { dateFormat: { name: 'local', format: 'l' }, timeFormat: { name: '12hour', format: 'h:mma' }, timeZone: 'client' } } }, id: 'fldOJHvKq8rrIYV90', name: 'Created',
58
+ },
59
+ {
60
+ type: 'lastModifiedTime', options: { isValid: true, referencedFieldIds: [], result: { type: 'dateTime', options: { dateFormat: { name: 'local', format: 'l' }, timeFormat: { name: '12hour', format: 'h:mma' }, timeZone: 'client' } } }, id: 'fldH5xY5CV0Q4g5fY', name: 'Last Modified',
61
+ },
62
+ { type: 'lastModifiedBy', id: 'fld6W3h7dnVJhIni9', name: 'Last Modified By' },
63
+ { type: 'createdBy', id: 'fldDcRoVGJXIhVSAi', name: 'Created By' },
64
+ { type: 'autoNumber', id: 'fldGM8wIYk3G8WMiE', name: 'ID' },
65
+ { type: 'barcode', id: 'fldMPs1eLH8X5bWeS', name: 'Barcode' },
66
+ { type: 'button', id: 'fldAG3LTbJRwQVEvS', name: 'Button' }],
67
+ }],
68
+ };
@@ -1,3 +1,7 @@
1
+ /**
2
+ * Reset the internal state - useful for testing
3
+ */
4
+ export declare function resetIdentifierState(): void;
1
5
  /**
2
6
  * Used for identifiers:
3
7
  * - If the name is already a valid JS identifier, return it unmodified.
@@ -5,6 +9,7 @@
5
9
  * - Remove invalid characters.
6
10
  * - Convert to PascalCase.
7
11
  * - If the result starts with a digit, prefix with `_`.
12
+ * - If duplicate, add a number suffix.
8
13
  * - Returns a default identifier if the identifier cannot be salvaged.
9
14
  */
10
15
  export declare function escapeIdentifier(name: string): string;
@@ -1,9 +1,19 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.resetIdentifierState = resetIdentifierState;
3
4
  exports.escapeIdentifier = escapeIdentifier;
4
5
  const diacritics_1 = require("diacritics");
5
6
  let invalidIdentifierCount = 0;
6
7
  const DEFAULT_IDENTIFIER = 'invalidIdentifier';
8
+ // Track used identifiers to avoid duplicates
9
+ const usedIdentifiers = new Set();
10
+ /**
11
+ * Reset the internal state - useful for testing
12
+ */
13
+ function resetIdentifierState() {
14
+ usedIdentifiers.clear();
15
+ invalidIdentifierCount = 0;
16
+ }
7
17
  /**
8
18
  * Checks if 'str' is already a valid JavaScript identifier.
9
19
  * If yes, returns true. Otherwise false.
@@ -46,6 +56,7 @@ function toPascalCase(str) {
46
56
  * - Remove invalid characters.
47
57
  * - Convert to PascalCase.
48
58
  * - If the result starts with a digit, prefix with `_`.
59
+ * - If duplicate, add a number suffix.
49
60
  * - Returns a default identifier if the identifier cannot be salvaged.
50
61
  */
51
62
  function escapeIdentifier(name) {
@@ -92,5 +103,13 @@ function escapeIdentifier(name) {
92
103
  return `${DEFAULT_IDENTIFIER}${invalidIdentifierCount}`;
93
104
  }
94
105
  }
95
- return pascal;
106
+ // Handle duplicates by adding numbers
107
+ let finalIdentifier = pascal;
108
+ let counter = 2;
109
+ while (usedIdentifiers.has(finalIdentifier)) {
110
+ finalIdentifier = `${pascal}${counter}`;
111
+ counter += 1;
112
+ }
113
+ usedIdentifiers.add(finalIdentifier);
114
+ return finalIdentifier;
96
115
  }
@@ -9,6 +9,7 @@ export type FieldSchema = {
9
9
  export type BaseSchema = {
10
10
  id: string;
11
11
  name: string;
12
+ primaryFieldId?: string;
12
13
  description?: string;
13
14
  fields: FieldSchema[];
14
15
  }[];
package/dist/index.js CHANGED
@@ -18,17 +18,19 @@ const main = async (config) => {
18
18
  ].join('\n');
19
19
  };
20
20
  exports.main = main;
21
- const generateInterfaceEntry = ({ jsName, jsType, name, type }) => {
21
+ const generateInterfaceEntry = ({ jsName, jsType, name, type, originalName }) => {
22
22
  if (jsType === null) {
23
- return `\n // Unsupported field ${name} of type ${type}`;
23
+ return `\n // Unsupported field "${name}" of type ${type}`;
24
24
  }
25
- return `\n ${jsName}: ${jsType},`;
25
+ const comment = originalName !== jsName ? ` // Original field: "${originalName}"` : '';
26
+ return `\n ${jsName}: ${jsType},${comment}`;
26
27
  };
27
- const generateMappingEntry = ({ jsName, id, jsType, name }) => {
28
+ const generateMappingEntry = ({ jsName, id, jsType, name, originalName }) => {
28
29
  if (jsType === null) {
29
- return `\n // Unsupported field ${name}: ${(0, escapeString_1.escapeString)(id)}`;
30
+ return `\n // Unsupported field "${name}": ${(0, escapeString_1.escapeString)(id)}`;
30
31
  }
31
- return `\n ${jsName}: '${(0, escapeString_1.escapeString)(id)}',`;
32
+ const comment = originalName !== jsName ? ` // Original field: "${originalName}"` : '';
33
+ return `\n ${jsName}: '${(0, escapeString_1.escapeString)(id)}',${comment}`;
32
34
  };
33
35
  const generateSchemaEntry = ({ jsName, jsType }) => {
34
36
  if (jsType === null) {
@@ -37,11 +39,13 @@ const generateSchemaEntry = ({ jsName, jsType }) => {
37
39
  return `\n ${jsName}: '${(0, escapeString_1.escapeString)(jsType)}',`;
38
40
  };
39
41
  const generateCode = (config, tableSchema) => {
42
+ (0, escapeIdentifier_1.resetIdentifierState)();
40
43
  const itemNameRaw = (0, escapeIdentifier_1.escapeIdentifier)((0, recase_1.recase)(null, 'pascal', tableSchema.name));
41
44
  const itemName = /.s$/.test(itemNameRaw) ? itemNameRaw.slice(0, itemNameRaw.length - 1) : itemNameRaw;
42
45
  const tableName = (0, escapeIdentifier_1.escapeIdentifier)(`${(0, recase_1.recase)(null, 'camel', tableSchema.name)}Table`);
43
46
  const fields = tableSchema.fields.map((f) => ({
44
47
  ...f,
48
+ originalName: f.name,
45
49
  jsName: (0, escapeIdentifier_1.escapeIdentifier)((0, recase_1.recase)(null, 'camel', (0, escapeIdentifier_1.escapeIdentifier)(f.name))),
46
50
  jsType: (0, jsTypeForAirtableType_1.jsTypeForAirtableType)(f),
47
51
  }));
@@ -18,10 +18,11 @@ const jsTypeForAirtableType = (field) => {
18
18
  case 'externalSyncSource':
19
19
  case 'aiText':
20
20
  case 'singleCollaborator':
21
- case 'createdBy':
22
21
  case 'lastModifiedBy':
23
22
  case 'barcode':
24
23
  case 'button':
24
+ return 'string | null';
25
+ case 'createdBy':
25
26
  return 'string';
26
27
  case 'multipleAttachments':
27
28
  case 'multipleCollaborators':
@@ -33,13 +34,15 @@ const jsTypeForAirtableType = (field) => {
33
34
  case 'duration':
34
35
  case 'currency':
35
36
  case 'percent':
37
+ return 'number | null';
36
38
  case 'count':
37
39
  case 'autoNumber':
38
40
  return 'number';
39
41
  case 'date':
40
42
  case 'dateTime':
41
- case 'createdTime':
42
43
  case 'lastModifiedTime':
44
+ return 'number | null'; // Unix timestamp in seconds
45
+ case 'createdTime':
43
46
  return 'number'; // Unix timestamp in seconds
44
47
  case 'checkbox':
45
48
  return 'boolean';
@@ -55,6 +58,9 @@ const jsTypeForAirtableType = (field) => {
55
58
  if (innerType === null) {
56
59
  return null;
57
60
  }
61
+ if (innerType.includes('null')) {
62
+ return innerType;
63
+ }
58
64
  return `${innerType} | null`;
59
65
  }
60
66
  throw new Error(`Invalid ${field.type} field (no options.result): ${field.id}`);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "airtable-ts-codegen",
3
- "version": "1.3.0",
3
+ "version": "2.0.1",
4
4
  "description": "Autogenerate TypeScript definitions for your Airtable base",
5
5
  "license": "MIT",
6
6
  "author": "Adam Jones (domdomegg)",