@prosopo/database 4.5.1 → 4.6.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.
- package/.turbo/turbo-build$colon$cjs.log +8 -7
- package/.turbo/turbo-build$colon$esm.log +8 -7
- package/.turbo/turbo-build$colon$tsc.log +15 -15
- package/CHANGELOG.md +44 -0
- package/dist/cjs/base/index.cjs +3 -0
- package/dist/cjs/base/mongo.cjs +5 -1
- package/dist/cjs/base/reconcileIndexes.cjs +162 -0
- package/dist/cjs/databases/captcha.cjs +20 -21
- package/dist/cjs/databases/provider.cjs +56 -29
- package/dist/cjs/index.cjs +3 -0
- package/dist/esm/base/index.js +2 -1
- package/dist/esm/base/mongo.js +5 -1
- package/dist/esm/base/reconcileIndexes.js +161 -0
- package/dist/esm/databases/captcha.js +20 -21
- package/dist/esm/databases/provider.js +56 -29
- package/dist/esm/index.js +2 -1
- package/dist/tsc/base/index.d.ts +1 -0
- package/dist/tsc/base/index.d.ts.map +1 -1
- package/dist/tsc/base/index.js +1 -0
- package/dist/tsc/base/index.js.map +1 -1
- package/dist/tsc/base/mongo.d.ts.map +1 -1
- package/dist/tsc/base/mongo.js +5 -1
- package/dist/tsc/base/mongo.js.map +1 -1
- package/dist/tsc/base/reconcileIndexes.d.ts +24 -0
- package/dist/tsc/base/reconcileIndexes.d.ts.map +1 -0
- package/dist/tsc/base/reconcileIndexes.js +177 -0
- package/dist/tsc/base/reconcileIndexes.js.map +1 -0
- package/dist/tsc/databases/captcha.d.ts.map +1 -1
- package/dist/tsc/databases/captcha.js +27 -25
- package/dist/tsc/databases/captcha.js.map +1 -1
- package/dist/tsc/databases/provider.d.ts +2 -8
- package/dist/tsc/databases/provider.d.ts.map +1 -1
- package/dist/tsc/databases/provider.js +61 -36
- package/dist/tsc/databases/provider.js.map +1 -1
- package/dist/tsc/tests/integration/insertMissingSpamEmailDomains.integration.test.d.ts +2 -0
- package/dist/tsc/tests/integration/insertMissingSpamEmailDomains.integration.test.d.ts.map +1 -0
- package/dist/tsc/tests/integration/insertMissingSpamEmailDomains.integration.test.js +91 -0
- package/dist/tsc/tests/integration/insertMissingSpamEmailDomains.integration.test.js.map +1 -0
- package/dist/tsc/tests/unit/base/mongo.unit.test.js +14 -0
- package/dist/tsc/tests/unit/base/mongo.unit.test.js.map +1 -1
- package/dist/tsc/tests/unit/base/reconcileIndexes.unit.test.d.ts +2 -0
- package/dist/tsc/tests/unit/base/reconcileIndexes.unit.test.d.ts.map +1 -0
- package/dist/tsc/tests/unit/base/reconcileIndexes.unit.test.js +350 -0
- package/dist/tsc/tests/unit/base/reconcileIndexes.unit.test.js.map +1 -0
- package/dist/types/base/index.d.ts +1 -0
- package/dist/types/base/index.d.ts.map +1 -1
- package/dist/types/base/mongo.d.ts.map +1 -1
- package/dist/types/base/reconcileIndexes.d.ts +24 -0
- package/dist/types/base/reconcileIndexes.d.ts.map +1 -0
- package/dist/types/databases/captcha.d.ts.map +1 -1
- package/dist/types/databases/provider.d.ts +2 -8
- package/dist/types/databases/provider.d.ts.map +1 -1
- package/dist/types/tests/integration/insertMissingSpamEmailDomains.integration.test.d.ts +2 -0
- package/dist/types/tests/integration/insertMissingSpamEmailDomains.integration.test.d.ts.map +1 -0
- package/dist/types/tests/unit/base/reconcileIndexes.unit.test.d.ts +2 -0
- package/dist/types/tests/unit/base/reconcileIndexes.unit.test.d.ts.map +1 -0
- package/package.json +7 -7
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
//#region src/base/reconcileIndexes.ts
|
|
2
|
+
var defaultIndexName = (key) => Object.entries(key).map(([field, direction]) => `${field}_${direction}`).join("_");
|
|
3
|
+
var toDriverKey = (key) => Object.fromEntries(Object.entries(key).map(([field, direction]) => [field, direction === "ascending" || direction === "asc" ? 1 : direction === "descending" || direction === "desc" ? -1 : direction]));
|
|
4
|
+
var keyOf = (index) => index.key instanceof Map ? Object.fromEntries(index.key) : index.key;
|
|
5
|
+
var sameKey = (a, b) => {
|
|
6
|
+
const left = Object.entries(a);
|
|
7
|
+
const right = Object.entries(b);
|
|
8
|
+
return left.length === right.length && left.every(([field, direction], i) => right[i]?.[0] === field && String(right[i]?.[1]) === String(direction));
|
|
9
|
+
};
|
|
10
|
+
var listIndexesOrEmpty = async (model) => {
|
|
11
|
+
try {
|
|
12
|
+
return await model.listIndexes();
|
|
13
|
+
} catch (err) {
|
|
14
|
+
if (err.codeName === "NamespaceNotFound") return [];
|
|
15
|
+
throw err;
|
|
16
|
+
}
|
|
17
|
+
};
|
|
18
|
+
var toDriverOptions = ({ name, options: { unique, ...options } }) => ({
|
|
19
|
+
...options,
|
|
20
|
+
...unique !== void 0 && { unique: Array.isArray(unique) ? unique[0] : unique },
|
|
21
|
+
name
|
|
22
|
+
});
|
|
23
|
+
var errorMessage = (err) => err instanceof Error ? err.message : String(err);
|
|
24
|
+
var specOf = (index) => {
|
|
25
|
+
const { key, v: _v, ns: _ns, ...options } = index;
|
|
26
|
+
return {
|
|
27
|
+
key,
|
|
28
|
+
options
|
|
29
|
+
};
|
|
30
|
+
};
|
|
31
|
+
var BRIDGE_SUFFIX = "_reconcile_bridge";
|
|
32
|
+
var BRIDGE_FIELD = "__reconcileBridge";
|
|
33
|
+
var bridgeOf = (index) => {
|
|
34
|
+
const { options } = specOf(index);
|
|
35
|
+
const { expireAfterSeconds: _ttl, ...bridgeOptions } = options;
|
|
36
|
+
return {
|
|
37
|
+
key: {
|
|
38
|
+
...index.key,
|
|
39
|
+
[BRIDGE_FIELD]: 1
|
|
40
|
+
},
|
|
41
|
+
options: {
|
|
42
|
+
...bridgeOptions,
|
|
43
|
+
name: `${index.name}${BRIDGE_SUFFIX}`
|
|
44
|
+
}
|
|
45
|
+
};
|
|
46
|
+
};
|
|
47
|
+
var dropIfPresent = async (model, name) => {
|
|
48
|
+
try {
|
|
49
|
+
await model.collection.dropIndex(name);
|
|
50
|
+
} catch (err) {
|
|
51
|
+
if (err.codeName !== "IndexNotFound") throw err;
|
|
52
|
+
}
|
|
53
|
+
};
|
|
54
|
+
/**
|
|
55
|
+
* Brings a collection's indexes in line with its schema without the window of
|
|
56
|
+
* a drop-everything-then-rebuild. Indexes that already match are left alone;
|
|
57
|
+
* an existing index that clashes with a declared one (same name or same key,
|
|
58
|
+
* different key or options) is rebuilt, because `createIndex` refuses to change
|
|
59
|
+
* an index in place and mongoose's autoIndex only logs that refusal. A bridge
|
|
60
|
+
* index is built before the old one is dropped, so no query loses its index
|
|
61
|
+
* during the rebuild. If a rebuild fails (e.g. a new unique index meets
|
|
62
|
+
* duplicates) the index it replaced is restored, so a failed run never leaves
|
|
63
|
+
* a collection with less than it started with.
|
|
64
|
+
*/
|
|
65
|
+
var reconcileIndexes = async (model, options = {}) => {
|
|
66
|
+
const result = {
|
|
67
|
+
collection: model.collection.collectionName,
|
|
68
|
+
dropped: [],
|
|
69
|
+
created: [],
|
|
70
|
+
restored: [],
|
|
71
|
+
failed: []
|
|
72
|
+
};
|
|
73
|
+
const existing = await listIndexesOrEmpty(model);
|
|
74
|
+
const diff = await model.diffIndexes({ indexOptionsToCreate: true });
|
|
75
|
+
const toCreate = diff.toCreate;
|
|
76
|
+
const undeclared = diff.toDrop;
|
|
77
|
+
const wanted = toCreate.map(([key, indexOptions]) => ({
|
|
78
|
+
key,
|
|
79
|
+
options: indexOptions,
|
|
80
|
+
name: indexOptions.name ?? defaultIndexName(key)
|
|
81
|
+
}));
|
|
82
|
+
const extra = options.extra ?? [];
|
|
83
|
+
const isExtra = (index) => extra.some((e) => sameKey(keyOf(e), index.key));
|
|
84
|
+
const missingExtra = extra.filter((e) => !existing.some((index) => sameKey(keyOf(e), index.key)) && !wanted.some((w) => sameKey(keyOf(e), w.key)));
|
|
85
|
+
const obsolete = new Set(options.obsolete ?? []);
|
|
86
|
+
const clashes = (index, w) => w.name === index.name || sameKey(w.key, index.key);
|
|
87
|
+
const toDrop = existing.filter((index) => index.name !== void 0 && undeclared.includes(index.name) && !isExtra(index) && (options.dropUndeclared || obsolete.has(index.name) || wanted.some((w) => clashes(index, w))));
|
|
88
|
+
const extraName = (e) => e.name ?? defaultIndexName(keyOf(e));
|
|
89
|
+
if (options.dryRun) {
|
|
90
|
+
result.dropped = toDrop.map((index) => index.name ?? "");
|
|
91
|
+
result.created = [...wanted.map((w) => w.name), ...missingExtra.map(extraName)];
|
|
92
|
+
return result;
|
|
93
|
+
}
|
|
94
|
+
for (const w of wanted) {
|
|
95
|
+
const replaced = toDrop.filter((index) => clashes(index, w) && !result.dropped.includes(index.name ?? ""));
|
|
96
|
+
const bridges = [];
|
|
97
|
+
try {
|
|
98
|
+
for (const index of replaced) {
|
|
99
|
+
const { key, options: bridgeOptions } = bridgeOf(index);
|
|
100
|
+
await model.collection.createIndex(key, bridgeOptions);
|
|
101
|
+
bridges.push(bridgeOptions.name ?? "");
|
|
102
|
+
}
|
|
103
|
+
} catch (err) {
|
|
104
|
+
result.failed.push({
|
|
105
|
+
name: w.name,
|
|
106
|
+
error: `bridge failed: ${errorMessage(err)}`
|
|
107
|
+
});
|
|
108
|
+
for (const bridge of bridges) await dropIfPresent(model, bridge);
|
|
109
|
+
continue;
|
|
110
|
+
}
|
|
111
|
+
for (const index of replaced) {
|
|
112
|
+
const name = index.name ?? "";
|
|
113
|
+
await dropIfPresent(model, name);
|
|
114
|
+
result.dropped.push(name);
|
|
115
|
+
}
|
|
116
|
+
let restoreFailed = false;
|
|
117
|
+
try {
|
|
118
|
+
await model.collection.createIndex(toDriverKey(w.key), toDriverOptions(w));
|
|
119
|
+
result.created.push(w.name);
|
|
120
|
+
} catch (err) {
|
|
121
|
+
result.failed.push({
|
|
122
|
+
name: w.name,
|
|
123
|
+
error: errorMessage(err)
|
|
124
|
+
});
|
|
125
|
+
for (const index of replaced) {
|
|
126
|
+
const { key, options: replacedOptions } = specOf(index);
|
|
127
|
+
try {
|
|
128
|
+
await model.collection.createIndex(key, replacedOptions);
|
|
129
|
+
result.restored.push(index.name ?? "");
|
|
130
|
+
} catch (restoreErr) {
|
|
131
|
+
restoreFailed = true;
|
|
132
|
+
result.failed.push({
|
|
133
|
+
name: index.name ?? "",
|
|
134
|
+
error: `restore failed: ${errorMessage(restoreErr)}`
|
|
135
|
+
});
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
if (!restoreFailed) for (const bridge of bridges) await dropIfPresent(model, bridge);
|
|
140
|
+
}
|
|
141
|
+
for (const e of missingExtra) try {
|
|
142
|
+
await model.collection.createIndexes([e]);
|
|
143
|
+
result.created.push(extraName(e));
|
|
144
|
+
} catch (err) {
|
|
145
|
+
result.failed.push({
|
|
146
|
+
name: extraName(e),
|
|
147
|
+
error: errorMessage(err)
|
|
148
|
+
});
|
|
149
|
+
}
|
|
150
|
+
for (const index of toDrop) {
|
|
151
|
+
const name = index.name ?? "";
|
|
152
|
+
if (!result.dropped.includes(name) && !wanted.some((w) => clashes(index, w))) {
|
|
153
|
+
await dropIfPresent(model, name);
|
|
154
|
+
result.dropped.push(name);
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
for (const index of existing) if (index.name?.endsWith(BRIDGE_SUFFIX)) await dropIfPresent(model, index.name);
|
|
158
|
+
return result;
|
|
159
|
+
};
|
|
160
|
+
//#endregion
|
|
161
|
+
export { defaultIndexName, reconcileIndexes };
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { MongoDatabase } from "../base/mongo.js";
|
|
2
|
+
import { reconcileIndexes } from "../base/reconcileIndexes.js";
|
|
2
3
|
import { ProsopoError } from "@prosopo/common";
|
|
3
4
|
import { getLogger } from "@prosopo/logger";
|
|
4
5
|
import { StoredAudioCaptchaRecordSchema, StoredIconOrderCaptchaRecordSchema, StoredPoWCaptchaRecordSchema, StoredPuzzleCaptchaRecordSchema, StoredSessionRecordSchema, StoredUserCommitmentRecordSchema } from "@prosopo/types-database";
|
|
@@ -60,27 +61,25 @@ var CaptchaDatabase = class CaptchaDatabase extends MongoDatabase {
|
|
|
60
61
|
return this.tables;
|
|
61
62
|
}
|
|
62
63
|
async ensureIndexes() {
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
if (
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
});
|
|
83
|
-
await Promise.all(indexPromises);
|
|
64
|
+
if (!this.indexesEnsured && this.connected) await Promise.all(CAPTCHA_TABLES.map(async ({ collectionName }) => {
|
|
65
|
+
try {
|
|
66
|
+
const result = await reconcileIndexes(this.tables[collectionName], { dropUndeclared: false });
|
|
67
|
+
if (result.failed.length > 0) this.logger.warn(() => ({
|
|
68
|
+
msg: `Error creating indexes for collection ${collectionName}`,
|
|
69
|
+
data: result
|
|
70
|
+
}));
|
|
71
|
+
else this.logger.info(() => ({
|
|
72
|
+
msg: `Indexes ensured for collection ${collectionName}`,
|
|
73
|
+
data: result
|
|
74
|
+
}));
|
|
75
|
+
} catch (err) {
|
|
76
|
+
this.logger.warn(() => ({
|
|
77
|
+
err,
|
|
78
|
+
msg: `Error creating indexes for collection ${collectionName}`
|
|
79
|
+
}));
|
|
80
|
+
}
|
|
81
|
+
}));
|
|
82
|
+
else if (!this.connected) this.logger.info(() => ({ msg: "Skipping index creation as not connected" }));
|
|
84
83
|
this.indexesEnsured = true;
|
|
85
84
|
}
|
|
86
85
|
/**
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { MongoDatabase } from "../base/mongo.js";
|
|
2
|
+
import { reconcileIndexes } from "../base/reconcileIndexes.js";
|
|
2
3
|
import { clientRecordCache } from "./clientRecordCache.js";
|
|
3
4
|
import { ProsopoError } from "@prosopo/common";
|
|
4
5
|
import { CaptchaStates, CaptchaStatus, CaptchaType, DatasetWithIdsAndTreeSchema, DecisionMachineKind, StoredStatusNames, UserCommitmentSchema, isBlockingCaptchaResult } from "@prosopo/types";
|
|
@@ -147,28 +148,25 @@ var ProviderDatabase = class ProviderDatabase extends MongoDatabase {
|
|
|
147
148
|
return this.redisConnection;
|
|
148
149
|
}
|
|
149
150
|
async ensureIndexes() {
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
if (
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
}));
|
|
170
|
-
});
|
|
171
|
-
await Promise.all(indexPromises);
|
|
151
|
+
if (!this.indexesEnsured && this.connected) await Promise.all(PROVIDER_TABLES.map(async ({ collectionName }) => {
|
|
152
|
+
try {
|
|
153
|
+
const result = await reconcileIndexes(this.tables[collectionName], { dropUndeclared: true });
|
|
154
|
+
if (result.failed.length > 0) this.logger.warn(() => ({
|
|
155
|
+
msg: `Error creating indexes for collection ${collectionName}`,
|
|
156
|
+
data: result
|
|
157
|
+
}));
|
|
158
|
+
else this.logger.info(() => ({
|
|
159
|
+
msg: `Indexes ensured for collection ${collectionName}`,
|
|
160
|
+
data: result
|
|
161
|
+
}));
|
|
162
|
+
} catch (err) {
|
|
163
|
+
this.logger.warn(() => ({
|
|
164
|
+
err,
|
|
165
|
+
msg: `Error creating indexes for collection ${collectionName}`
|
|
166
|
+
}));
|
|
167
|
+
}
|
|
168
|
+
}));
|
|
169
|
+
else if (!this.connected) this.logger.info(() => ({ msg: "Skipping index creation as not connected" }));
|
|
172
170
|
this.indexesEnsured = true;
|
|
173
171
|
}
|
|
174
172
|
getUserAccessRulesStorage() {
|
|
@@ -1054,6 +1052,19 @@ var ProviderDatabase = class ProviderDatabase extends MongoDatabase {
|
|
|
1054
1052
|
async updatePuzzleCaptchaRecord(challenge, updates) {
|
|
1055
1053
|
await this.updateInteractiveCaptchaRecord(this.interactiveCollection(CaptchaType.puzzle), challenge, updates);
|
|
1056
1054
|
}
|
|
1055
|
+
/**
|
|
1056
|
+
* Take a failed puzzle for one retry. Succeeds once per puzzle, and only
|
|
1057
|
+
* for a puzzle that was submitted and disapproved.
|
|
1058
|
+
* @returns true when this call took it
|
|
1059
|
+
*/
|
|
1060
|
+
async claimPuzzleCaptchaRetry(challenge) {
|
|
1061
|
+
return (await this.getTables()[this.interactiveCollection(CaptchaType.puzzle).table].updateOne({
|
|
1062
|
+
challenge,
|
|
1063
|
+
userSubmitted: true,
|
|
1064
|
+
"result.status": CaptchaStatus.disapproved,
|
|
1065
|
+
retried: { $ne: true }
|
|
1066
|
+
}, { $set: { retried: true } })).modifiedCount === 1;
|
|
1067
|
+
}
|
|
1057
1068
|
async updateIconOrderCaptchaRecord(challenge, updates) {
|
|
1058
1069
|
await this.updateInteractiveCaptchaRecord(this.interactiveCollection(CaptchaType.iconOrder), challenge, updates);
|
|
1059
1070
|
}
|
|
@@ -2087,19 +2098,35 @@ var ProviderDatabase = class ProviderDatabase extends MongoDatabase {
|
|
|
2087
2098
|
const query = suffixCandidates.length > 0 ? { domain: { $in: suffixCandidates } } : { domain };
|
|
2088
2099
|
return await this.tables.spamEmailDomain.findOne(query).exec();
|
|
2089
2100
|
}
|
|
2090
|
-
|
|
2101
|
+
/**
|
|
2102
|
+
* Store any of `domains` not already in the collection and return how many
|
|
2103
|
+
* were added. The source lists hold ~225k domains and barely change between
|
|
2104
|
+
* runs, so upserting all of them rewrote every row and held write locks for
|
|
2105
|
+
* ~8s per batch on each provider at the same moment. Reading the existing
|
|
2106
|
+
* set first is an index-only scan, and only the difference is written.
|
|
2107
|
+
*/
|
|
2108
|
+
async insertMissingSpamEmailDomains(domains) {
|
|
2091
2109
|
if (!this.tables?.spamEmailDomain) throw new ProsopoError({
|
|
2092
2110
|
code: "DB_QUERY_FAILED",
|
|
2093
2111
|
errorSite: "DBSE-51",
|
|
2094
2112
|
message: "Failed to import database engine",
|
|
2095
|
-
context: { failedFuncName: this.
|
|
2113
|
+
context: { failedFuncName: this.insertMissingSpamEmailDomains.name }
|
|
2096
2114
|
});
|
|
2097
|
-
const
|
|
2098
|
-
|
|
2099
|
-
|
|
2100
|
-
|
|
2115
|
+
const missing = new Set(domains);
|
|
2116
|
+
if (missing.size === 0) return 0;
|
|
2117
|
+
const existing = this.tables.spamEmailDomain.find({}, {
|
|
2118
|
+
domain: 1,
|
|
2119
|
+
_id: 0
|
|
2120
|
+
}).hint({ domain: 1 }).lean().cursor();
|
|
2121
|
+
for await (const record of existing) missing.delete(record.domain);
|
|
2122
|
+
if (missing.size === 0) return 0;
|
|
2123
|
+
const bulkOps = Array.from(missing, (domain) => ({ updateOne: {
|
|
2124
|
+
filter: { domain },
|
|
2125
|
+
update: { $set: { domain } },
|
|
2126
|
+
upsert: true
|
|
2101
2127
|
} }));
|
|
2102
|
-
|
|
2128
|
+
await this.tables.spamEmailDomain.bulkWrite(bulkOps, { ordered: false });
|
|
2129
|
+
return missing.size;
|
|
2103
2130
|
}
|
|
2104
2131
|
};
|
|
2105
2132
|
//#endregion
|
package/dist/esm/index.js
CHANGED
|
@@ -2,6 +2,7 @@ import "./_virtual/_rolldown/runtime.js";
|
|
|
2
2
|
import { DEFAULT_CONNECT_TIMEOUT_MS, DEFAULT_SERVER_SELECTION_TIMEOUT_MS, getMongoCompressors, getMongoConnectionOptions } from "./mongooseOptions.js";
|
|
3
3
|
import { MongoDatabase } from "./base/mongo.js";
|
|
4
4
|
import { MongoMemoryDatabase } from "./base/mongoMemory.js";
|
|
5
|
+
import { defaultIndexName, reconcileIndexes } from "./base/reconcileIndexes.js";
|
|
5
6
|
import { CaptchaDatabase } from "./databases/captcha.js";
|
|
6
7
|
import { ClientDatabase } from "./databases/client.js";
|
|
7
8
|
import { ProviderDatabase } from "./databases/provider.js";
|
|
@@ -12,4 +13,4 @@ import { makeDirectory } from "make-dir";
|
|
|
12
13
|
//#region src/index.ts
|
|
13
14
|
console.debug(makeDirectory);
|
|
14
15
|
//#endregion
|
|
15
|
-
export { CaptchaDatabase, CentralDbStreamer, ClientDatabase, DEFAULT_CONNECT_TIMEOUT_MS, DEFAULT_SERVER_SELECTION_TIMEOUT_MS, DETECTOR_BUNDLE_TTL_SECONDS, Databases, MongoDatabase, MongoMemoryDatabase, ProviderDatabase, RedisWriteQueue, getMongoCompressors, getMongoConnectionOptions };
|
|
16
|
+
export { CaptchaDatabase, CentralDbStreamer, ClientDatabase, DEFAULT_CONNECT_TIMEOUT_MS, DEFAULT_SERVER_SELECTION_TIMEOUT_MS, DETECTOR_BUNDLE_TTL_SECONDS, Databases, MongoDatabase, MongoMemoryDatabase, ProviderDatabase, RedisWriteQueue, defaultIndexName, getMongoCompressors, getMongoConnectionOptions, reconcileIndexes };
|
package/dist/tsc/base/index.d.ts
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/base/index.ts"],"names":[],"mappings":"AAaA,cAAc,YAAY,CAAC;AAC3B,cAAc,kBAAkB,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/base/index.ts"],"names":[],"mappings":"AAaA,cAAc,YAAY,CAAC;AAC3B,cAAc,kBAAkB,CAAC;AACjC,cAAc,uBAAuB,CAAC"}
|
package/dist/tsc/base/index.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/base/index.ts"],"names":[],"mappings":"AAaA,cAAc,YAAY,CAAC;AAC3B,cAAc,kBAAkB,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/base/index.ts"],"names":[],"mappings":"AAaA,cAAc,YAAY,CAAC;AAC3B,cAAc,kBAAkB,CAAC;AACjC,cAAc,uBAAuB,CAAC"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"mongo.d.ts","sourceRoot":"","sources":["../../../src/base/mongo.ts"],"names":[],"mappings":"AAeA,OAAO,EAAE,KAAK,MAAM,EAAa,MAAM,iBAAiB,CAAC;AACzD,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,yBAAyB,CAAC;AACzD,OAAO,QAAQ,EAAE,EAAE,KAAK,UAAU,EAAE,MAAM,UAAU,CAAC;
|
|
1
|
+
{"version":3,"file":"mongo.d.ts","sourceRoot":"","sources":["../../../src/base/mongo.ts"],"names":[],"mappings":"AAeA,OAAO,EAAE,KAAK,MAAM,EAAa,MAAM,iBAAiB,CAAC;AACzD,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,yBAAyB,CAAC;AACzD,OAAO,QAAQ,EAAE,EAAE,KAAK,UAAU,EAAE,MAAM,UAAU,CAAC;AAiBrD,MAAM,WAAW,uBAAuB;IACvC,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,wBAAwB,CAAC,EAAE,MAAM,CAAC;CAClC;AAQD,qBAAa,aAAc,YAAW,SAAS;IAC9C,SAAS,CAAC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IAChC,OAAO,EAAE,MAAM,CAAC;IAChB,MAAM,EAAE,MAAM,CAAC;IACf,UAAU,CAAC,EAAE,UAAU,CAAC;IACxB,MAAM,EAAE,MAAM,CAAC;IACf,SAAS,UAAS;IAClB,OAAO,CAAC,UAAU,CAAC,CAAgB;IACnC,SAAS,CAAC,QAAQ,CAAC,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC7C,SAAS,CAAC,QAAQ,CAAC,wBAAwB,CAAC,EAAE,MAAM,CAAC;gBAGpD,GAAG,EAAE,MAAM,EACX,MAAM,CAAC,EAAE,MAAM,EACf,UAAU,CAAC,EAAE,MAAM,EACnB,MAAM,CAAC,EAAE,MAAM,EACf,QAAQ,CAAC,EAAE,uBAAuB;IAoBnC,IAAI,GAAG,IAAI,MAAM,CAEhB;IAED,aAAa,IAAI,QAAQ,CAAC,UAAU;IAe9B,OAAO,IAAI,OAAO,CAAC,IAAI,CAAC;IAyIxB,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;CAM5B"}
|
package/dist/tsc/base/mongo.js
CHANGED
|
@@ -5,6 +5,10 @@ import mongoose from "mongoose";
|
|
|
5
5
|
import { getMongoConnectionOptions } from "../mongooseOptions.js";
|
|
6
6
|
mongoose.set("strictQuery", false);
|
|
7
7
|
const DEFAULT_ENDPOINT = "mongodb://127.0.0.1:27017";
|
|
8
|
+
const redactCredentials = (url) => {
|
|
9
|
+
const credentials = url.username || url.password ? "<Credentials>@" : "";
|
|
10
|
+
return `${url.protocol}//${credentials}${url.host}${url.pathname}${url.search}`;
|
|
11
|
+
};
|
|
8
12
|
export class MongoDatabase {
|
|
9
13
|
constructor(url, dbname, authSource, logger, timeouts) {
|
|
10
14
|
this.connected = false;
|
|
@@ -19,7 +23,7 @@ export class MongoDatabase {
|
|
|
19
23
|
parsedUrl.searchParams.set("authSource", authSource);
|
|
20
24
|
}
|
|
21
25
|
this._url = parsedUrl.toString();
|
|
22
|
-
this.safeURL =
|
|
26
|
+
this.safeURL = redactCredentials(parsedUrl);
|
|
23
27
|
this.dbname = dbname || parsedUrl.pathname.replace("/", "");
|
|
24
28
|
this.logger = (logger || getLogger("info", "database:mongo")).with({
|
|
25
29
|
mongoUrl: this.safeURL,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"mongo.js","sourceRoot":"","sources":["../../../src/base/mongo.ts"],"names":[],"mappings":"AAaA,OAAO,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AACzC,OAAO,EAAE,YAAY,EAAE,MAAM,iBAAiB,CAAC;AAC/C,OAAO,EAAe,SAAS,EAAE,MAAM,iBAAiB,CAAC;AAEzD,OAAO,QAA6B,MAAM,UAAU,CAAC;AACrD,OAAO,EAAE,yBAAyB,EAAE,MAAM,uBAAuB,CAAC;AAElE,QAAQ,CAAC,GAAG,CAAC,aAAa,EAAE,KAAK,CAAC,CAAC;AAGnC,MAAM,gBAAgB,GAAG,2BAA2B,CAAC;
|
|
1
|
+
{"version":3,"file":"mongo.js","sourceRoot":"","sources":["../../../src/base/mongo.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import type { IndexDescription } from "mongodb";
|
|
2
|
+
import type { Model } from "mongoose";
|
|
3
|
+
type IndexedModel = Pick<Model<unknown>, "collection" | "diffIndexes" | "listIndexes">;
|
|
4
|
+
export interface ReconcileIndexesOptions {
|
|
5
|
+
dropUndeclared?: boolean;
|
|
6
|
+
obsolete?: readonly string[];
|
|
7
|
+
extra?: readonly IndexDescription[];
|
|
8
|
+
dryRun?: boolean;
|
|
9
|
+
}
|
|
10
|
+
export interface IndexFailure {
|
|
11
|
+
name: string;
|
|
12
|
+
error: string;
|
|
13
|
+
}
|
|
14
|
+
export interface IndexReconciliation {
|
|
15
|
+
collection: string;
|
|
16
|
+
dropped: string[];
|
|
17
|
+
created: string[];
|
|
18
|
+
restored: string[];
|
|
19
|
+
failed: IndexFailure[];
|
|
20
|
+
}
|
|
21
|
+
export declare const defaultIndexName: (key: Readonly<Record<string, unknown>>) => string;
|
|
22
|
+
export declare const reconcileIndexes: (model: IndexedModel, options?: ReconcileIndexesOptions) => Promise<IndexReconciliation>;
|
|
23
|
+
export {};
|
|
24
|
+
//# sourceMappingURL=reconcileIndexes.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"reconcileIndexes.d.ts","sourceRoot":"","sources":["../../../src/base/reconcileIndexes.ts"],"names":[],"mappings":"AAaA,OAAO,KAAK,EAEX,gBAAgB,EAIhB,MAAM,SAAS,CAAC;AACjB,OAAO,KAAK,EAAiC,KAAK,EAAE,MAAM,UAAU,CAAC;AAErE,KAAK,YAAY,GAAG,IAAI,CACvB,KAAK,CAAC,OAAO,CAAC,EACd,YAAY,GAAG,aAAa,GAAG,aAAa,CAC5C,CAAC;AAEF,MAAM,WAAW,uBAAuB;IAMvC,cAAc,CAAC,EAAE,OAAO,CAAC;IAEzB,QAAQ,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;IAM7B,KAAK,CAAC,EAAE,SAAS,gBAAgB,EAAE,CAAC;IAEpC,MAAM,CAAC,EAAE,OAAO,CAAC;CACjB;AAED,MAAM,WAAW,YAAY;IAC5B,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,MAAM,CAAC;CACd;AAED,MAAM,WAAW,mBAAmB;IACnC,UAAU,EAAE,MAAM,CAAC;IACnB,OAAO,EAAE,MAAM,EAAE,CAAC;IAClB,OAAO,EAAE,MAAM,EAAE,CAAC;IAElB,QAAQ,EAAE,MAAM,EAAE,CAAC;IACnB,MAAM,EAAE,YAAY,EAAE,CAAC;CACvB;AAYD,eAAO,MAAM,gBAAgB,QACvB,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,KACpC,MAGS,CAAC;AAsHb,eAAO,MAAM,gBAAgB,UACrB,YAAY,YACV,uBAAuB,KAC9B,OAAO,CAAC,mBAAmB,CA4I7B,CAAC"}
|
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
export const defaultIndexName = (key) => Object.entries(key)
|
|
2
|
+
.map(([field, direction]) => `${field}_${direction}`)
|
|
3
|
+
.join("_");
|
|
4
|
+
const toDriverKey = (key) => Object.fromEntries(Object.entries(key).map(([field, direction]) => [
|
|
5
|
+
field,
|
|
6
|
+
direction === "ascending" || direction === "asc"
|
|
7
|
+
? 1
|
|
8
|
+
: direction === "descending" || direction === "desc"
|
|
9
|
+
? -1
|
|
10
|
+
: direction,
|
|
11
|
+
]));
|
|
12
|
+
const keyOf = (index) => index.key instanceof Map ? Object.fromEntries(index.key) : index.key;
|
|
13
|
+
const sameKey = (a, b) => {
|
|
14
|
+
const left = Object.entries(a);
|
|
15
|
+
const right = Object.entries(b);
|
|
16
|
+
return (left.length === right.length &&
|
|
17
|
+
left.every(([field, direction], i) => right[i]?.[0] === field && String(right[i]?.[1]) === String(direction)));
|
|
18
|
+
};
|
|
19
|
+
const listIndexesOrEmpty = async (model) => {
|
|
20
|
+
try {
|
|
21
|
+
return await model.listIndexes();
|
|
22
|
+
}
|
|
23
|
+
catch (err) {
|
|
24
|
+
if (err.codeName === "NamespaceNotFound") {
|
|
25
|
+
return [];
|
|
26
|
+
}
|
|
27
|
+
throw err;
|
|
28
|
+
}
|
|
29
|
+
};
|
|
30
|
+
const toDriverOptions = ({ name, options: { unique, ...options }, }) => ({
|
|
31
|
+
...options,
|
|
32
|
+
...(unique !== undefined && {
|
|
33
|
+
unique: Array.isArray(unique) ? unique[0] : unique,
|
|
34
|
+
}),
|
|
35
|
+
name,
|
|
36
|
+
});
|
|
37
|
+
const errorMessage = (err) => err instanceof Error ? err.message : String(err);
|
|
38
|
+
const specOf = (index) => {
|
|
39
|
+
const { key, v: _v, ns: _ns, ...options } = index;
|
|
40
|
+
return { key, options };
|
|
41
|
+
};
|
|
42
|
+
const BRIDGE_SUFFIX = "_reconcile_bridge";
|
|
43
|
+
const BRIDGE_FIELD = "__reconcileBridge";
|
|
44
|
+
const bridgeOf = (index) => {
|
|
45
|
+
const { options } = specOf(index);
|
|
46
|
+
const { expireAfterSeconds: _ttl, ...bridgeOptions } = options;
|
|
47
|
+
return {
|
|
48
|
+
key: { ...index.key, [BRIDGE_FIELD]: 1 },
|
|
49
|
+
options: { ...bridgeOptions, name: `${index.name}${BRIDGE_SUFFIX}` },
|
|
50
|
+
};
|
|
51
|
+
};
|
|
52
|
+
const dropIfPresent = async (model, name) => {
|
|
53
|
+
try {
|
|
54
|
+
await model.collection.dropIndex(name);
|
|
55
|
+
}
|
|
56
|
+
catch (err) {
|
|
57
|
+
if (err.codeName !== "IndexNotFound") {
|
|
58
|
+
throw err;
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
};
|
|
62
|
+
export const reconcileIndexes = async (model, options = {}) => {
|
|
63
|
+
const result = {
|
|
64
|
+
collection: model.collection.collectionName,
|
|
65
|
+
dropped: [],
|
|
66
|
+
created: [],
|
|
67
|
+
restored: [],
|
|
68
|
+
failed: [],
|
|
69
|
+
};
|
|
70
|
+
const existing = await listIndexesOrEmpty(model);
|
|
71
|
+
const diff = await model.diffIndexes({ indexOptionsToCreate: true });
|
|
72
|
+
const toCreate = diff.toCreate;
|
|
73
|
+
const undeclared = diff.toDrop;
|
|
74
|
+
const wanted = toCreate.map(([key, indexOptions]) => ({
|
|
75
|
+
key,
|
|
76
|
+
options: indexOptions,
|
|
77
|
+
name: indexOptions.name ?? defaultIndexName(key),
|
|
78
|
+
}));
|
|
79
|
+
const extra = options.extra ?? [];
|
|
80
|
+
const isExtra = (index) => extra.some((e) => sameKey(keyOf(e), index.key));
|
|
81
|
+
const missingExtra = extra.filter((e) => !existing.some((index) => sameKey(keyOf(e), index.key)) &&
|
|
82
|
+
!wanted.some((w) => sameKey(keyOf(e), w.key)));
|
|
83
|
+
const obsolete = new Set(options.obsolete ?? []);
|
|
84
|
+
const clashes = (index, w) => w.name === index.name || sameKey(w.key, index.key);
|
|
85
|
+
const toDrop = existing.filter((index) => index.name !== undefined &&
|
|
86
|
+
undeclared.includes(index.name) &&
|
|
87
|
+
!isExtra(index) &&
|
|
88
|
+
(options.dropUndeclared ||
|
|
89
|
+
obsolete.has(index.name) ||
|
|
90
|
+
wanted.some((w) => clashes(index, w))));
|
|
91
|
+
const extraName = (e) => e.name ?? defaultIndexName(keyOf(e));
|
|
92
|
+
if (options.dryRun) {
|
|
93
|
+
result.dropped = toDrop.map((index) => index.name ?? "");
|
|
94
|
+
result.created = [
|
|
95
|
+
...wanted.map((w) => w.name),
|
|
96
|
+
...missingExtra.map(extraName),
|
|
97
|
+
];
|
|
98
|
+
return result;
|
|
99
|
+
}
|
|
100
|
+
for (const w of wanted) {
|
|
101
|
+
const replaced = toDrop.filter((index) => clashes(index, w) && !result.dropped.includes(index.name ?? ""));
|
|
102
|
+
const bridges = [];
|
|
103
|
+
try {
|
|
104
|
+
for (const index of replaced) {
|
|
105
|
+
const { key, options: bridgeOptions } = bridgeOf(index);
|
|
106
|
+
await model.collection.createIndex(key, bridgeOptions);
|
|
107
|
+
bridges.push(bridgeOptions.name ?? "");
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
catch (err) {
|
|
111
|
+
result.failed.push({
|
|
112
|
+
name: w.name,
|
|
113
|
+
error: `bridge failed: ${errorMessage(err)}`,
|
|
114
|
+
});
|
|
115
|
+
for (const bridge of bridges) {
|
|
116
|
+
await dropIfPresent(model, bridge);
|
|
117
|
+
}
|
|
118
|
+
continue;
|
|
119
|
+
}
|
|
120
|
+
for (const index of replaced) {
|
|
121
|
+
const name = index.name ?? "";
|
|
122
|
+
await dropIfPresent(model, name);
|
|
123
|
+
result.dropped.push(name);
|
|
124
|
+
}
|
|
125
|
+
let restoreFailed = false;
|
|
126
|
+
try {
|
|
127
|
+
await model.collection.createIndex(toDriverKey(w.key), toDriverOptions(w));
|
|
128
|
+
result.created.push(w.name);
|
|
129
|
+
}
|
|
130
|
+
catch (err) {
|
|
131
|
+
result.failed.push({ name: w.name, error: errorMessage(err) });
|
|
132
|
+
for (const index of replaced) {
|
|
133
|
+
const { key, options: replacedOptions } = specOf(index);
|
|
134
|
+
try {
|
|
135
|
+
await model.collection.createIndex(key, replacedOptions);
|
|
136
|
+
result.restored.push(index.name ?? "");
|
|
137
|
+
}
|
|
138
|
+
catch (restoreErr) {
|
|
139
|
+
restoreFailed = true;
|
|
140
|
+
result.failed.push({
|
|
141
|
+
name: index.name ?? "",
|
|
142
|
+
error: `restore failed: ${errorMessage(restoreErr)}`,
|
|
143
|
+
});
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
if (!restoreFailed) {
|
|
148
|
+
for (const bridge of bridges) {
|
|
149
|
+
await dropIfPresent(model, bridge);
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
for (const e of missingExtra) {
|
|
154
|
+
try {
|
|
155
|
+
await model.collection.createIndexes([e]);
|
|
156
|
+
result.created.push(extraName(e));
|
|
157
|
+
}
|
|
158
|
+
catch (err) {
|
|
159
|
+
result.failed.push({ name: extraName(e), error: errorMessage(err) });
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
for (const index of toDrop) {
|
|
163
|
+
const name = index.name ?? "";
|
|
164
|
+
if (!result.dropped.includes(name) &&
|
|
165
|
+
!wanted.some((w) => clashes(index, w))) {
|
|
166
|
+
await dropIfPresent(model, name);
|
|
167
|
+
result.dropped.push(name);
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
for (const index of existing) {
|
|
171
|
+
if (index.name?.endsWith(BRIDGE_SUFFIX)) {
|
|
172
|
+
await dropIfPresent(model, index.name);
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
return result;
|
|
176
|
+
};
|
|
177
|
+
//# sourceMappingURL=reconcileIndexes.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"reconcileIndexes.js","sourceRoot":"","sources":["../../../src/base/reconcileIndexes.ts"],"names":[],"mappings":"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"}
|