@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 +1 -1
- package/src/auditing.test.js +76 -108
- package/src/database.js +2 -12
- package/src/handleJob.js +3 -1
- package/src/handleRequest.js +11 -1
- package/src/handleSubscriber.js +11 -6
- package/src/index.d.ts +12 -0
- package/src/tryExecuteFunction.js +15 -2
- package/src/tryExecuteJob.js +6 -2
- package/src/tryExecuteSubscriber.js +6 -2
package/package.json
CHANGED
package/src/auditing.test.js
CHANGED
|
@@ -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
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
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
|
-
|
|
80
|
-
|
|
76
|
+
expect(await identityIdFromConfigParam(transaction)).toEqual(identityId);
|
|
77
|
+
expect(await identityIdFromConfigParam(db)).toBeNull();
|
|
81
78
|
|
|
82
|
-
|
|
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
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
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
|
-
|
|
116
|
-
|
|
108
|
+
expect(await traceIdFromConfigParam(transaction)).toEqual(traceId);
|
|
109
|
+
expect(await traceIdFromConfigParam(db)).toBeNull();
|
|
117
110
|
|
|
118
|
-
|
|
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
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
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
|
-
|
|
146
|
-
|
|
134
|
+
expect(await identityIdFromConfigParam(sDb)).toBeNull();
|
|
135
|
+
expect(await identityIdFromConfigParam(db)).toBeNull();
|
|
147
136
|
|
|
148
|
-
|
|
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
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
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
|
-
|
|
178
|
-
|
|
162
|
+
expect(await traceIdFromConfigParam(sDb)).toBeNull();
|
|
163
|
+
expect(await traceIdFromConfigParam(db)).toBeNull();
|
|
179
164
|
|
|
180
|
-
|
|
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
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
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
|
-
|
|
240
|
-
|
|
220
|
+
expect(await identityIdFromConfigParam(transaction)).toEqual(identityId);
|
|
221
|
+
expect(await traceIdFromConfigParam(transaction)).toEqual(traceId);
|
|
241
222
|
|
|
242
|
-
|
|
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
|
-
|
|
271
|
-
|
|
272
|
-
|
|
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
|
-
|
|
278
|
-
|
|
254
|
+
expect(await identityIdFromConfigParam(transaction)).toEqual(identityId);
|
|
255
|
+
expect(await traceIdFromConfigParam(transaction)).toEqual(traceId);
|
|
279
256
|
|
|
280
|
-
|
|
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
|
-
|
|
309
|
-
|
|
310
|
-
|
|
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
|
-
|
|
316
|
-
|
|
288
|
+
expect(await identityIdFromConfigParam(transaction)).toEqual(identityId);
|
|
289
|
+
expect(await traceIdFromConfigParam(transaction)).toEqual(traceId);
|
|
317
290
|
|
|
318
|
-
|
|
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
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
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
|
-
|
|
352
|
-
|
|
320
|
+
expect(await identityIdFromConfigParam(transaction)).toEqual(identityId);
|
|
321
|
+
expect(await traceIdFromConfigParam(transaction)).toEqual(traceId);
|
|
353
322
|
|
|
354
|
-
|
|
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,
|
|
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);
|
package/src/handleRequest.js
CHANGED
|
@@ -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
|
-
{
|
|
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);
|
package/src/handleSubscriber.js
CHANGED
|
@@ -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
|
-
|
|
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
|
-
|
|
60
|
-
|
|
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
|
-
|
|
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
|
});
|
package/src/tryExecuteJob.js
CHANGED
|
@@ -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,
|
|
8
|
+
function tryExecuteJob({ db, permitted, request, functionConfig }, cb) {
|
|
9
9
|
return withPermissions(permitted, async ({ getPermissionState }) => {
|
|
10
|
-
|
|
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,
|
|
6
|
-
|
|
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
|
});
|