@teamkeel/functions-runtime 0.373.1 → 0.375.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/.env.test CHANGED
@@ -1,2 +1,2 @@
1
- KEEL_DB_CONN=postgresql://postgres:postgres@localhost:5432/functions-runtime
1
+ KEEL_DB_CONN=postgresql://postgres:postgres@localhost:7654/functions-runtime
2
2
  KEEL_DB_CONN_TYPE=pg
package/compose.yaml CHANGED
@@ -7,4 +7,4 @@ services:
7
7
  - POSTGRES_PASSWORD=postgres
8
8
  - POSTGRES_DB=functions-runtime
9
9
  ports:
10
- - "5432:5432"
10
+ - "7654:5432"
package/package.json CHANGED
@@ -1,10 +1,10 @@
1
1
  {
2
2
  "name": "@teamkeel/functions-runtime",
3
- "version": "0.373.1",
3
+ "version": "0.375.0",
4
4
  "description": "Internal package used by @teamkeel/sdk",
5
5
  "main": "src/index.js",
6
6
  "scripts": {
7
- "test": "DEBUG=true vitest run --reporter verbose --threads false",
7
+ "test": "vitest run --reporter verbose --threads false",
8
8
  "format": "npx prettier --write src/**/*.js"
9
9
  },
10
10
  "keywords": [],
@@ -15,20 +15,20 @@
15
15
  "access": "public"
16
16
  },
17
17
  "devDependencies": {
18
- "prettier": "2.7.1",
19
- "vitest": "^0.27.1"
18
+ "prettier": "3.1.1",
19
+ "vitest": "^0.34.6"
20
20
  },
21
21
  "dependencies": {
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",
22
+ "@opentelemetry/api": "^1.7.0",
23
+ "@opentelemetry/exporter-trace-otlp-proto": "^0.46.0",
24
+ "@opentelemetry/resources": "^1.19.0",
25
+ "@opentelemetry/sdk-trace-base": "^1.19.0",
26
+ "@opentelemetry/sdk-trace-node": "^1.19.0",
27
27
  "change-case": "^4.1.2",
28
- "json-rpc-2.0": "^1.4.1",
28
+ "json-rpc-2.0": "^1.7.0",
29
29
  "ksuid": "^3.0.0",
30
- "kysely": "^0.23.4",
31
- "pg": "^8.8.0",
30
+ "kysely": "^0.23.5",
31
+ "pg": "^8.11.3",
32
32
  "traceparent": "^1.0.0"
33
33
  }
34
34
  }
package/src/ModelAPI.js CHANGED
@@ -1,3 +1,4 @@
1
+ const { sql } = require("kysely");
1
2
  const { useDatabase } = require("./database");
2
3
  const { QueryBuilder } = require("./QueryBuilder");
3
4
  const { QueryContext } = require("./QueryContext");
@@ -50,26 +51,10 @@ class ModelAPI {
50
51
 
51
52
  async create(values) {
52
53
  const name = tracing.spanNameForModelAPI(this._modelName, "create");
53
- const db = useDatabase();
54
54
 
55
- return tracing.withSpan(name, async (span) => {
56
- try {
57
- const query = db
58
- .insertInto(this._tableName)
59
- .values(
60
- snakeCaseObject({
61
- ...values,
62
- })
63
- )
64
- .returningAll();
65
-
66
- span.setAttribute("sql", query.compile().sql);
67
- const row = await query.executeTakeFirstOrThrow();
68
-
69
- return camelCaseObject(row);
70
- } catch (e) {
71
- throw new DatabaseError(e);
72
- }
55
+ return tracing.withSpan(name, () => {
56
+ const db = useDatabase();
57
+ return create(db, this._tableName, this._tableConfigMap, values);
73
58
  });
74
59
  }
75
60
 
@@ -218,6 +203,111 @@ class ModelAPI {
218
203
  }
219
204
  }
220
205
 
206
+ async function create(conn, tableName, tableConfigs, values) {
207
+ try {
208
+ let query = conn.insertInto(tableName);
209
+
210
+ const keys = values ? Object.keys(values) : [];
211
+ const tableConfig = tableConfigs[tableName] || {};
212
+ const hasManyRecords = [];
213
+
214
+ if (keys.length === 0) {
215
+ // See https://github.com/kysely-org/kysely/issues/685#issuecomment-1711240534
216
+ query = query.expression(sql`default values`);
217
+ } else {
218
+ const row = {};
219
+ for (const key of keys) {
220
+ const value = values[key];
221
+ const columnConfig = tableConfig[key];
222
+
223
+ if (!columnConfig) {
224
+ row[key] = value;
225
+ continue;
226
+ }
227
+
228
+ switch (columnConfig.relationshipType) {
229
+ case "belongsTo":
230
+ if (!isPlainObject(value)) {
231
+ throw new Error(
232
+ `non-object provided for field ${key} of ${tableName}`
233
+ );
234
+ }
235
+
236
+ if (isReferencingExistingRecord(value)) {
237
+ row[columnConfig.foreignKey] = value.id;
238
+ break;
239
+ }
240
+
241
+ const created = await create(
242
+ conn,
243
+ columnConfig.referencesTable,
244
+ tableConfigs,
245
+ value
246
+ );
247
+ row[columnConfig.foreignKey] = created.id;
248
+ break;
249
+
250
+ case "hasMany":
251
+ if (!Array.isArray(value)) {
252
+ throw new Error(
253
+ `non-array provided for has-many field ${key} of ${tableName}`
254
+ );
255
+ }
256
+ for (const v of value) {
257
+ hasManyRecords.push({
258
+ key,
259
+ value: v,
260
+ columnConfig,
261
+ });
262
+ }
263
+ break;
264
+ default:
265
+ throw new Error(
266
+ `unsupported relationship type - ${tableName}.${key} (${columnConfig.relationshipType})`
267
+ );
268
+ }
269
+ }
270
+
271
+ query = query.values(row);
272
+ }
273
+
274
+ const created = await query.returningAll().executeTakeFirstOrThrow();
275
+
276
+ await Promise.all(
277
+ hasManyRecords.map(async ({ key, value, columnConfig }) => {
278
+ if (!isPlainObject(value)) {
279
+ throw new Error(
280
+ `non-object provided for field ${key} of ${tableName}`
281
+ );
282
+ }
283
+
284
+ if (isReferencingExistingRecord(value)) {
285
+ throw new Error(
286
+ `nested update as part of create not supported for ${key} of ${tableConfig}`
287
+ );
288
+ }
289
+
290
+ await create(conn, columnConfig.referencesTable, tableConfigs, {
291
+ ...value,
292
+ [columnConfig.foreignKey]: created.id,
293
+ });
294
+ })
295
+ );
296
+
297
+ return created;
298
+ } catch (e) {
299
+ throw new DatabaseError(e);
300
+ }
301
+ }
302
+
303
+ function isPlainObject(obj) {
304
+ return Object.prototype.toString.call(obj) === "[object Object]";
305
+ }
306
+
307
+ function isReferencingExistingRecord(value) {
308
+ return Object.keys(value).length === 1 && value.id;
309
+ }
310
+
221
311
  module.exports = {
222
312
  ModelAPI,
223
313
  DatabaseError,
@@ -517,6 +517,46 @@ test("ModelAPI.findMany - orderBy", async () => {
517
517
  expect(descendingDates.map((r) => r.name)).toEqual(["Sally", "Bob", "Jim"]);
518
518
  });
519
519
 
520
+ test("ModelAPI.findMany - orderBy ASC and DESC capitalised", async () => {
521
+ await personAPI.create({
522
+ id: KSUID.randomSync().string,
523
+ name: "Jim",
524
+ married: false,
525
+ favouriteNumber: 10,
526
+ date: new Date(2023, 12, 29),
527
+ });
528
+ await personAPI.create({
529
+ id: KSUID.randomSync().string,
530
+ name: "Bob",
531
+ married: true,
532
+ favouriteNumber: 11,
533
+ date: new Date(2023, 12, 30),
534
+ });
535
+ await personAPI.create({
536
+ id: KSUID.randomSync().string,
537
+ name: "Sally",
538
+ married: true,
539
+ favouriteNumber: 12,
540
+ date: new Date(2023, 12, 31),
541
+ });
542
+
543
+ const ascendingNames = await personAPI.findMany({
544
+ orderBy: {
545
+ name: "ASC",
546
+ },
547
+ });
548
+
549
+ expect(ascendingNames.map((r) => r.name)).toEqual(["Bob", "Jim", "Sally"]);
550
+
551
+ const descendingNames = await personAPI.findMany({
552
+ orderBy: {
553
+ name: "DESC",
554
+ },
555
+ });
556
+
557
+ expect(descendingNames.map((r) => r.name)).toEqual(["Sally", "Jim", "Bob"]);
558
+ });
559
+
520
560
  test("ModelAPI.findMany - offset", async () => {
521
561
  await personAPI.create({
522
562
  id: KSUID.randomSync().string,
@@ -10,7 +10,7 @@ function applyOffset(context, qb, offset) {
10
10
 
11
11
  function applyOrderBy(context, qb, tableName, orderBy = {}) {
12
12
  Object.entries(orderBy).forEach(([key, sortOrder]) => {
13
- qb = qb.orderBy(`${tableName}.${snakeCase(key)}`, sortOrder);
13
+ qb = qb.orderBy(`${tableName}.${snakeCase(key)}`, sortOrder.toLowerCase());
14
14
  });
15
15
  return qb;
16
16
  }
@@ -99,9 +99,6 @@ describe("ModelAPI error handling", () => {
99
99
  let db;
100
100
 
101
101
  beforeEach(async () => {
102
- process.env.KEEL_DB_CONN_TYPE = "pg";
103
- process.env.KEEL_DB_CONN = `postgresql://postgres:postgres@localhost:5432/functions-runtime`;
104
-
105
102
  db = useDatabase();
106
103
 
107
104
  await sql`
@@ -218,9 +218,6 @@ describe("ModelAPI error handling", () => {
218
218
  let db;
219
219
 
220
220
  beforeEach(async () => {
221
- process.env.KEEL_DB_CONN_TYPE = "pg";
222
- process.env.KEEL_DB_CONN = `postgresql://postgres:postgres@localhost:5432/functions-runtime`;
223
-
224
221
  db = useDatabase();
225
222
 
226
223
  await sql`
package/src/tracing.js CHANGED
@@ -119,6 +119,14 @@ function init() {
119
119
  patchConsoleLog();
120
120
  }
121
121
 
122
+ async function forceFlush() {
123
+ // The "delegate" is the actual provider set by the functions-runtime package
124
+ const provider = opentelemetry.trace.getTracerProvider().getDelegate();
125
+ if (provider && provider.forceFlush) {
126
+ await provider.forceFlush();
127
+ }
128
+ }
129
+
122
130
  function getTracer() {
123
131
  return opentelemetry.trace.getTracer("functions");
124
132
  }
@@ -131,5 +139,6 @@ module.exports = {
131
139
  getTracer,
132
140
  withSpan,
133
141
  init,
142
+ forceFlush,
134
143
  spanNameForModelAPI,
135
144
  };
@@ -33,7 +33,10 @@ test("withSpan span time", async () => {
33
33
 
34
34
  expect(spanEvents.map((e) => e.event)).toEqual(["onStart", "onEnd"]);
35
35
  const spanDuration = spanEvents.pop().span._duration.pop();
36
- const waitTimeNanos = waitTimeMillis * 1000 * 1000;
36
+
37
+ // The '- 1' here is because sometimes the test fails due to the span duration
38
+ // being something like 99.87ms. As long as it's at least 99ms we're happy
39
+ const waitTimeNanos = (waitTimeMillis - 1) * 1000 * 1000;
37
40
  expect(spanDuration).toBeGreaterThan(waitTimeNanos);
38
41
  });
39
42