@prosopo/database 3.0.12 → 3.4.5

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/CHANGELOG.md CHANGED
@@ -1,5 +1,322 @@
1
1
  # @prosopo/database
2
2
 
3
+ ## 3.4.5
4
+ ### Patch Changes
5
+
6
+ - Updated dependencies [1e3a838]
7
+ - @prosopo/config@3.1.20
8
+ - @prosopo/common@3.1.20
9
+ - @prosopo/locale@3.1.20
10
+ - @prosopo/redis-client@1.0.5
11
+ - @prosopo/types@3.5.3
12
+ - @prosopo/types-database@3.3.5
13
+ - @prosopo/user-access-policy@3.5.19
14
+
15
+ ## 3.4.4
16
+ ### Patch Changes
17
+
18
+ - 5659b24: Release 3.4.4
19
+ - Updated dependencies [f912439]
20
+ - Updated dependencies [5659b24]
21
+ - @prosopo/common@3.1.19
22
+ - @prosopo/user-access-policy@3.5.18
23
+ - @prosopo/types-database@3.3.4
24
+ - @prosopo/redis-client@1.0.4
25
+ - @prosopo/locale@3.1.19
26
+ - @prosopo/types@3.5.2
27
+ - @prosopo/config@3.1.19
28
+
29
+ ## 3.4.3
30
+ ### Patch Changes
31
+
32
+ - c72ecbd: Reduce noisy logs
33
+ - 50c4120: Release 3.4.3
34
+ - Updated dependencies [52cd544]
35
+ - Updated dependencies [b117ba3]
36
+ - Updated dependencies [50c4120]
37
+ - @prosopo/types@3.5.1
38
+ - @prosopo/user-access-policy@3.5.17
39
+ - @prosopo/types-database@3.3.3
40
+ - @prosopo/redis-client@1.0.3
41
+ - @prosopo/common@3.1.18
42
+ - @prosopo/locale@3.1.18
43
+ - @prosopo/config@3.1.18
44
+
45
+ ## 3.4.2
46
+ ### Patch Changes
47
+
48
+ - 618703f: Release 3.4.2
49
+ - Updated dependencies [618703f]
50
+ - Updated dependencies [e20ad6b]
51
+ - @prosopo/user-access-policy@3.5.16
52
+ - @prosopo/types-database@3.3.2
53
+ - @prosopo/redis-client@1.0.2
54
+ - @prosopo/common@3.1.17
55
+ - @prosopo/locale@3.1.17
56
+ - @prosopo/types@3.5.0
57
+ - @prosopo/config@3.1.17
58
+
59
+ ## 3.4.1
60
+ ### Patch Changes
61
+
62
+ - 11303d9: feat/pluggable-redis
63
+ - 11303d9: Release 3.4.0
64
+ - 18cb28b: Release 3.4.1
65
+ - 11303d9: feat/pluggable-redis
66
+ - Updated dependencies [11303d9]
67
+ - Updated dependencies [b6794f8]
68
+ - Updated dependencies [11303d9]
69
+ - Updated dependencies [bac2d91]
70
+ - Updated dependencies [18cb28b]
71
+ - Updated dependencies [11303d9]
72
+ - @prosopo/user-access-policy@3.5.15
73
+ - @prosopo/redis-client@1.0.1
74
+ - @prosopo/types-database@3.3.1
75
+ - @prosopo/common@3.1.16
76
+ - @prosopo/locale@3.1.16
77
+ - @prosopo/types@3.4.1
78
+ - @prosopo/config@3.1.16
79
+
80
+ ## 3.4.0
81
+ ### Minor Changes
82
+
83
+ - 6768f14: Update salt
84
+
85
+ ### Patch Changes
86
+
87
+ - f3f7aec: Release 3.4.0
88
+ - Updated dependencies [f3f7aec]
89
+ - Updated dependencies [6768f14]
90
+ - @prosopo/user-access-policy@3.5.14
91
+ - @prosopo/types-database@3.3.0
92
+ - @prosopo/common@3.1.15
93
+ - @prosopo/locale@3.1.15
94
+ - @prosopo/types@3.4.0
95
+ - @prosopo/config@3.1.15
96
+
97
+ ## 3.3.2
98
+ ### Patch Changes
99
+
100
+ - Release 3.3.1
101
+ - 0824221: Release 3.2.4
102
+ - Updated dependencies [97edf3f]
103
+ - Updated dependencies
104
+ - Updated dependencies [0824221]
105
+ - @prosopo/types@3.3.0
106
+ - @prosopo/user-access-policy@3.5.13
107
+ - @prosopo/types-database@3.2.2
108
+ - @prosopo/common@3.1.14
109
+ - @prosopo/locale@3.1.14
110
+ - @prosopo/config@3.1.14
111
+
112
+ ## 3.3.1
113
+ ### Patch Changes
114
+
115
+ - 5137f01: Update pow record at verify
116
+ - 008d112: Release 3.3.0
117
+ - Updated dependencies [5137f01]
118
+ - Updated dependencies [0555cd8]
119
+ - Updated dependencies [509be28]
120
+ - Updated dependencies [008d112]
121
+ - @prosopo/types-database@3.2.1
122
+ - @prosopo/types@3.2.1
123
+ - @prosopo/user-access-policy@3.5.12
124
+ - @prosopo/common@3.1.13
125
+ - @prosopo/locale@3.1.13
126
+ - @prosopo/config@3.1.13
127
+
128
+ ## 3.3.0
129
+ ### Minor Changes
130
+
131
+ - cf48565: Store additional details. Remove duplicate indexes.
132
+ - 260de39: Fix indexes so that stuff properly expires
133
+
134
+ ### Patch Changes
135
+
136
+ - 0824221: Release 3.2.4
137
+ - Updated dependencies [cf48565]
138
+ - Updated dependencies [d644c04]
139
+ - Updated dependencies [0824221]
140
+ - Updated dependencies [260de39]
141
+ - @prosopo/types-database@3.2.0
142
+ - @prosopo/types@3.2.0
143
+ - @prosopo/user-access-policy@3.5.11
144
+ - @prosopo/common@3.1.12
145
+ - @prosopo/locale@3.1.12
146
+ - @prosopo/config@3.1.12
147
+
148
+ ## 3.2.4
149
+ ### Patch Changes
150
+
151
+ - 1a23649: Release 3.2.3
152
+ - Updated dependencies [0d1a33e]
153
+ - Updated dependencies [0d1a33e]
154
+ - Updated dependencies [1a23649]
155
+ - @prosopo/types-database@3.1.5
156
+ - @prosopo/locale@3.1.11
157
+ - @prosopo/types@3.1.4
158
+ - @prosopo/user-access-policy@3.5.10
159
+ - @prosopo/common@3.1.11
160
+ - @prosopo/config@3.1.11
161
+
162
+ ## 3.2.3
163
+ ### Patch Changes
164
+
165
+ - 657a827: Release 3.2.2
166
+ - Updated dependencies [a8a9251]
167
+ - Updated dependencies [657a827]
168
+ - @prosopo/types-database@3.1.4
169
+ - @prosopo/user-access-policy@3.5.9
170
+ - @prosopo/common@3.1.10
171
+ - @prosopo/locale@3.1.10
172
+ - @prosopo/types@3.1.3
173
+ - @prosopo/config@3.1.10
174
+
175
+ ## 3.2.2
176
+ ### Patch Changes
177
+
178
+ - 4440947: fix type-only tsc compilation
179
+ - 7bdaca6: Release 3.2.1
180
+ - 1249ce0: Be more lenient with random provider selection
181
+ - Updated dependencies [4440947]
182
+ - Updated dependencies [7bdaca6]
183
+ - Updated dependencies [809b984]
184
+ - Updated dependencies [1249ce0]
185
+ - Updated dependencies [809b984]
186
+ - @prosopo/user-access-policy@3.5.8
187
+ - @prosopo/types-database@3.1.3
188
+ - @prosopo/common@3.1.9
189
+ - @prosopo/locale@3.1.9
190
+ - @prosopo/types@3.1.2
191
+ - @prosopo/config@3.1.9
192
+
193
+ ## 3.2.1
194
+ ### Patch Changes
195
+
196
+ - 6fe8570: Release 3.2.0
197
+ - Updated dependencies [1f980c4]
198
+ - Updated dependencies [6fe8570]
199
+ - @prosopo/types@3.1.1
200
+ - @prosopo/user-access-policy@3.5.7
201
+ - @prosopo/types-database@3.1.2
202
+ - @prosopo/common@3.1.8
203
+ - @prosopo/locale@3.1.8
204
+ - @prosopo/config@3.1.8
205
+
206
+ ## 3.2.0
207
+ ### Minor Changes
208
+
209
+ - 8bdc7f0: Using detector to select provider
210
+
211
+ ### Patch Changes
212
+
213
+ - f304be9: Release 3.1.13
214
+ - Updated dependencies [f304be9]
215
+ - Updated dependencies [8bdc7f0]
216
+ - @prosopo/user-access-policy@3.5.6
217
+ - @prosopo/types-database@3.1.1
218
+ - @prosopo/common@3.1.7
219
+ - @prosopo/locale@3.1.7
220
+ - @prosopo/types@3.1.0
221
+ - @prosopo/config@3.1.7
222
+
223
+ ## 3.1.0
224
+ ### Minor Changes
225
+
226
+ - 9b92339: fix/ipv6-in-captcha-flow
227
+
228
+ ### Patch Changes
229
+
230
+ - a07db04: Release 3.1.12
231
+ - Updated dependencies [9b92339]
232
+ - Updated dependencies [9eed772]
233
+ - Updated dependencies [a07db04]
234
+ - @prosopo/types-database@3.1.0
235
+ - @prosopo/config@3.1.6
236
+ - @prosopo/user-access-policy@3.5.5
237
+ - @prosopo/common@3.1.6
238
+ - @prosopo/locale@3.1.6
239
+ - @prosopo/types@3.0.10
240
+
241
+ ## 3.0.19
242
+ ### Patch Changes
243
+
244
+ - e64160c: The way mongo memory server works has changed
245
+ - Updated dependencies [553025d]
246
+ - @prosopo/user-access-policy@3.5.4
247
+ - @prosopo/types-database@3.0.19
248
+
249
+ ## 3.0.18
250
+ ### Patch Changes
251
+
252
+ - 6960643: lint detect missing and unneccessary imports
253
+ - Updated dependencies [d8e855c]
254
+ - Updated dependencies [6960643]
255
+ - @prosopo/types-database@3.0.18
256
+ - @prosopo/locale@3.1.5
257
+ - @prosopo/user-access-policy@3.5.3
258
+ - @prosopo/common@3.1.5
259
+ - @prosopo/types@3.0.9
260
+
261
+ ## 3.0.17
262
+ ### Patch Changes
263
+
264
+ - Updated dependencies [30e7d4d]
265
+ - @prosopo/config@3.1.5
266
+ - @prosopo/common@3.1.4
267
+ - @prosopo/types@3.0.8
268
+ - @prosopo/types-database@3.0.17
269
+ - @prosopo/user-access-policy@3.5.2
270
+
271
+ ## 3.0.16
272
+ ### Patch Changes
273
+
274
+ - 1f3a02f: Release 3.1.8
275
+ - Updated dependencies [1f3a02f]
276
+ - @prosopo/user-access-policy@3.5.1
277
+ - @prosopo/types-database@3.0.16
278
+
279
+ ## 3.0.15
280
+ ### Patch Changes
281
+
282
+ - Updated dependencies [e0628d9]
283
+ - @prosopo/user-access-policy@3.5.0
284
+ - @prosopo/types-database@3.0.15
285
+
286
+ ## 3.0.14
287
+ ### Patch Changes
288
+
289
+ - Updated dependencies [44ffda2]
290
+ - Updated dependencies [a49b538]
291
+ - Updated dependencies [e090e2f]
292
+ - @prosopo/config@3.1.4
293
+ - @prosopo/user-access-policy@3.4.1
294
+ - @prosopo/common@3.1.3
295
+ - @prosopo/types@3.0.7
296
+ - @prosopo/types-database@3.0.14
297
+
298
+ ## 3.0.13
299
+ ### Patch Changes
300
+
301
+ - 828066d: remove empty test npm scripts, add missing npm test scripts
302
+ - df4e030: Revising UAP rule getters
303
+ - 91bbe87: configure typecheck before bundle for vue packages
304
+ - 91bbe87: make typecheck script always recompile
305
+ - 346e092: NODE_ENV default to "development"
306
+ - 5d36e05: remove tsc --force
307
+ - Updated dependencies [828066d]
308
+ - Updated dependencies [df4e030]
309
+ - Updated dependencies [91bbe87]
310
+ - Updated dependencies [3ef4fd2]
311
+ - Updated dependencies [91bbe87]
312
+ - Updated dependencies [346e092]
313
+ - Updated dependencies [5d36e05]
314
+ - @prosopo/types-database@3.0.13
315
+ - @prosopo/common@3.1.2
316
+ - @prosopo/types@3.0.6
317
+ - @prosopo/config@3.1.3
318
+ - @prosopo/user-access-policy@3.4.0
319
+
3
320
  ## 3.0.12
4
321
  ### Patch Changes
5
322
 
@@ -35,20 +35,20 @@ class MongoDatabase {
35
35
  * @description Connect to the database and set the various tables
36
36
  */
37
37
  async connect() {
38
- this.logger.info(() => ({
38
+ this.logger.debug(() => ({
39
39
  data: { mongoUrl: this.safeURL },
40
40
  msg: "Connecting to database"
41
41
  }));
42
42
  try {
43
43
  if (this.connected) {
44
- this.logger.info(() => ({
44
+ this.logger.debug(() => ({
45
45
  data: { mongoUrl: this.safeURL },
46
46
  msg: "Database connection already open"
47
47
  }));
48
48
  return;
49
49
  }
50
50
  if (this.connecting) {
51
- this.logger.info(() => ({
51
+ this.logger.debug(() => ({
52
52
  data: { mongoUrl: this.safeURL },
53
53
  msg: "Database connection in progress, waiting for it to finish"
54
54
  }));
@@ -60,7 +60,7 @@ class MongoDatabase {
60
60
  serverApi: ServerApiVersion.v1
61
61
  });
62
62
  const onConnected = () => {
63
- this.logger.info(() => ({
63
+ this.logger.debug(() => ({
64
64
  data: { mongoUrl: this.safeURL },
65
65
  msg: "Database connection opened"
66
66
  }));
@@ -83,28 +83,28 @@ class MongoDatabase {
83
83
  connection.once("error", onError);
84
84
  connection.on("disconnected", () => {
85
85
  this.connected = false;
86
- this.logger.info(() => ({
86
+ this.logger.debug(() => ({
87
87
  data: { mongoUrl: this.safeURL },
88
88
  msg: "Database disconnected"
89
89
  }));
90
90
  });
91
91
  connection.on("reconnected", () => {
92
92
  this.connected = true;
93
- this.logger.info(() => ({
93
+ this.logger.debug(() => ({
94
94
  data: { mongoUrl: this.safeURL },
95
95
  msg: "Database reconnected"
96
96
  }));
97
97
  });
98
98
  connection.on("close", () => {
99
99
  this.connected = false;
100
- this.logger.info(() => ({
100
+ this.logger.debug(() => ({
101
101
  data: { mongoUrl: this.safeURL },
102
102
  msg: "Database connection closed"
103
103
  }));
104
104
  });
105
105
  connection.on("fullsetup", () => {
106
106
  this.connected = true;
107
- this.logger.info(() => ({
107
+ this.logger.debug(() => ({
108
108
  data: { mongoUrl: this.safeURL },
109
109
  msg: "Database connection is fully setup"
110
110
  }));
@@ -1,26 +1,21 @@
1
1
  import { MongoMemoryServer } from "mongodb-memory-server";
2
2
  import { MongoDatabase } from "./mongo.js";
3
3
  class MongoMemoryDatabase extends MongoDatabase {
4
- constructor(url, dbname, logger, authSource) {
5
- const mongod = new MongoMemoryServer();
6
- const mongoMemoryURL = mongod.getUri();
7
- super(mongoMemoryURL, dbname, authSource, logger);
8
- this.running = false;
9
- this.mongod = mongod;
10
- this._url = mongoMemoryURL;
4
+ constructor(_url, dbname, logger, authSource) {
5
+ super("", dbname, authSource, logger);
6
+ this._url = "";
11
7
  }
12
- connect() {
13
- if (!this.running) {
14
- this.mongod?.start();
15
- this.running = true;
16
- } else {
8
+ async connect() {
9
+ if (!this.mongod) {
10
+ this.mongod = await MongoMemoryServer.create();
11
+ this._url = this.mongod.getUri();
17
12
  }
18
- return super.connect();
13
+ await super.connect();
19
14
  }
20
15
  async close() {
21
16
  await super.close();
22
17
  await this.mongod?.stop();
23
- this.running = false;
18
+ this.mongod = void 0;
24
19
  }
25
20
  }
26
21
  export {
@@ -37,20 +37,20 @@ class MongoDatabase {
37
37
  * @description Connect to the database and set the various tables
38
38
  */
39
39
  async connect() {
40
- this.logger.info(() => ({
40
+ this.logger.debug(() => ({
41
41
  data: { mongoUrl: this.safeURL },
42
42
  msg: "Connecting to database"
43
43
  }));
44
44
  try {
45
45
  if (this.connected) {
46
- this.logger.info(() => ({
46
+ this.logger.debug(() => ({
47
47
  data: { mongoUrl: this.safeURL },
48
48
  msg: "Database connection already open"
49
49
  }));
50
50
  return;
51
51
  }
52
52
  if (this.connecting) {
53
- this.logger.info(() => ({
53
+ this.logger.debug(() => ({
54
54
  data: { mongoUrl: this.safeURL },
55
55
  msg: "Database connection in progress, waiting for it to finish"
56
56
  }));
@@ -62,7 +62,7 @@ class MongoDatabase {
62
62
  serverApi: mongodb.ServerApiVersion.v1
63
63
  });
64
64
  const onConnected = () => {
65
- this.logger.info(() => ({
65
+ this.logger.debug(() => ({
66
66
  data: { mongoUrl: this.safeURL },
67
67
  msg: "Database connection opened"
68
68
  }));
@@ -85,28 +85,28 @@ class MongoDatabase {
85
85
  connection.once("error", onError);
86
86
  connection.on("disconnected", () => {
87
87
  this.connected = false;
88
- this.logger.info(() => ({
88
+ this.logger.debug(() => ({
89
89
  data: { mongoUrl: this.safeURL },
90
90
  msg: "Database disconnected"
91
91
  }));
92
92
  });
93
93
  connection.on("reconnected", () => {
94
94
  this.connected = true;
95
- this.logger.info(() => ({
95
+ this.logger.debug(() => ({
96
96
  data: { mongoUrl: this.safeURL },
97
97
  msg: "Database reconnected"
98
98
  }));
99
99
  });
100
100
  connection.on("close", () => {
101
101
  this.connected = false;
102
- this.logger.info(() => ({
102
+ this.logger.debug(() => ({
103
103
  data: { mongoUrl: this.safeURL },
104
104
  msg: "Database connection closed"
105
105
  }));
106
106
  });
107
107
  connection.on("fullsetup", () => {
108
108
  this.connected = true;
109
- this.logger.info(() => ({
109
+ this.logger.debug(() => ({
110
110
  data: { mongoUrl: this.safeURL },
111
111
  msg: "Database connection is fully setup"
112
112
  }));
@@ -3,26 +3,21 @@ Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
3
3
  const mongodbMemoryServer = require("mongodb-memory-server");
4
4
  const mongo = require("./mongo.cjs");
5
5
  class MongoMemoryDatabase extends mongo.MongoDatabase {
6
- constructor(url, dbname, logger, authSource) {
7
- const mongod = new mongodbMemoryServer.MongoMemoryServer();
8
- const mongoMemoryURL = mongod.getUri();
9
- super(mongoMemoryURL, dbname, authSource, logger);
10
- this.running = false;
11
- this.mongod = mongod;
12
- this._url = mongoMemoryURL;
6
+ constructor(_url, dbname, logger, authSource) {
7
+ super("", dbname, authSource, logger);
8
+ this._url = "";
13
9
  }
14
- connect() {
15
- if (!this.running) {
16
- this.mongod?.start();
17
- this.running = true;
18
- } else {
10
+ async connect() {
11
+ if (!this.mongod) {
12
+ this.mongod = await mongodbMemoryServer.MongoMemoryServer.create();
13
+ this._url = this.mongod.getUri();
19
14
  }
20
- return super.connect();
15
+ await super.connect();
21
16
  }
22
17
  async close() {
23
18
  await super.close();
24
19
  await this.mongod?.stop();
25
- this.running = false;
20
+ this.mongod = void 0;
26
21
  }
27
22
  }
28
23
  exports.MongoMemoryDatabase = MongoMemoryDatabase;
@@ -32,6 +32,7 @@ const CAPTCHA_TABLES = [
32
32
  class CaptchaDatabase extends mongo.MongoDatabase {
33
33
  constructor(url, dbname, authSource, logger2) {
34
34
  super(url, dbname, authSource, logger2);
35
+ this.indexesEnsured = false;
35
36
  this.tables = {};
36
37
  }
37
38
  async connect() {
@@ -51,6 +52,37 @@ class CaptchaDatabase extends mongo.MongoDatabase {
51
52
  }
52
53
  return this.tables;
53
54
  }
55
+ async ensureIndexes() {
56
+ const indexPromises = [];
57
+ if (!this.indexesEnsured) {
58
+ CAPTCHA_TABLES.map(({ collectionName }) => {
59
+ indexPromises.push(
60
+ new Promise((resolve) => {
61
+ if (this.connected) {
62
+ this.tables[collectionName].collection.dropIndexes().then(() => {
63
+ this.tables[collectionName].ensureIndexes().then(() => {
64
+ resolve();
65
+ }).catch((err) => {
66
+ this.logger.warn(() => ({
67
+ err,
68
+ msg: `Error creating indexes for collection ${collectionName}`
69
+ }));
70
+ resolve();
71
+ });
72
+ });
73
+ } else {
74
+ this.logger.info(() => ({
75
+ msg: `Skipping index creation for collection ${collectionName} as not connected`
76
+ }));
77
+ resolve();
78
+ }
79
+ })
80
+ );
81
+ });
82
+ }
83
+ await Promise.all(indexPromises);
84
+ this.indexesEnsured = true;
85
+ }
54
86
  async saveCaptchas(sessionEvents, imageCaptchaEvents, powCaptchaEvents) {
55
87
  await this.connect();
56
88
  if (sessionEvents.length) {
@@ -2,10 +2,10 @@
2
2
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
3
3
  const is = require("@polkadot/util/is");
4
4
  const common = require("@prosopo/common");
5
+ const redisClient = require("@prosopo/redis-client");
5
6
  const types = require("@prosopo/types");
6
7
  const typesDatabase = require("@prosopo/types-database");
7
8
  const userAccessPolicy = require("@prosopo/user-access-policy");
8
- const redis = require("redis");
9
9
  const mongo = require("../base/mongo.cjs");
10
10
  var TableNames = /* @__PURE__ */ ((TableNames2) => {
11
11
  TableNames2["captcha"] = "captcha";
@@ -94,7 +94,10 @@ class ProviderDatabase extends mongo.MongoDatabase {
94
94
  );
95
95
  this.options = options;
96
96
  this.tables = {};
97
+ this.indexesEnsured = false;
97
98
  this.tables = {};
99
+ this.redisAccessRulesConnection = null;
100
+ this.redisConnection = null;
98
101
  this.userAccessRulesStorage = null;
99
102
  }
100
103
  async connect() {
@@ -103,24 +106,24 @@ class ProviderDatabase extends mongo.MongoDatabase {
103
106
  await this.setupRedis();
104
107
  }
105
108
  async setupRedis() {
106
- const redisClient = await this.createRedisClient();
107
- await userAccessPolicy.createRedisAccessRulesIndex(redisClient);
109
+ this.redisConnection = redisClient.connectToRedis({
110
+ url: this.options.redis?.url,
111
+ password: this.options.redis?.password,
112
+ logger: this.logger
113
+ });
114
+ this.redisAccessRulesConnection = redisClient.setupRedisIndex(
115
+ this.redisConnection,
116
+ {
117
+ ...userAccessPolicy.redisAccessRulesIndex,
118
+ name: this.options.redis?.indexName || userAccessPolicy.redisAccessRulesIndex.name
119
+ },
120
+ this.logger
121
+ );
108
122
  this.userAccessRulesStorage = userAccessPolicy.createRedisAccessRulesStorage(
109
- redisClient,
123
+ this.redisAccessRulesConnection,
110
124
  this.logger
111
125
  );
112
126
  }
113
- async createRedisClient() {
114
- return await redis.createClient({
115
- url: this.options.redis?.url,
116
- password: this.options.redis?.password
117
- }).on("error", (error) => {
118
- this.logger.error(() => ({
119
- err: error,
120
- msg: "Redis client error"
121
- }));
122
- }).connect();
123
- }
124
127
  loadTables() {
125
128
  const tables = {};
126
129
  PROVIDER_TABLES.map(({ collectionName, modelName, schema }) => {
@@ -139,6 +142,56 @@ class ProviderDatabase extends mongo.MongoDatabase {
139
142
  }
140
143
  return this.tables;
141
144
  }
145
+ getRedisAccessRulesConnection() {
146
+ if (null === this.redisAccessRulesConnection) {
147
+ throw new common.ProsopoDBError(
148
+ "DATABASE.REDIS_ACCESS_RULES_CONNECTION_UNDEFINED"
149
+ );
150
+ }
151
+ return this.redisAccessRulesConnection;
152
+ }
153
+ getRedisConnection() {
154
+ if (null === this.redisConnection) {
155
+ throw new common.ProsopoDBError(
156
+ "DATABASE.REDIS_ACCESS_RULES_CONNECTION_UNDEFINED"
157
+ );
158
+ }
159
+ return this.redisConnection;
160
+ }
161
+ async ensureIndexes() {
162
+ const indexPromises = [];
163
+ if (!this.indexesEnsured) {
164
+ PROVIDER_TABLES.map(({ collectionName }) => {
165
+ indexPromises.push(
166
+ new Promise((resolve) => {
167
+ if (this.connected) {
168
+ this.tables[collectionName].collection.dropIndexes().then(() => {
169
+ this.tables[collectionName].ensureIndexes().then(() => {
170
+ this.logger.info(() => ({
171
+ msg: `Indexes ensured for collection ${collectionName}`
172
+ }));
173
+ resolve();
174
+ }).catch((err) => {
175
+ this.logger.warn(() => ({
176
+ err,
177
+ msg: `Error creating indexes for collection ${collectionName}`
178
+ }));
179
+ resolve();
180
+ });
181
+ });
182
+ } else {
183
+ this.logger.info(() => ({
184
+ msg: `Skipping index creation for collection ${collectionName} as not connected`
185
+ }));
186
+ resolve();
187
+ }
188
+ })
189
+ );
190
+ });
191
+ }
192
+ await Promise.all(indexPromises);
193
+ this.indexesEnsured = true;
194
+ }
142
195
  getUserAccessRulesStorage() {
143
196
  if (null === this.userAccessRulesStorage) {
144
197
  throw new common.ProsopoDBError("DATABASE.USER_ACCESS_RULES_STORAGE_UNDEFINED");
@@ -536,7 +589,7 @@ class ProviderDatabase extends mongo.MongoDatabase {
536
589
  * @param userSignature
537
590
  * @returns {Promise<void>} A promise that resolves when the record is updated.
538
591
  */
539
- async updatePowCaptchaRecord(challenge, result, serverChecked = false, userSubmitted = false, userSignature) {
592
+ async updatePowCaptchaRecordResult(challenge, result, serverChecked = false, userSubmitted = false, userSignature) {
540
593
  const tables = this.getTables();
541
594
  const timestamp = Date.now();
542
595
  const update = {
@@ -590,6 +643,14 @@ class ProviderDatabase extends mongo.MongoDatabase {
590
643
  throw err;
591
644
  }
592
645
  }
646
+ async updatePowCaptchaRecord(challenge, updates) {
647
+ const tables = this.getTables();
648
+ await tables.powcaptcha.updateOne(
649
+ { challenge },
650
+ { $set: updates },
651
+ { upsert: false }
652
+ );
653
+ }
593
654
  /** @description Get serverChecked Dapp User image captcha commitments from the commitments table
594
655
  */
595
656
  async getCheckedDappUserCommitments() {
@@ -652,6 +713,12 @@ class ProviderDatabase extends mongo.MongoDatabase {
652
713
  { upsert: false }
653
714
  );
654
715
  }
716
+ /** @description Update an image captcha commitment
717
+ */
718
+ async updateDappUserCommitment(commitmentId, updates) {
719
+ const filter = { id: commitmentId };
720
+ await this.tables?.commitment.updateOne(filter, updates);
721
+ }
655
722
  /**
656
723
  * @description Get Dapp User PoW captcha commitments that have not been counted towards the client's total
657
724
  * @param {number} limit Maximum number of records to return
@@ -752,7 +819,7 @@ class ProviderDatabase extends mongo.MongoDatabase {
752
819
  const filter = {
753
820
  _id: { $in: tokenId }
754
821
  };
755
- return this.tables.frictionlessToken.find(filter);
822
+ return this.tables.frictionlessToken.find(filter).lean();
756
823
  }
757
824
  /**
758
825
  * Check if a frictionless token record exists.
@@ -853,7 +920,7 @@ class ProviderDatabase extends mongo.MongoDatabase {
853
920
  /** Mark a list of session records as stored */
854
921
  async markSessionRecordsStored(sessionIds) {
855
922
  const updateDoc = {
856
- storedAtTimestamp: Date.now()
923
+ storedAtTimestamp: /* @__PURE__ */ new Date()
857
924
  };
858
925
  await this.tables?.session.updateMany(
859
926
  { sessionId: { $in: sessionIds } },
@@ -861,6 +928,17 @@ class ProviderDatabase extends mongo.MongoDatabase {
861
928
  { upsert: false }
862
929
  );
863
930
  }
931
+ /** Mark a list of token records as stored */
932
+ async markFrictionlessTokenRecordsStored(tokenIds) {
933
+ const updateDoc = {
934
+ storedAtTimestamp: /* @__PURE__ */ new Date()
935
+ };
936
+ await this.tables?.frictionlessToken.updateMany(
937
+ { _id: { $in: tokenIds } },
938
+ { $set: updateDoc },
939
+ { upsert: false }
940
+ );
941
+ }
864
942
  /**
865
943
  * @description Store a Dapp User's pending record
866
944
  */
@@ -1077,13 +1155,15 @@ class ProviderDatabase extends mongo.MongoDatabase {
1077
1155
  /**
1078
1156
  * @description Approve a dapp user's solution
1079
1157
  * @param {string[]} commitmentId
1158
+ * @param coords
1080
1159
  */
1081
- async approveDappUserCommitment(commitmentId) {
1160
+ async approveDappUserCommitment(commitmentId, coords) {
1082
1161
  try {
1083
1162
  const result = { status: types.CaptchaStatus.approved };
1084
1163
  const updateDoc = {
1085
1164
  result,
1086
- lastUpdatedTimestamp: Date.now()
1165
+ lastUpdatedTimestamp: Date.now(),
1166
+ ...coords ? { coords } : {}
1087
1167
  };
1088
1168
  const filter = { id: commitmentId };
1089
1169
  await this.tables?.commitment?.findOneAndUpdate(filter, { $set: updateDoc }, { upsert: false }).lean();
@@ -1096,13 +1176,15 @@ class ProviderDatabase extends mongo.MongoDatabase {
1096
1176
  /**
1097
1177
  * @description Disapprove a dapp user's solution
1098
1178
  * @param {string} commitmentId
1179
+ * @param coords
1099
1180
  * @param reason
1100
1181
  */
1101
- async disapproveDappUserCommitment(commitmentId, reason) {
1182
+ async disapproveDappUserCommitment(commitmentId, reason, coords) {
1102
1183
  try {
1103
1184
  const updateDoc = {
1104
1185
  result: { status: types.CaptchaStatus.disapproved, reason },
1105
- lastUpdatedTimestamp: Date.now()
1186
+ lastUpdatedTimestamp: Date.now(),
1187
+ ...coords ? { coords } : {}
1106
1188
  };
1107
1189
  const filter = { id: commitmentId };
1108
1190
  await this.tables?.commitment?.findOneAndUpdate(filter, { $set: updateDoc }, { upsert: false }).lean();
@@ -1254,10 +1336,16 @@ class ProviderDatabase extends mongo.MongoDatabase {
1254
1336
  createdAt: /* @__PURE__ */ new Date()
1255
1337
  });
1256
1338
  }
1257
- /** @description Remove a detector key */
1258
- async removeDetectorKey(detectorKey) {
1339
+ /**
1340
+ * @description Remove a detector key
1341
+ * @param detectorKey The detector key to remove
1342
+ * @param expirationInSeconds Optional expiration time in seconds (default is 10 minutes)
1343
+ * */
1344
+ async removeDetectorKey(detectorKey, expirationInSeconds) {
1259
1345
  const filter = { detectorKey };
1260
- const expiresAt = new Date(Date.now() + 10 * 60 * 1e3);
1346
+ const expiresAt = new Date(
1347
+ Date.now() + (expirationInSeconds || 10 * 60) * 1e3
1348
+ );
1261
1349
  await this.tables?.detector.updateOne(filter, {
1262
1350
  $set: { expiresAt }
1263
1351
  });
@@ -30,6 +30,7 @@ const CAPTCHA_TABLES = [
30
30
  class CaptchaDatabase extends MongoDatabase {
31
31
  constructor(url, dbname, authSource, logger2) {
32
32
  super(url, dbname, authSource, logger2);
33
+ this.indexesEnsured = false;
33
34
  this.tables = {};
34
35
  }
35
36
  async connect() {
@@ -49,6 +50,37 @@ class CaptchaDatabase extends MongoDatabase {
49
50
  }
50
51
  return this.tables;
51
52
  }
53
+ async ensureIndexes() {
54
+ const indexPromises = [];
55
+ if (!this.indexesEnsured) {
56
+ CAPTCHA_TABLES.map(({ collectionName }) => {
57
+ indexPromises.push(
58
+ new Promise((resolve) => {
59
+ if (this.connected) {
60
+ this.tables[collectionName].collection.dropIndexes().then(() => {
61
+ this.tables[collectionName].ensureIndexes().then(() => {
62
+ resolve();
63
+ }).catch((err) => {
64
+ this.logger.warn(() => ({
65
+ err,
66
+ msg: `Error creating indexes for collection ${collectionName}`
67
+ }));
68
+ resolve();
69
+ });
70
+ });
71
+ } else {
72
+ this.logger.info(() => ({
73
+ msg: `Skipping index creation for collection ${collectionName} as not connected`
74
+ }));
75
+ resolve();
76
+ }
77
+ })
78
+ );
79
+ });
80
+ }
81
+ await Promise.all(indexPromises);
82
+ this.indexesEnsured = true;
83
+ }
52
84
  async saveCaptchas(sessionEvents, imageCaptchaEvents, powCaptchaEvents) {
53
85
  await this.connect();
54
86
  if (sessionEvents.length) {
@@ -1,9 +1,9 @@
1
1
  import { isHex } from "@polkadot/util/is";
2
2
  import { ProsopoDBError } from "@prosopo/common";
3
+ import { connectToRedis, setupRedisIndex } from "@prosopo/redis-client";
3
4
  import { DatasetWithIdsAndTreeSchema, StoredStatusNames, CaptchaStatus, ApiParams, CaptchaStates } from "@prosopo/types";
4
5
  import { CaptchaRecordSchema, PoWCaptchaRecordSchema, DatasetRecordSchema, SolutionRecordSchema, UserCommitmentRecordSchema, UserSolutionRecordSchema, PendingRecordSchema, ScheduledTaskRecordSchema, ClientRecordSchema, FrictionlessTokenRecordSchema, SessionRecordSchema, DetectorRecordSchema, UserCommitmentSchema, ScheduledTaskSchema } from "@prosopo/types-database";
5
- import { createRedisAccessRulesIndex, createRedisAccessRulesStorage } from "@prosopo/user-access-policy";
6
- import { createClient } from "redis";
6
+ import { redisAccessRulesIndex, createRedisAccessRulesStorage } from "@prosopo/user-access-policy";
7
7
  import { MongoDatabase } from "../base/mongo.js";
8
8
  var TableNames = /* @__PURE__ */ ((TableNames2) => {
9
9
  TableNames2["captcha"] = "captcha";
@@ -92,7 +92,10 @@ class ProviderDatabase extends MongoDatabase {
92
92
  );
93
93
  this.options = options;
94
94
  this.tables = {};
95
+ this.indexesEnsured = false;
95
96
  this.tables = {};
97
+ this.redisAccessRulesConnection = null;
98
+ this.redisConnection = null;
96
99
  this.userAccessRulesStorage = null;
97
100
  }
98
101
  async connect() {
@@ -101,24 +104,24 @@ class ProviderDatabase extends MongoDatabase {
101
104
  await this.setupRedis();
102
105
  }
103
106
  async setupRedis() {
104
- const redisClient = await this.createRedisClient();
105
- await createRedisAccessRulesIndex(redisClient);
107
+ this.redisConnection = connectToRedis({
108
+ url: this.options.redis?.url,
109
+ password: this.options.redis?.password,
110
+ logger: this.logger
111
+ });
112
+ this.redisAccessRulesConnection = setupRedisIndex(
113
+ this.redisConnection,
114
+ {
115
+ ...redisAccessRulesIndex,
116
+ name: this.options.redis?.indexName || redisAccessRulesIndex.name
117
+ },
118
+ this.logger
119
+ );
106
120
  this.userAccessRulesStorage = createRedisAccessRulesStorage(
107
- redisClient,
121
+ this.redisAccessRulesConnection,
108
122
  this.logger
109
123
  );
110
124
  }
111
- async createRedisClient() {
112
- return await createClient({
113
- url: this.options.redis?.url,
114
- password: this.options.redis?.password
115
- }).on("error", (error) => {
116
- this.logger.error(() => ({
117
- err: error,
118
- msg: "Redis client error"
119
- }));
120
- }).connect();
121
- }
122
125
  loadTables() {
123
126
  const tables = {};
124
127
  PROVIDER_TABLES.map(({ collectionName, modelName, schema }) => {
@@ -137,6 +140,56 @@ class ProviderDatabase extends MongoDatabase {
137
140
  }
138
141
  return this.tables;
139
142
  }
143
+ getRedisAccessRulesConnection() {
144
+ if (null === this.redisAccessRulesConnection) {
145
+ throw new ProsopoDBError(
146
+ "DATABASE.REDIS_ACCESS_RULES_CONNECTION_UNDEFINED"
147
+ );
148
+ }
149
+ return this.redisAccessRulesConnection;
150
+ }
151
+ getRedisConnection() {
152
+ if (null === this.redisConnection) {
153
+ throw new ProsopoDBError(
154
+ "DATABASE.REDIS_ACCESS_RULES_CONNECTION_UNDEFINED"
155
+ );
156
+ }
157
+ return this.redisConnection;
158
+ }
159
+ async ensureIndexes() {
160
+ const indexPromises = [];
161
+ if (!this.indexesEnsured) {
162
+ PROVIDER_TABLES.map(({ collectionName }) => {
163
+ indexPromises.push(
164
+ new Promise((resolve) => {
165
+ if (this.connected) {
166
+ this.tables[collectionName].collection.dropIndexes().then(() => {
167
+ this.tables[collectionName].ensureIndexes().then(() => {
168
+ this.logger.info(() => ({
169
+ msg: `Indexes ensured for collection ${collectionName}`
170
+ }));
171
+ resolve();
172
+ }).catch((err) => {
173
+ this.logger.warn(() => ({
174
+ err,
175
+ msg: `Error creating indexes for collection ${collectionName}`
176
+ }));
177
+ resolve();
178
+ });
179
+ });
180
+ } else {
181
+ this.logger.info(() => ({
182
+ msg: `Skipping index creation for collection ${collectionName} as not connected`
183
+ }));
184
+ resolve();
185
+ }
186
+ })
187
+ );
188
+ });
189
+ }
190
+ await Promise.all(indexPromises);
191
+ this.indexesEnsured = true;
192
+ }
140
193
  getUserAccessRulesStorage() {
141
194
  if (null === this.userAccessRulesStorage) {
142
195
  throw new ProsopoDBError("DATABASE.USER_ACCESS_RULES_STORAGE_UNDEFINED");
@@ -534,7 +587,7 @@ class ProviderDatabase extends MongoDatabase {
534
587
  * @param userSignature
535
588
  * @returns {Promise<void>} A promise that resolves when the record is updated.
536
589
  */
537
- async updatePowCaptchaRecord(challenge, result, serverChecked = false, userSubmitted = false, userSignature) {
590
+ async updatePowCaptchaRecordResult(challenge, result, serverChecked = false, userSubmitted = false, userSignature) {
538
591
  const tables = this.getTables();
539
592
  const timestamp = Date.now();
540
593
  const update = {
@@ -588,6 +641,14 @@ class ProviderDatabase extends MongoDatabase {
588
641
  throw err;
589
642
  }
590
643
  }
644
+ async updatePowCaptchaRecord(challenge, updates) {
645
+ const tables = this.getTables();
646
+ await tables.powcaptcha.updateOne(
647
+ { challenge },
648
+ { $set: updates },
649
+ { upsert: false }
650
+ );
651
+ }
591
652
  /** @description Get serverChecked Dapp User image captcha commitments from the commitments table
592
653
  */
593
654
  async getCheckedDappUserCommitments() {
@@ -650,6 +711,12 @@ class ProviderDatabase extends MongoDatabase {
650
711
  { upsert: false }
651
712
  );
652
713
  }
714
+ /** @description Update an image captcha commitment
715
+ */
716
+ async updateDappUserCommitment(commitmentId, updates) {
717
+ const filter = { id: commitmentId };
718
+ await this.tables?.commitment.updateOne(filter, updates);
719
+ }
653
720
  /**
654
721
  * @description Get Dapp User PoW captcha commitments that have not been counted towards the client's total
655
722
  * @param {number} limit Maximum number of records to return
@@ -750,7 +817,7 @@ class ProviderDatabase extends MongoDatabase {
750
817
  const filter = {
751
818
  _id: { $in: tokenId }
752
819
  };
753
- return this.tables.frictionlessToken.find(filter);
820
+ return this.tables.frictionlessToken.find(filter).lean();
754
821
  }
755
822
  /**
756
823
  * Check if a frictionless token record exists.
@@ -851,7 +918,7 @@ class ProviderDatabase extends MongoDatabase {
851
918
  /** Mark a list of session records as stored */
852
919
  async markSessionRecordsStored(sessionIds) {
853
920
  const updateDoc = {
854
- storedAtTimestamp: Date.now()
921
+ storedAtTimestamp: /* @__PURE__ */ new Date()
855
922
  };
856
923
  await this.tables?.session.updateMany(
857
924
  { sessionId: { $in: sessionIds } },
@@ -859,6 +926,17 @@ class ProviderDatabase extends MongoDatabase {
859
926
  { upsert: false }
860
927
  );
861
928
  }
929
+ /** Mark a list of token records as stored */
930
+ async markFrictionlessTokenRecordsStored(tokenIds) {
931
+ const updateDoc = {
932
+ storedAtTimestamp: /* @__PURE__ */ new Date()
933
+ };
934
+ await this.tables?.frictionlessToken.updateMany(
935
+ { _id: { $in: tokenIds } },
936
+ { $set: updateDoc },
937
+ { upsert: false }
938
+ );
939
+ }
862
940
  /**
863
941
  * @description Store a Dapp User's pending record
864
942
  */
@@ -1075,13 +1153,15 @@ class ProviderDatabase extends MongoDatabase {
1075
1153
  /**
1076
1154
  * @description Approve a dapp user's solution
1077
1155
  * @param {string[]} commitmentId
1156
+ * @param coords
1078
1157
  */
1079
- async approveDappUserCommitment(commitmentId) {
1158
+ async approveDappUserCommitment(commitmentId, coords) {
1080
1159
  try {
1081
1160
  const result = { status: CaptchaStatus.approved };
1082
1161
  const updateDoc = {
1083
1162
  result,
1084
- lastUpdatedTimestamp: Date.now()
1163
+ lastUpdatedTimestamp: Date.now(),
1164
+ ...coords ? { coords } : {}
1085
1165
  };
1086
1166
  const filter = { id: commitmentId };
1087
1167
  await this.tables?.commitment?.findOneAndUpdate(filter, { $set: updateDoc }, { upsert: false }).lean();
@@ -1094,13 +1174,15 @@ class ProviderDatabase extends MongoDatabase {
1094
1174
  /**
1095
1175
  * @description Disapprove a dapp user's solution
1096
1176
  * @param {string} commitmentId
1177
+ * @param coords
1097
1178
  * @param reason
1098
1179
  */
1099
- async disapproveDappUserCommitment(commitmentId, reason) {
1180
+ async disapproveDappUserCommitment(commitmentId, reason, coords) {
1100
1181
  try {
1101
1182
  const updateDoc = {
1102
1183
  result: { status: CaptchaStatus.disapproved, reason },
1103
- lastUpdatedTimestamp: Date.now()
1184
+ lastUpdatedTimestamp: Date.now(),
1185
+ ...coords ? { coords } : {}
1104
1186
  };
1105
1187
  const filter = { id: commitmentId };
1106
1188
  await this.tables?.commitment?.findOneAndUpdate(filter, { $set: updateDoc }, { upsert: false }).lean();
@@ -1252,10 +1334,16 @@ class ProviderDatabase extends MongoDatabase {
1252
1334
  createdAt: /* @__PURE__ */ new Date()
1253
1335
  });
1254
1336
  }
1255
- /** @description Remove a detector key */
1256
- async removeDetectorKey(detectorKey) {
1337
+ /**
1338
+ * @description Remove a detector key
1339
+ * @param detectorKey The detector key to remove
1340
+ * @param expirationInSeconds Optional expiration time in seconds (default is 10 minutes)
1341
+ * */
1342
+ async removeDetectorKey(detectorKey, expirationInSeconds) {
1257
1343
  const filter = { detectorKey };
1258
- const expiresAt = new Date(Date.now() + 10 * 60 * 1e3);
1344
+ const expiresAt = new Date(
1345
+ Date.now() + (expirationInSeconds || 10 * 60) * 1e3
1346
+ );
1259
1347
  await this.tables?.detector.updateOne(filter, {
1260
1348
  $set: { expiresAt }
1261
1349
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@prosopo/database",
3
- "version": "3.0.12",
3
+ "version": "3.4.5",
4
4
  "description": "Prosopo database plugins for provider",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
@@ -18,11 +18,10 @@
18
18
  },
19
19
  "scripts": {
20
20
  "clean": "del-cli --verbose dist tsconfig.tsbuildinfo",
21
- "build": "NODE_ENV=${NODE_ENV:-production}; vite build --config vite.esm.config.ts --mode $NODE_ENV",
21
+ "build": "NODE_ENV=${NODE_ENV:-development}; vite build --config vite.esm.config.ts --mode $NODE_ENV",
22
22
  "build:tsc": "tsc --build --verbose",
23
- "build:cjs": "NODE_ENV=${NODE_ENV:-production}; vite build --config vite.cjs.config.ts --mode $NODE_ENV",
24
- "typecheck": "tsc --build --declaration --emitDeclarationOnly --force",
25
- "test": "echo no tests"
23
+ "build:cjs": "NODE_ENV=${NODE_ENV:-development}; vite build --config vite.cjs.config.ts --mode $NODE_ENV",
24
+ "typecheck": "tsc --project tsconfig.types.json"
26
25
  },
27
26
  "repository": {
28
27
  "type": "git",
@@ -35,23 +34,20 @@
35
34
  },
36
35
  "homepage": "https://github.com/prosopo/captcha#readme",
37
36
  "dependencies": {
38
- "@prosopo/common": "3.1.1",
39
- "@prosopo/config": "3.1.2",
40
- "@prosopo/types": "3.0.5",
41
- "@prosopo/types-database": "3.0.12",
42
- "@prosopo/user-access-policy": "3.3.2",
43
- "@typegoose/auto-increment": "4.13.0",
44
- "axios": "1.10.0",
45
- "esbuild": "0.25.6",
46
- "express": "4.21.2",
47
- "mongodb": "6.9.0",
37
+ "@polkadot/util": "12.6.2",
38
+ "@prosopo/common": "3.1.20",
39
+ "@prosopo/config": "3.1.20",
40
+ "@prosopo/locale": "3.1.20",
41
+ "@prosopo/types": "3.5.3",
42
+ "@prosopo/types-database": "3.3.5",
43
+ "@prosopo/user-access-policy": "3.5.19",
44
+ "mongodb": "6.15.0",
48
45
  "mongodb-memory-server": "10.0.0",
49
46
  "mongoose": "8.13.0",
50
- "openpgp": "5.11.3",
51
- "redis": "5.0.0",
52
- "webpack-dev-server": "5.2.2"
47
+ "@prosopo/redis-client": "1.0.5"
53
48
  },
54
49
  "devDependencies": {
50
+ "@types/node": "22.10.2",
55
51
  "@vitest/coverage-v8": "3.0.9",
56
52
  "concurrently": "9.0.1",
57
53
  "del-cli": "6.0.0",