@teamkeel/functions-runtime 0.394.2 → 0.395.0-next.1
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 -1
- package/src/File.js +47 -11
- package/src/auditing.test.js +76 -108
- package/src/database.js +2 -13
- package/src/handleJob.js +3 -1
- package/src/handleRequest.js +12 -1
- package/src/handleSubscriber.js +11 -6
- package/src/index.d.ts +15 -0
- package/src/parsing.test.js +1 -1
- package/src/tryExecuteFunction.js +15 -2
- package/src/tryExecuteJob.js +6 -2
- package/src/tryExecuteSubscriber.js +6 -2
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@teamkeel/functions-runtime",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.395.0-next.1",
|
|
4
4
|
"description": "Internal package used by @teamkeel/sdk",
|
|
5
5
|
"main": "src/index.js",
|
|
6
6
|
"scripts": {
|
|
@@ -21,6 +21,7 @@
|
|
|
21
21
|
"dependencies": {
|
|
22
22
|
"@aws-sdk/client-s3": "^3.637.0",
|
|
23
23
|
"@aws-sdk/credential-providers": "^3.637.0",
|
|
24
|
+
"@aws-sdk/s3-request-presigner": "^3.637.0",
|
|
24
25
|
"@neondatabase/serverless": "^0.9.4",
|
|
25
26
|
"@opentelemetry/api": "^1.7.0",
|
|
26
27
|
"@opentelemetry/exporter-trace-otlp-proto": "^0.46.0",
|
package/src/File.js
CHANGED
|
@@ -4,7 +4,8 @@ const {
|
|
|
4
4
|
GetObjectCommand,
|
|
5
5
|
} = require("@aws-sdk/client-s3");
|
|
6
6
|
const { fromEnv } = require("@aws-sdk/credential-providers");
|
|
7
|
-
const {
|
|
7
|
+
const { getSignedUrl } = require("@aws-sdk/s3-request-presigner");
|
|
8
|
+
const { createDatabaseClient } = require("./database");
|
|
8
9
|
const { DatabaseError } = require("./errors");
|
|
9
10
|
const KSUID = require("ksuid");
|
|
10
11
|
|
|
@@ -105,9 +106,7 @@ class File extends InlineFile {
|
|
|
105
106
|
async read() {
|
|
106
107
|
if (this._contents) {
|
|
107
108
|
const arrayBuffer = await this._contents.arrayBuffer();
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
return buffer;
|
|
109
|
+
return Buffer.from(arrayBuffer);
|
|
111
110
|
}
|
|
112
111
|
|
|
113
112
|
if (isS3Storage()) {
|
|
@@ -122,12 +121,12 @@ class File extends InlineFile {
|
|
|
122
121
|
};
|
|
123
122
|
const command = new GetObjectCommand(params);
|
|
124
123
|
const response = await s3Client.send(command);
|
|
125
|
-
const blob = response.Body.transformToByteArray();
|
|
124
|
+
const blob = await response.Body.transformToByteArray();
|
|
126
125
|
return Buffer.from(blob);
|
|
127
126
|
}
|
|
128
127
|
|
|
129
128
|
// default to db storage
|
|
130
|
-
const db =
|
|
129
|
+
const db = createDatabaseClient();
|
|
131
130
|
|
|
132
131
|
try {
|
|
133
132
|
let query = db
|
|
@@ -139,6 +138,8 @@ class File extends InlineFile {
|
|
|
139
138
|
return row.data;
|
|
140
139
|
} catch (e) {
|
|
141
140
|
throw new DatabaseError(e);
|
|
141
|
+
} finally {
|
|
142
|
+
await db.destroy();
|
|
142
143
|
}
|
|
143
144
|
}
|
|
144
145
|
|
|
@@ -157,12 +158,39 @@ class File extends InlineFile {
|
|
|
157
158
|
return this;
|
|
158
159
|
}
|
|
159
160
|
|
|
161
|
+
async getPresignedUrl() {
|
|
162
|
+
if (isS3Storage()) {
|
|
163
|
+
const s3Client = new S3Client({
|
|
164
|
+
credentials: fromEnv(),
|
|
165
|
+
region: process.env.KEEL_REGION,
|
|
166
|
+
});
|
|
167
|
+
|
|
168
|
+
const command = new GetObjectCommand({
|
|
169
|
+
Bucket: process.env.KEEL_FILES_BUCKET_NAME,
|
|
170
|
+
Key: "files/" + this.key,
|
|
171
|
+
ResponseContentDisposition: `attachment; filename="${encodeURIComponent(
|
|
172
|
+
this.filename
|
|
173
|
+
)}"`,
|
|
174
|
+
});
|
|
175
|
+
|
|
176
|
+
const url = await getSignedUrl(s3Client, command, { expiresIn: 60 * 60 });
|
|
177
|
+
|
|
178
|
+
return new URL(url);
|
|
179
|
+
} else {
|
|
180
|
+
const contents = await this.read();
|
|
181
|
+
const dataurl = `data:${this.contentType};name=${
|
|
182
|
+
this.filename
|
|
183
|
+
};base64,${contents.toString("base64")}`;
|
|
184
|
+
return new URL(dataurl);
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
|
|
160
188
|
toDbRecord() {
|
|
161
189
|
return {
|
|
162
190
|
key: this.key,
|
|
163
191
|
filename: this.filename,
|
|
164
192
|
contentType: this.contentType,
|
|
165
|
-
size: this.
|
|
193
|
+
size: this.size,
|
|
166
194
|
};
|
|
167
195
|
}
|
|
168
196
|
|
|
@@ -188,14 +216,21 @@ async function storeFile(contents, key, filename, contentType, expires) {
|
|
|
188
216
|
Key: "files/" + key,
|
|
189
217
|
Body: contents,
|
|
190
218
|
ContentType: contentType,
|
|
219
|
+
ContentDisposition: `attachment; filename="${encodeURIComponent(
|
|
220
|
+
this.filename
|
|
221
|
+
)}"`,
|
|
191
222
|
Metadata: {
|
|
192
|
-
filename: filename,
|
|
223
|
+
filename: this.filename,
|
|
193
224
|
},
|
|
194
225
|
ACL: "private",
|
|
195
226
|
};
|
|
196
227
|
|
|
197
228
|
if (expires) {
|
|
198
|
-
|
|
229
|
+
if (expires instanceof Date) {
|
|
230
|
+
params.Expires = expires;
|
|
231
|
+
} else {
|
|
232
|
+
console.warn("Invalid expires value. Skipping Expires parameter.");
|
|
233
|
+
}
|
|
199
234
|
}
|
|
200
235
|
|
|
201
236
|
const command = new PutObjectCommand(params);
|
|
@@ -206,8 +241,7 @@ async function storeFile(contents, key, filename, contentType, expires) {
|
|
|
206
241
|
throw error;
|
|
207
242
|
}
|
|
208
243
|
} else {
|
|
209
|
-
|
|
210
|
-
const db = useDatabase();
|
|
244
|
+
const db = createDatabaseClient();
|
|
211
245
|
|
|
212
246
|
try {
|
|
213
247
|
let query = db
|
|
@@ -233,6 +267,8 @@ async function storeFile(contents, key, filename, contentType, expires) {
|
|
|
233
267
|
await query.execute();
|
|
234
268
|
} catch (e) {
|
|
235
269
|
throw new DatabaseError(e);
|
|
270
|
+
} finally {
|
|
271
|
+
await db.destroy();
|
|
236
272
|
}
|
|
237
273
|
}
|
|
238
274
|
}
|
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
|
@@ -3,7 +3,6 @@ const neonserverless = require("@neondatabase/serverless");
|
|
|
3
3
|
const { AsyncLocalStorage } = require("async_hooks");
|
|
4
4
|
const { AuditContextPlugin } = require("./auditing");
|
|
5
5
|
const pg = require("pg");
|
|
6
|
-
const { PROTO_ACTION_TYPES } = require("./consts");
|
|
7
6
|
const { withSpan } = require("./tracing");
|
|
8
7
|
const ws = require("ws");
|
|
9
8
|
|
|
@@ -14,18 +13,7 @@ const ws = require("ws");
|
|
|
14
13
|
// the user's custom function is wrapped in a transaction so we can rollback
|
|
15
14
|
// the transaction if something goes wrong.
|
|
16
15
|
// 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
|
-
|
|
16
|
+
async function withDatabase(db, requiresTransaction, cb) {
|
|
29
17
|
// db.transaction() provides a kysely instance bound to a transaction.
|
|
30
18
|
if (requiresTransaction) {
|
|
31
19
|
return db.transaction().execute(async (transaction) => {
|
|
@@ -70,6 +58,7 @@ function useDatabase() {
|
|
|
70
58
|
|
|
71
59
|
// If we've gotten to this point, then we know that we are in a custom function runtime server
|
|
72
60
|
// context and we haven't been able to retrieve the in-context instance of Kysely, which means we should throw an error.
|
|
61
|
+
console.trace();
|
|
73
62
|
throw new Error("useDatabase must be called within a function");
|
|
74
63
|
}
|
|
75
64
|
|
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);
|
|
@@ -80,6 +90,7 @@ async function handleRequest(request, config) {
|
|
|
80
90
|
return customFunction(ctx, inputs);
|
|
81
91
|
}
|
|
82
92
|
);
|
|
93
|
+
|
|
83
94
|
if (result instanceof Error) {
|
|
84
95
|
span.recordException(result);
|
|
85
96
|
span.setStatus({
|
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
|
@@ -176,6 +176,9 @@ export declare class File extends InlineFile {
|
|
|
176
176
|
get key(): string;
|
|
177
177
|
// Gets size of the file's contents in bytes
|
|
178
178
|
get isPublic(): boolean;
|
|
179
|
+
// Generates a presigned download URL
|
|
180
|
+
getPresignedUrl(): Promise<URL>;
|
|
181
|
+
// Creates a new instance from the database record
|
|
179
182
|
static fromDbRecord(input: FileDbRecord): File;
|
|
180
183
|
// Persists the file
|
|
181
184
|
toDbRecord(): FileDbRecord;
|
|
@@ -224,3 +227,15 @@ export type Errors = {
|
|
|
224
227
|
*/
|
|
225
228
|
Unknown: typeof UnknownError;
|
|
226
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
|
+
};
|
package/src/parsing.test.js
CHANGED
|
@@ -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 = false;
|
|
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 = false;
|
|
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
|
});
|