@teamkeel/functions-runtime 0.334.0 → 0.335.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,10 +1,10 @@
1
1
  {
2
2
  "name": "@teamkeel/functions-runtime",
3
- "version": "0.334.0",
3
+ "version": "0.335.0",
4
4
  "description": "Internal package used by @teamkeel/sdk",
5
5
  "main": "src/index.js",
6
6
  "scripts": {
7
- "test": "vitest run --reporter verbose --threads false",
7
+ "test": "DEBUG=true vitest run --reporter verbose --threads false",
8
8
  "format": "npx prettier --write src/**/*.js"
9
9
  },
10
10
  "keywords": [],
package/src/ModelAPI.js CHANGED
@@ -3,6 +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 {
7
+ applyLimit,
8
+ applyOffset,
9
+ applyOrderBy,
10
+ } = require("./applyAdditionalQueryConstraints");
6
11
  const {
7
12
  camelCaseObject,
8
13
  snakeCaseObject,
@@ -50,7 +55,7 @@ class ModelAPI {
50
55
  }
51
56
 
52
57
  async create(values) {
53
- const name = spanName(this._modelName, "create");
58
+ const name = tracing.spanNameForModelAPI(this._modelName, "create");
54
59
  const db = getDatabase();
55
60
 
56
61
  return tracing.withSpan(name, async (span) => {
@@ -77,7 +82,7 @@ class ModelAPI {
77
82
  }
78
83
 
79
84
  async findOne(where = {}) {
80
- const name = spanName(this._modelName, "findOne");
85
+ const name = tracing.spanNameForModelAPI(this._modelName, "findOne");
81
86
  const db = getDatabase();
82
87
 
83
88
  return tracing.withSpan(name, async (span) => {
@@ -101,21 +106,55 @@ class ModelAPI {
101
106
  });
102
107
  }
103
108
 
104
- async findMany(where = {}) {
105
- const name = spanName(this._modelName, "findMany");
109
+ async findMany(params) {
110
+ const name = tracing.spanNameForModelAPI(this._modelName, "findMany");
106
111
  const db = getDatabase();
112
+ const where = params?.where || {};
107
113
 
108
114
  return tracing.withSpan(name, async (span) => {
115
+ const context = new QueryContext([this._tableName], this._tableConfigMap);
116
+
109
117
  let builder = db
110
- .selectFrom(this._tableName)
111
- .distinctOn(`${this._tableName}.id`)
112
- .selectAll(this._tableName);
118
+ .selectFrom((qb) => {
119
+ // We need to wrap this query as a sub query in the selectFrom because you cannot apply a different order by column when using distinct(id)
120
+ let builder = qb
121
+ .selectFrom(this._tableName)
122
+ .distinctOn(`${this._tableName}.id`)
123
+ .selectAll(this._tableName);
113
124
 
114
- const context = new QueryContext([this._tableName], this._tableConfigMap);
125
+ builder = applyJoins(context, builder, where);
126
+ builder = applyWhereConditions(context, builder, where);
115
127
 
116
- builder = applyJoins(context, builder, where);
117
- builder = applyWhereConditions(context, builder, where);
118
- const query = builder.orderBy("id");
128
+ builder = builder.as(this._tableName);
129
+
130
+ return builder;
131
+ })
132
+ .selectAll();
133
+
134
+ // The only constraints added to the main query are the orderBy, limit and offset as they are performed on the "outer" set
135
+ if (params?.limit) {
136
+ builder = applyLimit(context, builder, params.limit);
137
+ }
138
+
139
+ if (params?.offset) {
140
+ builder = applyOffset(context, builder, params.offset);
141
+ }
142
+
143
+ if (
144
+ params?.orderBy !== undefined &&
145
+ Object.keys(params?.orderBy).length > 0
146
+ ) {
147
+ builder = applyOrderBy(
148
+ context,
149
+ builder,
150
+ this._tableName,
151
+ params.orderBy
152
+ );
153
+ } else {
154
+ builder = builder.orderBy(`${this._tableName}.id`);
155
+ }
156
+
157
+ const query = builder;
119
158
 
120
159
  span.setAttribute("sql", query.compile().sql);
121
160
  const rows = await builder.execute();
@@ -124,7 +163,7 @@ class ModelAPI {
124
163
  }
125
164
 
126
165
  async update(where, values) {
127
- const name = spanName(this._modelName, "update");
166
+ const name = tracing.spanNameForModelAPI(this._modelName, "update");
128
167
  const db = getDatabase();
129
168
 
130
169
  return tracing.withSpan(name, async (span) => {
@@ -149,7 +188,7 @@ class ModelAPI {
149
188
  }
150
189
 
151
190
  async delete(where) {
152
- const name = spanName(this._modelName, "delete");
191
+ const name = tracing.spanNameForModelAPI(this._modelName, "delete");
153
192
  const db = getDatabase();
154
193
 
155
194
  return tracing.withSpan(name, async (span) => {
@@ -187,10 +226,6 @@ class ModelAPI {
187
226
  }
188
227
  }
189
228
 
190
- function spanName(modelName, action) {
191
- return `Database ${modelName}.${action}`;
192
- }
193
-
194
229
  module.exports = {
195
230
  ModelAPI,
196
231
  DatabaseError,
@@ -163,7 +163,7 @@ test("ModelAPI.findOne - return null if not found", async () => {
163
163
  });
164
164
 
165
165
  test("ModelAPI.findMany", async () => {
166
- const jim = await personAPI.create({
166
+ await personAPI.create({
167
167
  name: "Jim",
168
168
  married: false,
169
169
  favouriteNumber: 10,
@@ -179,14 +179,16 @@ test("ModelAPI.findMany", async () => {
179
179
  favouriteNumber: 12,
180
180
  });
181
181
  const rows = await personAPI.findMany({
182
- married: true,
182
+ where: {
183
+ married: true,
184
+ },
183
185
  });
184
186
  expect(rows.length).toEqual(2);
185
187
  expect(rows.map((x) => x.id).sort()).toEqual([bob.id, sally.id].sort());
186
188
  });
187
189
 
188
190
  test("ModelAPI.findMany - no where conditions", async () => {
189
- const jim = await personAPI.create({
191
+ await personAPI.create({
190
192
  name: "Jim",
191
193
  });
192
194
  await personAPI.create({
@@ -206,8 +208,10 @@ test("ModelAPI.findMany - startsWith", async () => {
206
208
  name: "Bob",
207
209
  });
208
210
  const rows = await personAPI.findMany({
209
- name: {
210
- startsWith: "Ji",
211
+ where: {
212
+ name: {
213
+ startsWith: "Ji",
214
+ },
211
215
  },
212
216
  });
213
217
  expect(rows.length).toEqual(1);
@@ -222,8 +226,10 @@ test("ModelAPI.findMany - endsWith", async () => {
222
226
  name: "Bob",
223
227
  });
224
228
  const rows = await personAPI.findMany({
225
- name: {
226
- endsWith: "im",
229
+ where: {
230
+ name: {
231
+ endsWith: "im",
232
+ },
227
233
  },
228
234
  });
229
235
  expect(rows.length).toEqual(1);
@@ -241,8 +247,10 @@ test("ModelAPI.findMany - contains", async () => {
241
247
  name: "Jim",
242
248
  });
243
249
  const rows = await personAPI.findMany({
244
- name: {
245
- contains: "ll",
250
+ where: {
251
+ name: {
252
+ contains: "ll",
253
+ },
246
254
  },
247
255
  });
248
256
  expect(rows.length).toEqual(2);
@@ -260,8 +268,10 @@ test("ModelAPI.findMany - oneOf", async () => {
260
268
  name: "Jim",
261
269
  });
262
270
  const rows = await personAPI.findMany({
263
- name: {
264
- oneOf: ["Billy", "Sally"],
271
+ where: {
272
+ name: {
273
+ oneOf: ["Billy", "Sally"],
274
+ },
265
275
  },
266
276
  });
267
277
  expect(rows.length).toEqual(2);
@@ -276,8 +286,10 @@ test("ModelAPI.findMany - greaterThan", async () => {
276
286
  favouriteNumber: 2,
277
287
  });
278
288
  const rows = await personAPI.findMany({
279
- favouriteNumber: {
280
- greaterThan: 1,
289
+ where: {
290
+ favouriteNumber: {
291
+ greaterThan: 1,
292
+ },
281
293
  },
282
294
  });
283
295
  expect(rows.length).toEqual(1);
@@ -295,8 +307,10 @@ test("ModelAPI.findMany - greaterThanOrEquals", async () => {
295
307
  favouriteNumber: 3,
296
308
  });
297
309
  const rows = await personAPI.findMany({
298
- favouriteNumber: {
299
- greaterThanOrEquals: 2,
310
+ where: {
311
+ favouriteNumber: {
312
+ greaterThanOrEquals: 2,
313
+ },
300
314
  },
301
315
  });
302
316
  expect(rows.length).toEqual(2);
@@ -311,8 +325,10 @@ test("ModelAPI.findMany - lessThan", async () => {
311
325
  favouriteNumber: 2,
312
326
  });
313
327
  const rows = await personAPI.findMany({
314
- favouriteNumber: {
315
- lessThan: 2,
328
+ where: {
329
+ favouriteNumber: {
330
+ lessThan: 2,
331
+ },
316
332
  },
317
333
  });
318
334
  expect(rows.length).toEqual(1);
@@ -330,8 +346,10 @@ test("ModelAPI.findMany - lessThanOrEquals", async () => {
330
346
  favouriteNumber: 3,
331
347
  });
332
348
  const rows = await personAPI.findMany({
333
- favouriteNumber: {
334
- lessThanOrEquals: 2,
349
+ where: {
350
+ favouriteNumber: {
351
+ lessThanOrEquals: 2,
352
+ },
335
353
  },
336
354
  });
337
355
  expect(rows.length).toEqual(2);
@@ -346,8 +364,10 @@ test("ModelAPI.findMany - before", async () => {
346
364
  date: new Date("2022-01-02"),
347
365
  });
348
366
  const rows = await personAPI.findMany({
349
- date: {
350
- before: new Date("2022-01-02"),
367
+ where: {
368
+ date: {
369
+ before: new Date("2022-01-02"),
370
+ },
351
371
  },
352
372
  });
353
373
  expect(rows.length).toEqual(1);
@@ -387,14 +407,156 @@ test("ModelAPI.findMany - onOrBefore", async () => {
387
407
  date: new Date("2022-01-03"),
388
408
  });
389
409
  const rows = await personAPI.findMany({
390
- date: {
391
- onOrBefore: new Date("2022-01-02"),
410
+ where: {
411
+ date: {
412
+ onOrBefore: new Date("2022-01-02"),
413
+ },
392
414
  },
393
415
  });
394
416
  expect(rows.length).toEqual(2);
395
417
  expect(rows.map((x) => x.id).sort()).toEqual([p.id, p2.id].sort());
396
418
  });
397
419
 
420
+ test("ModelAPI.findMany - limit", async () => {
421
+ await personAPI.create({
422
+ id: "1",
423
+ name: "Jim",
424
+ married: false,
425
+ favouriteNumber: 10,
426
+ });
427
+ await personAPI.create({
428
+ id: "2",
429
+ name: "Bob",
430
+ married: true,
431
+ favouriteNumber: 11,
432
+ });
433
+ await personAPI.create({
434
+ id: "3",
435
+ name: "Sally",
436
+ married: true,
437
+ favouriteNumber: 12,
438
+ });
439
+
440
+ const rows = await personAPI.findMany({
441
+ limit: 2,
442
+ });
443
+
444
+ expect(rows.map((r) => r.name)).toEqual(["Jim", "Bob"]);
445
+ });
446
+
447
+ test("ModelAPI.findMany - orderBy", async () => {
448
+ await personAPI.create({
449
+ id: "1",
450
+ name: "Jim",
451
+ married: false,
452
+ favouriteNumber: 10,
453
+ date: new Date(2023, 12, 29),
454
+ });
455
+ await personAPI.create({
456
+ id: "2",
457
+ name: "Bob",
458
+ married: true,
459
+ favouriteNumber: 11,
460
+ date: new Date(2023, 12, 30),
461
+ });
462
+ await personAPI.create({
463
+ id: "3",
464
+ name: "Sally",
465
+ married: true,
466
+ favouriteNumber: 12,
467
+ date: new Date(2023, 12, 31),
468
+ });
469
+
470
+ const ascendingNames = await personAPI.findMany({
471
+ orderBy: {
472
+ name: "asc",
473
+ },
474
+ });
475
+
476
+ expect(ascendingNames.map((r) => r.name)).toEqual(["Bob", "Jim", "Sally"]);
477
+
478
+ const descendingNames = await personAPI.findMany({
479
+ orderBy: {
480
+ name: "desc",
481
+ },
482
+ });
483
+
484
+ expect(descendingNames.map((r) => r.name)).toEqual(["Sally", "Jim", "Bob"]);
485
+
486
+ const ascendingFavouriteNumbers = await personAPI.findMany({
487
+ orderBy: {
488
+ favouriteNumber: "asc",
489
+ },
490
+ });
491
+
492
+ expect(ascendingFavouriteNumbers.map((r) => r.name)).toEqual([
493
+ "Jim",
494
+ "Bob",
495
+ "Sally",
496
+ ]);
497
+
498
+ const descendingDates = await personAPI.findMany({
499
+ orderBy: {
500
+ date: "desc",
501
+ },
502
+ });
503
+
504
+ expect(descendingDates.map((r) => r.name)).toEqual(["Sally", "Bob", "Jim"]);
505
+ });
506
+
507
+ test("ModelAPI.findMany - offset", async () => {
508
+ await personAPI.create({
509
+ id: "1",
510
+ name: "Jim",
511
+ married: false,
512
+ favouriteNumber: 10,
513
+ date: new Date(2023, 12, 29),
514
+ });
515
+ await personAPI.create({
516
+ id: "2",
517
+ name: "Bob",
518
+ married: true,
519
+ favouriteNumber: 11,
520
+ date: new Date(2023, 12, 30),
521
+ });
522
+ await personAPI.create({
523
+ id: "3",
524
+ name: "Sally",
525
+ married: true,
526
+ favouriteNumber: 12,
527
+ date: new Date(2023, 12, 31),
528
+ });
529
+
530
+ const rows = await personAPI.findMany({
531
+ offset: 1,
532
+ limit: 2,
533
+ orderBy: {
534
+ name: "asc",
535
+ },
536
+ });
537
+
538
+ expect(rows.map((r) => r.name)).toEqual(["Jim", "Sally"]);
539
+
540
+ const rows2 = await personAPI.findMany({
541
+ offset: 2,
542
+ orderBy: {
543
+ name: "asc",
544
+ },
545
+ });
546
+
547
+ expect(rows2.map((r) => r.name)).toEqual(["Sally"]);
548
+
549
+ const rows3 = await personAPI.findMany({
550
+ offset: 1,
551
+ orderBy: {
552
+ name: "asc",
553
+ },
554
+ limit: 1,
555
+ });
556
+
557
+ expect(rows3.map((r) => r.name)).toEqual(["Jim"]);
558
+ });
559
+
398
560
  test("ModelAPI.findMany - after", async () => {
399
561
  await personAPI.create({
400
562
  date: new Date("2022-01-01"),
@@ -403,8 +565,10 @@ test("ModelAPI.findMany - after", async () => {
403
565
  date: new Date("2022-01-02"),
404
566
  });
405
567
  const rows = await personAPI.findMany({
406
- date: {
407
- after: new Date("2022-01-01"),
568
+ where: {
569
+ date: {
570
+ after: new Date("2022-01-01"),
571
+ },
408
572
  },
409
573
  });
410
574
  expect(rows.length).toEqual(1);
@@ -422,8 +586,10 @@ test("ModelAPI.findMany - onOrAfter", async () => {
422
586
  date: new Date("2022-01-03"),
423
587
  });
424
588
  const rows = await personAPI.findMany({
425
- date: {
426
- onOrAfter: new Date("2022-01-02"),
589
+ where: {
590
+ date: {
591
+ onOrAfter: new Date("2022-01-02"),
592
+ },
427
593
  },
428
594
  });
429
595
  expect(rows.length).toEqual(2);
@@ -438,8 +604,10 @@ test("ModelAPI.findMany - equals", async () => {
438
604
  name: "Sally",
439
605
  });
440
606
  const rows = await personAPI.findMany({
441
- name: {
442
- equals: "Jim",
607
+ where: {
608
+ name: {
609
+ equals: "Jim",
610
+ },
443
611
  },
444
612
  });
445
613
  expect(rows.length).toEqual(1);
@@ -454,8 +622,10 @@ test("ModelAPI.findMany - notEquals", async () => {
454
622
  name: "Sally",
455
623
  });
456
624
  const rows = await personAPI.findMany({
457
- name: {
458
- notEquals: "Sally",
625
+ where: {
626
+ name: {
627
+ notEquals: "Sally",
628
+ },
459
629
  },
460
630
  });
461
631
  expect(rows.length).toEqual(1);
@@ -523,8 +693,10 @@ test("ModelAPI.findMany - relationships - one to many", async () => {
523
693
  });
524
694
 
525
695
  const posts = await postAPI.findMany({
526
- author: {
527
- name: "Jim",
696
+ where: {
697
+ author: {
698
+ name: "Jim",
699
+ },
528
700
  },
529
701
  });
530
702
  expect(posts.length).toEqual(2);
@@ -549,10 +721,12 @@ test("ModelAPI.findMany - relationships - many to one", async () => {
549
721
  });
550
722
 
551
723
  const people = await personAPI.findMany({
552
- posts: {
553
- title: {
554
- startsWith: "My ",
555
- endsWith: " Post",
724
+ where: {
725
+ posts: {
726
+ title: {
727
+ startsWith: "My ",
728
+ endsWith: " Post",
729
+ },
556
730
  },
557
731
  },
558
732
  });
@@ -666,3 +840,35 @@ test("ModelAPI.delete", async () => {
666
840
  expect(deletedId).toEqual(id);
667
841
  await expect(personAPI.findOne({ id })).resolves.toEqual(null);
668
842
  });
843
+
844
+ test("ModelAPI chained findMany with offset/limit/order by", async () => {
845
+ await postAPI.create({
846
+ title: "adam",
847
+ });
848
+ await postAPI.create({
849
+ title: "dave",
850
+ });
851
+ const three = await postAPI.create({
852
+ title: "jon",
853
+ });
854
+ const four = await postAPI.create({
855
+ title: "jon bretman",
856
+ });
857
+
858
+ const results = await postAPI
859
+ .where({ title: { equals: "adam" } })
860
+ .orWhere({
861
+ title: { startsWith: "jon" },
862
+ })
863
+ .findMany({
864
+ limit: 3,
865
+ offset: 1,
866
+ orderBy: {
867
+ title: "asc",
868
+ },
869
+ });
870
+
871
+ // because we've offset by 1, adam should not appear in the results even though
872
+ // the query constraints match adam
873
+ expect(results).toEqual([three, four]);
874
+ });
@@ -1,6 +1,13 @@
1
1
  const { applyWhereConditions } = require("./applyWhereConditions");
2
+ const {
3
+ applyLimit,
4
+ applyOffset,
5
+ applyOrderBy,
6
+ } = require("./applyAdditionalQueryConstraints");
2
7
  const { applyJoins } = require("./applyJoins");
3
8
  const { camelCaseObject, upperCamelCase } = require("./casing");
9
+ const { getDatabase } = require("./database");
10
+ const { QueryContext } = require("./QueryContext");
4
11
  const tracing = require("./tracing");
5
12
 
6
13
  class QueryBuilder {
@@ -36,12 +43,50 @@ class QueryBuilder {
36
43
  return new QueryBuilder(this._tableName, context, builder);
37
44
  }
38
45
 
39
- async findMany() {
40
- const spanName = `Database ${upperCamelCase(this._tableName)}.findMany`;
41
- return tracing.withSpan(spanName, async (span) => {
42
- const query = this._db.orderBy("id");
46
+ async findMany(params) {
47
+ const name = tracing.spanNameForModelAPI(this._modelName, "findMany");
48
+ const db = getDatabase();
49
+
50
+ return tracing.withSpan(name, async (span) => {
51
+ const context = new QueryContext([this._tableName], this._tableConfigMap);
52
+
53
+ let builder = db
54
+ .selectFrom((qb) => {
55
+ // this._db contains all of the where constraints and joins
56
+ // we want to include that in the sub query in the same way we
57
+ // add all of this information into the sub query in the ModelAPI's
58
+ // implementation of findMany
59
+ return this._db.as(this._tableName);
60
+ })
61
+ .selectAll();
62
+
63
+ // The only constraints added to the main query are the orderBy, limit and offset as they are performed on the "outer" set
64
+ if (params?.limit) {
65
+ builder = applyLimit(context, builder, params.limit);
66
+ }
67
+
68
+ if (params?.offset) {
69
+ builder = applyOffset(context, builder, params.offset);
70
+ }
71
+
72
+ if (
73
+ params?.orderBy !== undefined &&
74
+ Object.keys(params?.orderBy).length > 0
75
+ ) {
76
+ builder = applyOrderBy(
77
+ context,
78
+ builder,
79
+ this._tableName,
80
+ params.orderBy
81
+ );
82
+ } else {
83
+ builder = builder.orderBy(`${this._tableName}.id`);
84
+ }
85
+
86
+ const query = builder;
87
+
43
88
  span.setAttribute("sql", query.compile().sql);
44
- const rows = await query.execute();
89
+ const rows = await builder.execute();
45
90
  return rows.map((x) => camelCaseObject(x));
46
91
  });
47
92
  }
@@ -0,0 +1,22 @@
1
+ const { snakeCase } = require("change-case");
2
+
3
+ function applyLimit(context, qb, limit) {
4
+ return qb.limit(limit);
5
+ }
6
+
7
+ function applyOffset(context, qb, offset) {
8
+ return qb.offset(offset);
9
+ }
10
+
11
+ function applyOrderBy(context, qb, tableName, orderBy = {}) {
12
+ Object.entries(orderBy).forEach(([key, sortOrder]) => {
13
+ qb = qb.orderBy(`${tableName}.${snakeCase(key)}`, sortOrder);
14
+ });
15
+ return qb;
16
+ }
17
+
18
+ module.exports = {
19
+ applyLimit,
20
+ applyOffset,
21
+ applyOrderBy,
22
+ };
package/src/database.js CHANGED
@@ -63,6 +63,14 @@ function getDatabase() {
63
63
 
64
64
  db = new Kysely({
65
65
  dialect: getDialect(),
66
+ log(event) {
67
+ if ("DEBUG" in process.env) {
68
+ if (event.level === "query") {
69
+ console.log(event.query.sql);
70
+ console.log(event.query.parameters);
71
+ }
72
+ }
73
+ },
66
74
  });
67
75
 
68
76
  return db;
package/src/tracing.js CHANGED
@@ -73,8 +73,13 @@ function getTracer() {
73
73
  return opentelemetry.trace.getTracer("functions");
74
74
  }
75
75
 
76
+ function spanNameForModelAPI(modelName, action) {
77
+ return `Database ${modelName}.${action}`;
78
+ }
79
+
76
80
  module.exports = {
77
81
  getTracer,
78
82
  withSpan,
79
83
  init,
84
+ spanNameForModelAPI,
80
85
  };