@teamkeel/functions-runtime 0.395.14 → 0.396.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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@teamkeel/functions-runtime",
3
- "version": "0.395.14",
3
+ "version": "0.396.0",
4
4
  "description": "Internal package used by @teamkeel/sdk",
5
5
  "main": "src/index.js",
6
6
  "scripts": {
@@ -65,23 +65,19 @@ test("auditing - capturing identity id in transaction", async () => {
65
65
 
66
66
  const identityId = request.meta.identity.id;
67
67
 
68
- const row = await withDatabase(
69
- db,
70
- PROTO_ACTION_TYPES.CREATE, // CREATE will ensure a transaction is opened
71
- async ({ transaction }) => {
72
- const row = withAuditContext(request, async () => {
73
- return await personAPI.create({
74
- id: KSUID.randomSync().string,
75
- name: "James",
76
- });
68
+ const row = await withDatabase(db, true, async ({ transaction }) => {
69
+ const row = withAuditContext(request, async () => {
70
+ return await personAPI.create({
71
+ id: KSUID.randomSync().string,
72
+ name: "James",
77
73
  });
74
+ });
78
75
 
79
- expect(await identityIdFromConfigParam(transaction)).toEqual(identityId);
80
- expect(await identityIdFromConfigParam(db)).toBeNull();
76
+ expect(await identityIdFromConfigParam(transaction)).toEqual(identityId);
77
+ expect(await identityIdFromConfigParam(db)).toBeNull();
81
78
 
82
- return row;
83
- }
84
- );
79
+ return row;
80
+ });
85
81
 
86
82
  expect(row.name).toEqual("James");
87
83
  expect(await identityIdFromConfigParam(db)).toBeNull();
@@ -101,23 +97,19 @@ test("auditing - capturing tracing in transaction", async () => {
101
97
  request.meta.tracing.traceparent
102
98
  ).traceId;
103
99
 
104
- const row = await withDatabase(
105
- db,
106
- PROTO_ACTION_TYPES.CREATE, // CREATE will ensure a transaction is opened
107
- async ({ transaction }) => {
108
- const row = withAuditContext(request, async () => {
109
- return await personAPI.create({
110
- id: KSUID.randomSync().string,
111
- name: "Jim",
112
- });
100
+ const row = await withDatabase(db, true, async ({ transaction }) => {
101
+ const row = withAuditContext(request, async () => {
102
+ return await personAPI.create({
103
+ id: KSUID.randomSync().string,
104
+ name: "Jim",
113
105
  });
106
+ });
114
107
 
115
- expect(await traceIdFromConfigParam(transaction)).toEqual(traceId);
116
- expect(await traceIdFromConfigParam(db)).toBeNull();
108
+ expect(await traceIdFromConfigParam(transaction)).toEqual(traceId);
109
+ expect(await traceIdFromConfigParam(db)).toBeNull();
117
110
 
118
- return row;
119
- }
120
- );
111
+ return row;
112
+ });
121
113
 
122
114
  expect(row.name).toEqual("Jim");
123
115
  expect(await traceIdFromConfigParam(db)).toBeNull();
@@ -131,23 +123,19 @@ test("auditing - capturing identity id without transaction", async () => {
131
123
  },
132
124
  };
133
125
 
134
- const row = await withDatabase(
135
- db,
136
- PROTO_ACTION_TYPES.GET, // GET will _not_ open a transaction
137
- async ({ sDb }) => {
138
- const row = withAuditContext(request, async () => {
139
- return await personAPI.create({
140
- id: KSUID.randomSync().string,
141
- name: "James",
142
- });
126
+ const row = await withDatabase(db, false, async ({ sDb }) => {
127
+ const row = withAuditContext(request, async () => {
128
+ return await personAPI.create({
129
+ id: KSUID.randomSync().string,
130
+ name: "James",
143
131
  });
132
+ });
144
133
 
145
- expect(await identityIdFromConfigParam(sDb)).toBeNull();
146
- expect(await identityIdFromConfigParam(db)).toBeNull();
134
+ expect(await identityIdFromConfigParam(sDb)).toBeNull();
135
+ expect(await identityIdFromConfigParam(db)).toBeNull();
147
136
 
148
- return row;
149
- }
150
- );
137
+ return row;
138
+ });
151
139
 
152
140
  expect(row.name).toEqual("James");
153
141
  expect(await identityIdFromConfigParam(db)).toBeNull();
@@ -163,23 +151,19 @@ test("auditing - capturing tracing without transaction", async () => {
163
151
  },
164
152
  };
165
153
 
166
- const row = await withDatabase(
167
- db,
168
- PROTO_ACTION_TYPES.GET, // GET will _not_ open a transaction
169
- async ({ sDb }) => {
170
- const row = withAuditContext(request, async () => {
171
- return await personAPI.create({
172
- id: KSUID.randomSync().string,
173
- name: "Jim",
174
- });
154
+ const row = await withDatabase(db, false, async ({ sDb }) => {
155
+ const row = withAuditContext(request, async () => {
156
+ return await personAPI.create({
157
+ id: KSUID.randomSync().string,
158
+ name: "Jim",
175
159
  });
160
+ });
176
161
 
177
- expect(await traceIdFromConfigParam(sDb)).toBeNull();
178
- expect(await traceIdFromConfigParam(db)).toBeNull();
162
+ expect(await traceIdFromConfigParam(sDb)).toBeNull();
163
+ expect(await traceIdFromConfigParam(db)).toBeNull();
179
164
 
180
- return row;
181
- }
182
- );
165
+ return row;
166
+ });
183
167
 
184
168
  expect(row.name).toEqual("Jim");
185
169
  expect(await traceIdFromConfigParam(db)).toBeNull();
@@ -225,23 +209,19 @@ test("auditing - ModelAPI.create", async () => {
225
209
  request.meta.tracing.traceparent
226
210
  ).traceId;
227
211
 
228
- const row = await withDatabase(
229
- db,
230
- PROTO_ACTION_TYPES.CREATE,
231
- async ({ transaction }) => {
232
- const row = withAuditContext(request, async () => {
233
- return await personAPI.create({
234
- id: KSUID.randomSync().string,
235
- name: "Jake",
236
- });
212
+ const row = await withDatabase(db, true, async ({ transaction }) => {
213
+ const row = withAuditContext(request, async () => {
214
+ return await personAPI.create({
215
+ id: KSUID.randomSync().string,
216
+ name: "Jake",
237
217
  });
218
+ });
238
219
 
239
- expect(await identityIdFromConfigParam(transaction)).toEqual(identityId);
240
- expect(await traceIdFromConfigParam(transaction)).toEqual(traceId);
220
+ expect(await identityIdFromConfigParam(transaction)).toEqual(identityId);
221
+ expect(await traceIdFromConfigParam(transaction)).toEqual(traceId);
241
222
 
242
- return row;
243
- }
244
- );
223
+ return row;
224
+ });
245
225
 
246
226
  expect(row.name).toEqual("Jake");
247
227
  });
@@ -266,20 +246,16 @@ test("auditing - ModelAPI.update", async () => {
266
246
  name: "Jake",
267
247
  });
268
248
 
269
- const row = await withDatabase(
270
- db,
271
- PROTO_ACTION_TYPES.CREATE,
272
- async ({ transaction }) => {
273
- const row = withAuditContext(request, async () => {
274
- return await personAPI.update({ id: created.id }, { name: "Jim" });
275
- });
249
+ const row = await withDatabase(db, true, async ({ transaction }) => {
250
+ const row = withAuditContext(request, async () => {
251
+ return await personAPI.update({ id: created.id }, { name: "Jim" });
252
+ });
276
253
 
277
- expect(await identityIdFromConfigParam(transaction)).toEqual(identityId);
278
- expect(await traceIdFromConfigParam(transaction)).toEqual(traceId);
254
+ expect(await identityIdFromConfigParam(transaction)).toEqual(identityId);
255
+ expect(await traceIdFromConfigParam(transaction)).toEqual(traceId);
279
256
 
280
- return row;
281
- }
282
- );
257
+ return row;
258
+ });
283
259
 
284
260
  expect(row.name).toEqual("Jim");
285
261
  });
@@ -304,20 +280,16 @@ test("auditing - ModelAPI.delete", async () => {
304
280
  name: "Jake",
305
281
  });
306
282
 
307
- const row = await withDatabase(
308
- db,
309
- PROTO_ACTION_TYPES.CREATE,
310
- async ({ transaction }) => {
311
- const row = withAuditContext(request, async () => {
312
- return await personAPI.delete({ id: created.id });
313
- });
283
+ const row = await withDatabase(db, true, async ({ transaction }) => {
284
+ const row = withAuditContext(request, async () => {
285
+ return await personAPI.delete({ id: created.id });
286
+ });
314
287
 
315
- expect(await identityIdFromConfigParam(transaction)).toEqual(identityId);
316
- expect(await traceIdFromConfigParam(transaction)).toEqual(traceId);
288
+ expect(await identityIdFromConfigParam(transaction)).toEqual(identityId);
289
+ expect(await traceIdFromConfigParam(transaction)).toEqual(traceId);
317
290
 
318
- return row;
319
- }
320
- );
291
+ return row;
292
+ });
321
293
 
322
294
  expect(row).toEqual(created.id);
323
295
  });
@@ -337,23 +309,19 @@ test("auditing - identity id and trace id fields dropped from result", async ()
337
309
  request.meta.tracing.traceparent
338
310
  ).traceId;
339
311
 
340
- const row = await withDatabase(
341
- db,
342
- PROTO_ACTION_TYPES.CREATE,
343
- async ({ transaction }) => {
344
- const row = withAuditContext(request, async () => {
345
- return await personAPI.create({
346
- id: KSUID.randomSync().string,
347
- name: "Jake",
348
- });
312
+ const row = await withDatabase(db, true, async ({ transaction }) => {
313
+ const row = withAuditContext(request, async () => {
314
+ return await personAPI.create({
315
+ id: KSUID.randomSync().string,
316
+ name: "Jake",
349
317
  });
318
+ });
350
319
 
351
- expect(await identityIdFromConfigParam(transaction)).toEqual(identityId);
352
- expect(await traceIdFromConfigParam(transaction)).toEqual(traceId);
320
+ expect(await identityIdFromConfigParam(transaction)).toEqual(identityId);
321
+ expect(await traceIdFromConfigParam(transaction)).toEqual(traceId);
353
322
 
354
- return row;
355
- }
356
- );
323
+ return row;
324
+ });
357
325
 
358
326
  expect(row.name).toEqual("Jake");
359
327
  expect(row.keelIdentityId).toBeUndefined();
package/src/database.js CHANGED
@@ -14,18 +14,7 @@ const ws = require("ws");
14
14
  // the user's custom function is wrapped in a transaction so we can rollback
15
15
  // the transaction if something goes wrong.
16
16
  // withDatabase shouldn't be exposed in the public api of the sdk
17
- async function withDatabase(db, actionType, cb) {
18
- let requiresTransaction = true;
19
-
20
- switch (actionType) {
21
- case PROTO_ACTION_TYPES.SUBSCRIBER:
22
- case PROTO_ACTION_TYPES.JOB:
23
- case PROTO_ACTION_TYPES.GET:
24
- case PROTO_ACTION_TYPES.LIST:
25
- requiresTransaction = false;
26
- break;
27
- }
28
-
17
+ async function withDatabase(db, requiresTransaction, cb) {
29
18
  // db.transaction() provides a kysely instance bound to a transaction.
30
19
  if (requiresTransaction) {
31
20
  return db.transaction().execute(async (transaction) => {
@@ -185,6 +174,7 @@ function getDialect() {
185
174
 
186
175
  const pool = new InstrumentedNeonServerlessPool({
187
176
  connectionString: mustEnv("KEEL_DB_CONN"),
177
+ max: 1, // Limit to 1 connection per Lambda instance
188
178
  });
189
179
 
190
180
  pool.on("connect", (client) => {
package/src/handleJob.js CHANGED
@@ -57,8 +57,10 @@ async function handleJob(request, config) {
57
57
  const jobFunction = jobs[request.method];
58
58
  const actionType = PROTO_ACTION_TYPES.JOB;
59
59
 
60
+ const functionConfig = jobFunction?.config ?? {};
61
+
60
62
  await tryExecuteJob(
61
- { request, permitted, db, actionType },
63
+ { request, permitted, db, actionType, functionConfig },
62
64
  async () => {
63
65
  // parse request params to convert objects into rich field types (e.g. InlineFile)
64
66
  const inputs = parseInputs(request.params);
@@ -68,8 +68,18 @@ async function handleRequest(request, config) {
68
68
  const customFunction = functions[request.method];
69
69
  const actionType = actionTypes[request.method];
70
70
 
71
+ const functionConfig = customFunction?.config ?? {};
72
+
71
73
  const result = await tryExecuteFunction(
72
- { request, ctx, permitted, db, permissionFns, actionType },
74
+ {
75
+ request,
76
+ ctx,
77
+ permitted,
78
+ db,
79
+ permissionFns,
80
+ actionType,
81
+ functionConfig,
82
+ },
73
83
  async () => {
74
84
  // parse request params to convert objects into rich field types (e.g. InlineFile)
75
85
  const inputs = parseInputs(request.params);
@@ -52,13 +52,18 @@ async function handleSubscriber(request, config) {
52
52
  const subscriberFunction = subscribers[request.method];
53
53
  const actionType = PROTO_ACTION_TYPES.SUBSCRIBER;
54
54
 
55
- await tryExecuteSubscriber({ request, db, actionType }, async () => {
56
- // parse request params to convert objects into rich field types (e.g. InlineFile)
57
- const inputs = parseInputs(request.params);
55
+ const functionConfig = subscriberFunction?.config ?? {};
58
56
 
59
- // Return the subscriber function to the containing tryExecuteSubscriber block
60
- return subscriberFunction(ctx, inputs);
61
- });
57
+ await tryExecuteSubscriber(
58
+ { request, db, actionType, functionConfig },
59
+ async () => {
60
+ // parse request params to convert objects into rich field types (e.g. InlineFile)
61
+ const inputs = parseInputs(request.params);
62
+
63
+ // Return the subscriber function to the containing tryExecuteSubscriber block
64
+ return subscriberFunction(ctx, inputs);
65
+ }
66
+ );
62
67
 
63
68
  return createJSONRPCSuccessResponse(request.id, null);
64
69
  } catch (e) {
package/src/index.d.ts CHANGED
@@ -227,3 +227,15 @@ export type Errors = {
227
227
  */
228
228
  Unknown: typeof UnknownError;
229
229
  };
230
+
231
+ export type FunctionConfig = {
232
+ /**
233
+ * All DB calls within the function will be executed within a transaction.
234
+ * The transaction is rolled back if the function throws an error.
235
+ */
236
+ dbTransaction?: boolean;
237
+ };
238
+
239
+ export type FuncWithConfig<T> = T & {
240
+ config: FunctionConfig;
241
+ };
@@ -11,11 +11,24 @@ const { PROTO_ACTION_TYPES } = require("./consts");
11
11
  // tryExecuteFunction will create a new database transaction around a function call
12
12
  // and handle any permissions checks. If a permission check fails, then an Error will be thrown and the catch block will be hit.
13
13
  function tryExecuteFunction(
14
- { request, db, permitted, permissionFns, actionType, ctx },
14
+ { request, db, permitted, permissionFns, actionType, ctx, functionConfig },
15
15
  cb
16
16
  ) {
17
17
  return withPermissions(permitted, async ({ getPermissionState }) => {
18
- return withDatabase(db, actionType, async ({ transaction }) => {
18
+ let requiresTransaction = true;
19
+ switch (actionType) {
20
+ case PROTO_ACTION_TYPES.GET:
21
+ case PROTO_ACTION_TYPES.LIST:
22
+ case PROTO_ACTION_TYPES.READ:
23
+ requiresTransaction = false;
24
+ break;
25
+ }
26
+
27
+ if (functionConfig?.dbTransaction !== undefined) {
28
+ requiresTransaction = functionConfig.dbTransaction;
29
+ }
30
+
31
+ return withDatabase(db, requiresTransaction, async ({ transaction }) => {
19
32
  const fnResult = await withAuditContext(request, async () => {
20
33
  return cb();
21
34
  });
@@ -5,9 +5,13 @@ const { PermissionError } = require("./errors");
5
5
 
6
6
  // tryExecuteJob will create a new database transaction around a function call
7
7
  // and handle any permissions checks. If a permission check fails, then an Error will be thrown and the catch block will be hit.
8
- function tryExecuteJob({ db, permitted, actionType, request }, cb) {
8
+ function tryExecuteJob({ db, permitted, request, functionConfig }, cb) {
9
9
  return withPermissions(permitted, async ({ getPermissionState }) => {
10
- return withDatabase(db, actionType, async () => {
10
+ let requiresTransaction = true;
11
+ if (functionConfig?.dbTransaction !== undefined) {
12
+ requiresTransaction = functionConfig.dbTransaction;
13
+ }
14
+ return withDatabase(db, requiresTransaction, async () => {
11
15
  await withAuditContext(request, async () => {
12
16
  return cb();
13
17
  });
@@ -2,8 +2,12 @@ const { withDatabase } = require("./database");
2
2
  const { withAuditContext } = require("./auditing");
3
3
 
4
4
  // tryExecuteSubscriber will create a new database connection and execute the function call.
5
- function tryExecuteSubscriber({ request, db, actionType }, cb) {
6
- return withDatabase(db, actionType, async () => {
5
+ function tryExecuteSubscriber({ request, db, functionConfig }, cb) {
6
+ let requiresTransaction = true;
7
+ if (functionConfig?.dbTransaction !== undefined) {
8
+ requiresTransaction = functionConfig.dbTransaction;
9
+ }
10
+ return withDatabase(db, requiresTransaction, async () => {
7
11
  await withAuditContext(request, async () => {
8
12
  return cb();
9
13
  });