@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/src/ModelAPI.js CHANGED
@@ -1,4 +1,4 @@
1
- const { getDatabase } = require("./database");
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 = getDatabase();
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 = getDatabase();
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 = getDatabase();
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 = getDatabase();
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 = getDatabase();
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 = getDatabase();
213
+ const db = useDatabase();
214
214
 
215
215
  let builder = db
216
216
  .selectFrom(this._tableName)
@@ -1,7 +1,7 @@
1
1
  import { test, expect, beforeEach } from "vitest";
2
- const { ModelAPI, DatabaseError } = require("./ModelAPI");
2
+ const { ModelAPI } = require("./ModelAPI");
3
3
  const { sql } = require("kysely");
4
- const { getDatabase } = require("./database");
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 = getDatabase();
15
+ const db = useDatabase();
16
16
 
17
17
  await sql`
18
18
  DROP TABLE IF EXISTS post;
@@ -5,8 +5,8 @@ const {
5
5
  applyOrderBy,
6
6
  } = require("./applyAdditionalQueryConstraints");
7
7
  const { applyJoins } = require("./applyJoins");
8
- const { camelCaseObject, upperCamelCase } = require("./casing");
9
- const { getDatabase } = require("./database");
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 = getDatabase();
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
- // withTransaction wraps the containing code with a transaction
7
- // and sets the transaction in the AsyncLocalStorage so consumers further
8
- // down the hierarchy can access the current transaction.
9
- // For read type operations such as list & get, no transaction is used
10
- async function withTransaction(db, actionType, cb) {
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
- return dbInstance.run(db, async () => {
15
- return cb({ transaction: db });
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
- function getDialect() {
35
- const dbConnType = process.env["KEEL_DB_CONN_TYPE"];
36
- switch (dbConnType) {
37
- case "pg":
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
- // getDatabase will first check for an instance of Kysely in AsyncLocalStorage,
53
- // otherwise it will create a new instance and reuse it..
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
- module.exports.getDatabase = getDatabase;
80
- module.exports.withTransaction = withTransaction;
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;
@@ -3,7 +3,7 @@ const {
3
3
  createJSONRPCSuccessResponse,
4
4
  JSONRPCErrorCode,
5
5
  } = require("json-rpc-2.0");
6
- const { getDatabase } = require("./database");
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 = getDatabase();
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 { getDatabase } from "./database";
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 = getDatabase();
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 { getDatabase } = require("./database");
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
- getDatabase,
17
+ useDatabase,
18
18
  Permissions,
19
19
  PERMISSION_STATE,
20
20
  checkBuiltInPermissions,
@@ -6,7 +6,7 @@ const {
6
6
  checkBuiltInPermissions,
7
7
  } = require("./permissions");
8
8
 
9
- import { getDatabase } from "./database";
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 = getDatabase();
18
+ let db = useDatabase();
19
19
 
20
20
  describe("explicit", () => {
21
21
  beforeEach(() => {
@@ -1,4 +1,4 @@
1
- const { withTransaction } = require("./database");
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 withTransaction(db, actionType, async ({ transaction }) => {
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).