monsqlize 2.0.2 → 2.0.3
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 +10 -5
- package/README.md +35 -19
- package/changelogs/README.md +8 -4
- package/changelogs/v2.0.0.md +1 -1
- package/changelogs/v2.0.3.md +58 -0
- package/dist/cjs/index.cjs +648 -192
- package/dist/cjs/transaction/Transaction.cjs +88 -3
- package/dist/cjs/transaction/TransactionManager.cjs +135 -11
- package/dist/esm/index.mjs +643 -187
- package/dist/types/collection.d.ts +5 -3
- package/dist/types/model.d.ts +175 -175
- package/dist/types/mongodb.d.ts +8 -1
- package/dist/types/monsqlize.d.ts +28 -3
- package/dist/types/pool.d.ts +1 -1
- package/dist/types/runtime.d.ts +31 -7
- package/dist/types/transaction.d.ts +12 -0
- package/package.json +22 -22
|
@@ -2,6 +2,91 @@
|
|
|
2
2
|
|
|
3
3
|
// src/capabilities/transaction/index.ts
|
|
4
4
|
var import_node_crypto = require("node:crypto");
|
|
5
|
+
|
|
6
|
+
// src/core/errors/index.ts
|
|
7
|
+
var ErrorCodes = {
|
|
8
|
+
INVALID_ARGUMENT: "INVALID_ARGUMENT",
|
|
9
|
+
INVALID_COLLECTION_NAME: "INVALID_COLLECTION_NAME",
|
|
10
|
+
INVALID_DATABASE_NAME: "INVALID_DATABASE_NAME",
|
|
11
|
+
INVALID_EXPRESSION: "INVALID_EXPRESSION",
|
|
12
|
+
INVALID_PAGINATION: "INVALID_PAGINATION",
|
|
13
|
+
INVALID_OPERATION: "INVALID_OPERATION",
|
|
14
|
+
CACHE_UNAVAILABLE: "CACHE_UNAVAILABLE",
|
|
15
|
+
MANAGEMENT_OPERATION_FAILED: "MANAGEMENT_OPERATION_FAILED",
|
|
16
|
+
NOT_CONNECTED: "NOT_CONNECTED",
|
|
17
|
+
CONNECTION_FAILED: "CONNECTION_FAILED",
|
|
18
|
+
CONNECTION_CLOSED: "CONNECTION_CLOSED",
|
|
19
|
+
INVALID_CONFIG: "INVALID_CONFIG",
|
|
20
|
+
OPERATION_TIMEOUT: "OPERATION_TIMEOUT",
|
|
21
|
+
UNSUPPORTED_DATABASE: "UNSUPPORTED_DATABASE",
|
|
22
|
+
/** v1 compat: insertOne requires a non-null, non-array object document */
|
|
23
|
+
DOCUMENT_REQUIRED: "DOCUMENT_REQUIRED",
|
|
24
|
+
/** v1 compat: MongoDB duplicate key (error code 11000) */
|
|
25
|
+
DUPLICATE_KEY: "DUPLICATE_KEY",
|
|
26
|
+
/** v1 compat: general write failure (maps from MongoError in insert/update/delete) */
|
|
27
|
+
WRITE_ERROR: "WRITE_ERROR",
|
|
28
|
+
/** v1 compat: model-layer schema validation failure */
|
|
29
|
+
VALIDATION_ERROR: "VALIDATION_ERROR",
|
|
30
|
+
/** v1 compat: stream mode cannot use page jump (page > 1 with stream: true) */
|
|
31
|
+
STREAM_NO_JUMP: "STREAM_NO_JUMP",
|
|
32
|
+
/** v1 compat: stream mode cannot compute totals */
|
|
33
|
+
STREAM_NO_TOTALS: "STREAM_NO_TOTALS",
|
|
34
|
+
/** v1 compat: stream mode cannot use explain */
|
|
35
|
+
STREAM_NO_EXPLAIN: "STREAM_NO_EXPLAIN",
|
|
36
|
+
/** v1 compat: page jump exceeds the maxHops limit */
|
|
37
|
+
JUMP_TOO_FAR: "JUMP_TOO_FAR",
|
|
38
|
+
/** v1 compat: generic MongoDB driver error (maps numeric MongoDB error codes) */
|
|
39
|
+
MONGODB_ERROR: "MONGODB_ERROR",
|
|
40
|
+
/** v1 compat: cursor sort options mismatch between pages */
|
|
41
|
+
CURSOR_SORT_MISMATCH: "CURSOR_SORT_MISMATCH",
|
|
42
|
+
/** v1 compat: invalid or expired cursor token */
|
|
43
|
+
INVALID_CURSOR: "INVALID_CURSOR",
|
|
44
|
+
/** v1 compat: connection timeout */
|
|
45
|
+
CONNECTION_TIMEOUT: "CONNECTION_TIMEOUT",
|
|
46
|
+
/** v1 compat: generic database error */
|
|
47
|
+
DATABASE_ERROR: "DATABASE_ERROR",
|
|
48
|
+
/** v1 compat: query execution timeout */
|
|
49
|
+
QUERY_TIMEOUT: "QUERY_TIMEOUT",
|
|
50
|
+
/** v1 compat: cache backend error */
|
|
51
|
+
CACHE_ERROR: "CACHE_ERROR",
|
|
52
|
+
/** v1 compat: cache operation timeout */
|
|
53
|
+
CACHE_TIMEOUT: "CACHE_TIMEOUT",
|
|
54
|
+
/** v1 compat: model.define() called without schema */
|
|
55
|
+
MISSING_SCHEMA: "MISSING_SCHEMA",
|
|
56
|
+
/** v1 compat: model already registered under same name */
|
|
57
|
+
MODEL_ALREADY_EXISTS: "MODEL_ALREADY_EXISTS",
|
|
58
|
+
/** v1 compat: write requires at least one document */
|
|
59
|
+
DOCUMENTS_REQUIRED: "DOCUMENTS_REQUIRED",
|
|
60
|
+
/** v1 compat: concurrent write conflict in transaction */
|
|
61
|
+
WRITE_CONFLICT: "WRITE_CONFLICT",
|
|
62
|
+
/** v1 compat: business lock acquire failed */
|
|
63
|
+
LOCK_ACQUIRE_FAILED: "LOCK_ACQUIRE_FAILED",
|
|
64
|
+
/** v1 compat: business lock wait timeout */
|
|
65
|
+
LOCK_TIMEOUT: "LOCK_TIMEOUT",
|
|
66
|
+
/** v1 compat: model.model() called when not connected */
|
|
67
|
+
MODEL_NOT_DEFINED: "MODEL_NOT_DEFINED",
|
|
68
|
+
/** v1 compat: pool() called without pools configured */
|
|
69
|
+
NO_POOL_MANAGER: "NO_POOL_MANAGER",
|
|
70
|
+
/** v1 compat: pool() called with a pool name that does not exist */
|
|
71
|
+
POOL_NOT_FOUND: "POOL_NOT_FOUND",
|
|
72
|
+
/** v1 compat: model definition is not a valid object */
|
|
73
|
+
INVALID_MODEL_DEFINITION: "INVALID_MODEL_DEFINITION",
|
|
74
|
+
/** v1 compat: schema property is not a function or object */
|
|
75
|
+
INVALID_SCHEMA_TYPE: "INVALID_SCHEMA_TYPE"
|
|
76
|
+
};
|
|
77
|
+
function createError(code, message, details, cause) {
|
|
78
|
+
const error = new Error(message);
|
|
79
|
+
error.code = code;
|
|
80
|
+
if (details !== void 0) {
|
|
81
|
+
error.details = details;
|
|
82
|
+
}
|
|
83
|
+
if (cause !== void 0) {
|
|
84
|
+
error.cause = cause;
|
|
85
|
+
}
|
|
86
|
+
return error;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
// src/capabilities/transaction/index.ts
|
|
5
90
|
function toPublicTransactionStatus(state) {
|
|
6
91
|
return state === "active" ? "started" : state;
|
|
7
92
|
}
|
|
@@ -22,9 +107,9 @@ var Transaction = class {
|
|
|
22
107
|
*/
|
|
23
108
|
async start() {
|
|
24
109
|
if (this.state !== "pending") {
|
|
25
|
-
throw
|
|
110
|
+
throw createError(ErrorCodes.INVALID_OPERATION, `Cannot start transaction in state: ${this.state}`);
|
|
26
111
|
}
|
|
27
|
-
this.session.startTransaction();
|
|
112
|
+
this.session.startTransaction(this.options.transactionOptions);
|
|
28
113
|
this.state = "active";
|
|
29
114
|
this.startedAt = Date.now();
|
|
30
115
|
const timeout = this.options.timeout ?? 3e4;
|
|
@@ -44,7 +129,7 @@ var Transaction = class {
|
|
|
44
129
|
*/
|
|
45
130
|
async commit() {
|
|
46
131
|
if (this.state !== "active") {
|
|
47
|
-
throw
|
|
132
|
+
throw createError(ErrorCodes.INVALID_OPERATION, `Cannot commit transaction in state: ${this.state}`);
|
|
48
133
|
}
|
|
49
134
|
if (typeof this.session.commitTransaction === "function") {
|
|
50
135
|
await this.session.commitTransaction();
|
|
@@ -2,6 +2,91 @@
|
|
|
2
2
|
|
|
3
3
|
// src/capabilities/transaction/index.ts
|
|
4
4
|
var import_node_crypto = require("node:crypto");
|
|
5
|
+
|
|
6
|
+
// src/core/errors/index.ts
|
|
7
|
+
var ErrorCodes = {
|
|
8
|
+
INVALID_ARGUMENT: "INVALID_ARGUMENT",
|
|
9
|
+
INVALID_COLLECTION_NAME: "INVALID_COLLECTION_NAME",
|
|
10
|
+
INVALID_DATABASE_NAME: "INVALID_DATABASE_NAME",
|
|
11
|
+
INVALID_EXPRESSION: "INVALID_EXPRESSION",
|
|
12
|
+
INVALID_PAGINATION: "INVALID_PAGINATION",
|
|
13
|
+
INVALID_OPERATION: "INVALID_OPERATION",
|
|
14
|
+
CACHE_UNAVAILABLE: "CACHE_UNAVAILABLE",
|
|
15
|
+
MANAGEMENT_OPERATION_FAILED: "MANAGEMENT_OPERATION_FAILED",
|
|
16
|
+
NOT_CONNECTED: "NOT_CONNECTED",
|
|
17
|
+
CONNECTION_FAILED: "CONNECTION_FAILED",
|
|
18
|
+
CONNECTION_CLOSED: "CONNECTION_CLOSED",
|
|
19
|
+
INVALID_CONFIG: "INVALID_CONFIG",
|
|
20
|
+
OPERATION_TIMEOUT: "OPERATION_TIMEOUT",
|
|
21
|
+
UNSUPPORTED_DATABASE: "UNSUPPORTED_DATABASE",
|
|
22
|
+
/** v1 compat: insertOne requires a non-null, non-array object document */
|
|
23
|
+
DOCUMENT_REQUIRED: "DOCUMENT_REQUIRED",
|
|
24
|
+
/** v1 compat: MongoDB duplicate key (error code 11000) */
|
|
25
|
+
DUPLICATE_KEY: "DUPLICATE_KEY",
|
|
26
|
+
/** v1 compat: general write failure (maps from MongoError in insert/update/delete) */
|
|
27
|
+
WRITE_ERROR: "WRITE_ERROR",
|
|
28
|
+
/** v1 compat: model-layer schema validation failure */
|
|
29
|
+
VALIDATION_ERROR: "VALIDATION_ERROR",
|
|
30
|
+
/** v1 compat: stream mode cannot use page jump (page > 1 with stream: true) */
|
|
31
|
+
STREAM_NO_JUMP: "STREAM_NO_JUMP",
|
|
32
|
+
/** v1 compat: stream mode cannot compute totals */
|
|
33
|
+
STREAM_NO_TOTALS: "STREAM_NO_TOTALS",
|
|
34
|
+
/** v1 compat: stream mode cannot use explain */
|
|
35
|
+
STREAM_NO_EXPLAIN: "STREAM_NO_EXPLAIN",
|
|
36
|
+
/** v1 compat: page jump exceeds the maxHops limit */
|
|
37
|
+
JUMP_TOO_FAR: "JUMP_TOO_FAR",
|
|
38
|
+
/** v1 compat: generic MongoDB driver error (maps numeric MongoDB error codes) */
|
|
39
|
+
MONGODB_ERROR: "MONGODB_ERROR",
|
|
40
|
+
/** v1 compat: cursor sort options mismatch between pages */
|
|
41
|
+
CURSOR_SORT_MISMATCH: "CURSOR_SORT_MISMATCH",
|
|
42
|
+
/** v1 compat: invalid or expired cursor token */
|
|
43
|
+
INVALID_CURSOR: "INVALID_CURSOR",
|
|
44
|
+
/** v1 compat: connection timeout */
|
|
45
|
+
CONNECTION_TIMEOUT: "CONNECTION_TIMEOUT",
|
|
46
|
+
/** v1 compat: generic database error */
|
|
47
|
+
DATABASE_ERROR: "DATABASE_ERROR",
|
|
48
|
+
/** v1 compat: query execution timeout */
|
|
49
|
+
QUERY_TIMEOUT: "QUERY_TIMEOUT",
|
|
50
|
+
/** v1 compat: cache backend error */
|
|
51
|
+
CACHE_ERROR: "CACHE_ERROR",
|
|
52
|
+
/** v1 compat: cache operation timeout */
|
|
53
|
+
CACHE_TIMEOUT: "CACHE_TIMEOUT",
|
|
54
|
+
/** v1 compat: model.define() called without schema */
|
|
55
|
+
MISSING_SCHEMA: "MISSING_SCHEMA",
|
|
56
|
+
/** v1 compat: model already registered under same name */
|
|
57
|
+
MODEL_ALREADY_EXISTS: "MODEL_ALREADY_EXISTS",
|
|
58
|
+
/** v1 compat: write requires at least one document */
|
|
59
|
+
DOCUMENTS_REQUIRED: "DOCUMENTS_REQUIRED",
|
|
60
|
+
/** v1 compat: concurrent write conflict in transaction */
|
|
61
|
+
WRITE_CONFLICT: "WRITE_CONFLICT",
|
|
62
|
+
/** v1 compat: business lock acquire failed */
|
|
63
|
+
LOCK_ACQUIRE_FAILED: "LOCK_ACQUIRE_FAILED",
|
|
64
|
+
/** v1 compat: business lock wait timeout */
|
|
65
|
+
LOCK_TIMEOUT: "LOCK_TIMEOUT",
|
|
66
|
+
/** v1 compat: model.model() called when not connected */
|
|
67
|
+
MODEL_NOT_DEFINED: "MODEL_NOT_DEFINED",
|
|
68
|
+
/** v1 compat: pool() called without pools configured */
|
|
69
|
+
NO_POOL_MANAGER: "NO_POOL_MANAGER",
|
|
70
|
+
/** v1 compat: pool() called with a pool name that does not exist */
|
|
71
|
+
POOL_NOT_FOUND: "POOL_NOT_FOUND",
|
|
72
|
+
/** v1 compat: model definition is not a valid object */
|
|
73
|
+
INVALID_MODEL_DEFINITION: "INVALID_MODEL_DEFINITION",
|
|
74
|
+
/** v1 compat: schema property is not a function or object */
|
|
75
|
+
INVALID_SCHEMA_TYPE: "INVALID_SCHEMA_TYPE"
|
|
76
|
+
};
|
|
77
|
+
function createError(code, message, details, cause) {
|
|
78
|
+
const error = new Error(message);
|
|
79
|
+
error.code = code;
|
|
80
|
+
if (details !== void 0) {
|
|
81
|
+
error.details = details;
|
|
82
|
+
}
|
|
83
|
+
if (cause !== void 0) {
|
|
84
|
+
error.cause = cause;
|
|
85
|
+
}
|
|
86
|
+
return error;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
// src/capabilities/transaction/index.ts
|
|
5
90
|
function toPublicTransactionStatus(state) {
|
|
6
91
|
return state === "active" ? "started" : state;
|
|
7
92
|
}
|
|
@@ -22,9 +107,9 @@ var Transaction = class {
|
|
|
22
107
|
*/
|
|
23
108
|
async start() {
|
|
24
109
|
if (this.state !== "pending") {
|
|
25
|
-
throw
|
|
110
|
+
throw createError(ErrorCodes.INVALID_OPERATION, `Cannot start transaction in state: ${this.state}`);
|
|
26
111
|
}
|
|
27
|
-
this.session.startTransaction();
|
|
112
|
+
this.session.startTransaction(this.options.transactionOptions);
|
|
28
113
|
this.state = "active";
|
|
29
114
|
this.startedAt = Date.now();
|
|
30
115
|
const timeout = this.options.timeout ?? 3e4;
|
|
@@ -44,7 +129,7 @@ var Transaction = class {
|
|
|
44
129
|
*/
|
|
45
130
|
async commit() {
|
|
46
131
|
if (this.state !== "active") {
|
|
47
|
-
throw
|
|
132
|
+
throw createError(ErrorCodes.INVALID_OPERATION, `Cannot commit transaction in state: ${this.state}`);
|
|
48
133
|
}
|
|
49
134
|
if (typeof this.session.commitTransaction === "function") {
|
|
50
135
|
await this.session.commitTransaction();
|
|
@@ -142,7 +227,9 @@ var TransactionManager = class {
|
|
|
142
227
|
this.stats = {
|
|
143
228
|
totalTransactions: 0,
|
|
144
229
|
successfulTransactions: 0,
|
|
145
|
-
failedTransactions: 0
|
|
230
|
+
failedTransactions: 0,
|
|
231
|
+
readOnlyTransactions: 0,
|
|
232
|
+
writeTransactions: 0
|
|
146
233
|
};
|
|
147
234
|
const options = "client" in input ? input : {
|
|
148
235
|
client: input,
|
|
@@ -153,6 +240,10 @@ var TransactionManager = class {
|
|
|
153
240
|
this.cache = options.cache ?? null;
|
|
154
241
|
this.logger = options.logger ?? null;
|
|
155
242
|
this.lockManager = options.lockManager ?? null;
|
|
243
|
+
this.defaultReadConcern = options.defaultReadConcern;
|
|
244
|
+
this.defaultWriteConcern = options.defaultWriteConcern;
|
|
245
|
+
this.defaultReadPreference = options.defaultReadPreference;
|
|
246
|
+
this.maxStatsSamples = options.maxStatsSamples ?? 1e3;
|
|
156
247
|
this.defaultOptions = {
|
|
157
248
|
maxDuration: options.maxDuration ?? 3e4,
|
|
158
249
|
enableRetry: options.enableRetry ?? true,
|
|
@@ -169,11 +260,17 @@ var TransactionManager = class {
|
|
|
169
260
|
const session = this.client.startSession({
|
|
170
261
|
causalConsistency: options.causalConsistency !== false
|
|
171
262
|
});
|
|
263
|
+
const transactionOptions = {
|
|
264
|
+
readConcern: options.readConcern ?? this.defaultReadConcern,
|
|
265
|
+
writeConcern: options.writeConcern ?? this.defaultWriteConcern,
|
|
266
|
+
readPreference: options.readPreference ?? this.defaultReadPreference
|
|
267
|
+
};
|
|
172
268
|
const transaction = new Transaction(session, {
|
|
173
269
|
cache: this.cache,
|
|
174
270
|
logger: this.logger,
|
|
175
271
|
lockManager: options.enableCacheLock === false ? null : this.lockManager,
|
|
176
|
-
timeout: options.timeout ?? options.maxDuration ?? this.defaultOptions.maxDuration
|
|
272
|
+
timeout: options.timeout ?? options.maxDuration ?? this.defaultOptions.maxDuration,
|
|
273
|
+
transactionOptions: compactUndefined(transactionOptions)
|
|
177
274
|
});
|
|
178
275
|
const originalEnd = transaction.end.bind(transaction);
|
|
179
276
|
transaction.end = async () => {
|
|
@@ -200,12 +297,12 @@ var TransactionManager = class {
|
|
|
200
297
|
await transaction.start();
|
|
201
298
|
const result = await callback(transaction);
|
|
202
299
|
await transaction.commit();
|
|
203
|
-
this.recordStats(Date.now() - startedAt, true);
|
|
300
|
+
this.recordStats(transaction, Date.now() - startedAt, true);
|
|
204
301
|
return result;
|
|
205
302
|
} catch (error) {
|
|
206
303
|
lastError = error;
|
|
207
304
|
await transaction.abort();
|
|
208
|
-
this.recordStats(Date.now() - startedAt, false);
|
|
305
|
+
this.recordStats(transaction, Date.now() - startedAt, false);
|
|
209
306
|
if (!enableRetry || attempt === maxRetries || !isTransientTransactionError(error)) {
|
|
210
307
|
throw error;
|
|
211
308
|
}
|
|
@@ -241,27 +338,54 @@ var TransactionManager = class {
|
|
|
241
338
|
*/
|
|
242
339
|
getStats() {
|
|
243
340
|
const averageDuration = this.durations.length === 0 ? 0 : this.durations.reduce((sum, item) => sum + item, 0) / this.durations.length;
|
|
341
|
+
const sortedDurations = [...this.durations].sort((a, b) => a - b);
|
|
342
|
+
const p95Duration = percentile(sortedDurations, 0.95);
|
|
343
|
+
const p99Duration = percentile(sortedDurations, 0.99);
|
|
344
|
+
const totalTransactions = this.stats.totalTransactions;
|
|
244
345
|
return {
|
|
245
|
-
totalTransactions
|
|
346
|
+
totalTransactions,
|
|
246
347
|
successfulTransactions: this.stats.successfulTransactions,
|
|
247
348
|
failedTransactions: this.stats.failedTransactions,
|
|
349
|
+
readOnlyTransactions: this.stats.readOnlyTransactions,
|
|
350
|
+
writeTransactions: this.stats.writeTransactions,
|
|
248
351
|
activeTransactions: this.activeTransactions.size,
|
|
249
|
-
averageDuration
|
|
352
|
+
averageDuration,
|
|
353
|
+
p95Duration,
|
|
354
|
+
p99Duration,
|
|
355
|
+
successRate: totalTransactions > 0 ? `${(this.stats.successfulTransactions / totalTransactions * 100).toFixed(2)}%` : "0%",
|
|
356
|
+
readOnlyRatio: totalTransactions > 0 ? `${(this.stats.readOnlyTransactions / totalTransactions * 100).toFixed(2)}%` : "0%",
|
|
357
|
+
sampleCount: this.durations.length
|
|
250
358
|
};
|
|
251
359
|
}
|
|
252
|
-
recordStats(duration, success) {
|
|
360
|
+
recordStats(transaction, duration, success) {
|
|
253
361
|
this.stats.totalTransactions += 1;
|
|
254
362
|
if (success) {
|
|
255
363
|
this.stats.successfulTransactions += 1;
|
|
256
364
|
} else {
|
|
257
365
|
this.stats.failedTransactions += 1;
|
|
258
366
|
}
|
|
367
|
+
if (transaction.pendingInvalidations.size > 0) {
|
|
368
|
+
this.stats.writeTransactions += 1;
|
|
369
|
+
} else {
|
|
370
|
+
this.stats.readOnlyTransactions += 1;
|
|
371
|
+
}
|
|
259
372
|
this.durations.push(duration);
|
|
260
|
-
if (this.durations.length >
|
|
373
|
+
if (this.durations.length > this.maxStatsSamples) {
|
|
261
374
|
this.durations.shift();
|
|
262
375
|
}
|
|
263
376
|
}
|
|
264
377
|
};
|
|
378
|
+
function percentile(sortedValues, ratio) {
|
|
379
|
+
if (sortedValues.length === 0) {
|
|
380
|
+
return 0;
|
|
381
|
+
}
|
|
382
|
+
const index = Math.floor(sortedValues.length * ratio);
|
|
383
|
+
return sortedValues[Math.min(index, sortedValues.length - 1)] ?? 0;
|
|
384
|
+
}
|
|
385
|
+
function compactUndefined(value) {
|
|
386
|
+
const entries = Object.entries(value).filter(([, item]) => item !== void 0);
|
|
387
|
+
return entries.length === 0 ? void 0 : Object.fromEntries(entries);
|
|
388
|
+
}
|
|
265
389
|
function stringifySessionId(id) {
|
|
266
390
|
if (typeof id === "string") {
|
|
267
391
|
return id;
|