@teamkeel/functions-runtime 0.365.13 → 0.365.15-27

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.365.13",
3
+ "version": "0.365.15-27",
4
4
  "description": "Internal package used by @teamkeel/sdk",
5
5
  "main": "src/index.js",
6
6
  "scripts": {
package/src/ModelAPI.js CHANGED
@@ -14,6 +14,7 @@ const {
14
14
  upperCamelCase,
15
15
  } = require("./casing");
16
16
  const tracing = require("./tracing");
17
+ const { DatabaseError } = require("./errors");
17
18
 
18
19
  /**
19
20
  * RelationshipConfig is a simple representation of a model field that
@@ -34,13 +35,6 @@ const tracing = require("./tracing");
34
35
  * @typedef {Object.<string, TableConfig>} TableConfigMap
35
36
  */
36
37
 
37
- class DatabaseError extends Error {
38
- constructor(error) {
39
- super(error.message);
40
- this.error = error;
41
- }
42
- }
43
-
44
38
  class ModelAPI {
45
39
  /**
46
40
  * @param {string} tableName The name of the table this API is for
@@ -1,4 +1,4 @@
1
- import { test, expect, beforeEach } from "vitest";
1
+ import { test, expect, beforeEach, describe } from "vitest";
2
2
  const { ModelAPI } = require("./ModelAPI");
3
3
  const { sql } = require("kysely");
4
4
  const { useDatabase } = require("./database");
@@ -654,49 +654,6 @@ test("ModelAPI.findMany - notEquals", async () => {
654
654
  expect(rows[0].id).toEqual(p.id);
655
655
  });
656
656
 
657
- test("ModelAPI.findMany - complex query", async () => {
658
- const p = await personAPI.create({
659
- id: KSUID.randomSync().string,
660
- name: "Jake",
661
- favouriteNumber: 8,
662
- date: new Date("2021-12-31"),
663
- });
664
- await personAPI.create({
665
- id: KSUID.randomSync().string,
666
- name: "Jane",
667
- favouriteNumber: 12,
668
- date: new Date("2022-01-11"),
669
- });
670
- const p2 = await personAPI.create({
671
- id: KSUID.randomSync().string,
672
- name: "Billy",
673
- favouriteNumber: 16,
674
- date: new Date("2022-01-05"),
675
- });
676
-
677
- const rows = await personAPI
678
- // Will match Jake
679
- .where({
680
- name: {
681
- startsWith: "J",
682
- endsWith: "e",
683
- },
684
- favouriteNumber: {
685
- lessThan: 10,
686
- },
687
- })
688
- // Will match Billy
689
- .orWhere({
690
- date: {
691
- after: new Date("2022-01-01"),
692
- before: new Date("2022-01-10"),
693
- },
694
- })
695
- .findMany();
696
- expect(rows.length).toEqual(2);
697
- expect(rows.map((x) => x.id).sort()).toEqual([p.id, p2.id].sort());
698
- });
699
-
700
657
  test("ModelAPI.findMany - relationships - one to many", async () => {
701
658
  const person = await personAPI.create({
702
659
  id: KSUID.randomSync().string,
@@ -883,38 +840,190 @@ test("ModelAPI.delete", async () => {
883
840
  await expect(personAPI.findOne({ id })).resolves.toEqual(null);
884
841
  });
885
842
 
886
- test("ModelAPI chained findMany with offset/limit/order by", async () => {
887
- await postAPI.create({
888
- id: KSUID.randomSync().string,
889
- title: "adam",
843
+ describe("QueryBuilder", () => {
844
+ test("ModelAPI chained findMany with offset/limit/order by", async () => {
845
+ await postAPI.create({
846
+ id: KSUID.randomSync().string,
847
+ title: "adam",
848
+ });
849
+ await postAPI.create({
850
+ id: KSUID.randomSync().string,
851
+ title: "dave",
852
+ });
853
+ const three = await postAPI.create({
854
+ id: KSUID.randomSync().string,
855
+ title: "jon",
856
+ });
857
+ const four = await postAPI.create({
858
+ id: KSUID.randomSync().string,
859
+ title: "jon bretman",
860
+ });
861
+
862
+ const results = await postAPI
863
+ .where({ title: { equals: "adam" } })
864
+ .orWhere({
865
+ title: { startsWith: "jon" },
866
+ })
867
+ .findMany({
868
+ limit: 3,
869
+ offset: 1,
870
+ orderBy: {
871
+ title: "asc",
872
+ },
873
+ });
874
+
875
+ // because we've offset by 1, adam should not appear in the results even though
876
+ // the query constraints match adam
877
+ expect(results).toEqual([three, four]);
890
878
  });
891
- await postAPI.create({
892
- id: KSUID.randomSync().string,
893
- title: "dave",
879
+
880
+ test("ModelAPI.findMany - complex query", async () => {
881
+ const p = await personAPI.create({
882
+ id: KSUID.randomSync().string,
883
+ name: "Jake",
884
+ favouriteNumber: 8,
885
+ date: new Date("2021-12-31"),
886
+ });
887
+ await personAPI.create({
888
+ id: KSUID.randomSync().string,
889
+ name: "Jane",
890
+ favouriteNumber: 12,
891
+ date: new Date("2022-01-11"),
892
+ });
893
+ const p2 = await personAPI.create({
894
+ id: KSUID.randomSync().string,
895
+ name: "Billy",
896
+ favouriteNumber: 16,
897
+ date: new Date("2022-01-05"),
898
+ });
899
+
900
+ const rows = await personAPI
901
+ // Will match Jake
902
+ .where({
903
+ name: {
904
+ startsWith: "J",
905
+ endsWith: "e",
906
+ },
907
+ favouriteNumber: {
908
+ lessThan: 10,
909
+ },
910
+ })
911
+ // Will match Billy
912
+ .orWhere({
913
+ date: {
914
+ after: new Date("2022-01-01"),
915
+ before: new Date("2022-01-10"),
916
+ },
917
+ })
918
+ .findMany();
919
+ expect(rows.length).toEqual(2);
920
+ expect(rows.map((x) => x.id).sort()).toEqual([p.id, p2.id].sort());
894
921
  });
895
- const three = await postAPI.create({
896
- id: KSUID.randomSync().string,
897
- title: "jon",
922
+
923
+ test("ModelAPI chained delete", async () => {
924
+ const p = await personAPI.create({
925
+ id: KSUID.randomSync().string,
926
+ name: "Jake",
927
+ favouriteNumber: 8,
928
+ date: new Date("2021-12-31"),
929
+ });
930
+
931
+ const deletedId = await personAPI.where({ id: p.id }).delete();
932
+
933
+ expect(deletedId).toEqual(p.id);
898
934
  });
899
- const four = await postAPI.create({
900
- id: KSUID.randomSync().string,
901
- title: "jon bretman",
935
+
936
+ test("Model API chained delete - non existent id", async () => {
937
+ const fakeId = "xxx";
938
+
939
+ // the error message returned from the runtime will actually be 'record not found'
940
+ // but this is handled at handleRequest level
941
+ // no result is the error msg returned by kysely.
942
+ await expect(personAPI.where({ id: fakeId }).delete()).rejects.toThrow(
943
+ "no result"
944
+ );
902
945
  });
903
946
 
904
- const results = await postAPI
905
- .where({ title: { equals: "adam" } })
906
- .orWhere({
907
- title: { startsWith: "jon" },
908
- })
909
- .findMany({
910
- limit: 3,
911
- offset: 1,
912
- orderBy: {
913
- title: "asc",
914
- },
947
+ test("Model API chained findOne", async () => {
948
+ const p = await personAPI.create({
949
+ id: KSUID.randomSync().string,
950
+ name: "Jake",
951
+ favouriteNumber: 8,
952
+ date: new Date("2021-12-31"),
915
953
  });
916
954
 
917
- // because we've offset by 1, adam should not appear in the results even though
918
- // the query constraints match adam
919
- expect(results).toEqual([three, four]);
955
+ const jake = await personAPI.where({ id: p.id }).findOne();
956
+
957
+ expect(jake).toEqual(p);
958
+ });
959
+
960
+ // test("Model API chained order by", async () => {
961
+ // const p1 = await postAPI.create({
962
+ // id: KSUID.randomSync().string,
963
+ // title: "adam",
964
+ // });
965
+ // const p2 = await postAPI.create({
966
+ // id: KSUID.randomSync().string,
967
+ // title: "dave",
968
+ // });
969
+ // const p3 = await postAPI.create({
970
+ // id: KSUID.randomSync().string,
971
+ // title: "jon",
972
+ // });
973
+ // const p4 = await postAPI.create({
974
+ // id: KSUID.randomSync().string,
975
+ // title: "jon bretman",
976
+ // });
977
+
978
+ // const query = postAPI
979
+ // .where({ title: "adam" })
980
+ // .orWhere({ title: "dave" })
981
+ // .orderBy({ title: "desc" });
982
+
983
+ // const results = await query.findMany();
984
+
985
+ // expect(results[0].id).toEqual(p2);
986
+ // });
987
+
988
+ test("Model API chained update", async () => {
989
+ const p1 = await postAPI.create({
990
+ id: KSUID.randomSync().string,
991
+ title: "adam",
992
+ });
993
+ const p2 = await postAPI.create({
994
+ id: KSUID.randomSync().string,
995
+ title: "adam",
996
+ });
997
+ const p3 = await postAPI.create({
998
+ id: KSUID.randomSync().string,
999
+ title: "adam",
1000
+ });
1001
+
1002
+ const updatedRow = await postAPI
1003
+ .where({ id: p2.id })
1004
+ .update({ title: "adam 2" });
1005
+
1006
+ expect(updatedRow.title).toEqual("adam 2");
1007
+ expect(updatedRow.id).toEqual(p2.id);
1008
+
1009
+ // will fail because there is more than 1 row matching the constraints (p1 and p3)
1010
+ await expect(
1011
+ postAPI
1012
+ .where({
1013
+ title: "adam",
1014
+ })
1015
+ .update({ title: "bob" })
1016
+ ).rejects.toThrowError(
1017
+ "more than one row matched update constraints - only unique fields should be used when updating."
1018
+ );
1019
+
1020
+ // will fail because there are no rows to update
1021
+ await expect(
1022
+ postAPI
1023
+ .where({
1024
+ title: "no match",
1025
+ })
1026
+ .update({ title: "bob" })
1027
+ ).resolves.toEqual(null);
1028
+ });
920
1029
  });
@@ -5,10 +5,15 @@ const {
5
5
  applyOrderBy,
6
6
  } = require("./applyAdditionalQueryConstraints");
7
7
  const { applyJoins } = require("./applyJoins");
8
- const { camelCaseObject } = require("./casing");
8
+ const {
9
+ camelCaseObject,
10
+ snakeCaseObject,
11
+ upperCamelCase,
12
+ } = require("./casing");
9
13
  const { useDatabase } = require("./database");
10
14
  const { QueryContext } = require("./QueryContext");
11
15
  const tracing = require("./tracing");
16
+ const { DatabaseError } = require("./errors");
12
17
 
13
18
  class QueryBuilder {
14
19
  /**
@@ -20,6 +25,7 @@ class QueryBuilder {
20
25
  this._tableName = tableName;
21
26
  this._context = context;
22
27
  this._db = db;
28
+ this._modelName = upperCamelCase(this._tableName);
23
29
  }
24
30
 
25
31
  where(where) {
@@ -43,6 +49,128 @@ class QueryBuilder {
43
49
  return new QueryBuilder(this._tableName, context, builder);
44
50
  }
45
51
 
52
+ sql() {
53
+ return this._db.compile().sql;
54
+ }
55
+
56
+ async update(values) {
57
+ const name = tracing.spanNameForModelAPI(this._modelName, "update");
58
+ const db = useDatabase();
59
+
60
+ return tracing.withSpan(name, async (span) => {
61
+ // we build a sub-query to add to the WHERE id IN (XXX) containing all of the
62
+ // wheres added in previous .where() chains.
63
+ const sub = this._db.clearSelect().select("id");
64
+
65
+ const query = db
66
+ .updateTable(this._tableName)
67
+ .set(snakeCaseObject(values))
68
+ .returningAll()
69
+ .where("id", "in", sub);
70
+
71
+ try {
72
+ const result = await query.execute();
73
+ const numUpdatedRows = result.length;
74
+
75
+ // the double (==) is important because we are comparing bigint to int
76
+ if (numUpdatedRows == 0) {
77
+ return null;
78
+ }
79
+
80
+ if (numUpdatedRows > 1) {
81
+ throw new DatabaseError(
82
+ new Error(
83
+ "more than one row matched update constraints - only unique fields should be used when updating."
84
+ )
85
+ );
86
+ }
87
+
88
+ return camelCaseObject(result[0]);
89
+ } catch (e) {
90
+ throw new DatabaseError(e);
91
+ }
92
+ });
93
+ }
94
+
95
+ async delete() {
96
+ const name = tracing.spanNameForModelAPI(this._modelName, "delete");
97
+ const db = useDatabase();
98
+
99
+ return tracing.withSpan(name, async (span) => {
100
+ // the original query selects the distinct id + the model.* so we need to clear
101
+ const sub = this._db.clearSelect().select("id");
102
+ let builder = db.deleteFrom(this._tableName).where("id", "in", sub);
103
+
104
+ const query = builder.returning(["id"]);
105
+
106
+ // final query looks something like:
107
+ // delete from "person" where "id" in (select distinct on ("person"."id") "id" from "person" where "person"."id" = $1) returning "id"
108
+
109
+ span.setAttribute("sql", query.compile().sql);
110
+
111
+ try {
112
+ const row = await query.executeTakeFirstOrThrow();
113
+ return row.id;
114
+ } catch (e) {
115
+ throw new DatabaseError(e);
116
+ }
117
+ });
118
+ }
119
+
120
+ async findOne() {
121
+ const name = tracing.spanNameForModelAPI(this._modelName, "findOne");
122
+ const db = useDatabase();
123
+
124
+ return tracing.withSpan(name, async (span) => {
125
+ let builder = db
126
+ .selectFrom((qb) => {
127
+ // this._db contains all of the where constraints and joins
128
+ // we want to include that in the sub query in the same way we
129
+ // add all of this information into the sub query in the ModelAPI's
130
+ // implementation of findOne
131
+ return this._db.as(this._tableName);
132
+ })
133
+ .selectAll();
134
+
135
+ span.setAttribute("sql", builder.compile().sql);
136
+
137
+ const row = await builder.executeTakeFirstOrThrow();
138
+
139
+ if (!row) {
140
+ return null;
141
+ }
142
+
143
+ return camelCaseObject(row);
144
+ });
145
+ }
146
+
147
+ // orderBy(conditions) {
148
+ // const context = this._context.clone();
149
+
150
+ // const builder = applyOrderBy(
151
+ // context,
152
+ // this._db,
153
+ // this._tableName,
154
+ // conditions
155
+ // );
156
+
157
+ // return new QueryBuilder(this._tableName, context, builder);
158
+ // }
159
+
160
+ // limit(limit) {
161
+ // const context = this._context.clone();
162
+ // const builder = applyLimit(context, this._db, limit);
163
+
164
+ // return new QueryBuilder(this._tableName, context, builder);
165
+ // }
166
+
167
+ // offset(offset) {
168
+ // const context = this._context.clone();
169
+ // const builder = applyOffset(context, builder, offset);
170
+
171
+ // return new QueryBuilder(this._tableName, context, builder);
172
+ // }
173
+
46
174
  async findMany(params) {
47
175
  const name = tracing.spanNameForModelAPI(this._modelName, "findMany");
48
176
  const db = useDatabase();
package/src/consts.js CHANGED
@@ -1,13 +1,14 @@
1
1
  const PROTO_ACTION_TYPES = {
2
- UNKNOWN: "OPERATION_TYPE_UNKNOWN",
3
- CREATE: "OPERATION_TYPE_CREATE",
4
- GET: "OPERATION_TYPE_GET",
5
- LIST: "OPERATION_TYPE_LIST",
6
- UPDATE: "OPERATION_TYPE_UPDATE",
7
- DELETE: "OPERATION_TYPE_DELETE",
8
- READ: "OPERATION_TYPE_READ",
9
- WRITE: "OPERATION_TYPE_WRITE",
2
+ UNKNOWN: "ACTION_TYPE_UNKNOWN",
3
+ CREATE: "ACTION_TYPE_CREATE",
4
+ GET: "ACTION_TYPE_GET",
5
+ LIST: "ACTION_TYPE_LIST",
6
+ UPDATE: "ACTION_TYPE_UPDATE",
7
+ DELETE: "ACTION_TYPE_DELETE",
8
+ READ: "ACTION_TYPE_READ",
9
+ WRITE: "ACTION_TYPE_WRITE",
10
10
  JOB: "JOB_TYPE",
11
+ SUBSCRIBER: "SUBSCRIBER_TYPE",
11
12
  };
12
13
 
13
14
  module.exports.PROTO_ACTION_TYPES = PROTO_ACTION_TYPES;
package/src/database.js CHANGED
@@ -22,6 +22,7 @@ async function withDatabase(db, actionType, cb) {
22
22
  break;
23
23
  }
24
24
 
25
+ // db.transaction() provides a kysely instance bound to a transaction.
25
26
  if (requiresTransaction) {
26
27
  return db.transaction().execute(async (transaction) => {
27
28
  return dbInstance.run(transaction, async () => {
@@ -30,8 +31,11 @@ async function withDatabase(db, actionType, cb) {
30
31
  });
31
32
  }
32
33
 
33
- return dbInstance.run(db, async () => {
34
- return cb({ transaction: db });
34
+ // db.connection() provides a kysely instance bound to a single database connection.
35
+ return db.connection().execute(async (sDb) => {
36
+ return dbInstance.run(sDb, async () => {
37
+ return cb({ sDb });
38
+ });
35
39
  });
36
40
  }
37
41
 
package/src/errors.js CHANGED
@@ -1,5 +1,13 @@
1
1
  const { createJSONRPCErrorResponse } = require("json-rpc-2.0");
2
- const { PermissionError } = require("./permissions");
2
+
3
+ class PermissionError extends Error {}
4
+
5
+ class DatabaseError extends Error {
6
+ constructor(error) {
7
+ super(error.message);
8
+ this.error = error;
9
+ }
10
+ }
3
11
 
4
12
  const RuntimeErrors = {
5
13
  // Catchall error type for unhandled execution errors during custom function
@@ -113,4 +121,6 @@ const parseKeyMessage = (msg) => {
113
121
  module.exports = {
114
122
  errorToJSONRPCResponse,
115
123
  RuntimeErrors,
124
+ DatabaseError,
125
+ PermissionError,
116
126
  };
package/src/handleJob.js CHANGED
@@ -4,7 +4,6 @@ const {
4
4
  JSONRPCErrorCode,
5
5
  } = require("json-rpc-2.0");
6
6
  const { getDatabaseClient } = require("./database");
7
- const { tryExecuteFunction } = require("./tryExecuteFunction");
8
7
  const { errorToJSONRPCResponse, RuntimeErrors } = require("./errors");
9
8
  const opentelemetry = require("@opentelemetry/api");
10
9
  const { withSpan } = require("./tracing");
@@ -54,15 +53,11 @@ async function handleJob(request, config) {
54
53
  const db = getDatabaseClient();
55
54
  const jobFunction = jobs[request.method];
56
55
  const actionType = PROTO_ACTION_TYPES.JOB;
57
- const permissionFns = new Object();
58
-
59
- // Jobs will have no permission functions yet.
60
- permissionFns[request.method] = [];
61
56
 
62
57
  await tryExecuteJob(
63
- { request, ctx, permissionFns, permitted, db, actionType },
58
+ { request, permitted, db, actionType },
64
59
  async () => {
65
- // Return the job function to the containing tryExecuteFunction block
60
+ // Return the job function to the containing tryExecuteJob block
66
61
  return jobFunction(ctx, request.params);
67
62
  }
68
63
  );
@@ -4,7 +4,6 @@ import { handleJob, RuntimeErrors } from "./handleJob";
4
4
  import { test, expect, beforeEach, describe } from "vitest";
5
5
  import { ModelAPI } from "./ModelAPI";
6
6
  import { useDatabase } from "./database";
7
- const { Permissions } = require("./permissions");
8
7
  import KSUID from "ksuid";
9
8
 
10
9
  test("when the job returns nothing as expected", async () => {
@@ -0,0 +1,88 @@
1
+ const {
2
+ createJSONRPCErrorResponse,
3
+ createJSONRPCSuccessResponse,
4
+ JSONRPCErrorCode,
5
+ } = require("json-rpc-2.0");
6
+ const { getDatabaseClient } = require("./database");
7
+ const { errorToJSONRPCResponse, RuntimeErrors } = require("./errors");
8
+ const opentelemetry = require("@opentelemetry/api");
9
+ const { withSpan } = require("./tracing");
10
+ const { PROTO_ACTION_TYPES } = require("./consts");
11
+ const { tryExecuteSubscriber } = require("./tryExecuteSubscriber");
12
+
13
+ // Generic handler function that is agnostic to runtime environment (local or lambda)
14
+ // to execute a subscriber function based on the contents of a jsonrpc-2.0 payload object.
15
+ // To read more about jsonrpc request and response shapes, please read https://www.jsonrpc.org/specification
16
+ async function handleSubscriber(request, config) {
17
+ // Try to extract trace context from caller
18
+ const activeContext = opentelemetry.propagation.extract(
19
+ opentelemetry.context.active(),
20
+ request.meta?.tracing
21
+ );
22
+
23
+ // Run the whole request with the extracted context
24
+ return opentelemetry.context.with(activeContext, () => {
25
+ // Wrapping span for the whole request
26
+ return withSpan(request.method, async (span) => {
27
+ try {
28
+ const { createSubscriberContextAPI, subscribers } = config;
29
+
30
+ if (!(request.method in subscribers)) {
31
+ const message = `no corresponding subscriber found for '${request.method}'`;
32
+ span.setStatus({
33
+ code: opentelemetry.SpanStatusCode.ERROR,
34
+ message: message,
35
+ });
36
+ return createJSONRPCErrorResponse(
37
+ request.id,
38
+ JSONRPCErrorCode.MethodNotFound,
39
+ message
40
+ );
41
+ }
42
+
43
+ // The ctx argument passed into the subscriber function.
44
+ const ctx = createSubscriberContextAPI({
45
+ meta: request.meta,
46
+ });
47
+
48
+ const db = getDatabaseClient();
49
+ const subscriberFunction = subscribers[request.method];
50
+ const actionType = PROTO_ACTION_TYPES.SUBSCRIBER;
51
+
52
+ await tryExecuteSubscriber({ db, actionType }, async () => {
53
+ // Return the subscriber function to the containing tryExecuteSubscriber block
54
+ return subscriberFunction(ctx, request.params);
55
+ });
56
+
57
+ return createJSONRPCSuccessResponse(request.id, null);
58
+ } catch (e) {
59
+ if (e instanceof Error) {
60
+ span.recordException(e);
61
+ span.setStatus({
62
+ code: opentelemetry.SpanStatusCode.ERROR,
63
+ message: e.message,
64
+ });
65
+ return errorToJSONRPCResponse(request, e);
66
+ }
67
+
68
+ const message = JSON.stringify(e);
69
+
70
+ span.setStatus({
71
+ code: opentelemetry.SpanStatusCode.ERROR,
72
+ message: message,
73
+ });
74
+
75
+ return createJSONRPCErrorResponse(
76
+ request.id,
77
+ RuntimeErrors.UnknownError,
78
+ message
79
+ );
80
+ }
81
+ });
82
+ });
83
+ }
84
+
85
+ module.exports = {
86
+ handleSubscriber,
87
+ RuntimeErrors,
88
+ };
package/src/index.js CHANGED
@@ -2,6 +2,7 @@ const { ModelAPI } = require("./ModelAPI");
2
2
  const { RequestHeaders } = require("./RequestHeaders");
3
3
  const { handleRequest } = require("./handleRequest");
4
4
  const { handleJob } = require("./handleJob");
5
+ const { handleSubscriber } = require("./handleSubscriber");
5
6
  const KSUID = require("ksuid");
6
7
  const { useDatabase } = require("./database");
7
8
  const {
@@ -16,6 +17,7 @@ module.exports = {
16
17
  RequestHeaders,
17
18
  handleRequest,
18
19
  handleJob,
20
+ handleSubscriber,
19
21
  useDatabase,
20
22
  Permissions,
21
23
  PERMISSION_STATE,
@@ -1,6 +1,5 @@
1
1
  const { AsyncLocalStorage } = require("async_hooks");
2
-
3
- class PermissionError extends Error {}
2
+ const { PermissionError } = require("./errors");
4
3
 
5
4
  const PERMISSION_STATE = {
6
5
  UNKNOWN: "unknown",
@@ -2,13 +2,11 @@ const {
2
2
  permissionsApiInstance,
3
3
  Permissions,
4
4
  PERMISSION_STATE,
5
- PermissionError,
6
5
  checkBuiltInPermissions,
7
6
  } = require("./permissions");
8
-
9
7
  import { useDatabase } from "./database";
10
-
11
8
  import { beforeEach, describe, expect, test } from "vitest";
9
+ const { PermissionError } = require("./errors");
12
10
 
13
11
  let permissions;
14
12
  let ctx = {};