@teamkeel/functions-runtime 0.366.0-auditident7 → 0.366.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.366.0-auditident7",
3
+ "version": "0.366.0",
4
4
  "description": "Internal package used by @teamkeel/sdk",
5
5
  "main": "src/index.js",
6
6
  "scripts": {
package/src/ModelAPI.js CHANGED
@@ -1,10 +1,8 @@
1
1
  const { useDatabase } = require("./database");
2
- const { getAuditContext } = require("./auditing");
3
2
  const { QueryBuilder } = require("./QueryBuilder");
4
3
  const { QueryContext } = require("./QueryContext");
5
4
  const { applyWhereConditions } = require("./applyWhereConditions");
6
5
  const { applyJoins } = require("./applyJoins");
7
- const { sql } = require("kysely");
8
6
 
9
7
  const {
10
8
  applyLimit,
@@ -50,52 +48,12 @@ class ModelAPI {
50
48
  this._modelName = upperCamelCase(this._tableName);
51
49
  }
52
50
 
53
- // execute sets the audit context in the database and then runs individual
54
- // database statements within a transaction if one hasn't been opened, which
55
- // is necessary because the audit parameters will only be available within transactions.
56
- async #execute(fn) {
57
- const db = useDatabase();
58
-
59
- try {
60
- if (db.isTransaction) {
61
- await this.#setAuditParameters(db);
62
- return await fn(db);
63
- } else {
64
- return await db.transaction().execute(async (transaction) => {
65
- await this.#setAuditParameters(transaction);
66
- return fn(transaction);
67
- });
68
- }
69
- } catch (e) {
70
- throw new DatabaseError(e);
71
- }
72
- }
73
-
74
- // setAuditParameters sets audit context data to configuration parameters
75
- // in the database so that they can be read by the triggered auditing function.
76
- async #setAuditParameters(transaction) {
77
- const audit = getAuditContext();
78
- const statements = [];
79
-
80
- if (audit.identityId) {
81
- statements.push(`CALL set_identity_id('${audit.identityId}');`);
82
- }
83
-
84
- if (audit.traceId) {
85
- statements.push(`CALL set_trace_id('${audit.traceId}');`);
86
- }
87
-
88
- if (statements.length > 0) {
89
- const stmt = statements.join("");
90
- await sql.raw(stmt).execute(transaction);
91
- }
92
- }
93
-
94
51
  async create(values) {
95
52
  const name = tracing.spanNameForModelAPI(this._modelName, "create");
53
+ const db = useDatabase();
96
54
 
97
55
  return tracing.withSpan(name, async (span) => {
98
- return await this.#execute(async (db) => {
56
+ try {
99
57
  const query = db
100
58
  .insertInto(this._tableName)
101
59
  .values(
@@ -107,8 +65,11 @@ class ModelAPI {
107
65
 
108
66
  span.setAttribute("sql", query.compile().sql);
109
67
  const row = await query.executeTakeFirstOrThrow();
68
+
110
69
  return camelCaseObject(row);
111
- });
70
+ } catch (e) {
71
+ throw new DatabaseError(e);
72
+ }
112
73
  });
113
74
  }
114
75
 
@@ -195,49 +156,48 @@ class ModelAPI {
195
156
 
196
157
  async update(where, values) {
197
158
  const name = tracing.spanNameForModelAPI(this._modelName, "update");
159
+ const db = useDatabase();
198
160
 
199
161
  return tracing.withSpan(name, async (span) => {
200
- return await this.#execute(async (db) => {
201
- let builder = db.updateTable(this._tableName).returningAll();
162
+ let builder = db.updateTable(this._tableName).returningAll();
202
163
 
203
- builder = builder.set(snakeCaseObject(values));
164
+ builder = builder.set(snakeCaseObject(values));
204
165
 
205
- const context = new QueryContext(
206
- [this._tableName],
207
- this._tableConfigMap
208
- );
166
+ const context = new QueryContext([this._tableName], this._tableConfigMap);
209
167
 
210
- // TODO: support joins for update
211
- builder = applyWhereConditions(context, builder, where);
168
+ // TODO: support joins for update
169
+ builder = applyWhereConditions(context, builder, where);
212
170
 
213
- span.setAttribute("sql", builder.compile().sql);
171
+ span.setAttribute("sql", builder.compile().sql);
214
172
 
173
+ try {
215
174
  const row = await builder.executeTakeFirstOrThrow();
216
175
  return camelCaseObject(row);
217
- });
176
+ } catch (e) {
177
+ throw new DatabaseError(e);
178
+ }
218
179
  });
219
180
  }
220
181
 
221
182
  async delete(where) {
222
183
  const name = tracing.spanNameForModelAPI(this._modelName, "delete");
184
+ const db = useDatabase();
223
185
 
224
186
  return tracing.withSpan(name, async (span) => {
225
- return await this.#execute(async (db) => {
226
- let builder = db.deleteFrom(this._tableName).returning(["id"]);
187
+ let builder = db.deleteFrom(this._tableName).returning(["id"]);
227
188
 
228
- const context = new QueryContext(
229
- [this._tableName],
230
- this._tableConfigMap
231
- );
232
-
233
- // TODO: support joins for delete
234
- builder = applyWhereConditions(context, builder, where);
189
+ const context = new QueryContext([this._tableName], this._tableConfigMap);
235
190
 
236
- span.setAttribute("sql", builder.compile().sql);
191
+ // TODO: support joins for delete
192
+ builder = applyWhereConditions(context, builder, where);
237
193
 
194
+ span.setAttribute("sql", builder.compile().sql);
195
+ try {
238
196
  const row = await builder.executeTakeFirstOrThrow();
239
197
  return row.id;
240
- });
198
+ } catch (e) {
199
+ throw new DatabaseError(e);
200
+ }
241
201
  });
242
202
  }
243
203
 
package/src/auditing.js CHANGED
@@ -44,14 +44,6 @@ class AuditContextPlugin {
44
44
  this.traceIdAlias = "__keel_trace_id";
45
45
  }
46
46
 
47
- #setIdentityClause(value) {
48
- return `set_identity_id('${value}')`;
49
- }
50
-
51
- #setTraceIdClause(value) {
52
- return `set_trace_id('${value}')`;
53
- }
54
-
55
47
  // Appends set_identity_id() and set_trace_id() function calls to the returning statement
56
48
  // of INSERT, UPDATE and DELETE operations.
57
49
  transformQuery(args) {
@@ -59,10 +51,13 @@ class AuditContextPlugin {
59
51
  case "InsertQueryNode":
60
52
  case "UpdateQueryNode":
61
53
  case "DeleteQueryNode":
54
+ // Represents a RETURNING clause in a SQL statement.
62
55
  const returning = {
63
56
  kind: "ReturningNode",
64
57
  selections: [],
65
58
  };
59
+
60
+ // If the query already has a selection, then append it.
66
61
  if (args.node.returning) {
67
62
  returning.selections.push(...args.node.returning.selections);
68
63
  }
@@ -71,10 +66,7 @@ class AuditContextPlugin {
71
66
  const audit = getAuditContext();
72
67
 
73
68
  if (audit.identityId) {
74
- const rawNode = sql
75
- .raw(
76
- this.#setIdentityClause(audit.identityId, this.identityIdAlias)
77
- )
69
+ const rawNode = sql`set_identity_id(${audit.identityId})`
78
70
  .as(this.identityIdAlias)
79
71
  .toOperationNode();
80
72
 
@@ -82,8 +74,7 @@ class AuditContextPlugin {
82
74
  }
83
75
 
84
76
  if (audit.traceId) {
85
- const rawNode = sql
86
- .raw(this.#setTraceIdClause(audit.traceId))
77
+ const rawNode = sql`set_trace_id(${audit.traceId})`
87
78
  .as(this.traceIdAlias)
88
79
  .toOperationNode();
89
80
 
package/src/database.js CHANGED
@@ -129,7 +129,7 @@ class InstrumentedClient extends pg.Client {
129
129
  sqlAttribute = true;
130
130
  }
131
131
 
132
- return await withSpan(spanName, function (span) {
132
+ return withSpan(spanName, function (span) {
133
133
  if (sqlAttribute) {
134
134
  span.setAttribute("sql", args[0]);
135
135
  }
@@ -282,10 +282,8 @@ describe("ModelAPI error handling", () => {
282
282
  },
283
283
  });
284
284
  });
285
-
286
285
  test("when there is a uniqueness constraint error", async () => {
287
286
  await sql`
288
-
289
287
  INSERT INTO post (id, title, author_id) values(${
290
288
  KSUID.randomSync().string
291
289
  }, 'hello', 'adam')
@@ -358,113 +356,3 @@ describe("ModelAPI error handling", () => {
358
356
  });
359
357
  });
360
358
  });
361
-
362
- // The following tests assert on the various
363
- // jsonrpc responses that *should* happen when a user
364
- // writes a custom function that inadvertently causes a pg constraint error to occur inside of our ModelAPI class instance.
365
- describe("ModelAPI error handling", () => {
366
- let functionConfig;
367
- let db;
368
-
369
- beforeEach(async () => {
370
- process.env.KEEL_DB_CONN_TYPE = "pg";
371
- process.env.KEEL_DB_CONN = `postgresql://postgres:postgres@localhost:5432/functions-runtime`;
372
-
373
- db = useDatabase();
374
-
375
- await sql`
376
- DROP TABLE IF EXISTS post;
377
- DROP TABLE IF EXISTS author;
378
-
379
- CREATE TABLE author(
380
- "id" text PRIMARY KEY,
381
- "name" text NOT NULL
382
- );
383
-
384
- CREATE TABLE post(
385
- "id" text PRIMARY KEY,
386
- "title" text NOT NULL UNIQUE,
387
- "author_id" text NOT NULL REFERENCES author(id)
388
- );
389
- `.execute(db);
390
-
391
- await sql`
392
- INSERT INTO author (id, name) VALUES ('adam', 'adam bull')
393
- `.execute(db);
394
-
395
- const models = {
396
- post: new ModelAPI("post", undefined, {
397
- post: {
398
- author: {
399
- relationshipType: "belongsTo",
400
- foreignKey: "author_id",
401
- referencesTable: "person",
402
- },
403
- },
404
- }),
405
- };
406
-
407
- functionConfig = {
408
- permissionFns: {},
409
- actionTypes: {
410
- createPost: PROTO_ACTION_TYPES.CREATE,
411
- deletePost: PROTO_ACTION_TYPES.DELETE,
412
- },
413
- functions: {
414
- createPost: async (ctx, inputs) => {
415
- new Permissions().allow();
416
-
417
- const post = await models.post.create({
418
- id: KSUID.randomSync().string,
419
- ...inputs,
420
- });
421
-
422
- return post;
423
- },
424
- deletePost: async (ctx, inputs) => {
425
- new Permissions().allow();
426
-
427
- const deleted = await models.post.delete(inputs);
428
-
429
- return deleted;
430
- },
431
- },
432
- createContextAPI: () => ({}),
433
- };
434
- });
435
-
436
- test("when kysely returns a no result error", async () => {
437
- // a kysely NoResultError is thrown when attempting to delete/update a non existent record.
438
- const rpcReq = createJSONRPCRequest("123", "deletePost", {
439
- id: "non-existent-id",
440
- });
441
-
442
- expect(await handleRequest(rpcReq, functionConfig)).toEqual({
443
- id: "123",
444
- jsonrpc: "2.0",
445
- error: {
446
- code: RuntimeErrors.RecordNotFoundError,
447
- message: "no result",
448
- },
449
- });
450
- });
451
-
452
- test("when there is a not null constraint error", async () => {
453
- const rpcReq = createJSONRPCRequest("123", "createPost", { title: null });
454
-
455
- expect(await handleRequest(rpcReq, functionConfig)).toEqual({
456
- id: "123",
457
- jsonrpc: "2.0",
458
- error: {
459
- code: RuntimeErrors.NotNullConstraintError,
460
- message: 'null value in column "title" violates not-null constraint',
461
- data: {
462
- code: "23502",
463
- column: "title",
464
- detail: expect.stringContaining("Failing row contains"),
465
- table: "post",
466
- },
467
- },
468
- });
469
- });
470
- });