@teamkeel/functions-runtime 0.383.0 → 0.385.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.383.0",
3
+ "version": "0.385.0",
4
4
  "description": "Internal package used by @teamkeel/sdk",
5
5
  "main": "src/index.js",
6
6
  "scripts": {
@@ -27,7 +27,7 @@
27
27
  "change-case": "^4.1.2",
28
28
  "json-rpc-2.0": "^1.7.0",
29
29
  "ksuid": "^3.0.0",
30
- "kysely": "^0.23.5",
30
+ "kysely": "^0.25.0",
31
31
  "pg": "^8.11.3",
32
32
  "traceparent": "^1.0.0"
33
33
  }
package/src/ModelAPI.js CHANGED
@@ -54,7 +54,12 @@ class ModelAPI {
54
54
 
55
55
  return tracing.withSpan(name, () => {
56
56
  const db = useDatabase();
57
- return create(db, this._tableName, this._tableConfigMap, values);
57
+ return create(
58
+ db,
59
+ this._tableName,
60
+ this._tableConfigMap,
61
+ snakeCaseObject(values)
62
+ );
58
63
  });
59
64
  }
60
65
 
@@ -26,6 +26,8 @@ beforeEach(async () => {
26
26
  CREATE TABLE post(
27
27
  id text PRIMARY KEY,
28
28
  title text,
29
+ tags text[],
30
+ rating numeric,
29
31
  author_id text references person(id)
30
32
  );
31
33
  CREATE TABLE author(
@@ -101,7 +103,6 @@ test("ModelAPI.create - throws if database constraint fails", async () => {
101
103
  favouriteNumber: 10,
102
104
  });
103
105
  const promise = personAPI.create({
104
- id: KSUID.randomSync().string,
105
106
  id: row.id,
106
107
  name: "Jim",
107
108
  married: false,
@@ -112,6 +113,25 @@ test("ModelAPI.create - throws if database constraint fails", async () => {
112
113
  );
113
114
  });
114
115
 
116
+ test("ModelAPI.create - arrays", async () => {
117
+ const person = await personAPI.create({
118
+ id: KSUID.randomSync().string,
119
+ name: "Jim",
120
+ married: false,
121
+ favouriteNumber: 10,
122
+ });
123
+
124
+ const row = await postAPI.create({
125
+ id: "id",
126
+ title: "My Post",
127
+ tags: ["tag 1", "tag 2"],
128
+ rating: 1.23,
129
+ authorId: person.id,
130
+ });
131
+ expect(row.tags).toEqual(["tag 1", "tag 2"]);
132
+ expect(row.rating).toEqual(1.23);
133
+ });
134
+
115
135
  test("ModelAPI.findOne", async () => {
116
136
  const created = await personAPI.create({
117
137
  id: KSUID.randomSync().string,
@@ -1006,7 +1026,7 @@ describe("QueryBuilder", () => {
1006
1026
  expect(deletedId).toEqual(p.id);
1007
1027
  });
1008
1028
 
1009
- test("Model API chained delete - non existent id", async () => {
1029
+ test("ModelAPI chained delete - non existent id", async () => {
1010
1030
  const fakeId = "xxx";
1011
1031
 
1012
1032
  // the error message returned from the runtime will actually be 'record not found'
@@ -1017,7 +1037,7 @@ describe("QueryBuilder", () => {
1017
1037
  );
1018
1038
  });
1019
1039
 
1020
- test("Model API chained findOne", async () => {
1040
+ test("ModelAPI chained findOne", async () => {
1021
1041
  const p = await personAPI.create({
1022
1042
  id: KSUID.randomSync().string,
1023
1043
  name: "Jake",
@@ -1058,7 +1078,7 @@ describe("QueryBuilder", () => {
1058
1078
  // expect(results[0].id).toEqual(p2);
1059
1079
  // });
1060
1080
 
1061
- test("Model API chained update", async () => {
1081
+ test("ModelAPI chained update", async () => {
1062
1082
  const p1 = await postAPI.create({
1063
1083
  id: KSUID.randomSync().string,
1064
1084
  title: "adam",
@@ -1099,4 +1119,336 @@ describe("QueryBuilder", () => {
1099
1119
  .update({ title: "bob" })
1100
1120
  ).resolves.toEqual(null);
1101
1121
  });
1122
+
1123
+ test("ModelAPI.findMany - array equals", async () => {
1124
+ const person = await personAPI.create({
1125
+ id: KSUID.randomSync().string,
1126
+ name: "Jake",
1127
+ favouriteNumber: 8,
1128
+ date: new Date("2021-12-31"),
1129
+ });
1130
+ const p1 = await postAPI.create({
1131
+ id: KSUID.randomSync().string,
1132
+ title: "Post 1",
1133
+ tags: ["Tag 1", "Tag 2"],
1134
+ authorId: person.id,
1135
+ });
1136
+ const p2 = await postAPI.create({
1137
+ id: KSUID.randomSync().string,
1138
+ title: "Post 2",
1139
+ tags: ["Tag 2", "Tag 3"],
1140
+ authorId: person.id,
1141
+ });
1142
+ const p3 = await postAPI.create({
1143
+ id: KSUID.randomSync().string,
1144
+ title: "Post 2",
1145
+ tags: [],
1146
+ authorId: person.id,
1147
+ });
1148
+ const p4 = await postAPI.create({
1149
+ id: KSUID.randomSync().string,
1150
+ title: "Post 2",
1151
+ tags: null,
1152
+ authorId: person.id,
1153
+ });
1154
+
1155
+ const rows = await postAPI.findMany({
1156
+ where: {
1157
+ tags: {
1158
+ equals: ["Tag 1", "Tag 2"],
1159
+ },
1160
+ },
1161
+ });
1162
+
1163
+ expect(rows.length).toEqual(1);
1164
+ expect(rows.map((x) => x.id).sort()).toEqual([p1.id].sort());
1165
+ });
1166
+
1167
+ test("ModelAPI.findMany - array equals implicit", async () => {
1168
+ const person = await personAPI.create({
1169
+ id: KSUID.randomSync().string,
1170
+ name: "Jake",
1171
+ favouriteNumber: 8,
1172
+ date: new Date("2021-12-31"),
1173
+ });
1174
+ const p1 = await postAPI.create({
1175
+ id: KSUID.randomSync().string,
1176
+ title: "Post 1",
1177
+ tags: ["Tag 1", "Tag 2"],
1178
+ authorId: person.id,
1179
+ });
1180
+ const p2 = await postAPI.create({
1181
+ id: KSUID.randomSync().string,
1182
+ title: "Post 2",
1183
+ tags: ["Tag 2", "Tag 3"],
1184
+ authorId: person.id,
1185
+ });
1186
+ const p3 = await postAPI.create({
1187
+ id: KSUID.randomSync().string,
1188
+ title: "Post 2",
1189
+ tags: [],
1190
+ authorId: person.id,
1191
+ });
1192
+
1193
+ const rows = await postAPI.findMany({
1194
+ where: {
1195
+ tags: ["Tag 1", "Tag 2"],
1196
+ },
1197
+ });
1198
+
1199
+ expect(rows.length).toEqual(1);
1200
+ expect(rows.map((x) => x.id).sort()).toEqual([p1.id].sort());
1201
+ });
1202
+
1203
+ test("ModelAPI.findMany - array not equals", async () => {
1204
+ const person = await personAPI.create({
1205
+ id: KSUID.randomSync().string,
1206
+ name: "Jake",
1207
+ favouriteNumber: 8,
1208
+ date: new Date("2021-12-31"),
1209
+ });
1210
+ const p1 = await postAPI.create({
1211
+ id: KSUID.randomSync().string,
1212
+ title: "Post 1",
1213
+ tags: ["Tag 1", "Tag 2"],
1214
+ authorId: person.id,
1215
+ });
1216
+ const p2 = await postAPI.create({
1217
+ id: KSUID.randomSync().string,
1218
+ title: "Post 2",
1219
+ tags: ["Tag 2", "Tag 3"],
1220
+ authorId: person.id,
1221
+ });
1222
+ const p3 = await postAPI.create({
1223
+ id: KSUID.randomSync().string,
1224
+ title: "Post 2",
1225
+ tags: [],
1226
+ authorId: person.id,
1227
+ });
1228
+ const p4 = await postAPI.create({
1229
+ id: KSUID.randomSync().string,
1230
+ title: "Post 2",
1231
+ tags: null,
1232
+ authorId: person.id,
1233
+ });
1234
+
1235
+ const rows = await postAPI.findMany({
1236
+ where: {
1237
+ tags: {
1238
+ notEquals: ["Tag 1", "Tag 2"],
1239
+ },
1240
+ },
1241
+ });
1242
+
1243
+ expect(rows.length).toEqual(3);
1244
+ expect(rows.map((x) => x.id).sort()).toEqual([p4.id, p3.id, p2.id].sort());
1245
+ });
1246
+
1247
+ test("ModelAPI.findMany - array any equals", async () => {
1248
+ const person = await personAPI.create({
1249
+ id: KSUID.randomSync().string,
1250
+ name: "Jake",
1251
+ favouriteNumber: 8,
1252
+ date: new Date("2021-12-31"),
1253
+ });
1254
+ const p1 = await postAPI.create({
1255
+ id: KSUID.randomSync().string,
1256
+ title: "Post 1",
1257
+ tags: ["Tag 1", "Tag 2"],
1258
+ authorId: person.id,
1259
+ });
1260
+ const p2 = await postAPI.create({
1261
+ id: KSUID.randomSync().string,
1262
+ title: "Post 2",
1263
+ tags: ["Tag 2", "Tag 3"],
1264
+ authorId: person.id,
1265
+ });
1266
+ const p3 = await postAPI.create({
1267
+ id: KSUID.randomSync().string,
1268
+ title: "Post 2",
1269
+ tags: [],
1270
+ authorId: person.id,
1271
+ });
1272
+ const p4 = await postAPI.create({
1273
+ id: KSUID.randomSync().string,
1274
+ title: "Post 2",
1275
+ tags: null,
1276
+ authorId: person.id,
1277
+ });
1278
+ const p5 = await postAPI.create({
1279
+ id: KSUID.randomSync().string,
1280
+ title: "Post 2",
1281
+ tags: ["Tag 3"],
1282
+ authorId: person.id,
1283
+ });
1284
+
1285
+ const rows = await postAPI.findMany({
1286
+ where: {
1287
+ tags: {
1288
+ any: {
1289
+ equals: "Tag 2",
1290
+ },
1291
+ },
1292
+ },
1293
+ });
1294
+
1295
+ expect(rows.length).toEqual(2);
1296
+ expect(rows.map((x) => x.id).sort()).toEqual([p1.id, p2.id].sort());
1297
+ });
1298
+
1299
+ test("ModelAPI.findMany - array any not equals", async () => {
1300
+ const person = await personAPI.create({
1301
+ id: KSUID.randomSync().string,
1302
+ name: "Jake",
1303
+ favouriteNumber: 8,
1304
+ date: new Date("2021-12-31"),
1305
+ });
1306
+ const p1 = await postAPI.create({
1307
+ id: KSUID.randomSync().string,
1308
+ title: "Post 1",
1309
+ tags: ["Tag 1", "Tag 2"],
1310
+ authorId: person.id,
1311
+ });
1312
+ const p2 = await postAPI.create({
1313
+ id: KSUID.randomSync().string,
1314
+ title: "Post 2",
1315
+ tags: ["Tag 2", "Tag 3"],
1316
+ authorId: person.id,
1317
+ });
1318
+ const p3 = await postAPI.create({
1319
+ id: KSUID.randomSync().string,
1320
+ title: "Post 2",
1321
+ tags: [],
1322
+ authorId: person.id,
1323
+ });
1324
+ const p4 = await postAPI.create({
1325
+ id: KSUID.randomSync().string,
1326
+ title: "Post 2",
1327
+ tags: null,
1328
+ authorId: person.id,
1329
+ });
1330
+ const p5 = await postAPI.create({
1331
+ id: KSUID.randomSync().string,
1332
+ title: "Post 2",
1333
+ tags: ["Tag 3"],
1334
+ authorId: person.id,
1335
+ });
1336
+
1337
+ const rows = await postAPI.findMany({
1338
+ where: {
1339
+ tags: {
1340
+ any: {
1341
+ notEquals: "Tag 3",
1342
+ },
1343
+ },
1344
+ },
1345
+ });
1346
+
1347
+ expect(rows.length).toEqual(2);
1348
+ expect(rows.map((x) => x.id).sort()).toEqual([p1.id, p3.id].sort());
1349
+ });
1350
+
1351
+ test("ModelAPI.findMany - array all equals", async () => {
1352
+ const person = await personAPI.create({
1353
+ id: KSUID.randomSync().string,
1354
+ name: "Jake",
1355
+ favouriteNumber: 8,
1356
+ date: new Date("2021-12-31"),
1357
+ });
1358
+ const p1 = await postAPI.create({
1359
+ id: KSUID.randomSync().string,
1360
+ title: "Post 1",
1361
+ tags: ["Tag 1", "Tag 2"],
1362
+ authorId: person.id,
1363
+ });
1364
+ const p2 = await postAPI.create({
1365
+ id: KSUID.randomSync().string,
1366
+ title: "Post 2",
1367
+ tags: ["Tag 2", "Tag 3"],
1368
+ authorId: person.id,
1369
+ });
1370
+ const p3 = await postAPI.create({
1371
+ id: KSUID.randomSync().string,
1372
+ title: "Post 2",
1373
+ tags: [],
1374
+ authorId: person.id,
1375
+ });
1376
+ const p4 = await postAPI.create({
1377
+ id: KSUID.randomSync().string,
1378
+ title: "Post 2",
1379
+ tags: null,
1380
+ authorId: person.id,
1381
+ });
1382
+ const p5 = await postAPI.create({
1383
+ id: KSUID.randomSync().string,
1384
+ title: "Post 2",
1385
+ tags: ["Tag 2", "Tag 2"],
1386
+ authorId: person.id,
1387
+ });
1388
+
1389
+ const rows = await postAPI.findMany({
1390
+ where: {
1391
+ tags: {
1392
+ all: {
1393
+ equals: "Tag 2",
1394
+ },
1395
+ },
1396
+ },
1397
+ });
1398
+
1399
+ expect(rows.length).toEqual(2);
1400
+ expect(rows.map((x) => x.id).sort()).toEqual([p3.id, p5.id].sort());
1401
+ });
1402
+
1403
+ test("ModelAPI.findMany - array all not equals", async () => {
1404
+ const person = await personAPI.create({
1405
+ id: KSUID.randomSync().string,
1406
+ name: "Jake",
1407
+ favouriteNumber: 8,
1408
+ date: new Date("2021-12-31"),
1409
+ });
1410
+ const p1 = await postAPI.create({
1411
+ id: KSUID.randomSync().string,
1412
+ title: "Post 1",
1413
+ tags: ["Tag 1", "Tag 2"],
1414
+ authorId: person.id,
1415
+ });
1416
+ const p2 = await postAPI.create({
1417
+ id: KSUID.randomSync().string,
1418
+ title: "Post 2",
1419
+ tags: ["Tag 2", "Tag 3"],
1420
+ authorId: person.id,
1421
+ });
1422
+ const p3 = await postAPI.create({
1423
+ id: KSUID.randomSync().string,
1424
+ title: "Post 2",
1425
+ tags: [],
1426
+ authorId: person.id,
1427
+ });
1428
+ const p4 = await postAPI.create({
1429
+ id: KSUID.randomSync().string,
1430
+ title: "Post 2",
1431
+ tags: null,
1432
+ authorId: person.id,
1433
+ });
1434
+ const p5 = await postAPI.create({
1435
+ id: KSUID.randomSync().string,
1436
+ title: "Post 2",
1437
+ tags: ["Tag 2", "Tag 2"],
1438
+ authorId: person.id,
1439
+ });
1440
+
1441
+ const rows = await postAPI.findMany({
1442
+ where: {
1443
+ tags: {
1444
+ all: {
1445
+ notEquals: "Tag 2",
1446
+ },
1447
+ },
1448
+ },
1449
+ });
1450
+
1451
+ expect(rows.length).toEqual(2);
1452
+ expect(rows.map((x) => x.id).sort()).toEqual([p1.id, p2.id].sort());
1453
+ });
1102
1454
  });
@@ -1,11 +1,11 @@
1
- const { sql } = require("kysely");
1
+ const { sql, Kysely } = require("kysely");
2
2
  const { snakeCase } = require("./casing");
3
3
 
4
4
  const opMapping = {
5
5
  startsWith: { op: "like", value: (v) => `${v}%` },
6
6
  endsWith: { op: "like", value: (v) => `%${v}` },
7
7
  contains: { op: "like", value: (v) => `%${v}%` },
8
- oneOf: { op: "in" },
8
+ oneOf: { op: "=", value: (v) => sql`ANY(${v})` },
9
9
  greaterThan: { op: ">" },
10
10
  greaterThanOrEquals: { op: ">=" },
11
11
  lessThan: { op: "<" },
@@ -16,6 +16,32 @@ const opMapping = {
16
16
  onOrAfter: { op: ">=" },
17
17
  equals: { op: sql`is not distinct from` },
18
18
  notEquals: { op: sql`is distinct from` },
19
+ any: {
20
+ isArrayQuery: true,
21
+ greaterThan: { op: ">" },
22
+ greaterThanOrEquals: { op: ">=" },
23
+ lessThan: { op: "<" },
24
+ lessThanOrEquals: { op: "<=" },
25
+ before: { op: "<" },
26
+ onOrBefore: { op: "<=" },
27
+ after: { op: ">" },
28
+ onOrAfter: { op: ">=" },
29
+ equals: { op: "=" },
30
+ notEquals: { op: "=", value: (v) => sql`NOT ${v}` },
31
+ },
32
+ all: {
33
+ isArrayQuery: true,
34
+ greaterThan: { op: ">" },
35
+ greaterThanOrEquals: { op: ">=" },
36
+ lessThan: { op: "<" },
37
+ lessThanOrEquals: { op: "<=" },
38
+ before: { op: "<" },
39
+ onOrBefore: { op: "<=" },
40
+ after: { op: ">" },
41
+ onOrAfter: { op: ">=" },
42
+ equals: { op: "=" },
43
+ notEquals: { op: "=", value: (v) => sql`NOT ${v}` },
44
+ },
19
45
  };
20
46
 
21
47
  /**
@@ -44,7 +70,7 @@ function applyWhereConditions(context, qb, where = {}) {
44
70
  const fieldName = `${context.tableAlias()}.${snakeCase(key)}`;
45
71
 
46
72
  if (Object.prototype.toString.call(v) !== "[object Object]") {
47
- qb = qb.where(fieldName, "=", v);
73
+ qb = qb.where(fieldName, sql`is not distinct from`, sql`${v}`);
48
74
  continue;
49
75
  }
50
76
 
@@ -54,11 +80,23 @@ function applyWhereConditions(context, qb, where = {}) {
54
80
  throw new Error(`invalid where condition: ${op}`);
55
81
  }
56
82
 
57
- qb = qb.where(
58
- fieldName,
59
- mapping.op,
60
- mapping.value ? mapping.value(v[op]) : v[op]
61
- );
83
+ if (mapping.isArrayQuery) {
84
+ for (const arrayOp of Object.keys(v[op])) {
85
+ qb = qb.where(
86
+ mapping[arrayOp].value
87
+ ? mapping[arrayOp].value(v[op][arrayOp])
88
+ : sql`${v[op][arrayOp]}`,
89
+ mapping[arrayOp].op,
90
+ sql`${sql(op)}(${sql.ref(fieldName)})`
91
+ );
92
+ }
93
+ } else {
94
+ qb = qb.where(
95
+ fieldName,
96
+ mapping.op,
97
+ mapping.value ? mapping.value(v[op]) : sql`${v[op]}`
98
+ );
99
+ }
62
100
  }
63
101
  }
64
102
 
package/src/database.js CHANGED
@@ -142,6 +142,12 @@ function getDialect() {
142
142
  const dbConnType = process.env["KEEL_DB_CONN_TYPE"];
143
143
  switch (dbConnType) {
144
144
  case "pg":
145
+ // Adding a custom type parser for numeric fields: see https://kysely.dev/docs/recipes/data-types#configuring-runtime-javascript-types
146
+ // 1700 = type for NUMERIC
147
+ pg.types.setTypeParser(1700, function (val) {
148
+ return parseFloat(val);
149
+ });
150
+
145
151
  return new PostgresDialect({
146
152
  pool: new InstrumentedPool({
147
153
  Client: InstrumentedClient,
package/src/index.d.ts CHANGED
@@ -27,7 +27,6 @@ export type NumberWhereCondition = {
27
27
  notEquals?: number | null;
28
28
  };
29
29
 
30
- // Date database API
31
30
  export type DateWhereCondition = {
32
31
  equals?: Date | string | null;
33
32
  before?: Date | string | null;
@@ -36,7 +35,6 @@ export type DateWhereCondition = {
36
35
  onOrAfter?: Date | string | null;
37
36
  };
38
37
 
39
- // Date query input
40
38
  export type DateQueryInput = {
41
39
  equals?: string | null;
42
40
  before?: string | null;
@@ -45,12 +43,67 @@ export type DateQueryInput = {
45
43
  onOrAfter?: string | null;
46
44
  };
47
45
 
48
- // Timestamp query input
49
46
  export type TimestampQueryInput = {
50
47
  before: string | null;
51
48
  after: string | null;
52
49
  };
53
50
 
51
+ export type StringArrayWhereCondition = {
52
+ equals?: string[] | null;
53
+ notEquals?: string[] | null;
54
+ any?: StringArrayQueryWhereCondition | null;
55
+ all?: StringArrayQueryWhereCondition | null;
56
+ };
57
+
58
+ export type StringArrayQueryWhereCondition = {
59
+ equals?: string | null;
60
+ notEquals?: string | null;
61
+ };
62
+
63
+ export type NumberArrayWhereCondition = {
64
+ equals?: number[] | null;
65
+ notEquals?: number[] | null;
66
+ any?: NumberArrayQueryWhereCondition | null;
67
+ all?: NumberArrayQueryWhereCondition | null;
68
+ };
69
+
70
+ export type NumberArrayQueryWhereCondition = {
71
+ greaterThan?: number | null;
72
+ greaterThanOrEquals?: number | null;
73
+ lessThan?: number | null;
74
+ lessThanOrEquals?: number | null;
75
+ equals?: number | null;
76
+ notEquals?: number | null;
77
+ };
78
+
79
+ export type BooleanArrayWhereCondition = {
80
+ equals?: bool[] | null;
81
+ notEquals?: bool[] | null;
82
+ any?: BooleanArrayQueryWhereCondition | null;
83
+ all?: BooleanArrayQueryWhereCondition | null;
84
+ };
85
+
86
+ export type BooleanArrayQueryWhereCondition = {
87
+ equals?: bool | null;
88
+ notEquals?: bool | null;
89
+ };
90
+
91
+ export type DateArrayWhereCondition = {
92
+ equals?: Date[] | null;
93
+ notEquals?: Date[] | null;
94
+ any?: DateArrayQueryWhereCondition | null;
95
+ all?: DateArrayQueryWhereCondition | null;
96
+ };
97
+
98
+ export type DateArrayQueryWhereCondition = {
99
+ greaterThan?: Date | null;
100
+ greaterThanOrEquals?: Date | null;
101
+ lessThan?: Date | null;
102
+ lessThanOrEquals?: number | null;
103
+ equals?: Date | null;
104
+ notEquals?: Date | null;
105
+ };
106
+
54
107
  export type ContextAPI = {
55
108
  headers: RequestHeaders;
56
109
  response: Response;