@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 +2 -0
- package/README.md +3 -0
- package/compose.yaml +10 -0
- package/package.json +33 -0
- package/src/ModelAPI.js +229 -0
- package/src/ModelAPI.test.js +920 -0
- package/src/QueryBuilder.js +95 -0
- package/src/QueryContext.js +90 -0
- package/src/RequestHeaders.js +21 -0
- package/src/applyAdditionalQueryConstraints.js +22 -0
- package/src/applyJoins.js +65 -0
- package/src/applyWhereConditions.js +70 -0
- package/src/casing.js +30 -0
- package/src/casing.test.js +25 -0
- package/src/consts.js +13 -0
- package/src/database.js +163 -0
- package/src/errors.js +116 -0
- package/src/handleJob.js +100 -0
- package/src/handleJob.test.js +271 -0
- package/src/handleRequest.js +124 -0
- package/src/handleRequest.test.js +360 -0
- package/src/index.d.ts +86 -0
- package/src/index.js +27 -0
- package/src/permissions.js +78 -0
- package/src/permissions.test.js +120 -0
- package/src/tracing.js +135 -0
- package/src/tracing.test.js +119 -0
- package/src/tryExecuteFunction.js +74 -0
- package/vite.config.js +7 -0
package/.env.test
ADDED
package/README.md
ADDED
package/compose.yaml
ADDED
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
|
+
}
|
package/src/ModelAPI.js
ADDED
|
@@ -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
|
+
};
|