@teamkeel/functions-runtime 0.393.1 → 0.393.3
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 +2 -3
- package/src/database.js +55 -21
- package/src/handleJob.js +8 -2
- package/src/handleRequest.js +8 -2
- package/src/handleSubscriber.js +8 -2
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@teamkeel/functions-runtime",
|
|
3
|
-
"version": "0.393.
|
|
3
|
+
"version": "0.393.3",
|
|
4
4
|
"description": "Internal package used by @teamkeel/sdk",
|
|
5
5
|
"main": "src/index.js",
|
|
6
6
|
"scripts": {
|
|
@@ -21,7 +21,7 @@
|
|
|
21
21
|
"dependencies": {
|
|
22
22
|
"@aws-sdk/client-s3": "^3.617.0",
|
|
23
23
|
"@aws-sdk/credential-providers": "^3.617.0",
|
|
24
|
-
"@neondatabase/serverless": "^0.9.
|
|
24
|
+
"@neondatabase/serverless": "^0.9.4",
|
|
25
25
|
"@opentelemetry/api": "^1.7.0",
|
|
26
26
|
"@opentelemetry/exporter-trace-otlp-proto": "^0.46.0",
|
|
27
27
|
"@opentelemetry/resources": "^1.19.0",
|
|
@@ -31,7 +31,6 @@
|
|
|
31
31
|
"json-rpc-2.0": "^1.7.0",
|
|
32
32
|
"ksuid": "^3.0.0",
|
|
33
33
|
"kysely": "^0.25.0",
|
|
34
|
-
"kysely-neon": "^1.3.0",
|
|
35
34
|
"pg": "^8.11.3",
|
|
36
35
|
"traceparent": "^1.0.0",
|
|
37
36
|
"ws": "^8.17.1"
|
package/src/database.js
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
const { Kysely, PostgresDialect, CamelCasePlugin } = require("kysely");
|
|
2
|
+
const neonserverless = require("@neondatabase/serverless");
|
|
2
3
|
const { AsyncLocalStorage } = require("async_hooks");
|
|
3
4
|
const { AuditContextPlugin } = require("./auditing");
|
|
4
5
|
const pg = require("pg");
|
|
5
6
|
const { PROTO_ACTION_TYPES } = require("./consts");
|
|
6
7
|
const { withSpan } = require("./tracing");
|
|
7
|
-
const { NeonDialect } = require("kysely-neon");
|
|
8
8
|
const ws = require("ws");
|
|
9
9
|
|
|
10
10
|
// withDatabase is responsible for setting the correct database client in our AsyncLocalStorage
|
|
@@ -43,10 +43,11 @@ async function withDatabase(db, actionType, cb) {
|
|
|
43
43
|
});
|
|
44
44
|
}
|
|
45
45
|
|
|
46
|
-
let db = null;
|
|
47
|
-
|
|
48
46
|
const dbInstance = new AsyncLocalStorage();
|
|
49
47
|
|
|
48
|
+
// used to establish a singleton for our vitest environment
|
|
49
|
+
let vitestDb = null;
|
|
50
|
+
|
|
50
51
|
// useDatabase will retrieve the database client set by withDatabase from the local storage
|
|
51
52
|
function useDatabase() {
|
|
52
53
|
// retrieve the instance of the database client from the store which is aware of
|
|
@@ -61,7 +62,10 @@ function useDatabase() {
|
|
|
61
62
|
// which covers any test files ending in *.test.ts. Custom function code runs in a different node process which will not have this environment variable. Tests written using our testing
|
|
62
63
|
// framework call actions (and in turn custom function code) over http using the ActionExecutor class
|
|
63
64
|
if ("NODE_ENV" in process.env && process.env.NODE_ENV == "test") {
|
|
64
|
-
|
|
65
|
+
if (!vitestDb) {
|
|
66
|
+
vitestDb = createDatabaseClient();
|
|
67
|
+
}
|
|
68
|
+
return vitestDb;
|
|
65
69
|
}
|
|
66
70
|
|
|
67
71
|
// If we've gotten to this point, then we know that we are in a custom function runtime server
|
|
@@ -69,17 +73,11 @@ function useDatabase() {
|
|
|
69
73
|
throw new Error("useDatabase must be called within a function");
|
|
70
74
|
}
|
|
71
75
|
|
|
72
|
-
//
|
|
76
|
+
// createDatabaseClient will return a brand new instance of Kysely. Every instance of Kysely
|
|
73
77
|
// represents an individual connection to the database.
|
|
74
78
|
// not to be exported externally from our sdk - consumers should use useDatabase
|
|
75
|
-
function
|
|
76
|
-
|
|
77
|
-
// as a module scope variable.
|
|
78
|
-
if (db) {
|
|
79
|
-
return db;
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
db = new Kysely({
|
|
79
|
+
function createDatabaseClient() {
|
|
80
|
+
const db = new Kysely({
|
|
83
81
|
dialect: getDialect(),
|
|
84
82
|
plugins: [
|
|
85
83
|
// ensures that the audit context data is written to Postgres configuration parameters
|
|
@@ -107,7 +105,18 @@ function getDatabaseClient() {
|
|
|
107
105
|
class InstrumentedPool extends pg.Pool {
|
|
108
106
|
async connect(...args) {
|
|
109
107
|
const _super = super.connect.bind(this);
|
|
110
|
-
return withSpan("Database Connect", function () {
|
|
108
|
+
return withSpan("Database Connect", function (span) {
|
|
109
|
+
span.setAttribute("dialect", process.env["KEEL_DB_CONN_TYPE"]);
|
|
110
|
+
return _super(...args);
|
|
111
|
+
});
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
class InstrumentedNeonServerlessPool extends neonserverless.Pool {
|
|
116
|
+
async connect(...args) {
|
|
117
|
+
const _super = super.connect.bind(this);
|
|
118
|
+
return withSpan("Database Connect", function (span) {
|
|
119
|
+
span.setAttribute("dialect", process.env["KEEL_DB_CONN_TYPE"]);
|
|
111
120
|
return _super(...args);
|
|
112
121
|
});
|
|
113
122
|
}
|
|
@@ -134,6 +143,7 @@ class InstrumentedClient extends pg.Client {
|
|
|
134
143
|
return withSpan(spanName, function (span) {
|
|
135
144
|
if (sqlAttribute) {
|
|
136
145
|
span.setAttribute("sql", args[0]);
|
|
146
|
+
span.setAttribute("dialect", process.env["KEEL_DB_CONN_TYPE"]);
|
|
137
147
|
}
|
|
138
148
|
return _super(...args);
|
|
139
149
|
});
|
|
@@ -165,14 +175,41 @@ function getDialect() {
|
|
|
165
175
|
// Although I doubt we will run into these freeze/thaw issues if idleTimeoutMillis is always shorter than the
|
|
166
176
|
// time is takes for a lambda to freeze (which is not a constant, but could be as short as several minutes,
|
|
167
177
|
// https://www.pluralsight.com/resources/blog/cloud/how-long-does-aws-lambda-keep-your-idle-functions-around-before-a-cold-start)
|
|
168
|
-
idleTimeoutMillis:
|
|
178
|
+
idleTimeoutMillis: 50000,
|
|
169
179
|
connectionString: mustEnv("KEEL_DB_CONN"),
|
|
170
180
|
}),
|
|
171
181
|
});
|
|
172
182
|
case "neon":
|
|
173
|
-
|
|
183
|
+
neonserverless.neonConfig.webSocketConstructor = ws;
|
|
184
|
+
|
|
185
|
+
const pool = new InstrumentedNeonServerlessPool({
|
|
174
186
|
connectionString: mustEnv("KEEL_DB_CONN"),
|
|
175
|
-
|
|
187
|
+
});
|
|
188
|
+
|
|
189
|
+
pool.on("connect", (client) => {
|
|
190
|
+
const originalQuery = client.query;
|
|
191
|
+
client.query = function (...args) {
|
|
192
|
+
const sql = args[0];
|
|
193
|
+
|
|
194
|
+
let sqlAttribute = false;
|
|
195
|
+
let spanName = txStatements[sql.toLowerCase()];
|
|
196
|
+
if (!spanName) {
|
|
197
|
+
spanName = "Database Query";
|
|
198
|
+
sqlAttribute = true;
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
return withSpan(spanName, function (span) {
|
|
202
|
+
if (sqlAttribute) {
|
|
203
|
+
span.setAttribute("sql", args[0]);
|
|
204
|
+
span.setAttribute("dialect", process.env["KEEL_DB_CONN_TYPE"]);
|
|
205
|
+
}
|
|
206
|
+
return originalQuery.apply(client, args);
|
|
207
|
+
});
|
|
208
|
+
};
|
|
209
|
+
});
|
|
210
|
+
|
|
211
|
+
return new PostgresDialect({
|
|
212
|
+
pool: pool,
|
|
176
213
|
});
|
|
177
214
|
default:
|
|
178
215
|
throw Error("unexpected KEEL_DB_CONN_TYPE: " + dbConnType);
|
|
@@ -187,9 +224,6 @@ function mustEnv(key) {
|
|
|
187
224
|
return v;
|
|
188
225
|
}
|
|
189
226
|
|
|
190
|
-
|
|
191
|
-
getDatabaseClient();
|
|
192
|
-
|
|
193
|
-
module.exports.getDatabaseClient = getDatabaseClient;
|
|
227
|
+
module.exports.createDatabaseClient = createDatabaseClient;
|
|
194
228
|
module.exports.useDatabase = useDatabase;
|
|
195
229
|
module.exports.withDatabase = withDatabase;
|
package/src/handleJob.js
CHANGED
|
@@ -3,7 +3,7 @@ const {
|
|
|
3
3
|
createJSONRPCSuccessResponse,
|
|
4
4
|
JSONRPCErrorCode,
|
|
5
5
|
} = require("json-rpc-2.0");
|
|
6
|
-
const {
|
|
6
|
+
const { createDatabaseClient } = require("./database");
|
|
7
7
|
const { errorToJSONRPCResponse, RuntimeErrors } = require("./errors");
|
|
8
8
|
const opentelemetry = require("@opentelemetry/api");
|
|
9
9
|
const { withSpan } = require("./tracing");
|
|
@@ -25,6 +25,8 @@ async function handleJob(request, config) {
|
|
|
25
25
|
return opentelemetry.context.with(activeContext, () => {
|
|
26
26
|
// Wrapping span for the whole request
|
|
27
27
|
return withSpan(request.method, async (span) => {
|
|
28
|
+
let db = null;
|
|
29
|
+
|
|
28
30
|
try {
|
|
29
31
|
const { createJobContextAPI, jobs } = config;
|
|
30
32
|
|
|
@@ -51,7 +53,7 @@ async function handleJob(request, config) {
|
|
|
51
53
|
? true
|
|
52
54
|
: null;
|
|
53
55
|
|
|
54
|
-
|
|
56
|
+
db = createDatabaseClient();
|
|
55
57
|
const jobFunction = jobs[request.method];
|
|
56
58
|
const actionType = PROTO_ACTION_TYPES.JOB;
|
|
57
59
|
|
|
@@ -89,6 +91,10 @@ async function handleJob(request, config) {
|
|
|
89
91
|
RuntimeErrors.UnknownError,
|
|
90
92
|
message
|
|
91
93
|
);
|
|
94
|
+
} finally {
|
|
95
|
+
if (db) {
|
|
96
|
+
await db.destroy();
|
|
97
|
+
}
|
|
92
98
|
}
|
|
93
99
|
});
|
|
94
100
|
});
|
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 { createDatabaseClient } = require("./database");
|
|
7
7
|
const { tryExecuteFunction } = require("./tryExecuteFunction");
|
|
8
8
|
const { errorToJSONRPCResponse, RuntimeErrors } = require("./errors");
|
|
9
9
|
const opentelemetry = require("@opentelemetry/api");
|
|
@@ -28,6 +28,8 @@ async function handleRequest(request, config) {
|
|
|
28
28
|
return opentelemetry.context.with(activeContext, () => {
|
|
29
29
|
// Wrapping span for the whole request
|
|
30
30
|
return withSpan(request.method, async (span) => {
|
|
31
|
+
let db = null;
|
|
32
|
+
|
|
31
33
|
try {
|
|
32
34
|
const { createContextAPI, functions, permissionFns, actionTypes } =
|
|
33
35
|
config;
|
|
@@ -62,7 +64,7 @@ async function handleRequest(request, config) {
|
|
|
62
64
|
? true
|
|
63
65
|
: null;
|
|
64
66
|
|
|
65
|
-
|
|
67
|
+
db = createDatabaseClient();
|
|
66
68
|
const customFunction = functions[request.method];
|
|
67
69
|
const actionType = actionTypes[request.method];
|
|
68
70
|
|
|
@@ -122,6 +124,10 @@ async function handleRequest(request, config) {
|
|
|
122
124
|
RuntimeErrors.UnknownError,
|
|
123
125
|
message
|
|
124
126
|
);
|
|
127
|
+
} finally {
|
|
128
|
+
if (db) {
|
|
129
|
+
await db.destroy();
|
|
130
|
+
}
|
|
125
131
|
}
|
|
126
132
|
});
|
|
127
133
|
});
|
package/src/handleSubscriber.js
CHANGED
|
@@ -3,7 +3,7 @@ const {
|
|
|
3
3
|
createJSONRPCSuccessResponse,
|
|
4
4
|
JSONRPCErrorCode,
|
|
5
5
|
} = require("json-rpc-2.0");
|
|
6
|
-
const {
|
|
6
|
+
const { createDatabaseClient } = require("./database");
|
|
7
7
|
const { errorToJSONRPCResponse, RuntimeErrors } = require("./errors");
|
|
8
8
|
const opentelemetry = require("@opentelemetry/api");
|
|
9
9
|
const { withSpan } = require("./tracing");
|
|
@@ -24,6 +24,8 @@ async function handleSubscriber(request, config) {
|
|
|
24
24
|
return opentelemetry.context.with(activeContext, () => {
|
|
25
25
|
// Wrapping span for the whole request
|
|
26
26
|
return withSpan(request.method, async (span) => {
|
|
27
|
+
let db = null;
|
|
28
|
+
|
|
27
29
|
try {
|
|
28
30
|
const { createSubscriberContextAPI, subscribers } = config;
|
|
29
31
|
|
|
@@ -45,7 +47,7 @@ async function handleSubscriber(request, config) {
|
|
|
45
47
|
meta: request.meta,
|
|
46
48
|
});
|
|
47
49
|
|
|
48
|
-
|
|
50
|
+
db = createDatabaseClient();
|
|
49
51
|
const subscriberFunction = subscribers[request.method];
|
|
50
52
|
const actionType = PROTO_ACTION_TYPES.SUBSCRIBER;
|
|
51
53
|
|
|
@@ -77,6 +79,10 @@ async function handleSubscriber(request, config) {
|
|
|
77
79
|
RuntimeErrors.UnknownError,
|
|
78
80
|
message
|
|
79
81
|
);
|
|
82
|
+
} finally {
|
|
83
|
+
if (db) {
|
|
84
|
+
await db.destroy();
|
|
85
|
+
}
|
|
80
86
|
}
|
|
81
87
|
});
|
|
82
88
|
});
|