@haustle/notion-orm 0.0.41 → 0.0.43
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 +33 -27
- package/build/src/DatabaseActions.d.ts +6 -3
- package/build/src/DatabaseActions.d.ts.map +1 -1
- package/build/src/DatabaseActions.js +63 -6
- package/build/src/DatabaseActions.js.map +1 -1
- package/build/src/GenerateTypes.d.ts +1 -0
- package/build/src/GenerateTypes.d.ts.map +1 -1
- package/build/src/GenerateTypes.js +22 -6
- package/build/src/GenerateTypes.js.map +1 -1
- package/build/src/queryTypes.d.ts +5 -1
- package/build/src/queryTypes.d.ts.map +1 -1
- package/package.json +10 -2
- package/src/BuildCall.ts +118 -0
- package/src/DatabaseActions.ts +227 -0
- package/src/GenerateTypes.ts +448 -0
- package/src/cli.ts +40 -0
- package/src/index.ts +136 -0
- package/src/queryTypes.ts +177 -0
|
@@ -0,0 +1,227 @@
|
|
|
1
|
+
import {
|
|
2
|
+
CreatePageParameters,
|
|
3
|
+
CreatePageResponse,
|
|
4
|
+
PageObjectResponse,
|
|
5
|
+
QueryDatabaseParameters,
|
|
6
|
+
QueryDatabaseResponse,
|
|
7
|
+
} from "@notionhq/client/build/src/api-endpoints";
|
|
8
|
+
import { Client } from "@notionhq/client";
|
|
9
|
+
import { getCall } from "./BuildCall";
|
|
10
|
+
import path from "path";
|
|
11
|
+
import { NotionConfigType } from "./index";
|
|
12
|
+
import {
|
|
13
|
+
apiFilterType,
|
|
14
|
+
apiSingleFilter,
|
|
15
|
+
CompoundFilters,
|
|
16
|
+
Query,
|
|
17
|
+
QueryFilter,
|
|
18
|
+
SimpleQueryResponse,
|
|
19
|
+
SingleFilter,
|
|
20
|
+
SupportedNotionColumnTypes,
|
|
21
|
+
} from "./queryTypes";
|
|
22
|
+
|
|
23
|
+
import { camelize } from "./GenerateTypes";
|
|
24
|
+
export type propNameToColumnNameType = Record<
|
|
25
|
+
string,
|
|
26
|
+
{ columnName: string; type: SupportedNotionColumnTypes }
|
|
27
|
+
>;
|
|
28
|
+
|
|
29
|
+
// Import auth key from config file
|
|
30
|
+
const { auth }: NotionConfigType = require(path.join(
|
|
31
|
+
process.cwd(),
|
|
32
|
+
"notion.config"
|
|
33
|
+
));
|
|
34
|
+
|
|
35
|
+
export class DatabaseActions<
|
|
36
|
+
DatabaseSchemaType extends Record<string, any>,
|
|
37
|
+
ColumnNameToColumnType extends Record<
|
|
38
|
+
keyof DatabaseSchemaType,
|
|
39
|
+
SupportedNotionColumnTypes
|
|
40
|
+
>
|
|
41
|
+
> {
|
|
42
|
+
private NotionClient: Client = new Client({
|
|
43
|
+
auth,
|
|
44
|
+
});
|
|
45
|
+
private databaseId: string;
|
|
46
|
+
private propNameToColumnName: propNameToColumnNameType;
|
|
47
|
+
private columnNames: string[];
|
|
48
|
+
|
|
49
|
+
constructor(
|
|
50
|
+
datbaseId: string,
|
|
51
|
+
propNameToColumnName: propNameToColumnNameType
|
|
52
|
+
) {
|
|
53
|
+
this.databaseId = datbaseId;
|
|
54
|
+
this.propNameToColumnName = propNameToColumnName;
|
|
55
|
+
this.columnNames = Object.keys(propNameToColumnName);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
// Add page to a database
|
|
59
|
+
async add(
|
|
60
|
+
pageObject: DatabaseSchemaType,
|
|
61
|
+
getCallBody?: boolean
|
|
62
|
+
): Promise<CreatePageParameters | CreatePageResponse> {
|
|
63
|
+
const callBody: CreatePageParameters = {
|
|
64
|
+
parent: {
|
|
65
|
+
database_id: this.databaseId,
|
|
66
|
+
},
|
|
67
|
+
properties: {},
|
|
68
|
+
};
|
|
69
|
+
|
|
70
|
+
const columnTypePropNames = Object.keys(pageObject);
|
|
71
|
+
columnTypePropNames.forEach((propName) => {
|
|
72
|
+
const { type, columnName } = this.propNameToColumnName[propName];
|
|
73
|
+
const columnObject = getCall({
|
|
74
|
+
type,
|
|
75
|
+
value: pageObject[propName],
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
callBody.properties[columnName] = columnObject!;
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
// CORS: If user wants the body of the call. Can then send to API
|
|
82
|
+
if (getCallBody) {
|
|
83
|
+
return callBody;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
return await this.NotionClient.pages.create(callBody);
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
// Look for page inside the database
|
|
90
|
+
async query(
|
|
91
|
+
query: Query<DatabaseSchemaType, ColumnNameToColumnType>
|
|
92
|
+
): Promise<SimpleQueryResponse<DatabaseSchemaType>> {
|
|
93
|
+
const queryCall: QueryDatabaseParameters = {
|
|
94
|
+
database_id: this.databaseId,
|
|
95
|
+
};
|
|
96
|
+
|
|
97
|
+
const filters = query.filter
|
|
98
|
+
? this.recursivelyBuildFilter(query.filter)
|
|
99
|
+
: undefined;
|
|
100
|
+
if (filters) {
|
|
101
|
+
// @ts-ignore errors vs notion api types
|
|
102
|
+
queryCall["filter"] = filters;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
const sort = query.sort;
|
|
106
|
+
|
|
107
|
+
const response = await this.NotionClient.databases.query(queryCall);
|
|
108
|
+
|
|
109
|
+
return this.simplifyQueryResponse(response);
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
private simplifyQueryResponse(
|
|
113
|
+
res: QueryDatabaseResponse
|
|
114
|
+
): SimpleQueryResponse<DatabaseSchemaType> {
|
|
115
|
+
// Is this smart too do...idk
|
|
116
|
+
const rawResults = res.results as PageObjectResponse[];
|
|
117
|
+
const rawResponse = res;
|
|
118
|
+
|
|
119
|
+
const results: Partial<DatabaseSchemaType>[] = rawResults.map((result) => {
|
|
120
|
+
const simpleResult: Partial<DatabaseSchemaType> = {};
|
|
121
|
+
const properties = Object.entries(result.properties);
|
|
122
|
+
|
|
123
|
+
for (const [columnName, result] of properties) {
|
|
124
|
+
const camelizeColumnName = camelize(columnName);
|
|
125
|
+
|
|
126
|
+
const columnType = this.propNameToColumnName[camelizeColumnName].type;
|
|
127
|
+
|
|
128
|
+
// @ts-ignore
|
|
129
|
+
simpleResult[camelizeColumnName] = this.getResponseValue(
|
|
130
|
+
columnType,
|
|
131
|
+
result
|
|
132
|
+
);
|
|
133
|
+
}
|
|
134
|
+
return simpleResult;
|
|
135
|
+
});
|
|
136
|
+
|
|
137
|
+
return {
|
|
138
|
+
results,
|
|
139
|
+
rawResponse,
|
|
140
|
+
};
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
private getResponseValue(
|
|
144
|
+
prop: SupportedNotionColumnTypes,
|
|
145
|
+
x: Record<string, any>
|
|
146
|
+
) {
|
|
147
|
+
switch (prop) {
|
|
148
|
+
case "select": {
|
|
149
|
+
const { select } = x;
|
|
150
|
+
if (select) {
|
|
151
|
+
return select["name"];
|
|
152
|
+
}
|
|
153
|
+
return undefined;
|
|
154
|
+
}
|
|
155
|
+
case "title": {
|
|
156
|
+
const { title } = x;
|
|
157
|
+
if (title) {
|
|
158
|
+
const combinedText = title.map(
|
|
159
|
+
({ plain_text }: { plain_text: string }) => plain_text
|
|
160
|
+
);
|
|
161
|
+
return combinedText.join("");
|
|
162
|
+
}
|
|
163
|
+
return undefined;
|
|
164
|
+
}
|
|
165
|
+
case "url": {
|
|
166
|
+
const { url } = x;
|
|
167
|
+
return url;
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
case "multi_select": {
|
|
171
|
+
const { multi_select } = x;
|
|
172
|
+
if (multi_select) {
|
|
173
|
+
const multi_selectArr: string[] = multi_select.map(
|
|
174
|
+
({ name }: { name: string }) => name
|
|
175
|
+
);
|
|
176
|
+
return multi_selectArr;
|
|
177
|
+
}
|
|
178
|
+
return undefined;
|
|
179
|
+
}
|
|
180
|
+
default: {
|
|
181
|
+
return "lol";
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
private recursivelyBuildFilter(
|
|
187
|
+
queryFilter: QueryFilter<DatabaseSchemaType, ColumnNameToColumnType>
|
|
188
|
+
): apiFilterType {
|
|
189
|
+
// Need to loop because we don't kno
|
|
190
|
+
for (const prop in queryFilter) {
|
|
191
|
+
// if the filter is "and" || "or" we need to recursively
|
|
192
|
+
if (prop === "and" || prop === "or") {
|
|
193
|
+
const compoundFilters: QueryFilter<
|
|
194
|
+
DatabaseSchemaType,
|
|
195
|
+
ColumnNameToColumnType
|
|
196
|
+
>[] =
|
|
197
|
+
// @ts-ignore
|
|
198
|
+
queryFilter[prop];
|
|
199
|
+
|
|
200
|
+
const compoundApiFilters = compoundFilters.map(
|
|
201
|
+
(i: QueryFilter<DatabaseSchemaType, ColumnNameToColumnType>) => {
|
|
202
|
+
return this.recursivelyBuildFilter(i);
|
|
203
|
+
}
|
|
204
|
+
);
|
|
205
|
+
|
|
206
|
+
// Either have an `and` or an `or` compound filter
|
|
207
|
+
let temp: apiFilterType = {
|
|
208
|
+
...(prop === "and"
|
|
209
|
+
? { and: compoundApiFilters }
|
|
210
|
+
: { or: compoundApiFilters }),
|
|
211
|
+
};
|
|
212
|
+
return temp;
|
|
213
|
+
} else {
|
|
214
|
+
const propType = this.propNameToColumnName[prop].type;
|
|
215
|
+
const temp: apiSingleFilter = {
|
|
216
|
+
property: this.propNameToColumnName[prop].columnName,
|
|
217
|
+
};
|
|
218
|
+
|
|
219
|
+
//@ts-ignore
|
|
220
|
+
temp[propType] = (queryFilter as SingleFilter<ColumnNameToColumnType>)[
|
|
221
|
+
prop
|
|
222
|
+
];
|
|
223
|
+
return temp;
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
}
|
|
@@ -0,0 +1,448 @@
|
|
|
1
|
+
import {
|
|
2
|
+
DatabaseObjectResponse,
|
|
3
|
+
GetDatabaseResponse,
|
|
4
|
+
} from "@notionhq/client/build/src/api-endpoints";
|
|
5
|
+
import * as ts from "typescript";
|
|
6
|
+
import fs from "fs";
|
|
7
|
+
import path from "path";
|
|
8
|
+
import { DATABASES_DIR } from "./index";
|
|
9
|
+
import { NotionColumnTypes } from "queryTypes";
|
|
10
|
+
|
|
11
|
+
type propNameToColumnNameType = Record<
|
|
12
|
+
string,
|
|
13
|
+
{ columnName: string; type: NotionColumnTypes }
|
|
14
|
+
>;
|
|
15
|
+
|
|
16
|
+
/*
|
|
17
|
+
Responsible for generating `.ts` files
|
|
18
|
+
*/
|
|
19
|
+
export async function createTypescriptFileForDatabase(
|
|
20
|
+
dbResponse: GetDatabaseResponse
|
|
21
|
+
) {
|
|
22
|
+
const {
|
|
23
|
+
id: databaseId,
|
|
24
|
+
properties,
|
|
25
|
+
title,
|
|
26
|
+
} = dbResponse as DatabaseObjectResponse;
|
|
27
|
+
const propNameToColumnName: propNameToColumnNameType = {};
|
|
28
|
+
const databaseName = title[0].plain_text;
|
|
29
|
+
const databaseClassName = camelize(databaseName).replace(/[^a-zA-Z0-9]/g, "");
|
|
30
|
+
|
|
31
|
+
const databaseColumnTypeProps: ts.TypeElement[] = [];
|
|
32
|
+
|
|
33
|
+
// Looping through each column of database
|
|
34
|
+
Object.values(properties).forEach((value) => {
|
|
35
|
+
const { type: columnType, name: columnName } = value;
|
|
36
|
+
|
|
37
|
+
// Taking the column name and camelizing it for typescript use
|
|
38
|
+
const camelizedColumnName = camelize(columnName);
|
|
39
|
+
|
|
40
|
+
// Creating map of column name to the column's name in the database's typescript type
|
|
41
|
+
propNameToColumnName[camelizedColumnName] = {
|
|
42
|
+
columnName,
|
|
43
|
+
type: columnType,
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
if (
|
|
47
|
+
columnType === "title" ||
|
|
48
|
+
columnType === "rich_text" ||
|
|
49
|
+
columnType === "email" ||
|
|
50
|
+
columnType === "phone_number"
|
|
51
|
+
) {
|
|
52
|
+
// add text column to collection type
|
|
53
|
+
databaseColumnTypeProps.push(
|
|
54
|
+
createTextProperty({
|
|
55
|
+
name: camelizedColumnName,
|
|
56
|
+
isTitle: columnType === "title",
|
|
57
|
+
})
|
|
58
|
+
);
|
|
59
|
+
} else if (columnType === "number") {
|
|
60
|
+
// add number column to collection type
|
|
61
|
+
databaseColumnTypeProps.push(createNumberProperty(camelizedColumnName));
|
|
62
|
+
} else if (columnType === "url") {
|
|
63
|
+
// add url column to collection type
|
|
64
|
+
databaseColumnTypeProps.push(
|
|
65
|
+
createTextProperty({ name: camelizedColumnName, isTitle: false })
|
|
66
|
+
);
|
|
67
|
+
} else if (columnType === "date") {
|
|
68
|
+
// add Date column to collection type
|
|
69
|
+
databaseColumnTypeProps.push(createDateProperty(camelizedColumnName));
|
|
70
|
+
} else if (
|
|
71
|
+
columnType === "select" ||
|
|
72
|
+
columnType === "status" ||
|
|
73
|
+
columnType === "multi_select"
|
|
74
|
+
) {
|
|
75
|
+
// @ts-ignore
|
|
76
|
+
const options = value[columnType].options.map((x) => x.name);
|
|
77
|
+
databaseColumnTypeProps.push(
|
|
78
|
+
createMultiOptionProp({
|
|
79
|
+
name: camelizedColumnName,
|
|
80
|
+
options,
|
|
81
|
+
isArray: columnType === "multi_select", // Union or Union Array
|
|
82
|
+
})
|
|
83
|
+
);
|
|
84
|
+
}
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
// Object type that represents the database schema
|
|
88
|
+
const DatabaseSchemaType = ts.factory.createTypeAliasDeclaration(
|
|
89
|
+
[ts.factory.createToken(ts.SyntaxKind.ExportKeyword)],
|
|
90
|
+
ts.factory.createIdentifier("DatabaseSchemaType"),
|
|
91
|
+
undefined,
|
|
92
|
+
ts.factory.createTypeLiteralNode(databaseColumnTypeProps)
|
|
93
|
+
);
|
|
94
|
+
|
|
95
|
+
// Top level non-nested variable, functions, types for database files
|
|
96
|
+
const TsNodesForDatabaseFile = ts.factory.createNodeArray([
|
|
97
|
+
createNameImport({
|
|
98
|
+
namedImport: "DatabaseActions",
|
|
99
|
+
path: "../src/DatabaseActions",
|
|
100
|
+
}),
|
|
101
|
+
createNameImport({
|
|
102
|
+
namedImport: "Query",
|
|
103
|
+
path: "../src/queryTypes",
|
|
104
|
+
}),
|
|
105
|
+
createDatabaseIdVariable(databaseId),
|
|
106
|
+
DatabaseSchemaType,
|
|
107
|
+
createColumnNameToColumnProperties(propNameToColumnName),
|
|
108
|
+
createColumnNameToColumnType(),
|
|
109
|
+
createQueryTypeExport(),
|
|
110
|
+
createDatabaseClassExport({ databaseName: databaseClassName }),
|
|
111
|
+
]);
|
|
112
|
+
|
|
113
|
+
const sourceFile = ts.createSourceFile(
|
|
114
|
+
"",
|
|
115
|
+
"",
|
|
116
|
+
ts.ScriptTarget.ESNext,
|
|
117
|
+
true,
|
|
118
|
+
ts.ScriptKind.TS
|
|
119
|
+
);
|
|
120
|
+
const printer = ts.createPrinter();
|
|
121
|
+
|
|
122
|
+
const typescriptCodeToString = printer.printList(
|
|
123
|
+
ts.ListFormat.MultiLine,
|
|
124
|
+
TsNodesForDatabaseFile,
|
|
125
|
+
sourceFile
|
|
126
|
+
);
|
|
127
|
+
const transpileToJavaScript = ts.transpile(typescriptCodeToString, {
|
|
128
|
+
module: ts.ModuleKind.None,
|
|
129
|
+
target: ts.ScriptTarget.ESNext,
|
|
130
|
+
});
|
|
131
|
+
|
|
132
|
+
// Create databases output folder
|
|
133
|
+
if (!fs.existsSync(DATABASES_DIR)) {
|
|
134
|
+
fs.mkdirSync(DATABASES_DIR);
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
// Create TypeScript and JavaScript files
|
|
138
|
+
fs.writeFileSync(
|
|
139
|
+
path.resolve(DATABASES_DIR, `${databaseClassName}.ts`),
|
|
140
|
+
typescriptCodeToString
|
|
141
|
+
);
|
|
142
|
+
fs.writeFileSync(
|
|
143
|
+
path.resolve(DATABASES_DIR, `${databaseClassName}.js`),
|
|
144
|
+
transpileToJavaScript
|
|
145
|
+
);
|
|
146
|
+
|
|
147
|
+
return { databaseName, databaseClassName, databaseId };
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
// generate text property
|
|
151
|
+
function createTextProperty(args: { name: string; isTitle: boolean }) {
|
|
152
|
+
const { name, isTitle } = args;
|
|
153
|
+
const text = ts.factory.createPropertySignature(
|
|
154
|
+
undefined,
|
|
155
|
+
ts.factory.createIdentifier(name),
|
|
156
|
+
!isTitle ? ts.factory.createToken(ts.SyntaxKind.QuestionToken) : undefined,
|
|
157
|
+
ts.factory.createKeywordTypeNode(ts.SyntaxKind.StringKeyword)
|
|
158
|
+
);
|
|
159
|
+
return text;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
/**
|
|
163
|
+
* Generate number property to go inside a type
|
|
164
|
+
* name: number
|
|
165
|
+
*/
|
|
166
|
+
function createNumberProperty(name: string) {
|
|
167
|
+
const number = ts.factory.createPropertySignature(
|
|
168
|
+
undefined,
|
|
169
|
+
ts.factory.createIdentifier(name),
|
|
170
|
+
ts.factory.createToken(ts.SyntaxKind.QuestionToken),
|
|
171
|
+
ts.factory.createKeywordTypeNode(ts.SyntaxKind.NumberKeyword)
|
|
172
|
+
);
|
|
173
|
+
return number;
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
/**
|
|
177
|
+
* For selects and multi-select collection properties
|
|
178
|
+
* array = true for multi-select
|
|
179
|
+
*/
|
|
180
|
+
function createMultiOptionProp(args: {
|
|
181
|
+
name: string;
|
|
182
|
+
options: string[];
|
|
183
|
+
isArray: boolean;
|
|
184
|
+
}) {
|
|
185
|
+
const { isArray, name, options } = args;
|
|
186
|
+
return ts.factory.createPropertySignature(
|
|
187
|
+
undefined,
|
|
188
|
+
ts.factory.createIdentifier(name),
|
|
189
|
+
ts.factory.createToken(ts.SyntaxKind.QuestionToken),
|
|
190
|
+
isArray
|
|
191
|
+
? ts.factory.createArrayTypeNode(
|
|
192
|
+
ts.factory.createParenthesizedType(
|
|
193
|
+
ts.factory.createUnionTypeNode([
|
|
194
|
+
...options.map((option) =>
|
|
195
|
+
ts.factory.createLiteralTypeNode(
|
|
196
|
+
ts.factory.createStringLiteral(option)
|
|
197
|
+
)
|
|
198
|
+
),
|
|
199
|
+
createOtherStringProp(),
|
|
200
|
+
])
|
|
201
|
+
)
|
|
202
|
+
)
|
|
203
|
+
: ts.factory.createUnionTypeNode([
|
|
204
|
+
...options.map((option) =>
|
|
205
|
+
ts.factory.createLiteralTypeNode(
|
|
206
|
+
ts.factory.createStringLiteral(option)
|
|
207
|
+
)
|
|
208
|
+
),
|
|
209
|
+
createOtherStringProp(),
|
|
210
|
+
])
|
|
211
|
+
);
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
// string & {}. Allows users to pass in values
|
|
215
|
+
function createOtherStringProp() {
|
|
216
|
+
return ts.factory.createIntersectionTypeNode([
|
|
217
|
+
ts.factory.createKeywordTypeNode(ts.SyntaxKind.StringKeyword),
|
|
218
|
+
ts.factory.createTypeLiteralNode([]),
|
|
219
|
+
]);
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
function createDateProperty(name: string) {
|
|
223
|
+
return ts.factory.createPropertySignature(
|
|
224
|
+
undefined,
|
|
225
|
+
ts.factory.createIdentifier(name),
|
|
226
|
+
ts.factory.createToken(ts.SyntaxKind.QuestionToken),
|
|
227
|
+
ts.factory.createTypeLiteralNode([
|
|
228
|
+
ts.factory.createPropertySignature(
|
|
229
|
+
undefined,
|
|
230
|
+
ts.factory.createIdentifier("start"),
|
|
231
|
+
undefined,
|
|
232
|
+
ts.factory.createKeywordTypeNode(ts.SyntaxKind.StringKeyword)
|
|
233
|
+
),
|
|
234
|
+
ts.factory.createPropertySignature(
|
|
235
|
+
undefined,
|
|
236
|
+
ts.factory.createIdentifier("end"),
|
|
237
|
+
ts.factory.createToken(ts.SyntaxKind.QuestionToken),
|
|
238
|
+
ts.factory.createKeywordTypeNode(ts.SyntaxKind.StringKeyword)
|
|
239
|
+
),
|
|
240
|
+
])
|
|
241
|
+
);
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
// Generate database Id variable
|
|
245
|
+
// const databaseId = <database-id>
|
|
246
|
+
function createDatabaseIdVariable(databaseId: string) {
|
|
247
|
+
return ts.factory.createVariableStatement(
|
|
248
|
+
undefined,
|
|
249
|
+
ts.factory.createVariableDeclarationList(
|
|
250
|
+
[
|
|
251
|
+
ts.factory.createVariableDeclaration(
|
|
252
|
+
ts.factory.createIdentifier("databaseId"),
|
|
253
|
+
undefined,
|
|
254
|
+
undefined,
|
|
255
|
+
ts.factory.createStringLiteral(databaseId)
|
|
256
|
+
),
|
|
257
|
+
],
|
|
258
|
+
ts.NodeFlags.Const
|
|
259
|
+
)
|
|
260
|
+
);
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
/**
|
|
264
|
+
* Instead of refering to the column names 1:1 such as "Book Rating", we transform them to
|
|
265
|
+
* camelcase (eg. bookRating). So we need to keep track of the original name and the type
|
|
266
|
+
* for when we construct request for API
|
|
267
|
+
*
|
|
268
|
+
* Example
|
|
269
|
+
*
|
|
270
|
+
* const columnNameToColumnProperties = {
|
|
271
|
+
*
|
|
272
|
+
* "bookRating": {
|
|
273
|
+
* columnName: "Book Rating",
|
|
274
|
+
* type: "select"
|
|
275
|
+
* },
|
|
276
|
+
* "genre": {
|
|
277
|
+
* columnName: "Genre",
|
|
278
|
+
* type: "multi_select"
|
|
279
|
+
* }
|
|
280
|
+
*
|
|
281
|
+
* }
|
|
282
|
+
*/
|
|
283
|
+
function createColumnNameToColumnProperties(colMap: propNameToColumnNameType) {
|
|
284
|
+
return ts.factory.createVariableDeclarationList(
|
|
285
|
+
[
|
|
286
|
+
ts.factory.createVariableDeclaration(
|
|
287
|
+
ts.factory.createIdentifier("columnNameToColumnProperties"),
|
|
288
|
+
undefined,
|
|
289
|
+
undefined,
|
|
290
|
+
ts.factory.createAsExpression(
|
|
291
|
+
ts.factory.createObjectLiteralExpression(
|
|
292
|
+
[
|
|
293
|
+
...Object.entries(colMap).map(([propName, value]) =>
|
|
294
|
+
ts.factory.createPropertyAssignment(
|
|
295
|
+
ts.factory.createStringLiteral(propName),
|
|
296
|
+
ts.factory.createObjectLiteralExpression(
|
|
297
|
+
[
|
|
298
|
+
ts.factory.createPropertyAssignment(
|
|
299
|
+
ts.factory.createIdentifier("columnName"),
|
|
300
|
+
ts.factory.createStringLiteral(value.columnName)
|
|
301
|
+
),
|
|
302
|
+
ts.factory.createPropertyAssignment(
|
|
303
|
+
ts.factory.createIdentifier("type"),
|
|
304
|
+
ts.factory.createStringLiteral(value.type)
|
|
305
|
+
),
|
|
306
|
+
],
|
|
307
|
+
true
|
|
308
|
+
)
|
|
309
|
+
)
|
|
310
|
+
),
|
|
311
|
+
],
|
|
312
|
+
true
|
|
313
|
+
),
|
|
314
|
+
ts.factory.createTypeReferenceNode(
|
|
315
|
+
ts.factory.createIdentifier("const"),
|
|
316
|
+
undefined
|
|
317
|
+
)
|
|
318
|
+
)
|
|
319
|
+
),
|
|
320
|
+
],
|
|
321
|
+
ts.NodeFlags.Const
|
|
322
|
+
);
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
function createColumnNameToColumnType() {
|
|
326
|
+
return ts.factory.createTypeAliasDeclaration(
|
|
327
|
+
undefined,
|
|
328
|
+
ts.factory.createIdentifier("ColumnNameToColumnType"),
|
|
329
|
+
undefined,
|
|
330
|
+
ts.factory.createMappedTypeNode(
|
|
331
|
+
undefined,
|
|
332
|
+
ts.factory.createTypeParameterDeclaration(
|
|
333
|
+
undefined,
|
|
334
|
+
ts.factory.createIdentifier("Property"),
|
|
335
|
+
ts.factory.createTypeOperatorNode(
|
|
336
|
+
ts.SyntaxKind.KeyOfKeyword,
|
|
337
|
+
ts.factory.createTypeQueryNode(
|
|
338
|
+
ts.factory.createIdentifier("columnNameToColumnProperties"),
|
|
339
|
+
undefined
|
|
340
|
+
)
|
|
341
|
+
),
|
|
342
|
+
undefined
|
|
343
|
+
),
|
|
344
|
+
undefined,
|
|
345
|
+
undefined,
|
|
346
|
+
ts.factory.createIndexedAccessTypeNode(
|
|
347
|
+
ts.factory.createIndexedAccessTypeNode(
|
|
348
|
+
ts.factory.createTypeQueryNode(
|
|
349
|
+
ts.factory.createIdentifier("columnNameToColumnProperties"),
|
|
350
|
+
undefined
|
|
351
|
+
),
|
|
352
|
+
ts.factory.createTypeReferenceNode(
|
|
353
|
+
ts.factory.createIdentifier("Property"),
|
|
354
|
+
undefined
|
|
355
|
+
)
|
|
356
|
+
),
|
|
357
|
+
ts.factory.createLiteralTypeNode(ts.factory.createStringLiteral("type"))
|
|
358
|
+
),
|
|
359
|
+
undefined
|
|
360
|
+
/* unknown */
|
|
361
|
+
)
|
|
362
|
+
);
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
// Need to import the database class used to execute database actions (adding + querying)
|
|
366
|
+
function createNameImport(args: { namedImport: string; path: string }) {
|
|
367
|
+
const { namedImport, path } = args;
|
|
368
|
+
return ts.factory.createImportDeclaration(
|
|
369
|
+
undefined,
|
|
370
|
+
ts.factory.createImportClause(
|
|
371
|
+
false,
|
|
372
|
+
undefined,
|
|
373
|
+
ts.factory.createNamedImports([
|
|
374
|
+
ts.factory.createImportSpecifier(
|
|
375
|
+
false,
|
|
376
|
+
undefined,
|
|
377
|
+
ts.factory.createIdentifier(namedImport)
|
|
378
|
+
),
|
|
379
|
+
])
|
|
380
|
+
),
|
|
381
|
+
ts.factory.createStringLiteral(path),
|
|
382
|
+
undefined
|
|
383
|
+
);
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
function createQueryTypeExport() {
|
|
387
|
+
return ts.factory.createTypeAliasDeclaration(
|
|
388
|
+
[ts.factory.createToken(ts.SyntaxKind.ExportKeyword)],
|
|
389
|
+
ts.factory.createIdentifier("QuerySchemaType"),
|
|
390
|
+
undefined,
|
|
391
|
+
ts.factory.createTypeReferenceNode(ts.factory.createIdentifier("Query"), [
|
|
392
|
+
ts.factory.createTypeReferenceNode(
|
|
393
|
+
ts.factory.createIdentifier("DatabaseSchemaType"),
|
|
394
|
+
undefined
|
|
395
|
+
),
|
|
396
|
+
ts.factory.createTypeReferenceNode(
|
|
397
|
+
ts.factory.createIdentifier("ColumnNameToColumnType"),
|
|
398
|
+
undefined
|
|
399
|
+
),
|
|
400
|
+
])
|
|
401
|
+
);
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
/**
|
|
405
|
+
* Create export statement for the database class
|
|
406
|
+
* export const <databaseName> = new DatabaseActions<DatabaseSchemaType>(datbaseId, columnNameToColumnProperties)
|
|
407
|
+
*/
|
|
408
|
+
function createDatabaseClassExport(args: { databaseName: string }) {
|
|
409
|
+
const { databaseName } = args;
|
|
410
|
+
return ts.factory.createVariableStatement(
|
|
411
|
+
[ts.factory.createModifier(ts.SyntaxKind.ExportKeyword)],
|
|
412
|
+
ts.factory.createVariableDeclarationList(
|
|
413
|
+
[
|
|
414
|
+
ts.factory.createVariableDeclaration(
|
|
415
|
+
ts.factory.createIdentifier(databaseName),
|
|
416
|
+
undefined,
|
|
417
|
+
undefined,
|
|
418
|
+
ts.factory.createNewExpression(
|
|
419
|
+
ts.factory.createIdentifier("DatabaseActions"),
|
|
420
|
+
[
|
|
421
|
+
ts.factory.createTypeReferenceNode(
|
|
422
|
+
ts.factory.createIdentifier("DatabaseSchemaType"),
|
|
423
|
+
undefined
|
|
424
|
+
),
|
|
425
|
+
ts.factory.createTypeReferenceNode(
|
|
426
|
+
ts.factory.createIdentifier("ColumnNameToColumnType"),
|
|
427
|
+
undefined
|
|
428
|
+
),
|
|
429
|
+
],
|
|
430
|
+
[
|
|
431
|
+
ts.factory.createIdentifier("databaseId"),
|
|
432
|
+
ts.factory.createIdentifier("columnNameToColumnProperties"),
|
|
433
|
+
]
|
|
434
|
+
)
|
|
435
|
+
),
|
|
436
|
+
],
|
|
437
|
+
ts.NodeFlags.Const
|
|
438
|
+
)
|
|
439
|
+
);
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
// for a type's property name
|
|
443
|
+
export function camelize(str: string) {
|
|
444
|
+
return str.replace(/(?:^\w|[A-Z]|\b\w|\s+)/g, function (match, index) {
|
|
445
|
+
if (+match === 0) return ""; // or if (/\s+/.test(match)) for white spaces
|
|
446
|
+
return index === 0 ? match.toLowerCase() : match.toUpperCase();
|
|
447
|
+
});
|
|
448
|
+
}
|
package/src/cli.ts
ADDED
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
#! /usr/bin/env node
|
|
2
|
+
|
|
3
|
+
import fs from "fs";
|
|
4
|
+
import { createDatabaseTypes } from "./index";
|
|
5
|
+
import path from "path";
|
|
6
|
+
|
|
7
|
+
async function main() {
|
|
8
|
+
const args = process.argv.slice(2);
|
|
9
|
+
|
|
10
|
+
if (args.length === 1 && args[0] === "generate") {
|
|
11
|
+
const projDir = process.cwd();
|
|
12
|
+
|
|
13
|
+
const notionConfigDirJS = fs.existsSync(
|
|
14
|
+
path.join(projDir, "notion.config.js")
|
|
15
|
+
);
|
|
16
|
+
const notionConfigDirTS = fs.existsSync(
|
|
17
|
+
path.join(projDir, "notion.config.ts")
|
|
18
|
+
);
|
|
19
|
+
|
|
20
|
+
console.log(path.join(projDir, "notion.config"));
|
|
21
|
+
if (notionConfigDirJS || notionConfigDirTS) {
|
|
22
|
+
const config = require(path.join(projDir, "notion.config"));
|
|
23
|
+
|
|
24
|
+
const { databaseNames } = await createDatabaseTypes(config);
|
|
25
|
+
if (databaseNames.length < 0) {
|
|
26
|
+
console.log("generated no types");
|
|
27
|
+
} else {
|
|
28
|
+
console.log("Generated types for the following Database's: ");
|
|
29
|
+
for (let x = 0; x < databaseNames.length; x++) {
|
|
30
|
+
console.log(`${x}. ${databaseNames[x]}`);
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
} else {
|
|
34
|
+
console.error("Could not find file `notion.config.ts` in root");
|
|
35
|
+
process.exit(1);
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
main();
|