airtable-ts-codegen 1.0.0 → 1.1.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 +1 -0
- package/dist/escape/escapeIdentifier.js +15 -5
- package/dist/index.js +29 -7
- package/dist/jsTypeForAirtableType.d.ts +6 -1
- package/dist/jsTypeForAirtableType.js +25 -8
- package/package.json +15 -3
package/README.md
CHANGED
|
@@ -2,26 +2,36 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.escapeIdentifier = void 0;
|
|
4
4
|
const recase_1 = require("@kristiandupont/recase");
|
|
5
|
+
const diacritics_1 = require("diacritics");
|
|
5
6
|
const toPascalCase = (0, recase_1.recase)(null, 'pascal');
|
|
6
7
|
/** Used for identifiers. If the name has symbols or is illegal Typescript, strip out symbols and make it PascalCase. */
|
|
7
8
|
// NB: A wider set of things than are accepted by this function are valid identifiers in TypeScript (see https://stackoverflow.com/a/9337047). However, this works well for our purposes.
|
|
8
9
|
const escapeIdentifier = (name) => {
|
|
9
|
-
const
|
|
10
|
+
const preprocessed = (0, diacritics_1.remove)(name).trim();
|
|
10
11
|
let isLegalIdentifier = true;
|
|
11
|
-
if (!/^[$A-Z_a-z][\w$]*$/.test(
|
|
12
|
+
if (!/^[$A-Z_a-z][\w$]*$/.test(preprocessed)) {
|
|
12
13
|
isLegalIdentifier = false;
|
|
13
14
|
}
|
|
14
15
|
try {
|
|
15
16
|
// eslint-disable-next-line @typescript-eslint/no-implied-eval, no-new
|
|
16
|
-
new Function(`const ${
|
|
17
|
+
new Function(`const ${preprocessed} = 1;`);
|
|
17
18
|
}
|
|
18
19
|
catch {
|
|
19
20
|
isLegalIdentifier = false;
|
|
20
21
|
}
|
|
21
22
|
if (isLegalIdentifier) {
|
|
22
|
-
return
|
|
23
|
+
return preprocessed;
|
|
23
24
|
}
|
|
24
|
-
|
|
25
|
+
// Remove all characters up to the first valid identifier start character (A-Z, a-z, $), then
|
|
26
|
+
// replace all invalid characters with underscores, and finally collapse multiple underscores into one.
|
|
27
|
+
const validIdentifierStartIndex = preprocessed.search(/[$A-Za-z]/);
|
|
28
|
+
if (validIdentifierStartIndex === -1) {
|
|
29
|
+
throw new Error(`Invalid and unsalvageable identifier: ${name}`);
|
|
30
|
+
}
|
|
31
|
+
const snaked = preprocessed
|
|
32
|
+
.slice(validIdentifierStartIndex)
|
|
33
|
+
.replace(/[^$A-Z_a-z\d]/g, '_')
|
|
34
|
+
.replace(/_+/g, '_');
|
|
25
35
|
const result = toPascalCase(snaked);
|
|
26
36
|
if (result.length === 0) {
|
|
27
37
|
throw new Error(`Invalid and unsalvageable identifier: ${name}`);
|
package/dist/index.js
CHANGED
|
@@ -6,14 +6,36 @@ const getBaseSchema_1 = require("./getBaseSchema");
|
|
|
6
6
|
const escapeString_1 = require("./escape/escapeString");
|
|
7
7
|
const escapeIdentifier_1 = require("./escape/escapeIdentifier");
|
|
8
8
|
const jsTypeForAirtableType_1 = require("./jsTypeForAirtableType");
|
|
9
|
-
// This
|
|
10
|
-
// - an `index.ts` that exports all the tables
|
|
11
|
-
// - (under the hood?) various `tbl1234.ts` files that contain table definitions
|
|
9
|
+
// This generates a single typescript file containing all table definitions for a given base.
|
|
12
10
|
const main = async (config) => {
|
|
13
11
|
const baseSchema = await (0, getBaseSchema_1.getBaseSchema)(config.baseId, config);
|
|
14
|
-
return
|
|
12
|
+
return [
|
|
13
|
+
'/* DO NOT EDIT: this file was automatically generated by airtable-ts-codegen */',
|
|
14
|
+
'/* eslint-disable */',
|
|
15
|
+
"import type { Item, Table } from 'airtable-ts';",
|
|
16
|
+
'',
|
|
17
|
+
baseSchema.map((tableSchema) => generateCode(config, tableSchema)).join('\n\n'),
|
|
18
|
+
].join('\n');
|
|
15
19
|
};
|
|
16
20
|
exports.main = main;
|
|
21
|
+
const generateInterfaceEntry = ({ jsName, jsType, name, type }) => {
|
|
22
|
+
if (jsType == null) {
|
|
23
|
+
return `\n // Unsupported field ${name} of type ${type}`;
|
|
24
|
+
}
|
|
25
|
+
return `\n ${jsName}: ${jsType},`;
|
|
26
|
+
};
|
|
27
|
+
const generateMappingEntry = ({ jsName, id, jsType, name }) => {
|
|
28
|
+
if (jsType == null) {
|
|
29
|
+
return `\n // Unsupported field ${name}: ${(0, escapeString_1.escapeString)(id)}`;
|
|
30
|
+
}
|
|
31
|
+
return `\n ${jsName}: '${(0, escapeString_1.escapeString)(id)}',`;
|
|
32
|
+
};
|
|
33
|
+
const generateSchemaEntry = ({ jsName, jsType }) => {
|
|
34
|
+
if (jsType == null) {
|
|
35
|
+
return null;
|
|
36
|
+
}
|
|
37
|
+
return `\n ${jsName}: '${(0, escapeString_1.escapeString)(jsType)}',`;
|
|
38
|
+
};
|
|
17
39
|
const generateCode = (config, tableSchema) => {
|
|
18
40
|
const itemNameRaw = (0, escapeIdentifier_1.escapeIdentifier)((0, recase_1.recase)(null, 'pascal', tableSchema.name));
|
|
19
41
|
const itemName = /.s$/.test(itemNameRaw) ? itemNameRaw.slice(0, itemNameRaw.length - 1) : itemNameRaw;
|
|
@@ -24,16 +46,16 @@ const generateCode = (config, tableSchema) => {
|
|
|
24
46
|
jsType: (0, jsTypeForAirtableType_1.jsTypeForAirtableType)(f),
|
|
25
47
|
}));
|
|
26
48
|
return `export interface ${itemName} extends Item {
|
|
27
|
-
id: string,${fields.map(
|
|
49
|
+
id: string,${fields.map(generateInterfaceEntry).join('')}
|
|
28
50
|
}
|
|
29
51
|
|
|
30
52
|
export const ${tableName}: Table<${itemName}> = {
|
|
31
53
|
name: '${(0, escapeString_1.escapeString)(tableSchema.name)}',
|
|
32
54
|
baseId: '${(0, escapeString_1.escapeString)(config.baseId)}',
|
|
33
55
|
tableId: '${(0, escapeString_1.escapeString)(tableSchema.id)}',
|
|
34
|
-
mappings: {${fields.map(
|
|
56
|
+
mappings: {${fields.map(generateMappingEntry).join('')}
|
|
35
57
|
},
|
|
36
|
-
schema: {${fields.map(
|
|
58
|
+
schema: {${fields.map(generateSchemaEntry).filter(Boolean).join('')}
|
|
37
59
|
},
|
|
38
60
|
};`;
|
|
39
61
|
};
|
|
@@ -1,2 +1,7 @@
|
|
|
1
1
|
import { FieldSchema } from './getBaseSchema';
|
|
2
|
-
|
|
2
|
+
/**
|
|
3
|
+
* Returns the corresponding Typescript type for the given Airtable field type.
|
|
4
|
+
*
|
|
5
|
+
* Unsupported fields return `null` and will be filtered out by the caller.
|
|
6
|
+
*/
|
|
7
|
+
export declare const jsTypeForAirtableType: (field: FieldSchema) => string | null;
|
|
@@ -1,6 +1,11 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.jsTypeForAirtableType = void 0;
|
|
4
|
+
/**
|
|
5
|
+
* Returns the corresponding Typescript type for the given Airtable field type.
|
|
6
|
+
*
|
|
7
|
+
* Unsupported fields return `null` and will be filtered out by the caller.
|
|
8
|
+
*/
|
|
4
9
|
const jsTypeForAirtableType = (field) => {
|
|
5
10
|
switch (field.type) {
|
|
6
11
|
case 'url':
|
|
@@ -38,24 +43,36 @@ const jsTypeForAirtableType = (field) => {
|
|
|
38
43
|
&& 'result' in field.options
|
|
39
44
|
&& typeof field.options.result === 'object'
|
|
40
45
|
&& field.options.result != null) {
|
|
41
|
-
|
|
46
|
+
const innerType = (0, exports.jsTypeForAirtableType)(field.options.result);
|
|
47
|
+
if (innerType == null)
|
|
48
|
+
return null;
|
|
49
|
+
return `${innerType} | null`;
|
|
42
50
|
}
|
|
43
51
|
throw new Error(`Invalid ${field.type} field (no options.result): ${field.id}`);
|
|
44
|
-
// Special cases we don't yet support
|
|
52
|
+
// Special cases we don't yet support; for now, skip these fields
|
|
53
|
+
// case 'aiText':
|
|
54
|
+
// return 'AiTextObject';
|
|
55
|
+
// case 'singleCollaborator':
|
|
56
|
+
// case 'createdBy':
|
|
57
|
+
// case 'lastModifiedBy':
|
|
58
|
+
// return 'CollaboratorObject';
|
|
59
|
+
// case 'multipleCollaborators':
|
|
60
|
+
// return 'CollaboratorObject[]';
|
|
61
|
+
// case 'multipleAttachments':
|
|
62
|
+
// return 'AttachmentObject[]';
|
|
63
|
+
// case 'barcode':
|
|
64
|
+
// return 'BarcodeObject';
|
|
65
|
+
// case 'button':
|
|
66
|
+
// return 'ButtonObject';
|
|
45
67
|
case 'aiText':
|
|
46
|
-
return 'AiTextObject';
|
|
47
68
|
case 'singleCollaborator':
|
|
48
69
|
case 'createdBy':
|
|
49
70
|
case 'lastModifiedBy':
|
|
50
|
-
return 'CollaboratorObject';
|
|
51
71
|
case 'multipleCollaborators':
|
|
52
|
-
return 'CollaboratorObject[]';
|
|
53
72
|
case 'multipleAttachments':
|
|
54
|
-
return 'AttachmentObject[]';
|
|
55
73
|
case 'barcode':
|
|
56
|
-
return 'BarcodeObject';
|
|
57
74
|
case 'button':
|
|
58
|
-
return
|
|
75
|
+
return null;
|
|
59
76
|
default:
|
|
60
77
|
throw new Error(`Could not convert Airtable type '${field.type}' to a TypeScript type for field ${field.id}`);
|
|
61
78
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "airtable-ts-codegen",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.1.0",
|
|
4
4
|
"description": "Autogenerate TypeScript definitions for your Airtable base",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "Adam Jones (domdomegg)",
|
|
@@ -17,6 +17,7 @@
|
|
|
17
17
|
"dist"
|
|
18
18
|
],
|
|
19
19
|
"scripts": {
|
|
20
|
+
"start": "npm run build && node dist/cli.js",
|
|
20
21
|
"test": "vitest run",
|
|
21
22
|
"test:watch": "vitest --watch",
|
|
22
23
|
"lint": "eslint --ext .js,.jsx,.ts,.tsx .",
|
|
@@ -26,10 +27,12 @@
|
|
|
26
27
|
},
|
|
27
28
|
"dependencies": {
|
|
28
29
|
"@kristiandupont/recase": "^1.2.1",
|
|
29
|
-
"axios": "^1.6.8"
|
|
30
|
+
"axios": "^1.6.8",
|
|
31
|
+
"diacritics": "^1.3.0"
|
|
30
32
|
},
|
|
31
33
|
"devDependencies": {
|
|
32
34
|
"@tsconfig/node-lts": "^20.1.3",
|
|
35
|
+
"@types/diacritics": "^1.3.3",
|
|
33
36
|
"@types/node": "^20.12.8",
|
|
34
37
|
"airtable-ts": "^1.1.0",
|
|
35
38
|
"eslint": "^8.57.0",
|
|
@@ -41,6 +44,15 @@
|
|
|
41
44
|
"eslintConfig": {
|
|
42
45
|
"extends": [
|
|
43
46
|
"eslint-config-domdomegg"
|
|
44
|
-
]
|
|
47
|
+
],
|
|
48
|
+
"rules": {
|
|
49
|
+
"object-curly-newline": [
|
|
50
|
+
"error",
|
|
51
|
+
{
|
|
52
|
+
"multiline": true,
|
|
53
|
+
"consistent": true
|
|
54
|
+
}
|
|
55
|
+
]
|
|
56
|
+
}
|
|
45
57
|
}
|
|
46
58
|
}
|