@prosopo/database 0.2.0 → 0.2.1

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.
@@ -0,0 +1,10 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
3
+ const types = require("@prosopo/types");
4
+ const mongo = require("./mongo.cjs");
5
+ const mongoMemory = require("./mongoMemory.cjs");
6
+ const Databases = {
7
+ [types.DatabaseTypes.Values.mongo]: mongo.ProsopoDatabase,
8
+ [types.DatabaseTypes.Values.mongoMemory]: mongoMemory.MongoMemoryDatabase
9
+ };
10
+ exports.Databases = Databases;
@@ -0,0 +1,614 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
3
+ const types = require("@prosopo/types");
4
+ const common = require("@prosopo/common");
5
+ const typesDatabase = require("@prosopo/types-database");
6
+ const mongodb = require("mongodb");
7
+ const util = require("@polkadot/util");
8
+ const mongoose = require("mongoose");
9
+ mongoose.set("strictQuery", false);
10
+ const DEFAULT_ENDPOINT = "mongodb://127.0.0.1:27017";
11
+ class ProsopoDatabase extends common.AsyncFactory {
12
+ constructor() {
13
+ super();
14
+ this.url = "";
15
+ this.dbname = "";
16
+ this.logger = common.getLoggerDefault();
17
+ }
18
+ async init(url, dbname, logger, authSource) {
19
+ const baseEndpoint = url || DEFAULT_ENDPOINT;
20
+ const parsedUrl = new URL(baseEndpoint);
21
+ parsedUrl.pathname = dbname;
22
+ if (authSource) {
23
+ parsedUrl.searchParams.set("authSource", authSource);
24
+ }
25
+ this.url = parsedUrl.toString();
26
+ this.dbname = dbname;
27
+ this.logger = logger;
28
+ return this;
29
+ }
30
+ /**
31
+ * @description Connect to the database and set the various tables
32
+ */
33
+ async connect() {
34
+ return new Promise((resolve, reject) => {
35
+ if (this.connection) {
36
+ resolve();
37
+ }
38
+ this.logger.info(`Mongo url: ${this.url.replace(/\w+:\w+/, "<Credentials>")}`);
39
+ this.connection = mongoose.createConnection(this.url, {
40
+ dbName: this.dbname,
41
+ serverApi: mongodb.ServerApiVersion.v1
42
+ });
43
+ this.tables = {
44
+ captcha: this.connection.models.Captcha || this.connection.model("Captcha", typesDatabase.CaptchaRecordSchema),
45
+ dataset: this.connection.models.Dataset || this.connection.model("Dataset", typesDatabase.DatasetRecordSchema),
46
+ solution: this.connection.models.Solution || this.connection.model("Solution", typesDatabase.SolutionRecordSchema),
47
+ commitment: this.connection.models.UserCommitment || this.connection.model("UserCommitment", typesDatabase.UserCommitmentRecordSchema),
48
+ usersolution: this.connection.models.UserSolution || this.connection.model("UserSolution", typesDatabase.UserSolutionRecordSchema),
49
+ pending: this.connection.models.Pending || this.connection.model("Pending", typesDatabase.PendingRecordSchema),
50
+ scheduler: this.connection.models.Scheduler || this.connection.model("Scheduler", typesDatabase.ScheduledTaskRecordSchema)
51
+ };
52
+ this.connection.once("open", resolve).on("error", (e) => {
53
+ this.logger.warn(`mongoose connection error`);
54
+ if (e.message.code === "ETIMEDOUT") {
55
+ this.logger.error(e);
56
+ mongoose.connect(this.url);
57
+ }
58
+ this.logger.error(e);
59
+ reject(new common.ProsopoEnvError(e, "DATABASE.CONNECT_ERROR", {}, this.url));
60
+ });
61
+ });
62
+ }
63
+ /** Close connection to the database */
64
+ async close() {
65
+ this.logger.info(`Closing connection to ${this.url}`);
66
+ await new Promise(async (resolve, reject) => {
67
+ mongoose.connection.close().then(() => {
68
+ this.logger.info(`Connection to ${this.url} closed`);
69
+ resolve();
70
+ }).catch(reject);
71
+ });
72
+ }
73
+ /**
74
+ * @description Load a dataset to the database
75
+ * @param {Dataset} dataset
76
+ */
77
+ async storeDataset(dataset) {
78
+ var _a, _b, _c;
79
+ try {
80
+ this.logger.info(`Storing dataset in database`);
81
+ const parsedDataset = types.DatasetWithIdsAndTreeSchema.parse(dataset);
82
+ const datasetDoc = {
83
+ datasetId: parsedDataset.datasetId,
84
+ datasetContentId: parsedDataset.datasetContentId,
85
+ format: parsedDataset.format,
86
+ contentTree: parsedDataset.contentTree,
87
+ solutionTree: parsedDataset.solutionTree
88
+ };
89
+ await ((_a = this.tables) == null ? void 0 : _a.dataset.updateOne(
90
+ { datasetId: parsedDataset.datasetId },
91
+ { $set: datasetDoc },
92
+ { upsert: true }
93
+ ));
94
+ this.logger.info("parsedDataset.captchas", parsedDataset.captchas);
95
+ const captchaDocs = parsedDataset.captchas.map(({ solution, ...captcha }, index) => ({
96
+ ...captcha,
97
+ datasetId: parsedDataset.datasetId,
98
+ datasetContentId: parsedDataset.datasetContentId,
99
+ index,
100
+ solved: !!(solution == null ? void 0 : solution.length)
101
+ }));
102
+ this.logger.info(`Inserting captcha records`);
103
+ if (captchaDocs.length) {
104
+ await ((_b = this.tables) == null ? void 0 : _b.captcha.bulkWrite(
105
+ captchaDocs.map((captchaDoc) => ({
106
+ updateOne: {
107
+ filter: { captchaId: captchaDoc.captchaId },
108
+ update: { $set: captchaDoc },
109
+ upsert: true
110
+ }
111
+ }))
112
+ ));
113
+ }
114
+ const captchaSolutionDocs = parsedDataset.captchas.filter(({ solution }) => solution == null ? void 0 : solution.length).map((captcha) => ({
115
+ captchaId: captcha.captchaId,
116
+ captchaContentId: captcha.captchaContentId,
117
+ solution: captcha.solution,
118
+ salt: captcha.salt,
119
+ datasetId: parsedDataset.datasetId,
120
+ datasetContentId: parsedDataset.datasetContentId
121
+ }));
122
+ this.logger.info(`Inserting solution records`);
123
+ if (captchaSolutionDocs.length) {
124
+ await ((_c = this.tables) == null ? void 0 : _c.solution.bulkWrite(
125
+ captchaSolutionDocs.map((captchaSolutionDoc) => ({
126
+ updateOne: {
127
+ filter: { captchaId: captchaSolutionDoc.captchaId },
128
+ update: { $set: captchaSolutionDoc },
129
+ upsert: true
130
+ }
131
+ }))
132
+ ));
133
+ }
134
+ this.logger.info(`Dataset stored in database`);
135
+ } catch (err) {
136
+ throw new common.ProsopoEnvError(err, "DATABASE.DATASET_LOAD_FAILED");
137
+ }
138
+ }
139
+ /** @description Get a dataset from the database
140
+ * @param {string} datasetId
141
+ */
142
+ async getDataset(datasetId) {
143
+ var _a, _b, _c;
144
+ const datasetDoc = await ((_a = this.tables) == null ? void 0 : _a.dataset.findOne({ datasetId }).lean());
145
+ if (datasetDoc) {
146
+ const { datasetContentId, format, contentTree, solutionTree } = datasetDoc;
147
+ const captchas = await ((_b = this.tables) == null ? void 0 : _b.captcha.find({ datasetId }).lean()) || [];
148
+ const solutions = await ((_c = this.tables) == null ? void 0 : _c.solution.find({ datasetId }).lean()) || [];
149
+ const solutionsKeyed = {};
150
+ for (const solution of solutions) {
151
+ solutionsKeyed[solution.captchaId] = solution;
152
+ }
153
+ return {
154
+ datasetId,
155
+ datasetContentId,
156
+ format,
157
+ contentTree: contentTree || [],
158
+ solutionTree: solutionTree || [],
159
+ captchas: captchas.map((captchaDoc) => {
160
+ const { captchaId, captchaContentId, items, target, salt, solved } = captchaDoc;
161
+ const solution = solutionsKeyed[captchaId];
162
+ return {
163
+ captchaId,
164
+ captchaContentId,
165
+ solved: !!solved,
166
+ salt,
167
+ items,
168
+ target,
169
+ solution: solved && solution ? solution.solution : []
170
+ };
171
+ })
172
+ };
173
+ }
174
+ throw new common.ProsopoEnvError("DATABASE.DATASET_GET_FAILED", void 0, {}, datasetId);
175
+ }
176
+ /**
177
+ * @description Get random captchas that are solved or not solved
178
+ * @param {boolean} solved `true` when captcha is solved
179
+ * @param {string} datasetId the id of the data set
180
+ * @param {number} size the number of records to be returned
181
+ */
182
+ async getRandomCaptcha(solved, datasetId, size) {
183
+ var _a;
184
+ if (!util.isHex(datasetId)) {
185
+ throw new common.ProsopoEnvError("DATABASE.INVALID_HASH", this.getRandomCaptcha.name, {}, datasetId);
186
+ }
187
+ const sampleSize = size ? Math.abs(Math.trunc(size)) : 1;
188
+ const cursor = (_a = this.tables) == null ? void 0 : _a.captcha.aggregate([
189
+ { $match: { datasetId, solved } },
190
+ { $sample: { size: sampleSize } },
191
+ {
192
+ $project: {
193
+ datasetId: 1,
194
+ datasetContentId: 1,
195
+ captchaId: 1,
196
+ captchaContentId: 1,
197
+ items: 1,
198
+ target: 1
199
+ }
200
+ }
201
+ ]);
202
+ const docs = await cursor;
203
+ if (docs && docs.length) {
204
+ return docs.map(({ _id, ...keepAttrs }) => keepAttrs);
205
+ }
206
+ throw new common.ProsopoEnvError(
207
+ "DATABASE.CAPTCHA_GET_FAILED",
208
+ this.getRandomCaptcha.name,
209
+ {},
210
+ {
211
+ solved,
212
+ datasetId,
213
+ size
214
+ }
215
+ );
216
+ }
217
+ /**
218
+ * @description Get captchas by id
219
+ * @param {string[]} captchaId
220
+ */
221
+ async getCaptchaById(captchaId) {
222
+ var _a;
223
+ const cursor = (_a = this.tables) == null ? void 0 : _a.captcha.find({ captchaId: { $in: captchaId } }).lean();
224
+ const docs = await cursor;
225
+ if (docs && docs.length) {
226
+ return docs.map(({ _id, ...keepAttrs }) => keepAttrs);
227
+ }
228
+ throw new common.ProsopoEnvError("DATABASE.CAPTCHA_GET_FAILED", this.getCaptchaById.name, {}, captchaId);
229
+ }
230
+ /**
231
+ * @description Update a captcha
232
+ * @param {Captcha} captcha
233
+ * @param {string} datasetId the id of the data set
234
+ */
235
+ async updateCaptcha(captcha, datasetId) {
236
+ var _a;
237
+ if (!util.isHex(datasetId)) {
238
+ throw new common.ProsopoEnvError("DATABASE.INVALID_HASH", this.updateCaptcha.name, {}, datasetId);
239
+ }
240
+ try {
241
+ await ((_a = this.tables) == null ? void 0 : _a.captcha.updateOne({ datasetId }, { $set: captcha }, { upsert: false }));
242
+ } catch (err) {
243
+ throw new common.ProsopoEnvError(err);
244
+ }
245
+ }
246
+ /**
247
+ * @description Remove captchas
248
+ */
249
+ async removeCaptchas(captchaIds) {
250
+ var _a;
251
+ await ((_a = this.tables) == null ? void 0 : _a.captcha.deleteMany({ captchaId: { $in: captchaIds } }));
252
+ }
253
+ /**
254
+ * @description Get a dataset by Id
255
+ */
256
+ async getDatasetDetails(datasetId) {
257
+ var _a;
258
+ if (!util.isHex(datasetId)) {
259
+ throw new common.ProsopoEnvError("DATABASE.INVALID_HASH", this.getDatasetDetails.name, {}, datasetId);
260
+ }
261
+ const doc = await ((_a = this.tables) == null ? void 0 : _a.dataset.findOne({ datasetId }).lean());
262
+ if (doc) {
263
+ return doc;
264
+ }
265
+ throw new common.ProsopoEnvError("DATABASE.DATASET_GET_FAILED", this.getDatasetDetails.name, {}, datasetId);
266
+ }
267
+ /**
268
+ * @description Store a Dapp User's captcha solution commitment
269
+ */
270
+ async storeDappUserSolution(captchas, commit) {
271
+ var _a, _b;
272
+ const commitmentRecord = typesDatabase.UserCommitmentSchema.parse(commit);
273
+ if (captchas.length) {
274
+ await ((_a = this.tables) == null ? void 0 : _a.commitment.updateOne(
275
+ {
276
+ id: commit.id
277
+ },
278
+ commitmentRecord,
279
+ { upsert: true }
280
+ ));
281
+ const ops = captchas.map((captcha) => ({
282
+ updateOne: {
283
+ filter: { commitmentId: commit.id, captchaId: captcha.captchaId },
284
+ update: {
285
+ $set: {
286
+ captchaId: captcha.captchaId,
287
+ captchaContentId: captcha.captchaContentId,
288
+ salt: captcha.salt,
289
+ solution: captcha.solution,
290
+ commitmentId: commit.id,
291
+ processed: false
292
+ }
293
+ },
294
+ upsert: true
295
+ }
296
+ }));
297
+ await ((_b = this.tables) == null ? void 0 : _b.usersolution.bulkWrite(ops));
298
+ }
299
+ }
300
+ /** @description Get processed Dapp User captcha solutions from the user solution table
301
+ */
302
+ async getProcessedDappUserSolutions() {
303
+ var _a;
304
+ const docs = await ((_a = this.tables) == null ? void 0 : _a.usersolution.find({ processed: true }).lean());
305
+ return docs ? docs.map((doc) => typesDatabase.UserSolutionSchema.parse(doc)) : [];
306
+ }
307
+ /** @description Get processed Dapp User captcha commitments from the commitments table
308
+ */
309
+ async getProcessedDappUserCommitments() {
310
+ var _a;
311
+ const docs = await ((_a = this.tables) == null ? void 0 : _a.commitment.find({ processed: true }).lean());
312
+ return docs ? docs.map((doc) => typesDatabase.UserCommitmentSchema.parse(doc)) : [];
313
+ }
314
+ /** @description Get Dapp User captcha commitments from the commitments table that have not been batched on-chain
315
+ */
316
+ async getUnbatchedDappUserCommitments() {
317
+ var _a;
318
+ const docs = await ((_a = this.tables) == null ? void 0 : _a.commitment.find({ batched: false }).lean());
319
+ return docs ? docs.map((doc) => typesDatabase.UserCommitmentSchema.parse(doc)) : [];
320
+ }
321
+ /** @description Get Dapp User captcha commitments from the commitments table that have been batched on-chain
322
+ */
323
+ async getBatchedDappUserCommitments() {
324
+ var _a;
325
+ const docs = await ((_a = this.tables) == null ? void 0 : _a.commitment.find({ batched: true }).lean());
326
+ return docs ? docs.map((doc) => typesDatabase.UserCommitmentSchema.parse(doc)) : [];
327
+ }
328
+ /** @description Remove processed Dapp User captcha solutions from the user solution table
329
+ */
330
+ async removeProcessedDappUserSolutions(commitmentIds) {
331
+ var _a;
332
+ return await ((_a = this.tables) == null ? void 0 : _a.usersolution.deleteMany({ processed: true, commitmentId: { $in: commitmentIds } }));
333
+ }
334
+ /** @description Remove processed Dapp User captcha commitments from the user commitments table
335
+ */
336
+ async removeProcessedDappUserCommitments(commitmentIds) {
337
+ var _a;
338
+ return await ((_a = this.tables) == null ? void 0 : _a.commitment.deleteMany({ processed: true, id: { $in: commitmentIds } }));
339
+ }
340
+ /**
341
+ * @description Store a Dapp User's pending record
342
+ */
343
+ async storeDappUserPending(userAccount, requestHash, salt, deadlineTimestamp, requestedAtBlock) {
344
+ var _a;
345
+ if (!util.isHex(requestHash)) {
346
+ throw new common.ProsopoEnvError("DATABASE.INVALID_HASH", this.storeDappUserPending.name, {}, requestHash);
347
+ }
348
+ const pendingRecord = {
349
+ accountId: userAccount,
350
+ pending: true,
351
+ salt,
352
+ requestHash,
353
+ deadlineTimestamp,
354
+ requestedAtBlock
355
+ };
356
+ await ((_a = this.tables) == null ? void 0 : _a.pending.updateOne({ requestHash }, { $set: pendingRecord }, { upsert: true }));
357
+ }
358
+ /**
359
+ * @description Get a Dapp user's pending record
360
+ */
361
+ async getDappUserPending(requestHash) {
362
+ var _a;
363
+ if (!util.isHex(requestHash)) {
364
+ throw new common.ProsopoEnvError("DATABASE.INVALID_HASH", this.getDappUserPending.name, {}, requestHash);
365
+ }
366
+ const doc = await ((_a = this.tables) == null ? void 0 : _a.pending.findOne({ requestHash }).lean());
367
+ if (doc) {
368
+ return doc;
369
+ }
370
+ throw new common.ProsopoEnvError("DATABASE.PENDING_RECORD_NOT_FOUND", this.getDappUserPending.name);
371
+ }
372
+ /**
373
+ * @description Update a Dapp User's pending record
374
+ */
375
+ async updateDappUserPendingStatus(userAccount, requestHash, approve) {
376
+ var _a;
377
+ if (!util.isHex(requestHash)) {
378
+ throw new common.ProsopoEnvError("DATABASE.INVALID_HASH", this.updateDappUserPendingStatus.name, {}, requestHash);
379
+ }
380
+ await ((_a = this.tables) == null ? void 0 : _a.pending.updateOne(
381
+ { requestHash },
382
+ {
383
+ $set: {
384
+ accountId: userAccount,
385
+ pending: false,
386
+ approved: approve,
387
+ requestHash
388
+ }
389
+ },
390
+ { upsert: true }
391
+ ));
392
+ }
393
+ /**
394
+ * @description Get all unsolved captchas
395
+ */
396
+ async getAllCaptchasByDatasetId(datasetId, state) {
397
+ var _a;
398
+ const cursor = (_a = this.tables) == null ? void 0 : _a.captcha.find({
399
+ datasetId,
400
+ solved: state === types.CaptchaStates.Solved
401
+ }).lean();
402
+ const docs = await cursor;
403
+ if (docs) {
404
+ return docs.map(({ _id, ...keepAttrs }) => keepAttrs);
405
+ }
406
+ throw new common.ProsopoEnvError("DATABASE.CAPTCHA_GET_FAILED");
407
+ }
408
+ /**
409
+ * @description Get all dapp user solutions by captchaIds
410
+ */
411
+ async getAllDappUserSolutions(captchaId) {
412
+ var _a, _b;
413
+ const cursor = (_b = (_a = this.tables) == null ? void 0 : _a.usersolution) == null ? void 0 : _b.find({ captchaId: { $in: captchaId } }).lean();
414
+ const docs = await cursor;
415
+ if (docs) {
416
+ return docs.map(({ _id, ...keepAttrs }) => keepAttrs);
417
+ }
418
+ throw new common.ProsopoEnvError("DATABASE.SOLUTION_GET_FAILED");
419
+ }
420
+ async getDatasetIdWithSolvedCaptchasOfSizeN(solvedCaptchaCount) {
421
+ var _a;
422
+ const cursor = (_a = this.tables) == null ? void 0 : _a.solution.aggregate([
423
+ {
424
+ $match: {}
425
+ },
426
+ {
427
+ $group: {
428
+ _id: "$datasetId",
429
+ count: { $sum: 1 }
430
+ }
431
+ },
432
+ {
433
+ $match: {
434
+ count: { $gte: solvedCaptchaCount }
435
+ }
436
+ },
437
+ {
438
+ $sample: { size: 1 }
439
+ }
440
+ ]);
441
+ const docs = await cursor;
442
+ if (docs && docs.length) {
443
+ return docs[0]._id;
444
+ }
445
+ throw new common.ProsopoEnvError("DATABASE.DATASET_WITH_SOLUTIONS_GET_FAILED");
446
+ }
447
+ async getRandomSolvedCaptchasFromSingleDataset(datasetId, size) {
448
+ var _a;
449
+ if (!util.isHex(datasetId)) {
450
+ throw new common.ProsopoEnvError(
451
+ "DATABASE.INVALID_HASH",
452
+ this.getRandomSolvedCaptchasFromSingleDataset.name,
453
+ {},
454
+ datasetId
455
+ );
456
+ }
457
+ const sampleSize = size ? Math.abs(Math.trunc(size)) : 1;
458
+ const cursor = (_a = this.tables) == null ? void 0 : _a.solution.aggregate([
459
+ { $match: { datasetId } },
460
+ { $sample: { size: sampleSize } },
461
+ {
462
+ $project: {
463
+ captchaId: 1,
464
+ captchaContentId: 1,
465
+ solution: 1
466
+ }
467
+ }
468
+ ]);
469
+ const docs = await cursor;
470
+ if (docs && docs.length) {
471
+ return docs;
472
+ }
473
+ throw new common.ProsopoEnvError("DATABASE.SOLUTION_GET_FAILED");
474
+ }
475
+ /**
476
+ * @description Get dapp user solution by ID
477
+ * @param {string[]} commitmentId
478
+ */
479
+ async getDappUserSolutionById(commitmentId) {
480
+ var _a, _b;
481
+ const cursor = (_b = (_a = this.tables) == null ? void 0 : _a.usersolution) == null ? void 0 : _b.findOne(
482
+ {
483
+ commitmentId
484
+ },
485
+ { projection: { _id: 0 } }
486
+ ).lean();
487
+ const doc = await cursor;
488
+ if (doc) {
489
+ return doc;
490
+ }
491
+ throw new common.ProsopoEnvError("DATABASE.SOLUTION_GET_FAILED", this.getCaptchaById.name, {}, commitmentId);
492
+ }
493
+ /**
494
+ * @description Get dapp user commitment by user account
495
+ * @param commitmentId
496
+ */
497
+ async getDappUserCommitmentById(commitmentId) {
498
+ var _a, _b;
499
+ const commitmentCursor = (_b = (_a = this.tables) == null ? void 0 : _a.commitment) == null ? void 0 : _b.findOne({ id: commitmentId }).lean();
500
+ const doc = await commitmentCursor;
501
+ return doc ? typesDatabase.UserCommitmentSchema.parse(doc) : void 0;
502
+ }
503
+ /**
504
+ * @description Get dapp user commitment by user account
505
+ * @param {string[]} userAccount
506
+ */
507
+ async getDappUserCommitmentByAccount(userAccount) {
508
+ var _a, _b;
509
+ const docs = await ((_b = (_a = this.tables) == null ? void 0 : _a.commitment) == null ? void 0 : _b.find({ userAccount }).lean());
510
+ return docs ? docs : [];
511
+ }
512
+ /**
513
+ * @description Approve a dapp user's solution
514
+ * @param {string[]} commitmentId
515
+ */
516
+ async approveDappUserCommitment(commitmentId) {
517
+ var _a, _b;
518
+ try {
519
+ await ((_b = (_a = this.tables) == null ? void 0 : _a.commitment) == null ? void 0 : _b.findOneAndUpdate(
520
+ { id: commitmentId },
521
+ { $set: { status: types.CaptchaStatus.approved } },
522
+ { upsert: false }
523
+ ).lean());
524
+ } catch (err) {
525
+ throw new common.ProsopoEnvError(err, "DATABASE.SOLUTION_APPROVE_FAILED", {}, commitmentId);
526
+ }
527
+ }
528
+ /**
529
+ * @description Flag a dapp user's solutions as used by calculated solution
530
+ * @param {string[]} captchaIds
531
+ */
532
+ async flagProcessedDappUserSolutions(captchaIds) {
533
+ var _a, _b;
534
+ try {
535
+ await ((_b = (_a = this.tables) == null ? void 0 : _a.usersolution) == null ? void 0 : _b.updateMany({ captchaId: { $in: captchaIds } }, { $set: { processed: true } }, { upsert: false }).lean());
536
+ } catch (err) {
537
+ throw new common.ProsopoEnvError(err, "DATABASE.SOLUTION_FLAG_FAILED", {}, captchaIds);
538
+ }
539
+ }
540
+ /**
541
+ * @description Flag dapp users' commitments as used by calculated solution
542
+ * @param {string[]} commitmentIds
543
+ */
544
+ async flagProcessedDappUserCommitments(commitmentIds) {
545
+ var _a, _b;
546
+ try {
547
+ const distinctCommitmentIds = [...new Set(commitmentIds)];
548
+ await ((_b = (_a = this.tables) == null ? void 0 : _a.commitment) == null ? void 0 : _b.updateMany({ id: { $in: distinctCommitmentIds } }, { $set: { processed: true } }, { upsert: false }).lean());
549
+ } catch (err) {
550
+ throw new common.ProsopoEnvError(err, "DATABASE.COMMITMENT_FLAG_FAILED", {}, commitmentIds);
551
+ }
552
+ }
553
+ /**
554
+ * @description Flag dapp users' commitments as used by calculated solution
555
+ * @param {string[]} commitmentIds
556
+ */
557
+ async flagBatchedDappUserCommitments(commitmentIds) {
558
+ var _a, _b;
559
+ try {
560
+ const distinctCommitmentIds = [...new Set(commitmentIds)];
561
+ await ((_b = (_a = this.tables) == null ? void 0 : _a.commitment) == null ? void 0 : _b.updateMany({ id: { $in: distinctCommitmentIds } }, { $set: { batched: true } }, { upsert: false }).lean());
562
+ } catch (err) {
563
+ throw new common.ProsopoEnvError(err, "DATABASE.COMMITMENT_FLAG_FAILED", {}, commitmentIds);
564
+ }
565
+ }
566
+ /**
567
+ * @description Get the last batch commit time or return 0 if none
568
+ */
569
+ async getLastBatchCommitTime() {
570
+ var _a, _b;
571
+ const cursor = (_b = (_a = this.tables) == null ? void 0 : _a.scheduler) == null ? void 0 : _b.findOne({ processName: types.ScheduledTaskNames.BatchCommitment, status: types.ScheduledTaskStatus.Completed }).sort({ timestamp: -1 });
572
+ const doc = await (cursor == null ? void 0 : cursor.lean());
573
+ if (doc) {
574
+ return doc.datetime;
575
+ }
576
+ return /* @__PURE__ */ new Date(0);
577
+ }
578
+ /**
579
+ * @description Get a scheduled task status record by task ID and status
580
+ */
581
+ async getScheduledTaskStatus(taskId, status) {
582
+ var _a, _b;
583
+ const cursor = await ((_b = (_a = this.tables) == null ? void 0 : _a.scheduler) == null ? void 0 : _b.findOne({ taskId, status }).lean());
584
+ return cursor ? cursor : void 0;
585
+ }
586
+ /**
587
+ * @description Get the most recent scheduled task status record for a given task
588
+ */
589
+ async getLastScheduledTaskStatus(task, status) {
590
+ var _a, _b;
591
+ const lookup = { processName: task };
592
+ if (status) {
593
+ lookup["status"] = status;
594
+ }
595
+ const cursor = await ((_b = (_a = this.tables) == null ? void 0 : _a.scheduler) == null ? void 0 : _b.findOne(lookup).sort({ datetime: -1 }).lean());
596
+ return cursor ? cursor : void 0;
597
+ }
598
+ /**
599
+ * @description Store the status of a scheduled task and an optional result
600
+ */
601
+ async storeScheduledTaskStatus(taskId, task, status, result) {
602
+ var _a;
603
+ const now = /* @__PURE__ */ new Date();
604
+ const doc = typesDatabase.ScheduledTaskSchema.parse({
605
+ taskId,
606
+ processName: task,
607
+ datetime: now,
608
+ status,
609
+ ...result && { result }
610
+ });
611
+ await ((_a = this.tables) == null ? void 0 : _a.scheduler.create(doc));
612
+ }
613
+ }
614
+ exports.ProsopoDatabase = ProsopoDatabase;
@@ -0,0 +1,13 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
3
+ const mongo = require("./mongo.cjs");
4
+ const mongodbMemoryServer = require("mongodb-memory-server");
5
+ class MongoMemoryDatabase extends mongo.ProsopoDatabase {
6
+ async init(url, dbname, logger, authSource) {
7
+ const mongod = await mongodbMemoryServer.MongoMemoryServer.create();
8
+ const mongoMemoryURL = mongod.getUri();
9
+ await super.init(mongoMemoryURL, dbname, logger, authSource);
10
+ return this;
11
+ }
12
+ }
13
+ exports.MongoMemoryDatabase = MongoMemoryDatabase;
@@ -0,0 +1,4 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
3
+ const index = require("./databases/index.cjs");
4
+ exports.Databases = index.Databases;
package/package.json CHANGED
@@ -1,12 +1,19 @@
1
1
  {
2
2
  "name": "@prosopo/database",
3
- "version": "0.2.0",
3
+ "version": "0.2.1",
4
4
  "description": "Prosopo database plugins for provider",
5
5
  "main": "dist/index.js",
6
6
  "type": "module",
7
+ "exports": {
8
+ ".": {
9
+ "import": "./dist/index.js",
10
+ "require": "./dist/cjs/index.cjs"
11
+ }
12
+ },
7
13
  "scripts": {
8
14
  "clean": "tsc --build --clean",
9
- "build": "tsc --build --verbose",
15
+ "build": "tsc --build --verbose tsconfig.json",
16
+ "build:cjs": "npx vite --config vite.cjs.config.ts build",
10
17
  "lint": "npx eslint .",
11
18
  "lint:fix": "npx eslint . --fix --config ../../.eslintrc.js"
12
19
  },
@@ -21,9 +28,9 @@
21
28
  },
22
29
  "homepage": "https://github.com/prosopo/captcha#readme",
23
30
  "dependencies": {
24
- "@prosopo/common": "0.2.0",
25
- "@prosopo/types": "0.2.0",
26
- "@prosopo/types-database": "0.2.0",
31
+ "@prosopo/common": "0.2.1",
32
+ "@prosopo/types": "0.2.1",
33
+ "@prosopo/types-database": "0.2.1",
27
34
  "mongodb": "5.8.0",
28
35
  "mongodb-memory-server": "^8.7.2",
29
36
  "mongoose": "^7.3.3",
@@ -0,0 +1,6 @@
1
+ import { ViteCommonJSConfig } from '@prosopo/config'
2
+ import path from 'path'
3
+
4
+ export default function () {
5
+ return ViteCommonJSConfig('database', path.resolve('./tsconfig.cjs.json'))
6
+ }