@teamkeel/functions-runtime 0.318.0 → 0.318.2

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.318.0",
3
+ "version": "0.318.2",
4
4
  "description": "Internal package used by @teamkeel/sdk",
5
5
  "main": "src/index.js",
6
6
  "scripts": {
@@ -15,12 +15,15 @@
15
15
  "access": "public"
16
16
  },
17
17
  "devDependencies": {
18
- "@opentelemetry/sdk-trace-node": "^1.12.0",
19
18
  "prettier": "2.7.1",
20
19
  "vitest": "^0.27.1"
21
20
  },
22
21
  "dependencies": {
23
22
  "@opentelemetry/api": "^1.4.1",
23
+ "@opentelemetry/exporter-trace-otlp-proto": "^0.38.0",
24
+ "@opentelemetry/resources": "^1.12.0",
25
+ "@opentelemetry/sdk-trace-base": "^1.12.0",
26
+ "@opentelemetry/sdk-trace-node": "^1.12.0",
24
27
  "change-case": "^4.1.2",
25
28
  "json-rpc-2.0": "^1.4.1",
26
29
  "ksuid": "^3.0.0",
package/pnpm-lock.yaml CHANGED
@@ -2,6 +2,9 @@ lockfileVersion: 5.4
2
2
 
3
3
  specifiers:
4
4
  '@opentelemetry/api': ^1.4.1
5
+ '@opentelemetry/exporter-trace-otlp-proto': ^0.38.0
6
+ '@opentelemetry/resources': ^1.12.0
7
+ '@opentelemetry/sdk-trace-base': ^1.12.0
5
8
  '@opentelemetry/sdk-trace-node': ^1.12.0
6
9
  change-case: ^4.1.2
7
10
  json-rpc-2.0: ^1.4.1
@@ -13,6 +16,10 @@ specifiers:
13
16
 
14
17
  dependencies:
15
18
  '@opentelemetry/api': 1.4.1
19
+ '@opentelemetry/exporter-trace-otlp-proto': 0.38.0_@opentelemetry+api@1.4.1
20
+ '@opentelemetry/resources': 1.12.0_@opentelemetry+api@1.4.1
21
+ '@opentelemetry/sdk-trace-base': 1.12.0_@opentelemetry+api@1.4.1
22
+ '@opentelemetry/sdk-trace-node': 1.12.0_@opentelemetry+api@1.4.1
16
23
  change-case: 4.1.2
17
24
  json-rpc-2.0: 1.4.2
18
25
  ksuid: 3.0.0
@@ -20,7 +27,6 @@ dependencies:
20
27
  pg: 8.8.0
21
28
 
22
29
  devDependencies:
23
- '@opentelemetry/sdk-trace-node': 1.12.0_@opentelemetry+api@1.4.1
24
30
  prettier: 2.7.1
25
31
  vitest: 0.27.1
26
32
 
@@ -227,6 +233,7 @@ packages:
227
233
  /@opentelemetry/api/1.4.1:
228
234
  resolution: {integrity: sha512-O2yRJce1GOc6PAy3QxFM4NzFiWzvScDC1/5ihYBL6BUEVdq0XMWN01sppE+H6bBXbaFYipjwFLEWLg5PaSOThA==}
229
235
  engines: {node: '>=8.0.0'}
236
+ dev: false
230
237
 
231
238
  /@opentelemetry/context-async-hooks/1.12.0_@opentelemetry+api@1.4.1:
232
239
  resolution: {integrity: sha512-PmwAanPNWCyS9JYFzhzVzHgviLhc0UHjOwdth+hp3HgQQ9XZZNE635P8JhAUHZmbghW9/qQFafRWOS4VN9VVnQ==}
@@ -235,7 +242,7 @@ packages:
235
242
  '@opentelemetry/api': '>=1.0.0 <1.5.0'
236
243
  dependencies:
237
244
  '@opentelemetry/api': 1.4.1
238
- dev: true
245
+ dev: false
239
246
 
240
247
  /@opentelemetry/core/1.12.0_@opentelemetry+api@1.4.1:
241
248
  resolution: {integrity: sha512-4DWYNb3dLs2mSCGl65jY3aEgbvPWSHVQV/dmDWiYeWUrMakZQFcymqZOSUNZO0uDrEJoxMu8O5tZktX6UKFwag==}
@@ -245,7 +252,57 @@ packages:
245
252
  dependencies:
246
253
  '@opentelemetry/api': 1.4.1
247
254
  '@opentelemetry/semantic-conventions': 1.12.0
248
- dev: true
255
+ dev: false
256
+
257
+ /@opentelemetry/exporter-trace-otlp-proto/0.38.0_@opentelemetry+api@1.4.1:
258
+ resolution: {integrity: sha512-M1YctP+T6485noDAJPsnpsx85xsfqyCr06CadTQBJHIgjStgsKTDA86iVpv7XEqW5lwdIThn/boDou2vyi0bQA==}
259
+ engines: {node: '>=14'}
260
+ peerDependencies:
261
+ '@opentelemetry/api': ^1.0.0
262
+ dependencies:
263
+ '@opentelemetry/api': 1.4.1
264
+ '@opentelemetry/core': 1.12.0_@opentelemetry+api@1.4.1
265
+ '@opentelemetry/otlp-exporter-base': 0.38.0_@opentelemetry+api@1.4.1
266
+ '@opentelemetry/otlp-proto-exporter-base': 0.38.0_@opentelemetry+api@1.4.1
267
+ '@opentelemetry/otlp-transformer': 0.38.0_@opentelemetry+api@1.4.1
268
+ '@opentelemetry/resources': 1.12.0_@opentelemetry+api@1.4.1
269
+ '@opentelemetry/sdk-trace-base': 1.12.0_@opentelemetry+api@1.4.1
270
+ dev: false
271
+
272
+ /@opentelemetry/otlp-exporter-base/0.38.0_@opentelemetry+api@1.4.1:
273
+ resolution: {integrity: sha512-VWQo7vUDyW/7/FT8RErAtM/29i/fllCc9xMtnK7kDuheAjJU68zrZ88bQOsLamHvOCU3KVpozjfTZVxZKQRYXw==}
274
+ engines: {node: '>=14'}
275
+ peerDependencies:
276
+ '@opentelemetry/api': ^1.0.0
277
+ dependencies:
278
+ '@opentelemetry/api': 1.4.1
279
+ '@opentelemetry/core': 1.12.0_@opentelemetry+api@1.4.1
280
+ dev: false
281
+
282
+ /@opentelemetry/otlp-proto-exporter-base/0.38.0_@opentelemetry+api@1.4.1:
283
+ resolution: {integrity: sha512-/Z68pIgFv+IwQQfJOJQ9ga7KZ5ET2cFAnpWO9JsxrHjW9glmX+T9RgcF7rfSAFl2JSM9A+kQ11WYRjE2tNKxqg==}
284
+ engines: {node: '>=14'}
285
+ peerDependencies:
286
+ '@opentelemetry/api': ^1.0.0
287
+ dependencies:
288
+ '@opentelemetry/api': 1.4.1
289
+ '@opentelemetry/core': 1.12.0_@opentelemetry+api@1.4.1
290
+ '@opentelemetry/otlp-exporter-base': 0.38.0_@opentelemetry+api@1.4.1
291
+ protobufjs: 7.2.3
292
+ dev: false
293
+
294
+ /@opentelemetry/otlp-transformer/0.38.0_@opentelemetry+api@1.4.1:
295
+ resolution: {integrity: sha512-ykQEipby0NVSi2ih5E8J2GNJ6y9zYDPSef0nD8j33XPKxfyVG5184rUrCsh6TIk1d/GlYl8gB9Wy4TdRvwl6kA==}
296
+ engines: {node: '>=14'}
297
+ peerDependencies:
298
+ '@opentelemetry/api': '>=1.3.0 <1.5.0'
299
+ dependencies:
300
+ '@opentelemetry/api': 1.4.1
301
+ '@opentelemetry/core': 1.12.0_@opentelemetry+api@1.4.1
302
+ '@opentelemetry/resources': 1.12.0_@opentelemetry+api@1.4.1
303
+ '@opentelemetry/sdk-metrics': 1.12.0_@opentelemetry+api@1.4.1
304
+ '@opentelemetry/sdk-trace-base': 1.12.0_@opentelemetry+api@1.4.1
305
+ dev: false
249
306
 
250
307
  /@opentelemetry/propagator-b3/1.12.0_@opentelemetry+api@1.4.1:
251
308
  resolution: {integrity: sha512-WFcn98075QPc2zE1obhKydJHUehI5/HuLoelPEVwATj+487hjCwjHj9r2fgmQkWpvuNSB7CJaA0ys6qqq1N6lg==}
@@ -255,7 +312,7 @@ packages:
255
312
  dependencies:
256
313
  '@opentelemetry/api': 1.4.1
257
314
  '@opentelemetry/core': 1.12.0_@opentelemetry+api@1.4.1
258
- dev: true
315
+ dev: false
259
316
 
260
317
  /@opentelemetry/propagator-jaeger/1.12.0_@opentelemetry+api@1.4.1:
261
318
  resolution: {integrity: sha512-ugtWF7GC6X5RIJ0+iMwW2iVAGNs206CAeq8XQ8OkJRg+v0lp4H0/i+gJ4hubTT8NIL5a3IxtIrAENPLIGdLucQ==}
@@ -265,7 +322,7 @@ packages:
265
322
  dependencies:
266
323
  '@opentelemetry/api': 1.4.1
267
324
  '@opentelemetry/core': 1.12.0_@opentelemetry+api@1.4.1
268
- dev: true
325
+ dev: false
269
326
 
270
327
  /@opentelemetry/resources/1.12.0_@opentelemetry+api@1.4.1:
271
328
  resolution: {integrity: sha512-gunMKXG0hJrR0LXrqh7BVbziA/+iJBL3ZbXCXO64uY+SrExkwoyJkpiq9l5ismkGF/A20mDEV7tGwh+KyPw00Q==}
@@ -276,7 +333,19 @@ packages:
276
333
  '@opentelemetry/api': 1.4.1
277
334
  '@opentelemetry/core': 1.12.0_@opentelemetry+api@1.4.1
278
335
  '@opentelemetry/semantic-conventions': 1.12.0
279
- dev: true
336
+ dev: false
337
+
338
+ /@opentelemetry/sdk-metrics/1.12.0_@opentelemetry+api@1.4.1:
339
+ resolution: {integrity: sha512-zOy88Jfk88eTxqu+9ypHLs184dGydJocSWtvWMY10QKVVaxhC3SLKa0uxI/zBtD9S+x0LP65wxrTSfSoUNtCOA==}
340
+ engines: {node: '>=14'}
341
+ peerDependencies:
342
+ '@opentelemetry/api': '>=1.3.0 <1.5.0'
343
+ dependencies:
344
+ '@opentelemetry/api': 1.4.1
345
+ '@opentelemetry/core': 1.12.0_@opentelemetry+api@1.4.1
346
+ '@opentelemetry/resources': 1.12.0_@opentelemetry+api@1.4.1
347
+ lodash.merge: 4.6.2
348
+ dev: false
280
349
 
281
350
  /@opentelemetry/sdk-trace-base/1.12.0_@opentelemetry+api@1.4.1:
282
351
  resolution: {integrity: sha512-pfCOB3tNDlYVoWuz4D7Ji+Jmy9MHnATWHVpkERdCEiwUGEZ+4IvNPXUcPc37wJVmMpjGLeaWgPPrie0KIpWf1A==}
@@ -288,7 +357,7 @@ packages:
288
357
  '@opentelemetry/core': 1.12.0_@opentelemetry+api@1.4.1
289
358
  '@opentelemetry/resources': 1.12.0_@opentelemetry+api@1.4.1
290
359
  '@opentelemetry/semantic-conventions': 1.12.0
291
- dev: true
360
+ dev: false
292
361
 
293
362
  /@opentelemetry/sdk-trace-node/1.12.0_@opentelemetry+api@1.4.1:
294
363
  resolution: {integrity: sha512-PxpDemnNZLLeFNLAu95/K3QubjlaScXVjVQPlwPui65VRxIvxGVysnN7DFfsref+qoh1hI6nlrYSij43vxdm2w==}
@@ -303,12 +372,55 @@ packages:
303
372
  '@opentelemetry/propagator-jaeger': 1.12.0_@opentelemetry+api@1.4.1
304
373
  '@opentelemetry/sdk-trace-base': 1.12.0_@opentelemetry+api@1.4.1
305
374
  semver: 7.5.0
306
- dev: true
375
+ dev: false
307
376
 
308
377
  /@opentelemetry/semantic-conventions/1.12.0:
309
378
  resolution: {integrity: sha512-hO+bdeGOlJwqowUBoZF5LyP3ORUFOP1G0GRv8N45W/cztXbT2ZEXaAzfokRS9Xc9FWmYrDj32mF6SzH6wuoIyA==}
310
379
  engines: {node: '>=14'}
311
- dev: true
380
+ dev: false
381
+
382
+ /@protobufjs/aspromise/1.1.2:
383
+ resolution: {integrity: sha512-j+gKExEuLmKwvz3OgROXtrJ2UG2x8Ch2YZUxahh+s1F2HZ+wAceUNLkvy6zKCPVRkU++ZWQrdxsUeQXmcg4uoQ==}
384
+ dev: false
385
+
386
+ /@protobufjs/base64/1.1.2:
387
+ resolution: {integrity: sha512-AZkcAA5vnN/v4PDqKyMR5lx7hZttPDgClv83E//FMNhR2TMcLUhfRUBHCmSl0oi9zMgDDqRUJkSxO3wm85+XLg==}
388
+ dev: false
389
+
390
+ /@protobufjs/codegen/2.0.4:
391
+ resolution: {integrity: sha512-YyFaikqM5sH0ziFZCN3xDC7zeGaB/d0IUb9CATugHWbd1FRFwWwt4ld4OYMPWu5a3Xe01mGAULCdqhMlPl29Jg==}
392
+ dev: false
393
+
394
+ /@protobufjs/eventemitter/1.1.0:
395
+ resolution: {integrity: sha512-j9ednRT81vYJ9OfVuXG6ERSTdEL1xVsNgqpkxMsbIabzSo3goCjDIveeGv5d03om39ML71RdmrGNjG5SReBP/Q==}
396
+ dev: false
397
+
398
+ /@protobufjs/fetch/1.1.0:
399
+ resolution: {integrity: sha512-lljVXpqXebpsijW71PZaCYeIcE5on1w5DlQy5WH6GLbFryLUrBD4932W/E2BSpfRJWseIL4v/KPgBFxDOIdKpQ==}
400
+ dependencies:
401
+ '@protobufjs/aspromise': 1.1.2
402
+ '@protobufjs/inquire': 1.1.0
403
+ dev: false
404
+
405
+ /@protobufjs/float/1.0.2:
406
+ resolution: {integrity: sha512-Ddb+kVXlXst9d+R9PfTIxh1EdNkgoRe5tOX6t01f1lYWOvJnSPDBlG241QLzcyPdoNTsblLUdujGSE4RzrTZGQ==}
407
+ dev: false
408
+
409
+ /@protobufjs/inquire/1.1.0:
410
+ resolution: {integrity: sha512-kdSefcPdruJiFMVSbn801t4vFK7KB/5gd2fYvrxhuJYg8ILrmn9SKSX2tZdV6V+ksulWqS7aXjBcRXl3wHoD9Q==}
411
+ dev: false
412
+
413
+ /@protobufjs/path/1.1.2:
414
+ resolution: {integrity: sha512-6JOcJ5Tm08dOHAbdR3GrvP+yUUfkjG5ePsHYczMFLq3ZmMkAD98cDgcT2iA1lJ9NVwFd4tH/iSSoe44YWkltEA==}
415
+ dev: false
416
+
417
+ /@protobufjs/pool/1.1.0:
418
+ resolution: {integrity: sha512-0kELaGSIDBKvcgS4zkjz1PeddatrjYcmMWOlAuAPwAeccUrPHdUqo/J6LiymHHEiJT5NrF1UVwxY14f+fy4WQw==}
419
+ dev: false
420
+
421
+ /@protobufjs/utf8/1.1.0:
422
+ resolution: {integrity: sha512-Vvn3zZrhQZkkBE8LSuW3em98c0FwgO4nxzv6OdSxPKJIEKY2bGbHn+mhGIPerzI4twdxaP8/0+06HBpwf345Lw==}
423
+ dev: false
312
424
 
313
425
  /@types/chai-subset/1.3.3:
314
426
  resolution: {integrity: sha512-frBecisrNGz+F4T6bcc+NLeolfiojh5FxW2klu669+8BARtyQv2C/GkNW6FUodVe4BroGMP/wER/YDGc7rEllw==}
@@ -322,7 +434,6 @@ packages:
322
434
 
323
435
  /@types/node/18.11.18:
324
436
  resolution: {integrity: sha512-DHQpWGjyQKSHj3ebjFI/wRKcqQcdR+MoFBygntYOZytCqNfkd2ZC4ARDJ2DQqhjH5p85Nnd3jhUJIXrszFX/JA==}
325
- dev: true
326
437
 
327
438
  /acorn-walk/8.2.0:
328
439
  resolution: {integrity: sha512-k+iyHEuPgSw6SbuDpGQM+06HQUa04DZ3o+F6CSzXMvvI5KMvnaEqXe+YVe555R9nn6GPt404fos4wcgpw12SDA==}
@@ -530,6 +641,14 @@ packages:
530
641
  engines: {node: '>=14'}
531
642
  dev: true
532
643
 
644
+ /lodash.merge/4.6.2:
645
+ resolution: {integrity: sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==}
646
+ dev: false
647
+
648
+ /long/5.2.3:
649
+ resolution: {integrity: sha512-lcHwpNoggQTObv5apGNCTdJrO69eHOZMi4BNC+rTLER8iHAqGrUVeLh/irVIM7zTw2bOXA8T6uNPeujwOLg/2Q==}
650
+ dev: false
651
+
533
652
  /loupe/2.3.6:
534
653
  resolution: {integrity: sha512-RaPMZKiMy8/JruncMU5Bt6na1eftNoo++R4Y+N2FrxkDVTrGvcyzFTsaGif4QTeKESheMGegbhw6iUAq+5A8zA==}
535
654
  dependencies:
@@ -547,7 +666,6 @@ packages:
547
666
  engines: {node: '>=10'}
548
667
  dependencies:
549
668
  yallist: 4.0.0
550
- dev: true
551
669
 
552
670
  /mlly/1.1.0:
553
671
  resolution: {integrity: sha512-cwzBrBfwGC1gYJyfcy8TcZU1f+dbH/T+TuOhtYP2wLv/Fb51/uV7HJQfBPtEupZ2ORLRU1EKFS/QfS3eo9+kBQ==}
@@ -721,6 +839,25 @@ packages:
721
839
  hasBin: true
722
840
  dev: true
723
841
 
842
+ /protobufjs/7.2.3:
843
+ resolution: {integrity: sha512-TtpvOqwB5Gdz/PQmOjgsrGH1nHjAQVCN7JG4A6r1sXRWESL5rNMAiRcBQlCAdKxZcAbstExQePYG8xof/JVRgg==}
844
+ engines: {node: '>=12.0.0'}
845
+ requiresBuild: true
846
+ dependencies:
847
+ '@protobufjs/aspromise': 1.1.2
848
+ '@protobufjs/base64': 1.1.2
849
+ '@protobufjs/codegen': 2.0.4
850
+ '@protobufjs/eventemitter': 1.1.0
851
+ '@protobufjs/fetch': 1.1.0
852
+ '@protobufjs/float': 1.0.2
853
+ '@protobufjs/inquire': 1.1.0
854
+ '@protobufjs/path': 1.1.2
855
+ '@protobufjs/pool': 1.1.0
856
+ '@protobufjs/utf8': 1.1.0
857
+ '@types/node': 18.11.18
858
+ long: 5.2.3
859
+ dev: false
860
+
724
861
  /resolve/1.22.1:
725
862
  resolution: {integrity: sha512-nBpuuYuY5jFsli/JIs1oldw6fOQCBioohqWZg/2hiaOybXOft4lonv85uDOKXdf8rhyK159cxU5cDcK/NKk8zw==}
726
863
  hasBin: true
@@ -744,7 +881,7 @@ packages:
744
881
  hasBin: true
745
882
  dependencies:
746
883
  lru-cache: 6.0.0
747
- dev: true
884
+ dev: false
748
885
 
749
886
  /sentence-case/3.0.4:
750
887
  resolution: {integrity: sha512-8LS0JInaQMCRoQ7YUytAo/xUu5W2XnQxV2HI/6uM6U7CITS1RqPElr30V6uIqyMKM9lJGRVFy5/4CuzcixNYSg==}
@@ -963,4 +1100,3 @@ packages:
963
1100
 
964
1101
  /yallist/4.0.0:
965
1102
  resolution: {integrity: sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==}
966
- dev: true
package/src/ModelAPI.js CHANGED
@@ -3,7 +3,11 @@ const { QueryBuilder } = require("./QueryBuilder");
3
3
  const { QueryContext } = require("./QueryContext");
4
4
  const { applyWhereConditions } = require("./applyWhereConditions");
5
5
  const { applyJoins } = require("./applyJoins");
6
- const { camelCaseObject, snakeCaseObject } = require("./casing");
6
+ const {
7
+ camelCaseObject,
8
+ snakeCaseObject,
9
+ upperCamelCase,
10
+ } = require("./casing");
7
11
  const tracing = require("./tracing");
8
12
 
9
13
  /**
@@ -44,129 +48,123 @@ class ModelAPI {
44
48
  this._defaultValues = defaultValues;
45
49
  this._tableName = tableName;
46
50
  this._tableConfigMap = tableConfigMap;
51
+ this._modelName = upperCamelCase(this._tableName);
47
52
  }
48
53
 
49
54
  async create(values) {
50
- try {
51
- const defaults = this._defaultValues();
52
- const query = this._db
53
- .insertInto(this._tableName)
54
- .values(
55
- snakeCaseObject({
56
- ...defaults,
57
- ...values,
58
- })
59
- )
60
- .returningAll();
61
- const sql = query.compile().sql;
62
- const row = await tracing.withSpan(
63
- `${this._tableName}.create`,
64
- (span) => {
65
- span.setAttribute("sql", sql);
66
- return query.executeTakeFirstOrThrow();
67
- }
68
- );
69
-
70
- return camelCaseObject(row);
71
- } catch (e) {
72
- throw new DatabaseError(e);
73
- }
55
+ const name = spanName(this._modelName, "create");
56
+
57
+ return tracing.withSpan(name, async (span) => {
58
+ try {
59
+ const defaults = this._defaultValues();
60
+ const query = this._db
61
+ .insertInto(this._tableName)
62
+ .values(
63
+ snakeCaseObject({
64
+ ...defaults,
65
+ ...values,
66
+ })
67
+ )
68
+ .returningAll();
69
+
70
+ span.setAttribute("sql", query.compile().sql);
71
+ const row = await query.executeTakeFirstOrThrow();
72
+
73
+ return camelCaseObject(row);
74
+ } catch (e) {
75
+ throw new DatabaseError(e);
76
+ }
77
+ });
74
78
  }
75
79
 
76
80
  async findOne(where = {}) {
77
- let builder = this._db
78
- .selectFrom(this._tableName)
79
- .distinctOn(`${this._tableName}.id`)
80
- .selectAll(this._tableName);
81
+ const name = spanName(this._modelName, "findOne");
81
82
 
82
- const context = new QueryContext([this._tableName], this._tableConfigMap);
83
+ return tracing.withSpan(name, async (span) => {
84
+ let builder = this._db
85
+ .selectFrom(this._tableName)
86
+ .distinctOn(`${this._tableName}.id`)
87
+ .selectAll(this._tableName);
83
88
 
84
- builder = applyJoins(context, builder, where);
85
- builder = applyWhereConditions(context, builder, where);
89
+ const context = new QueryContext([this._tableName], this._tableConfigMap);
86
90
 
87
- const sql = builder.compile().sql;
88
- const row = await tracing.withSpan(`${this._tableName}.findOne`, (span) => {
89
- span.setAttribute("sql", sql);
90
- return builder.executeTakeFirst();
91
- });
92
- if (!row) {
93
- return null;
94
- }
91
+ builder = applyJoins(context, builder, where);
92
+ builder = applyWhereConditions(context, builder, where);
95
93
 
96
- return camelCaseObject(row);
94
+ span.setAttribute("sql", builder.compile().sql);
95
+ const row = await builder.executeTakeFirst();
96
+ if (!row) {
97
+ return null;
98
+ }
99
+
100
+ return camelCaseObject(row);
101
+ });
97
102
  }
98
103
 
99
104
  async findMany(where = {}) {
100
- let builder = this._db
101
- .selectFrom(this._tableName)
102
- .distinctOn(`${this._tableName}.id`)
103
- .selectAll(this._tableName);
105
+ const name = spanName(this._modelName, "findMany");
104
106
 
105
- const context = new QueryContext([this._tableName], this._tableConfigMap);
107
+ return tracing.withSpan(name, async (span) => {
108
+ let builder = this._db
109
+ .selectFrom(this._tableName)
110
+ .distinctOn(`${this._tableName}.id`)
111
+ .selectAll(this._tableName);
106
112
 
107
- builder = applyJoins(context, builder, where);
108
- builder = applyWhereConditions(context, builder, where);
109
- const query = builder.orderBy("id");
110
-
111
- const sql = query.compile().sql;
112
- const rows = await tracing.withSpan(
113
- `${this._tableName}.findMany`,
114
- (span) => {
115
- span.setAttribute("sql", sql);
116
- return builder.execute();
117
- }
118
- );
119
- return rows.map((x) => camelCaseObject(x));
113
+ const context = new QueryContext([this._tableName], this._tableConfigMap);
114
+
115
+ builder = applyJoins(context, builder, where);
116
+ builder = applyWhereConditions(context, builder, where);
117
+ const query = builder.orderBy("id");
118
+
119
+ span.setAttribute("sql", query.compile().sql);
120
+ const rows = await builder.execute();
121
+ return rows.map((x) => camelCaseObject(x));
122
+ });
120
123
  }
121
124
 
122
125
  async update(where, values) {
123
- let builder = this._db.updateTable(this._tableName).returningAll();
126
+ const name = spanName(this._modelName, "update");
124
127
 
125
- builder = builder.set(snakeCaseObject(values));
128
+ return tracing.withSpan(name, async (span) => {
129
+ let builder = this._db.updateTable(this._tableName).returningAll();
126
130
 
127
- const context = new QueryContext([this._tableName], this._tableConfigMap);
131
+ builder = builder.set(snakeCaseObject(values));
128
132
 
129
- // TODO: support joins for update
130
- builder = applyWhereConditions(context, builder, where);
133
+ const context = new QueryContext([this._tableName], this._tableConfigMap);
131
134
 
132
- try {
133
- const sql = builder.compile().sql;
134
- const row = await tracing.withSpan(
135
- `${this._tableName}.update`,
136
- (span) => {
137
- span.setAttribute("sql", sql);
138
- return builder.executeTakeFirstOrThrow();
139
- }
140
- );
135
+ // TODO: support joins for update
136
+ builder = applyWhereConditions(context, builder, where);
141
137
 
142
- return camelCaseObject(row);
143
- } catch (e) {
144
- throw new DatabaseError(e);
145
- }
138
+ span.setAttribute("sql", builder.compile().sql);
139
+
140
+ try {
141
+ const row = await builder.executeTakeFirstOrThrow();
142
+ return camelCaseObject(row);
143
+ } catch (e) {
144
+ throw new DatabaseError(e);
145
+ }
146
+ });
146
147
  }
147
148
 
148
149
  async delete(where) {
149
- let builder = this._db.deleteFrom(this._tableName).returning(["id"]);
150
+ const name = spanName(this._modelName, "delete");
150
151
 
151
- const context = new QueryContext([this._tableName], this._tableConfigMap);
152
+ return tracing.withSpan(name, async (span) => {
153
+ let builder = this._db.deleteFrom(this._tableName).returning(["id"]);
152
154
 
153
- // TODO: support joins for delete
154
- builder = applyWhereConditions(context, builder, where);
155
+ const context = new QueryContext([this._tableName], this._tableConfigMap);
155
156
 
156
- try {
157
- const sql = builder.compile().sql;
158
- const row = await tracing.withSpan(
159
- `${this._tableName}.delete`,
160
- (span) => {
161
- span.setAttribute("sql", sql);
162
- return builder.executeTakeFirstOrThrow();
163
- }
164
- );
165
-
166
- return row.id;
167
- } catch (e) {
168
- throw new DatabaseError(e);
169
- }
157
+ // TODO: support joins for delete
158
+ builder = applyWhereConditions(context, builder, where);
159
+
160
+ span.setAttribute("sql", builder.compile().sql);
161
+ try {
162
+ const row = await builder.executeTakeFirstOrThrow();
163
+ return row.id;
164
+ } catch (e) {
165
+ throw new DatabaseError(e);
166
+ }
167
+ });
170
168
  }
171
169
 
172
170
  where(where) {
@@ -180,10 +178,14 @@ class ModelAPI {
180
178
  builder = applyJoins(context, builder, where);
181
179
  builder = applyWhereConditions(context, builder, where);
182
180
 
183
- return new QueryBuilder(context, builder);
181
+ return new QueryBuilder(this._tableName, context, builder);
184
182
  }
185
183
  }
186
184
 
185
+ function spanName(modelName, action) {
186
+ return `Database ${modelName}.${action}`;
187
+ }
188
+
187
189
  module.exports = {
188
190
  ModelAPI,
189
191
  DatabaseError,
@@ -1,14 +1,16 @@
1
1
  const { applyWhereConditions } = require("./applyWhereConditions");
2
2
  const { applyJoins } = require("./applyJoins");
3
- const { camelCaseObject } = require("./casing");
3
+ const { camelCaseObject, upperCamelCase } = require("./casing");
4
4
  const tracing = require("./tracing");
5
5
 
6
6
  class QueryBuilder {
7
7
  /**
8
+ * @param {string} tableName
8
9
  * @param {import("./QueryContext").QueryContext} context
9
10
  * @param {import("kysely").Kysely} db
10
11
  */
11
- constructor(context, db) {
12
+ constructor(tableName, context, db) {
13
+ this._tableName = tableName;
12
14
  this._context = context;
13
15
  this._db = db;
14
16
  }
@@ -19,7 +21,7 @@ class QueryBuilder {
19
21
  let builder = applyJoins(context, this._db, where);
20
22
  builder = applyWhereConditions(context, builder, where);
21
23
 
22
- return new QueryBuilder(context, builder);
24
+ return new QueryBuilder(this._tableName, context, builder);
23
25
  }
24
26
 
25
27
  orWhere(where) {
@@ -31,17 +33,17 @@ class QueryBuilder {
31
33
  return applyWhereConditions(context, qb, where);
32
34
  });
33
35
 
34
- return new QueryBuilder(context, builder);
36
+ return new QueryBuilder(this._tableName, context, builder);
35
37
  }
36
38
 
37
39
  async findMany() {
38
- const query = this._db.orderBy("id");
39
- const sql = query.compile().sql;
40
- const rows = await tracing.withSpan(`query`, (span) => {
41
- span.setAttribute("sql", sql);
42
- return query.execute();
40
+ const spanName = `Database ${upperCamelCase(this._tableName)}.findMany`;
41
+ return tracing.withSpan(spanName, async (span) => {
42
+ const query = this._db.orderBy("id");
43
+ span.setAttribute("sql", query.compile().sql);
44
+ const rows = await query.execute();
45
+ return rows.map((x) => camelCaseObject(x));
43
46
  });
44
- return rows.map((x) => camelCaseObject(x));
45
47
  }
46
48
  }
47
49
 
package/src/casing.js CHANGED
@@ -16,9 +16,15 @@ function snakeCaseObject(obj) {
16
16
  return r;
17
17
  }
18
18
 
19
+ function upperCamelCase(s) {
20
+ s = camelCase(s);
21
+ return s[0].toUpperCase() + s.substring(1);
22
+ }
23
+
19
24
  module.exports = {
20
25
  camelCaseObject,
21
26
  snakeCaseObject,
22
27
  snakeCase,
23
28
  camelCase,
29
+ upperCamelCase,
24
30
  };
@@ -14,166 +14,161 @@ const { PROTO_ACTION_TYPES } = require("./consts");
14
14
 
15
15
  const { errorToJSONRPCResponse, RuntimeErrors } = require("./errors");
16
16
  const opentelemetry = require("@opentelemetry/api");
17
- const { getTracer } = require("./tracing");
17
+ const { getTracer, withSpan } = require("./tracing");
18
18
 
19
19
  // Generic handler function that is agnostic to runtime environment (local or lambda)
20
20
  // to execute a custom function based on the contents of a jsonrpc-2.0 payload object.
21
21
  // To read more about jsonrpc request and response shapes, please read https://www.jsonrpc.org/specification
22
22
  async function handleRequest(request, config) {
23
+ // Try to extract trace context from caller
23
24
  const activeContext = opentelemetry.propagation.extract(
24
25
  opentelemetry.context.active(),
25
- // "?." is so we don't have to provide this field on tests
26
26
  request.meta?.tracing
27
27
  );
28
- let span = getTracer().startSpan(
29
- `Function/${request.method}`,
30
- { attributes: {} },
31
- activeContext
32
- );
33
- opentelemetry.trace.setSpan(activeContext, span);
34
-
35
- //
36
- // WARNING: Nothing should be done before this try block, as the finally closes the span
37
- //
38
- try {
39
- const {
40
- createFunctionAPI,
41
- createContextAPI,
42
- functions,
43
- permissions,
44
- actionTypes,
45
- } = config;
46
-
47
- if (!(request.method in functions)) {
48
- const message = `no corresponding function found for '${request.method}'`;
49
- span.setStatus({
50
- code: opentelemetry.SpanStatusCode.ERROR,
51
- message: message,
52
- });
53
- return createJSONRPCErrorResponse(
54
- request.id,
55
- JSONRPCErrorCode.MethodNotFound,
56
- message
57
- );
58
- }
59
-
60
- // headers reference passed to custom function where object data can be modified
61
- const headers = new Headers();
62
-
63
- const db = getDatabase();
64
-
65
- // We want to wrap the execution of the custom function in a transaction so that any call the user makes
66
- // to any of the model apis we provide to the custom function is processed in a transaction.
67
- // This is useful for permissions where we want to only proceed with database writes if all permission rules
68
- // have been validated.
69
- const result = await db.transaction().execute(async (transaction) => {
70
- const ctx = createContextAPI({
71
- responseHeaders: headers,
72
- meta: request.meta,
73
- });
74
- const api = createFunctionAPI({
75
- meta: request.meta,
76
- db: transaction,
77
- });
78
-
79
- const customFunction = functions[request.method];
80
-
81
- // Call the user's custom function!
82
- const fnResult = await customFunction(ctx, request.params, api);
83
-
84
- // api.permissions maintains an internal state of whether the current operation has been *explicitly* permitted/denied by the user in the course of their custom function, or if execution has already been permitted by a role based permission (evaluated in the main runtime).
85
- // we need to check that the final state is permitted or unpermitted. if it's not, then it means that the user has taken no explicit action to permit/deny
86
- // and therefore we default to checking the permissions defined in the schema automatically.
87
- switch (api.permissions.getState()) {
88
- case PERMISSION_STATE.PERMITTED:
89
- return fnResult;
90
- case PERMISSION_STATE.UNPERMITTED:
91
- throw new PermissionError(
92
- `Not permitted to access ${request.method}`
28
+
29
+ // Run the whole request with the extracted context
30
+ return opentelemetry.context.with(activeContext, () => {
31
+ // Wrapping span for the whole request
32
+ return withSpan(request.method, async (span) => {
33
+ try {
34
+ const {
35
+ createFunctionAPI,
36
+ createContextAPI,
37
+ functions,
38
+ permissions,
39
+ actionTypes,
40
+ } = config;
41
+
42
+ if (!(request.method in functions)) {
43
+ const message = `no corresponding function found for '${request.method}'`;
44
+ span.setStatus({
45
+ code: opentelemetry.SpanStatusCode.ERROR,
46
+ message: message,
47
+ });
48
+ return createJSONRPCErrorResponse(
49
+ request.id,
50
+ JSONRPCErrorCode.MethodNotFound,
51
+ message
93
52
  );
94
- default:
95
- // unknown state, proceed with checking against the built in permissions in the schema
96
- const relevantPermissions = permissions[request.method];
53
+ }
97
54
 
98
- const actionType = actionTypes[request.method];
55
+ // headers reference passed to custom function where object data can be modified
56
+ const headers = new Headers();
99
57
 
100
- const peakInsideTransaction =
101
- actionType === PROTO_ACTION_TYPES.CREATE;
58
+ const db = getDatabase();
102
59
 
103
- let rowsForPermissions = [];
104
- switch (actionType) {
105
- case PROTO_ACTION_TYPES.LIST:
106
- rowsForPermissions = fnResult;
60
+ // We want to wrap the execution of the custom function in a transaction so that any call the user makes
61
+ // to any of the model apis we provide to the custom function is processed in a transaction.
62
+ // This is useful for permissions where we want to only proceed with database writes if all permission rules
63
+ // have been validated.
64
+ const result = await db.transaction().execute(async (transaction) => {
65
+ const ctx = createContextAPI({
66
+ responseHeaders: headers,
67
+ meta: request.meta,
68
+ });
69
+ const api = createFunctionAPI({
70
+ meta: request.meta,
71
+ db: transaction,
72
+ });
107
73
 
108
- break;
109
- case PROTO_ACTION_TYPES.DELETE:
110
- rowsForPermissions = [{ id: fnResult }];
111
- break;
74
+ const customFunction = functions[request.method];
75
+
76
+ // Call the user's custom function!
77
+ const fnResult = await customFunction(ctx, request.params, api);
78
+
79
+ // api.permissions maintains an internal state of whether the current operation has been *explicitly* permitted/denied by the user in the course of their custom function, or if execution has already been permitted by a role based permission (evaluated in the main runtime).
80
+ // we need to check that the final state is permitted or unpermitted. if it's not, then it means that the user has taken no explicit action to permit/deny
81
+ // and therefore we default to checking the permissions defined in the schema automatically.
82
+ switch (api.permissions.getState()) {
83
+ case PERMISSION_STATE.PERMITTED:
84
+ return fnResult;
85
+ case PERMISSION_STATE.UNPERMITTED:
86
+ throw new PermissionError(
87
+ `Not permitted to access ${request.method}`
88
+ );
112
89
  default:
113
- rowsForPermissions = [fnResult];
114
- break;
90
+ // unknown state, proceed with checking against the built in permissions in the schema
91
+ const relevantPermissions = permissions[request.method];
92
+
93
+ const actionType = actionTypes[request.method];
94
+
95
+ const peakInsideTransaction =
96
+ actionType === PROTO_ACTION_TYPES.CREATE;
97
+
98
+ let rowsForPermissions = [];
99
+ switch (actionType) {
100
+ case PROTO_ACTION_TYPES.LIST:
101
+ rowsForPermissions = fnResult;
102
+
103
+ break;
104
+ case PROTO_ACTION_TYPES.DELETE:
105
+ rowsForPermissions = [{ id: fnResult }];
106
+ break;
107
+ default:
108
+ rowsForPermissions = [fnResult];
109
+ break;
110
+ }
111
+
112
+ // check will throw a PermissionError if a permission rule is invalid
113
+ await checkBuiltInPermissions({
114
+ rows: rowsForPermissions,
115
+ permissions: relevantPermissions,
116
+ // it is important that we pass db here as db represents the connection to the database
117
+ // *outside* of the current transaction. Given that any changes inside of a transaction
118
+ // are opaque to the outside, we can utilize this when running permission rules and then deciding to
119
+ // rollback any changes if they do not pass. However, for creates we need to be able to 'peak' inside the transaction to read the created record, as this won't exist outside of the transaction.
120
+ db: peakInsideTransaction ? transaction : db,
121
+ ctx,
122
+ functionName: request.method,
123
+ });
124
+
125
+ // If the built in permission check above doesn't throw, then it means that the request is permitted and we can continue returning the return value from the custom function out of the transaction
126
+ return fnResult;
115
127
  }
116
-
117
- // check will throw a PermissionError if a permission rule is invalid
118
- await checkBuiltInPermissions({
119
- rows: rowsForPermissions,
120
- permissions: relevantPermissions,
121
- // it is important that we pass db here as db represents the connection to the database
122
- // *outside* of the current transaction. Given that any changes inside of a transaction
123
- // are opaque to the outside, we can utilize this when running permission rules and then deciding to
124
- // rollback any changes if they do not pass. However, for creates we need to be able to 'peak' inside the transaction to read the created record, as this won't exist outside of the transaction.
125
- db: peakInsideTransaction ? transaction : db,
126
- ctx,
127
- functionName: request.method,
128
+ });
129
+
130
+ if (result === undefined) {
131
+ // no result returned from custom function
132
+ return createJSONRPCErrorResponse(
133
+ request.id,
134
+ RuntimeErrors.NoResultError,
135
+ `no result returned from function '${request.method}'`
136
+ );
137
+ }
138
+
139
+ const response = createJSONRPCSuccessResponse(request.id, result);
140
+
141
+ const responseHeaders = {};
142
+ for (const pair of headers.entries()) {
143
+ responseHeaders[pair[0]] = pair[1].split(", ");
144
+ }
145
+ response.meta = { headers: responseHeaders };
146
+
147
+ return response;
148
+ } catch (e) {
149
+ if (e instanceof Error) {
150
+ span.recordException(e);
151
+ span.setStatus({
152
+ code: opentelemetry.SpanStatusCode.ERROR,
153
+ message: e.message,
128
154
  });
129
-
130
- // If the built in permission check above doesn't throw, then it means that the request is permitted and we can continue returning the return value from the custom function out of the transaction
131
- return fnResult;
155
+ return errorToJSONRPCResponse(request, e);
156
+ }
157
+
158
+ const message = JSON.stringify(e);
159
+
160
+ span.setStatus({
161
+ code: opentelemetry.SpanStatusCode.ERROR,
162
+ message: message,
163
+ });
164
+ return createJSONRPCErrorResponse(
165
+ request.id,
166
+ RuntimeErrors.UnknownError,
167
+ message
168
+ );
132
169
  }
133
170
  });
134
-
135
- if (result === undefined) {
136
- // no result returned from custom function
137
- return createJSONRPCErrorResponse(
138
- request.id,
139
- RuntimeErrors.NoResultError,
140
- `no result returned from function '${request.method}'`
141
- );
142
- }
143
-
144
- const response = createJSONRPCSuccessResponse(request.id, result);
145
-
146
- const responseHeaders = {};
147
- for (const pair of headers.entries()) {
148
- responseHeaders[pair[0]] = pair[1].split(", ");
149
- }
150
- response.meta = { headers: responseHeaders };
151
-
152
- return response;
153
- } catch (e) {
154
- if (e instanceof Error) {
155
- span.recordException(e);
156
- span.setStatus({
157
- code: opentelemetry.SpanStatusCode.ERROR,
158
- message: e.message,
159
- });
160
- return errorToJSONRPCResponse(request, e);
161
- }
162
-
163
- const message = JSON.stringify(e);
164
-
165
- span.setStatus({
166
- code: opentelemetry.SpanStatusCode.ERROR,
167
- message: message,
168
- });
169
- return createJSONRPCErrorResponse(
170
- request.id,
171
- RuntimeErrors.UnknownError,
172
- message
173
- );
174
- } finally {
175
- span.end();
176
- }
171
+ });
177
172
  }
178
173
 
179
174
  module.exports = {
package/src/index.js CHANGED
@@ -8,6 +8,7 @@ const {
8
8
  PERMISSION_STATE,
9
9
  checkBuiltInPermissions,
10
10
  } = require("./permissions");
11
+ const tracing = require("./tracing");
11
12
 
12
13
  module.exports = {
13
14
  ModelAPI,
@@ -17,6 +18,7 @@ module.exports = {
17
18
  Permissions,
18
19
  PERMISSION_STATE,
19
20
  checkBuiltInPermissions,
21
+ tracing,
20
22
  ksuid() {
21
23
  return KSUID.randomSync().string;
22
24
  },
package/src/tracing.js CHANGED
@@ -1,4 +1,10 @@
1
1
  const opentelemetry = require("@opentelemetry/api");
2
+ const { BatchSpanProcessor } = require("@opentelemetry/sdk-trace-base");
3
+ const {
4
+ OTLPTraceExporter,
5
+ } = require("@opentelemetry/exporter-trace-otlp-proto");
6
+ const { NodeTracerProvider } = require("@opentelemetry/sdk-trace-node");
7
+ const { envDetectorSync } = require("@opentelemetry/resources");
2
8
 
3
9
  function withSpan(name, fn) {
4
10
  return getTracer().startActiveSpan(name, async (span) => {
@@ -21,7 +27,7 @@ function withSpan(name, fn) {
21
27
  });
22
28
  }
23
29
 
24
- function init() {
30
+ function patchFetch() {
25
31
  if (!globalThis.fetch.patched) {
26
32
  const originalFetch = globalThis.fetch;
27
33
 
@@ -47,6 +53,20 @@ function init() {
47
53
  }
48
54
  }
49
55
 
56
+ function init() {
57
+ if (process.env.KEEL_TRACING_ENABLED == "true") {
58
+ const provider = new NodeTracerProvider({
59
+ resource: envDetectorSync.detect(),
60
+ });
61
+ const exporter = new OTLPTraceExporter();
62
+ const processor = new BatchSpanProcessor(exporter);
63
+ provider.addSpanProcessor(processor);
64
+ provider.register();
65
+ }
66
+
67
+ patchFetch();
68
+ }
69
+
50
70
  function getTracer() {
51
71
  return opentelemetry.trace.getTracer("functions");
52
72
  }