@teamkeel/functions-runtime 0.335.0 → 0.336.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/package.json +1 -1
- package/pnpm-lock.yaml +768 -340
- package/src/ModelAPI.js +7 -7
- package/src/ModelAPI.test.js +3 -3
- package/src/QueryBuilder.js +3 -3
- package/src/database.js +51 -39
- package/src/handleRequest.js +2 -2
- package/src/handleRequest.test.js +2 -2
- package/src/index.js +2 -2
- package/src/permissions.test.js +2 -2
- package/src/tryExecuteFunction.js +2 -2
package/src/ModelAPI.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
const {
|
|
1
|
+
const { useDatabase } = require("./database");
|
|
2
2
|
const { QueryBuilder } = require("./QueryBuilder");
|
|
3
3
|
const { QueryContext } = require("./QueryContext");
|
|
4
4
|
const { applyWhereConditions } = require("./applyWhereConditions");
|
|
@@ -56,7 +56,7 @@ class ModelAPI {
|
|
|
56
56
|
|
|
57
57
|
async create(values) {
|
|
58
58
|
const name = tracing.spanNameForModelAPI(this._modelName, "create");
|
|
59
|
-
const db =
|
|
59
|
+
const db = useDatabase();
|
|
60
60
|
|
|
61
61
|
return tracing.withSpan(name, async (span) => {
|
|
62
62
|
try {
|
|
@@ -83,7 +83,7 @@ class ModelAPI {
|
|
|
83
83
|
|
|
84
84
|
async findOne(where = {}) {
|
|
85
85
|
const name = tracing.spanNameForModelAPI(this._modelName, "findOne");
|
|
86
|
-
const db =
|
|
86
|
+
const db = useDatabase();
|
|
87
87
|
|
|
88
88
|
return tracing.withSpan(name, async (span) => {
|
|
89
89
|
let builder = db
|
|
@@ -108,7 +108,7 @@ class ModelAPI {
|
|
|
108
108
|
|
|
109
109
|
async findMany(params) {
|
|
110
110
|
const name = tracing.spanNameForModelAPI(this._modelName, "findMany");
|
|
111
|
-
const db =
|
|
111
|
+
const db = useDatabase();
|
|
112
112
|
const where = params?.where || {};
|
|
113
113
|
|
|
114
114
|
return tracing.withSpan(name, async (span) => {
|
|
@@ -164,7 +164,7 @@ class ModelAPI {
|
|
|
164
164
|
|
|
165
165
|
async update(where, values) {
|
|
166
166
|
const name = tracing.spanNameForModelAPI(this._modelName, "update");
|
|
167
|
-
const db =
|
|
167
|
+
const db = useDatabase();
|
|
168
168
|
|
|
169
169
|
return tracing.withSpan(name, async (span) => {
|
|
170
170
|
let builder = db.updateTable(this._tableName).returningAll();
|
|
@@ -189,7 +189,7 @@ class ModelAPI {
|
|
|
189
189
|
|
|
190
190
|
async delete(where) {
|
|
191
191
|
const name = tracing.spanNameForModelAPI(this._modelName, "delete");
|
|
192
|
-
const db =
|
|
192
|
+
const db = useDatabase();
|
|
193
193
|
|
|
194
194
|
return tracing.withSpan(name, async (span) => {
|
|
195
195
|
let builder = db.deleteFrom(this._tableName).returning(["id"]);
|
|
@@ -210,7 +210,7 @@ class ModelAPI {
|
|
|
210
210
|
}
|
|
211
211
|
|
|
212
212
|
where(where) {
|
|
213
|
-
const db =
|
|
213
|
+
const db = useDatabase();
|
|
214
214
|
|
|
215
215
|
let builder = db
|
|
216
216
|
.selectFrom(this._tableName)
|
package/src/ModelAPI.test.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { test, expect, beforeEach } from "vitest";
|
|
2
|
-
const { ModelAPI
|
|
2
|
+
const { ModelAPI } = require("./ModelAPI");
|
|
3
3
|
const { sql } = require("kysely");
|
|
4
|
-
const {
|
|
4
|
+
const { useDatabase } = require("./database");
|
|
5
5
|
const KSUID = require("ksuid");
|
|
6
6
|
|
|
7
7
|
process.env.KEEL_DB_CONN_TYPE = "pg";
|
|
@@ -12,7 +12,7 @@ let postAPI;
|
|
|
12
12
|
let authorAPI;
|
|
13
13
|
|
|
14
14
|
beforeEach(async () => {
|
|
15
|
-
const db =
|
|
15
|
+
const db = useDatabase();
|
|
16
16
|
|
|
17
17
|
await sql`
|
|
18
18
|
DROP TABLE IF EXISTS post;
|
package/src/QueryBuilder.js
CHANGED
|
@@ -5,8 +5,8 @@ const {
|
|
|
5
5
|
applyOrderBy,
|
|
6
6
|
} = require("./applyAdditionalQueryConstraints");
|
|
7
7
|
const { applyJoins } = require("./applyJoins");
|
|
8
|
-
const { camelCaseObject
|
|
9
|
-
const {
|
|
8
|
+
const { camelCaseObject } = require("./casing");
|
|
9
|
+
const { useDatabase } = require("./database");
|
|
10
10
|
const { QueryContext } = require("./QueryContext");
|
|
11
11
|
const tracing = require("./tracing");
|
|
12
12
|
|
|
@@ -45,7 +45,7 @@ class QueryBuilder {
|
|
|
45
45
|
|
|
46
46
|
async findMany(params) {
|
|
47
47
|
const name = tracing.spanNameForModelAPI(this._modelName, "findMany");
|
|
48
|
-
const db =
|
|
48
|
+
const db = useDatabase();
|
|
49
49
|
|
|
50
50
|
return tracing.withSpan(name, async (span) => {
|
|
51
51
|
const context = new QueryContext([this._tableName], this._tableConfigMap);
|
package/src/database.js
CHANGED
|
@@ -3,55 +3,41 @@ const { AsyncLocalStorage } = require("async_hooks");
|
|
|
3
3
|
const pg = require("pg");
|
|
4
4
|
const { PROTO_ACTION_TYPES } = require("./consts");
|
|
5
5
|
|
|
6
|
-
//
|
|
7
|
-
//
|
|
8
|
-
//
|
|
9
|
-
//
|
|
10
|
-
|
|
6
|
+
// withDatabase is responsible for setting the correct database client in our AsyncLocalStorage
|
|
7
|
+
// so that the the code in a custom function uses the correct client.
|
|
8
|
+
// For GET and LIST action types, no transaction is used, but for
|
|
9
|
+
// actions that mutate data such as CREATE, DELETE & UPDATE, all of the code inside
|
|
10
|
+
// the user's custom function is wrapped in a transaction so we can rollback
|
|
11
|
+
// the transaction if something goes wrong.
|
|
12
|
+
// withDatabase shouldn't be exposed in the public api of the sdk
|
|
13
|
+
async function withDatabase(db, actionType, cb) {
|
|
14
|
+
let requiresTransaction = true;
|
|
15
|
+
|
|
11
16
|
switch (actionType) {
|
|
12
17
|
case PROTO_ACTION_TYPES.GET:
|
|
13
18
|
case PROTO_ACTION_TYPES.LIST:
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
});
|
|
17
|
-
default:
|
|
18
|
-
return db.transaction().execute(async (transaction) => {
|
|
19
|
-
return dbInstance.run(transaction, async () => {
|
|
20
|
-
return cb({ transaction });
|
|
21
|
-
});
|
|
22
|
-
});
|
|
19
|
+
requiresTransaction = false;
|
|
20
|
+
break;
|
|
23
21
|
}
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
function mustEnv(key) {
|
|
27
|
-
const v = process.env[key];
|
|
28
|
-
if (!v) {
|
|
29
|
-
throw new Error(`expected environment variable ${key} to be set`);
|
|
30
|
-
}
|
|
31
|
-
return v;
|
|
32
|
-
}
|
|
33
22
|
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
return new PostgresDialect({
|
|
39
|
-
pool: new pg.Pool({
|
|
40
|
-
connectionString: mustEnv("KEEL_DB_CONN"),
|
|
41
|
-
}),
|
|
23
|
+
if (requiresTransaction) {
|
|
24
|
+
return db.transaction().execute(async (transaction) => {
|
|
25
|
+
return dbInstance.run(transaction, async () => {
|
|
26
|
+
return cb({ transaction });
|
|
42
27
|
});
|
|
43
|
-
|
|
44
|
-
default:
|
|
45
|
-
throw Error("unexpected KEEL_DB_CONN_TYPE: " + dbConnType);
|
|
28
|
+
});
|
|
46
29
|
}
|
|
30
|
+
|
|
31
|
+
return dbInstance.run(db, async () => {
|
|
32
|
+
return cb({ transaction: db });
|
|
33
|
+
});
|
|
47
34
|
}
|
|
48
35
|
|
|
49
36
|
let db = null;
|
|
50
37
|
const dbInstance = new AsyncLocalStorage();
|
|
51
38
|
|
|
52
|
-
//
|
|
53
|
-
|
|
54
|
-
function getDatabase() {
|
|
39
|
+
// useDatabase will retrieve the database client set by withDatabase from the local storage
|
|
40
|
+
function useDatabase() {
|
|
55
41
|
let fromStore = dbInstance.getStore();
|
|
56
42
|
if (fromStore) {
|
|
57
43
|
return fromStore;
|
|
@@ -61,6 +47,9 @@ function getDatabase() {
|
|
|
61
47
|
return db;
|
|
62
48
|
}
|
|
63
49
|
|
|
50
|
+
// todo: ideally we wouldn't want to give you a fresh Kysely instance here if nothing
|
|
51
|
+
// has been found in the context, but the @teamkeel/testing package needs some restructuring
|
|
52
|
+
// to allow for the database client to be set in the store so that this method can throw an error at this line instead of returning a fresh kysely instance.
|
|
64
53
|
db = new Kysely({
|
|
65
54
|
dialect: getDialect(),
|
|
66
55
|
log(event) {
|
|
@@ -76,5 +65,28 @@ function getDatabase() {
|
|
|
76
65
|
return db;
|
|
77
66
|
}
|
|
78
67
|
|
|
79
|
-
|
|
80
|
-
|
|
68
|
+
function mustEnv(key) {
|
|
69
|
+
const v = process.env[key];
|
|
70
|
+
if (!v) {
|
|
71
|
+
throw new Error(`expected environment variable ${key} to be set`);
|
|
72
|
+
}
|
|
73
|
+
return v;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
function getDialect() {
|
|
77
|
+
const dbConnType = process.env["KEEL_DB_CONN_TYPE"];
|
|
78
|
+
switch (dbConnType) {
|
|
79
|
+
case "pg":
|
|
80
|
+
return new PostgresDialect({
|
|
81
|
+
pool: new pg.Pool({
|
|
82
|
+
connectionString: mustEnv("KEEL_DB_CONN"),
|
|
83
|
+
}),
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
default:
|
|
87
|
+
throw Error("unexpected KEEL_DB_CONN_TYPE: " + dbConnType);
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
module.exports.useDatabase = useDatabase;
|
|
92
|
+
module.exports.withDatabase = withDatabase;
|
package/src/handleRequest.js
CHANGED
|
@@ -3,7 +3,7 @@ const {
|
|
|
3
3
|
createJSONRPCSuccessResponse,
|
|
4
4
|
JSONRPCErrorCode,
|
|
5
5
|
} = require("json-rpc-2.0");
|
|
6
|
-
const {
|
|
6
|
+
const { useDatabase } = require("./database");
|
|
7
7
|
const { tryExecuteFunction } = require("./tryExecuteFunction");
|
|
8
8
|
const { errorToJSONRPCResponse, RuntimeErrors } = require("./errors");
|
|
9
9
|
const opentelemetry = require("@opentelemetry/api");
|
|
@@ -57,7 +57,7 @@ async function handleRequest(request, config) {
|
|
|
57
57
|
? true
|
|
58
58
|
: null;
|
|
59
59
|
|
|
60
|
-
const db =
|
|
60
|
+
const db = useDatabase();
|
|
61
61
|
const customFunction = functions[request.method];
|
|
62
62
|
|
|
63
63
|
const result = await tryExecuteFunction(
|
|
@@ -3,7 +3,7 @@ import { sql } from "kysely";
|
|
|
3
3
|
import { handleRequest, RuntimeErrors } from "./handleRequest";
|
|
4
4
|
import { test, expect, beforeEach, describe } from "vitest";
|
|
5
5
|
import { ModelAPI } from "./ModelAPI";
|
|
6
|
-
import {
|
|
6
|
+
import { useDatabase } from "./database";
|
|
7
7
|
const { Permissions } = require("./permissions");
|
|
8
8
|
import { PROTO_ACTION_TYPES } from "./consts";
|
|
9
9
|
import KSUID from "ksuid";
|
|
@@ -188,7 +188,7 @@ describe("ModelAPI error handling", () => {
|
|
|
188
188
|
process.env.KEEL_DB_CONN_TYPE = "pg";
|
|
189
189
|
process.env.KEEL_DB_CONN = `postgresql://postgres:postgres@localhost:5432/functions-runtime`;
|
|
190
190
|
|
|
191
|
-
db =
|
|
191
|
+
db = useDatabase();
|
|
192
192
|
|
|
193
193
|
await sql`
|
|
194
194
|
DROP TABLE IF EXISTS post;
|
package/src/index.js
CHANGED
|
@@ -2,7 +2,7 @@ const { ModelAPI } = require("./ModelAPI");
|
|
|
2
2
|
const { RequestHeaders } = require("./RequestHeaders");
|
|
3
3
|
const { handleRequest } = require("./handleRequest");
|
|
4
4
|
const KSUID = require("ksuid");
|
|
5
|
-
const {
|
|
5
|
+
const { useDatabase } = require("./database");
|
|
6
6
|
const {
|
|
7
7
|
Permissions,
|
|
8
8
|
PERMISSION_STATE,
|
|
@@ -14,7 +14,7 @@ module.exports = {
|
|
|
14
14
|
ModelAPI,
|
|
15
15
|
RequestHeaders,
|
|
16
16
|
handleRequest,
|
|
17
|
-
|
|
17
|
+
useDatabase,
|
|
18
18
|
Permissions,
|
|
19
19
|
PERMISSION_STATE,
|
|
20
20
|
checkBuiltInPermissions,
|
package/src/permissions.test.js
CHANGED
|
@@ -6,7 +6,7 @@ const {
|
|
|
6
6
|
checkBuiltInPermissions,
|
|
7
7
|
} = require("./permissions");
|
|
8
8
|
|
|
9
|
-
import {
|
|
9
|
+
import { useDatabase } from "./database";
|
|
10
10
|
|
|
11
11
|
import { beforeEach, describe, expect, test } from "vitest";
|
|
12
12
|
|
|
@@ -15,7 +15,7 @@ process.env.KEEL_DB_CONN = `postgresql://postgres:postgres@localhost:5432/functi
|
|
|
15
15
|
|
|
16
16
|
let permissions;
|
|
17
17
|
let ctx = {};
|
|
18
|
-
let db =
|
|
18
|
+
let db = useDatabase();
|
|
19
19
|
|
|
20
20
|
describe("explicit", () => {
|
|
21
21
|
beforeEach(() => {
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
const {
|
|
1
|
+
const { withDatabase } = require("./database");
|
|
2
2
|
const {
|
|
3
3
|
withPermissions,
|
|
4
4
|
PERMISSION_STATE,
|
|
@@ -16,7 +16,7 @@ function tryExecuteFunction(
|
|
|
16
16
|
const actionType = actionTypes[request.method];
|
|
17
17
|
|
|
18
18
|
return withPermissions(permitted, async ({ getPermissionState }) => {
|
|
19
|
-
return
|
|
19
|
+
return withDatabase(db, actionType, async ({ transaction }) => {
|
|
20
20
|
const fnResult = await cb();
|
|
21
21
|
|
|
22
22
|
// api.permissions maintains an internal state of whether the current operation has been *explicitly* permitted/denied by the user in the course of their custom function, or if execution has already been permitted by a role based permission (evaluated in the main runtime).
|