@teamkeel/functions-runtime 0.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.
package/.env.test ADDED
@@ -0,0 +1,2 @@
1
+ KEEL_DB_CONN=postgresql://postgres:postgres@localhost:5432/functions-runtime
2
+ KEEL_DB_CONN_TYPE=pg
package/README.md ADDED
@@ -0,0 +1,3 @@
1
+ # `@teamkeel/functions-runtime`
2
+
3
+ `@teamkeel/functions-runtime` is an internal package used by `@teamkeel/sdk`. Do not install this package directly.
package/compose.yaml ADDED
@@ -0,0 +1,10 @@
1
+ services:
2
+ postgres:
3
+ image: postgres:11.13-alpine
4
+ restart: always
5
+ environment:
6
+ - POSTGRES_USER=postgres
7
+ - POSTGRES_PASSWORD=postgres
8
+ - POSTGRES_DB=functions-runtime
9
+ ports:
10
+ - "5432:5432"
package/package.json ADDED
@@ -0,0 +1,33 @@
1
+ {
2
+ "name": "@teamkeel/functions-runtime",
3
+ "version": "0.0.1",
4
+ "description": "Internal package used by @teamkeel/sdk",
5
+ "main": "src/index.js",
6
+ "scripts": {
7
+ "test": "DEBUG=true vitest run --reporter verbose --threads false",
8
+ "format": "npx prettier --write src/**/*.js"
9
+ },
10
+ "keywords": [],
11
+ "author": "Keel (www.keel.so)",
12
+ "license": "ASL (Apache 2.0)",
13
+ "typings": "src/index.d.ts",
14
+ "publishConfig": {
15
+ "access": "public"
16
+ },
17
+ "devDependencies": {
18
+ "prettier": "2.7.1",
19
+ "vitest": "^0.27.1"
20
+ },
21
+ "dependencies": {
22
+ "@opentelemetry/api": "^1.4.1",
23
+ "@opentelemetry/exporter-trace-otlp-proto": "^0.38.0",
24
+ "@opentelemetry/resources": "^1.12.0",
25
+ "@opentelemetry/sdk-trace-base": "^1.12.0",
26
+ "@opentelemetry/sdk-trace-node": "^1.12.0",
27
+ "change-case": "^4.1.2",
28
+ "json-rpc-2.0": "^1.4.1",
29
+ "ksuid": "^3.0.0",
30
+ "kysely": "^0.23.4",
31
+ "pg": "^8.8.0"
32
+ }
33
+ }
@@ -0,0 +1,229 @@
1
+ const { useDatabase } = require("./database");
2
+ const { QueryBuilder } = require("./QueryBuilder");
3
+ const { QueryContext } = require("./QueryContext");
4
+ const { applyWhereConditions } = require("./applyWhereConditions");
5
+ const { applyJoins } = require("./applyJoins");
6
+ const {
7
+ applyLimit,
8
+ applyOffset,
9
+ applyOrderBy,
10
+ } = require("./applyAdditionalQueryConstraints");
11
+ const {
12
+ camelCaseObject,
13
+ snakeCaseObject,
14
+ upperCamelCase,
15
+ } = require("./casing");
16
+ const tracing = require("./tracing");
17
+
18
+ /**
19
+ * RelationshipConfig is a simple representation of a model field that
20
+ * is a relationship. It is used by applyJoins and applyWhereConditions
21
+ * to build the correct query.
22
+ * @typedef {{
23
+ * relationshipType: "belongsTo" | "hasMany",
24
+ * foreignKey: string,
25
+ * referencesTable: string,
26
+ * }} RelationshipConfig
27
+ *
28
+ * TableConfig is an object where the keys are relationship field names
29
+ * (which don't exist in the database) and the values are RelationshipConfig
30
+ * objects describing that relationship.
31
+ * @typedef {Object.<string, RelationshipConfig} TableConfig
32
+ *
33
+ * TableConfigMap is mapping of database table names to TableConfig objects
34
+ * @typedef {Object.<string, TableConfig>} TableConfigMap
35
+ */
36
+
37
+ class DatabaseError extends Error {
38
+ constructor(error) {
39
+ super(error.message);
40
+ this.error = error;
41
+ }
42
+ }
43
+
44
+ class ModelAPI {
45
+ /**
46
+ * @param {string} tableName The name of the table this API is for
47
+ * @param {Function} _ Used to be a function that returns the default values for a row in this table. No longer used.
48
+ * @param {TableConfigMap} tableConfigMap
49
+ */
50
+ constructor(tableName, _, tableConfigMap = {}) {
51
+ this._tableName = tableName;
52
+ this._tableConfigMap = tableConfigMap;
53
+ this._modelName = upperCamelCase(this._tableName);
54
+ }
55
+
56
+ async create(values) {
57
+ const name = tracing.spanNameForModelAPI(this._modelName, "create");
58
+ const db = useDatabase();
59
+
60
+ return tracing.withSpan(name, async (span) => {
61
+ try {
62
+ const query = db
63
+ .insertInto(this._tableName)
64
+ .values(
65
+ snakeCaseObject({
66
+ ...values,
67
+ })
68
+ )
69
+ .returningAll();
70
+
71
+ span.setAttribute("sql", query.compile().sql);
72
+ const row = await query.executeTakeFirstOrThrow();
73
+
74
+ return camelCaseObject(row);
75
+ } catch (e) {
76
+ throw new DatabaseError(e);
77
+ }
78
+ });
79
+ }
80
+
81
+ async findOne(where = {}) {
82
+ const name = tracing.spanNameForModelAPI(this._modelName, "findOne");
83
+ const db = useDatabase();
84
+
85
+ return tracing.withSpan(name, async (span) => {
86
+ let builder = db
87
+ .selectFrom(this._tableName)
88
+ .distinctOn(`${this._tableName}.id`)
89
+ .selectAll(this._tableName);
90
+
91
+ const context = new QueryContext([this._tableName], this._tableConfigMap);
92
+
93
+ builder = applyJoins(context, builder, where);
94
+ builder = applyWhereConditions(context, builder, where);
95
+
96
+ span.setAttribute("sql", builder.compile().sql);
97
+ const row = await builder.executeTakeFirst();
98
+ if (!row) {
99
+ return null;
100
+ }
101
+
102
+ return camelCaseObject(row);
103
+ });
104
+ }
105
+
106
+ async findMany(params) {
107
+ const name = tracing.spanNameForModelAPI(this._modelName, "findMany");
108
+ const db = useDatabase();
109
+ const where = params?.where || {};
110
+
111
+ return tracing.withSpan(name, async (span) => {
112
+ const context = new QueryContext([this._tableName], this._tableConfigMap);
113
+
114
+ let builder = db
115
+ .selectFrom((qb) => {
116
+ // We need to wrap this query as a sub query in the selectFrom because you cannot apply a different order by column when using distinct(id)
117
+ let builder = qb
118
+ .selectFrom(this._tableName)
119
+ .distinctOn(`${this._tableName}.id`)
120
+ .selectAll(this._tableName);
121
+
122
+ builder = applyJoins(context, builder, where);
123
+ builder = applyWhereConditions(context, builder, where);
124
+
125
+ builder = builder.as(this._tableName);
126
+
127
+ return builder;
128
+ })
129
+ .selectAll();
130
+
131
+ // The only constraints added to the main query are the orderBy, limit and offset as they are performed on the "outer" set
132
+ if (params?.limit) {
133
+ builder = applyLimit(context, builder, params.limit);
134
+ }
135
+
136
+ if (params?.offset) {
137
+ builder = applyOffset(context, builder, params.offset);
138
+ }
139
+
140
+ if (
141
+ params?.orderBy !== undefined &&
142
+ Object.keys(params?.orderBy).length > 0
143
+ ) {
144
+ builder = applyOrderBy(
145
+ context,
146
+ builder,
147
+ this._tableName,
148
+ params.orderBy
149
+ );
150
+ } else {
151
+ builder = builder.orderBy(`${this._tableName}.id`);
152
+ }
153
+
154
+ const query = builder;
155
+
156
+ span.setAttribute("sql", query.compile().sql);
157
+ const rows = await builder.execute();
158
+ return rows.map((x) => camelCaseObject(x));
159
+ });
160
+ }
161
+
162
+ async update(where, values) {
163
+ const name = tracing.spanNameForModelAPI(this._modelName, "update");
164
+ const db = useDatabase();
165
+
166
+ return tracing.withSpan(name, async (span) => {
167
+ let builder = db.updateTable(this._tableName).returningAll();
168
+
169
+ builder = builder.set(snakeCaseObject(values));
170
+
171
+ const context = new QueryContext([this._tableName], this._tableConfigMap);
172
+
173
+ // TODO: support joins for update
174
+ builder = applyWhereConditions(context, builder, where);
175
+
176
+ span.setAttribute("sql", builder.compile().sql);
177
+
178
+ try {
179
+ const row = await builder.executeTakeFirstOrThrow();
180
+ return camelCaseObject(row);
181
+ } catch (e) {
182
+ throw new DatabaseError(e);
183
+ }
184
+ });
185
+ }
186
+
187
+ async delete(where) {
188
+ const name = tracing.spanNameForModelAPI(this._modelName, "delete");
189
+ const db = useDatabase();
190
+
191
+ return tracing.withSpan(name, async (span) => {
192
+ let builder = db.deleteFrom(this._tableName).returning(["id"]);
193
+
194
+ const context = new QueryContext([this._tableName], this._tableConfigMap);
195
+
196
+ // TODO: support joins for delete
197
+ builder = applyWhereConditions(context, builder, where);
198
+
199
+ span.setAttribute("sql", builder.compile().sql);
200
+ try {
201
+ const row = await builder.executeTakeFirstOrThrow();
202
+ return row.id;
203
+ } catch (e) {
204
+ throw new DatabaseError(e);
205
+ }
206
+ });
207
+ }
208
+
209
+ where(where) {
210
+ const db = useDatabase();
211
+
212
+ let builder = db
213
+ .selectFrom(this._tableName)
214
+ .distinctOn(`${this._tableName}.id`)
215
+ .selectAll(this._tableName);
216
+
217
+ const context = new QueryContext([this._tableName], this._tableConfigMap);
218
+
219
+ builder = applyJoins(context, builder, where);
220
+ builder = applyWhereConditions(context, builder, where);
221
+
222
+ return new QueryBuilder(this._tableName, context, builder);
223
+ }
224
+ }
225
+
226
+ module.exports = {
227
+ ModelAPI,
228
+ DatabaseError,
229
+ };