mongodb 7.1.0 → 7.2.0
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/README.md +11 -0
- package/lib/bson.js +26 -5
- package/lib/bson.js.map +1 -1
- package/lib/change_stream.js +4 -0
- package/lib/change_stream.js.map +1 -1
- package/lib/client-side-encryption/auto_encrypter.js +19 -10
- package/lib/client-side-encryption/auto_encrypter.js.map +1 -1
- package/lib/client-side-encryption/client_encryption.js +1 -3
- package/lib/client-side-encryption/client_encryption.js.map +1 -1
- package/lib/cmap/auth/aws4.js +4 -4
- package/lib/cmap/auth/aws4.js.map +1 -1
- package/lib/cmap/auth/gssapi.js +3 -6
- package/lib/cmap/auth/gssapi.js.map +1 -1
- package/lib/cmap/auth/mongodb_aws.js +3 -2
- package/lib/cmap/auth/mongodb_aws.js.map +1 -1
- package/lib/cmap/auth/mongodb_oidc/azure_machine_workflow.js +3 -3
- package/lib/cmap/auth/mongodb_oidc/azure_machine_workflow.js.map +1 -1
- package/lib/cmap/auth/mongodb_oidc/gcp_machine_workflow.js +3 -3
- package/lib/cmap/auth/mongodb_oidc/gcp_machine_workflow.js.map +1 -1
- package/lib/cmap/auth/mongodb_oidc/k8s_machine_workflow.js +3 -3
- package/lib/cmap/auth/mongodb_oidc/k8s_machine_workflow.js.map +1 -1
- package/lib/cmap/auth/mongodb_oidc/token_machine_workflow.js +3 -3
- package/lib/cmap/auth/mongodb_oidc/token_machine_workflow.js.map +1 -1
- package/lib/cmap/auth/mongodb_oidc.js +4 -4
- package/lib/cmap/auth/mongodb_oidc.js.map +1 -1
- package/lib/cmap/auth/plain.js +1 -1
- package/lib/cmap/auth/plain.js.map +1 -1
- package/lib/cmap/auth/scram.js +53 -40
- package/lib/cmap/auth/scram.js.map +1 -1
- package/lib/cmap/commands.js +46 -39
- package/lib/cmap/commands.js.map +1 -1
- package/lib/cmap/connect.js +19 -2
- package/lib/cmap/connect.js.map +1 -1
- package/lib/cmap/connection.js +5 -2
- package/lib/cmap/connection.js.map +1 -1
- package/lib/cmap/handshake/client_metadata.js +3 -4
- package/lib/cmap/handshake/client_metadata.js.map +1 -1
- package/lib/cmap/wire_protocol/compression.js +8 -7
- package/lib/cmap/wire_protocol/compression.js.map +1 -1
- package/lib/cmap/wire_protocol/on_data.js.map +1 -1
- package/lib/cmap/wire_protocol/on_demand/document.js +9 -9
- package/lib/cmap/wire_protocol/on_demand/document.js.map +1 -1
- package/lib/connection_string.js +21 -5
- package/lib/connection_string.js.map +1 -1
- package/lib/gridfs/download.js +2 -1
- package/lib/gridfs/download.js.map +1 -1
- package/lib/gridfs/upload.js +7 -7
- package/lib/gridfs/upload.js.map +1 -1
- package/lib/mongo_client.js.map +1 -1
- package/lib/operations/execute_operation.js +114 -41
- package/lib/operations/execute_operation.js.map +1 -1
- package/lib/operations/operation.js +1 -0
- package/lib/operations/operation.js.map +1 -1
- package/lib/runtime_adapters.js +32 -0
- package/lib/runtime_adapters.js.map +1 -0
- package/lib/sdam/srv_polling.js +1 -1
- package/lib/sdam/srv_polling.js.map +1 -1
- package/lib/sdam/topology.js +4 -2
- package/lib/sdam/topology.js.map +1 -1
- package/lib/sessions.js +124 -79
- package/lib/sessions.js.map +1 -1
- package/lib/utils.js +28 -36
- package/lib/utils.js.map +1 -1
- package/mongodb.d.ts +45 -2
- package/package.json +30 -21
- package/src/bson.ts +28 -5
- package/src/change_stream.ts +5 -0
- package/src/client-side-encryption/auto_encrypter.ts +17 -11
- package/src/client-side-encryption/client_encryption.ts +1 -3
- package/src/cmap/auth/auth_provider.ts +1 -1
- package/src/cmap/auth/aws4.ts +5 -5
- package/src/cmap/auth/gssapi.ts +9 -6
- package/src/cmap/auth/mongodb_aws.ts +2 -2
- package/src/cmap/auth/mongodb_oidc/azure_machine_workflow.ts +1 -1
- package/src/cmap/auth/mongodb_oidc/gcp_machine_workflow.ts +1 -1
- package/src/cmap/auth/mongodb_oidc/k8s_machine_workflow.ts +1 -1
- package/src/cmap/auth/mongodb_oidc/token_machine_workflow.ts +1 -1
- package/src/cmap/auth/mongodb_oidc.ts +4 -4
- package/src/cmap/auth/plain.ts +2 -2
- package/src/cmap/auth/scram.ts +82 -55
- package/src/cmap/commands.ts +70 -51
- package/src/cmap/connect.ts +21 -1
- package/src/cmap/connection.ts +11 -4
- package/src/cmap/handshake/client_metadata.ts +6 -6
- package/src/cmap/wire_protocol/compression.ts +18 -14
- package/src/cmap/wire_protocol/on_data.ts +5 -5
- package/src/cmap/wire_protocol/on_demand/document.ts +12 -14
- package/src/connection_string.ts +26 -8
- package/src/deps.ts +4 -4
- package/src/gridfs/download.ts +2 -2
- package/src/gridfs/upload.ts +13 -12
- package/src/index.ts +1 -0
- package/src/mongo_client.ts +24 -0
- package/src/operations/client_bulk_write/command_builder.ts +1 -1
- package/src/operations/execute_operation.ts +146 -45
- package/src/operations/operation.ts +8 -0
- package/src/runtime_adapters.ts +64 -0
- package/src/sdam/srv_polling.ts +1 -1
- package/src/sdam/topology.ts +10 -7
- package/src/sessions.ts +140 -96
- package/src/utils.ts +40 -45
- package/tsconfig.json +1 -1
package/src/sessions.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { setTimeout } from 'timers/promises';
|
|
2
2
|
|
|
3
|
-
import { Binary, type Document, Long, type Timestamp } from './bson';
|
|
3
|
+
import { Binary, ByteUtils, type Document, Long, type Timestamp } from './bson';
|
|
4
4
|
import type { CommandOptions, Connection } from './cmap/connection';
|
|
5
5
|
import { ConnectionPoolMetrics } from './cmap/metrics';
|
|
6
6
|
import { type MongoDBResponse } from './cmap/wire_protocol/responses';
|
|
@@ -17,6 +17,7 @@ import {
|
|
|
17
17
|
MongoErrorLabel,
|
|
18
18
|
MongoExpiredSessionError,
|
|
19
19
|
MongoInvalidArgumentError,
|
|
20
|
+
MongoOperationTimeoutError,
|
|
20
21
|
MongoRuntimeError,
|
|
21
22
|
MongoServerError,
|
|
22
23
|
MongoTransactionError,
|
|
@@ -37,7 +38,6 @@ import {
|
|
|
37
38
|
TxnState
|
|
38
39
|
} from './transactions';
|
|
39
40
|
import {
|
|
40
|
-
ByteUtils,
|
|
41
41
|
calculateDurationInMs,
|
|
42
42
|
commandSupportsReadConcern,
|
|
43
43
|
isPromiseLike,
|
|
@@ -468,7 +468,11 @@ export class ClientSession
|
|
|
468
468
|
} else {
|
|
469
469
|
const wcKeys = Object.keys(wc);
|
|
470
470
|
if (wcKeys.length > 2 || (!wcKeys.includes('wtimeoutMS') && !wcKeys.includes('wTimeoutMS')))
|
|
471
|
-
// if the write concern was specified with wTimeoutMS, then we set both wtimeoutMS
|
|
471
|
+
// if the write concern was specified with wTimeoutMS, then we set both wtimeoutMS
|
|
472
|
+
// and wTimeoutMS, guaranteeing at least two keys, so if we have more than two keys,
|
|
473
|
+
// then we can automatically assume that we should add the write concern to the command.
|
|
474
|
+
// If it has 2 or fewer keys, we need to check that those keys aren't the wtimeoutMS
|
|
475
|
+
// or wTimeoutMS options before we add the write concern to the command
|
|
472
476
|
WriteConcern.apply(command, { ...wc, wtimeoutMS: undefined });
|
|
473
477
|
}
|
|
474
478
|
}
|
|
@@ -494,6 +498,7 @@ export class ClientSession
|
|
|
494
498
|
readPreference: ReadPreference.primary,
|
|
495
499
|
bypassPinningCheck: true
|
|
496
500
|
});
|
|
501
|
+
operation.maxAttempts = this.clientOptions.maxAdaptiveRetries + 1;
|
|
497
502
|
|
|
498
503
|
const timeoutContext =
|
|
499
504
|
this.timeoutContext ??
|
|
@@ -511,6 +516,12 @@ export class ClientSession
|
|
|
511
516
|
return;
|
|
512
517
|
} catch (firstCommitError) {
|
|
513
518
|
this.commitAttempted = true;
|
|
519
|
+
|
|
520
|
+
const remainingAttempts = this.clientOptions.maxAdaptiveRetries + 1 - operation.attemptsMade;
|
|
521
|
+
if (remainingAttempts <= 0) {
|
|
522
|
+
throw firstCommitError;
|
|
523
|
+
}
|
|
524
|
+
|
|
514
525
|
if (firstCommitError instanceof MongoError && isRetryableWriteError(firstCommitError)) {
|
|
515
526
|
// SPEC-1185: apply majority write concern when retrying commitTransaction
|
|
516
527
|
WriteConcern.apply(command, { wtimeoutMS: 10000, ...wc, w: 'majority' });
|
|
@@ -518,15 +529,13 @@ export class ClientSession
|
|
|
518
529
|
this.unpin({ force: true });
|
|
519
530
|
|
|
520
531
|
try {
|
|
521
|
-
|
|
522
|
-
this
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
timeoutContext
|
|
529
|
-
);
|
|
532
|
+
const op = new RunCommandOperation(new MongoDBNamespace('admin'), command, {
|
|
533
|
+
session: this,
|
|
534
|
+
readPreference: ReadPreference.primary,
|
|
535
|
+
bypassPinningCheck: true
|
|
536
|
+
});
|
|
537
|
+
op.maxAttempts = remainingAttempts;
|
|
538
|
+
await executeOperation(this.client, op, timeoutContext);
|
|
530
539
|
return;
|
|
531
540
|
} catch (retryCommitError) {
|
|
532
541
|
// If the retry failed, we process that error instead of the original
|
|
@@ -716,7 +725,7 @@ export class ClientSession
|
|
|
716
725
|
timeoutMS?: number;
|
|
717
726
|
}
|
|
718
727
|
): Promise<T> {
|
|
719
|
-
const MAX_TIMEOUT =
|
|
728
|
+
const MAX_TIMEOUT = 120_000;
|
|
720
729
|
|
|
721
730
|
const timeoutMS = options?.timeoutMS ?? this.timeoutMS ?? null;
|
|
722
731
|
this.timeoutContext =
|
|
@@ -728,10 +737,27 @@ export class ClientSession
|
|
|
728
737
|
})
|
|
729
738
|
: null;
|
|
730
739
|
|
|
731
|
-
// 1.
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
740
|
+
// 1. Define the following:
|
|
741
|
+
// 1.1 Record the current monotonic time, which will be used to enforce the 120-second / CSOT timeout before later retry attempts.
|
|
742
|
+
// 1.2 Set `transactionAttempt` to `0`.
|
|
743
|
+
// 1.3 Set `TIMEOUT_MS` to be `timeoutMS` if given, otherwise MAX_TIMEOUT (120-seconds).
|
|
744
|
+
|
|
745
|
+
// Timeout Error propagation
|
|
746
|
+
// When the previously encountered error needs to be propagated because there is no more time for another attempt,
|
|
747
|
+
// and it is not already a timeout error, then:
|
|
748
|
+
// - A timeout error MUST be propagated instead. It MUST expose the previously encountered error as specified in
|
|
749
|
+
// the "Errors" section of the CSOT specification.
|
|
750
|
+
// - If exposing the previously encountered error from a timeout error is impossible in a driver, then the driver
|
|
751
|
+
// is exempt from the requirement and MUST propagate the previously encountered error as is. The timeout error
|
|
752
|
+
// MUST copy all error labels from the previously encountered error.
|
|
753
|
+
|
|
754
|
+
// The spec describes timeout checks as "elapsed time < TIMEOUT_MS" (where elapsed = now - start).
|
|
755
|
+
// We precompute `deadline = now + remainingTimeMS` so each check becomes simply `now < deadline`.
|
|
756
|
+
const csotEnabled = !!this.timeoutContext?.csotEnabled();
|
|
757
|
+
const remainingTimeMS = this.timeoutContext?.csotEnabled()
|
|
758
|
+
? this.timeoutContext.remainingTimeMS
|
|
759
|
+
: MAX_TIMEOUT;
|
|
760
|
+
const deadline = processTimeMS() + remainingTimeMS;
|
|
735
761
|
|
|
736
762
|
let committed = false;
|
|
737
763
|
let result: T;
|
|
@@ -740,20 +766,20 @@ export class ClientSession
|
|
|
740
766
|
|
|
741
767
|
try {
|
|
742
768
|
retryTransaction: for (
|
|
743
|
-
// 2. Set `transactionAttempt` to `0`.
|
|
744
769
|
let transactionAttempt = 0, isRetry = false;
|
|
745
770
|
!committed;
|
|
746
771
|
++transactionAttempt, isRetry = transactionAttempt > 0
|
|
747
772
|
) {
|
|
748
773
|
// 2. If `transactionAttempt` > 0:
|
|
749
774
|
if (isRetry) {
|
|
750
|
-
// 2.
|
|
751
|
-
//
|
|
752
|
-
//
|
|
753
|
-
//
|
|
754
|
-
//
|
|
755
|
-
//
|
|
756
|
-
//
|
|
775
|
+
// 2.1 Calculate backoffMS to be jitter * min(BACKOFF_INITIAL * 1.5 ** (transactionAttempt - 1), BACKOFF_MAX).
|
|
776
|
+
// If elapsed time + backoffMS > TIMEOUT_MS, then propagate the previously encountered error to the caller of
|
|
777
|
+
// withTransaction as per timeout error propagation and return immediately. Otherwise, sleep for backoffMS.
|
|
778
|
+
// 2.1.1 jitter is a random float between [0, 1), optionally including 1, depending on what is most natural
|
|
779
|
+
// for the given driver language.
|
|
780
|
+
// 2.1.2 transactionAttempt is the variable defined in step 1.
|
|
781
|
+
// 2.1.3 BACKOFF_INITIAL is 5ms
|
|
782
|
+
// 2.1.4 BACKOFF_MAX is 500ms
|
|
757
783
|
const BACKOFF_INITIAL_MS = 5;
|
|
758
784
|
const BACKOFF_MAX_MS = 500;
|
|
759
785
|
const BACKOFF_GROWTH = 1.5;
|
|
@@ -765,30 +791,30 @@ export class ClientSession
|
|
|
765
791
|
BACKOFF_MAX_MS
|
|
766
792
|
);
|
|
767
793
|
|
|
768
|
-
|
|
769
|
-
(
|
|
770
|
-
backoffMS > this.timeoutContext.remainingTimeMS) ||
|
|
771
|
-
processTimeMS() + backoffMS > startTime + MAX_TIMEOUT;
|
|
772
|
-
|
|
773
|
-
if (willExceedTransactionDeadline) {
|
|
774
|
-
throw (
|
|
794
|
+
if (processTimeMS() + backoffMS >= deadline) {
|
|
795
|
+
throw makeTimeoutError(
|
|
775
796
|
lastError ??
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
797
|
+
new MongoRuntimeError(
|
|
798
|
+
`Transaction retry did not record an error: should never occur. Please file a bug.`
|
|
799
|
+
),
|
|
800
|
+
csotEnabled
|
|
779
801
|
);
|
|
780
802
|
}
|
|
781
803
|
|
|
782
804
|
await setTimeout(backoffMS);
|
|
783
805
|
}
|
|
784
806
|
|
|
785
|
-
// 3. Invoke startTransaction on the session
|
|
786
|
-
//
|
|
787
|
-
|
|
807
|
+
// 3. Invoke startTransaction on the session and increment transactionAttempt. If TransactionOptions were
|
|
808
|
+
// specified in the call to withTransaction, those MUST be used for startTransaction. Note that
|
|
809
|
+
// ClientSession.defaultTransactionOptions will be used in the absence of any explicit TransactionOptions.
|
|
810
|
+
// 4. If startTransaction reported an error, propagate that error to the caller of withTransaction as is and
|
|
811
|
+
// return immediately.
|
|
812
|
+
this.startTransaction(options);
|
|
788
813
|
|
|
789
814
|
try {
|
|
790
|
-
// 5. Invoke the callback.
|
|
791
|
-
//
|
|
815
|
+
// 5. Invoke the callback. Drivers MUST ensure that the ClientSession can be accessed within the callback
|
|
816
|
+
// (e.g. pass ClientSession as the first parameter, rely on lexical scoping). Drivers MAY pass additional
|
|
817
|
+
// parameters as needed (e.g. user data solicited by withTransaction).
|
|
792
818
|
const promise = fn(this);
|
|
793
819
|
if (!isPromiseLike(promise)) {
|
|
794
820
|
throw new MongoInvalidArgumentError(
|
|
@@ -796,21 +822,21 @@ export class ClientSession
|
|
|
796
822
|
);
|
|
797
823
|
}
|
|
798
824
|
|
|
825
|
+
// 6. Control returns to withTransaction. Determine the current state of the ClientSession and whether the
|
|
826
|
+
// callback reported an error (e.g. thrown exception, error output parameter).
|
|
799
827
|
result = await promise;
|
|
800
828
|
|
|
801
|
-
//
|
|
829
|
+
// 8. If the ClientSession is in the "no transaction", "transaction aborted", or "transaction committed"
|
|
830
|
+
// state, assume the callback intentionally aborted or committed the transaction and return immediately.
|
|
802
831
|
if (
|
|
803
832
|
this.transaction.state === TxnState.NO_TRANSACTION ||
|
|
804
833
|
this.transaction.state === TxnState.TRANSACTION_COMMITTED ||
|
|
805
834
|
this.transaction.state === TxnState.TRANSACTION_ABORTED
|
|
806
835
|
) {
|
|
807
|
-
// 8. If the ClientSession is in the "no transaction", "transaction aborted", or "transaction committed" state,
|
|
808
|
-
// assume the callback intentionally aborted or committed the transaction and return immediately.
|
|
809
836
|
return result;
|
|
810
837
|
}
|
|
811
|
-
// 5. (cont.) and whether the callback reported an error
|
|
812
|
-
// 7. If the callback reported an error:
|
|
813
838
|
} catch (fnError) {
|
|
839
|
+
// 7. If the callback reported an error
|
|
814
840
|
if (!(fnError instanceof MongoError) || fnError instanceof MongoInvalidArgumentError) {
|
|
815
841
|
// This first preemptive abort regardless of TxnState isn't spec,
|
|
816
842
|
// and it's unclear whether it's serving a practical purpose, but this logic is OLD
|
|
@@ -818,83 +844,70 @@ export class ClientSession
|
|
|
818
844
|
throw fnError;
|
|
819
845
|
}
|
|
820
846
|
|
|
847
|
+
lastError = fnError;
|
|
848
|
+
|
|
849
|
+
// 7.1 If the ClientSession is in the "starting transaction" or "transaction in progress"
|
|
850
|
+
// state, invoke abortTransaction on the session.
|
|
821
851
|
if (
|
|
822
852
|
this.transaction.state === TxnState.STARTING_TRANSACTION ||
|
|
823
853
|
this.transaction.state === TxnState.TRANSACTION_IN_PROGRESS
|
|
824
854
|
) {
|
|
825
|
-
// 7.i If the ClientSession is in the "starting transaction" or "transaction in progress" state,
|
|
826
|
-
// invoke abortTransaction on the session
|
|
827
855
|
await this.abortTransaction();
|
|
828
856
|
}
|
|
829
857
|
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
(
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
// is less than 120 seconds, jump back to step two.
|
|
836
|
-
lastError = fnError;
|
|
858
|
+
// 7.2 If the callback's error includes a "TransientTransactionError" label, jump back to step two.
|
|
859
|
+
if (fnError.hasErrorLabel(MongoErrorLabel.TransientTransactionError)) {
|
|
860
|
+
if (processTimeMS() >= deadline) {
|
|
861
|
+
throw makeTimeoutError(lastError, csotEnabled);
|
|
862
|
+
}
|
|
837
863
|
continue retryTransaction;
|
|
838
864
|
}
|
|
839
865
|
|
|
840
|
-
// 7.
|
|
841
|
-
// propagate the callback's error to the caller of withTransaction
|
|
842
|
-
//
|
|
843
|
-
// 7.
|
|
866
|
+
// 7.3 If the callback's error includes a "UnknownTransactionCommitResult" label, the callback must
|
|
867
|
+
// have manually committed a transaction, propagate the callback's error to the caller of withTransaction
|
|
868
|
+
// as is and return immediately.
|
|
869
|
+
// 7.4 Otherwise, propagate the callback's error to the caller of withTransaction as is and return immediately.
|
|
844
870
|
throw fnError;
|
|
845
871
|
}
|
|
846
872
|
|
|
847
873
|
retryCommit: while (!committed) {
|
|
848
874
|
try {
|
|
849
|
-
/*
|
|
850
|
-
* We will rely on ClientSession.commitTransaction() to
|
|
851
|
-
* apply a majority write concern if commitTransaction is
|
|
852
|
-
* being retried (see: DRIVERS-601)
|
|
853
|
-
*/
|
|
854
875
|
// 9. Invoke commitTransaction on the session.
|
|
855
876
|
await this.commitTransaction();
|
|
856
877
|
committed = true;
|
|
857
|
-
// 10. If commitTransaction reported an error:
|
|
858
878
|
} catch (commitError) {
|
|
859
|
-
//
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
// If
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
* { ok:1, writeConcernError: { code: 50, codeName: 'MaxTimeMSExpired' } }
|
|
873
|
-
*/
|
|
874
|
-
if (
|
|
875
|
-
!isMaxTimeMSExpiredError(commitError) &&
|
|
876
|
-
commitError.hasErrorLabel(MongoErrorLabel.UnknownTransactionCommitResult)
|
|
877
|
-
) {
|
|
878
|
-
// 10.i If the `commitTransaction` error includes a "UnknownTransactionCommitResult" label and the error is not
|
|
879
|
-
// MaxTimeMSExpired and the elapsed time of `withTransaction` is less than 120 seconds, jump back to step eight.
|
|
880
|
-
continue retryCommit;
|
|
879
|
+
// 10. If commitTransaction reported an error:
|
|
880
|
+
lastError = commitError;
|
|
881
|
+
|
|
882
|
+
// 10.1 If the commitTransaction error includes a UnknownTransactionCommitResult label and the error is
|
|
883
|
+
// not MaxTimeMSExpired
|
|
884
|
+
if (
|
|
885
|
+
commitError.hasErrorLabel(MongoErrorLabel.UnknownTransactionCommitResult) &&
|
|
886
|
+
!isMaxTimeMSExpiredError(commitError)
|
|
887
|
+
) {
|
|
888
|
+
// 10.1.1 If the elapsed time of withTransaction exceeded TIMEOUT_MS, propagate the commitTransaction
|
|
889
|
+
// error to the caller of withTransaction as per timeout error propagation and return immediately.
|
|
890
|
+
if (processTimeMS() >= deadline) {
|
|
891
|
+
throw makeTimeoutError(commitError, csotEnabled);
|
|
881
892
|
}
|
|
893
|
+
// 10.1.2 Otherwise, jump back to step nine. We will trust commitTransaction to apply a majority write
|
|
894
|
+
// concern on retry attempts (see: Majority write concern is used when retrying commitTransaction).
|
|
895
|
+
continue retryCommit;
|
|
896
|
+
}
|
|
882
897
|
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
lastError = commitError;
|
|
887
|
-
|
|
888
|
-
continue retryTransaction;
|
|
889
|
-
}
|
|
898
|
+
// 10.2 If the commitTransaction error includes a TransientTransactionError label, jump back to step two.
|
|
899
|
+
if (commitError.hasErrorLabel(MongoErrorLabel.TransientTransactionError)) {
|
|
900
|
+
continue retryTransaction;
|
|
890
901
|
}
|
|
891
902
|
|
|
892
|
-
// 10.
|
|
903
|
+
// 10.3 Otherwise, propagate the commitTransaction error to the caller of withTransaction as is and return
|
|
904
|
+
// immediately.
|
|
893
905
|
throw commitError;
|
|
894
906
|
}
|
|
895
907
|
}
|
|
896
908
|
}
|
|
897
909
|
|
|
910
|
+
// 11. The transaction was committed successfully. Return immediately.
|
|
898
911
|
// @ts-expect-error Result is always defined if we reach here, the for-loop above convinces TS it is not.
|
|
899
912
|
return result;
|
|
900
913
|
} finally {
|
|
@@ -903,6 +916,25 @@ export class ClientSession
|
|
|
903
916
|
}
|
|
904
917
|
}
|
|
905
918
|
|
|
919
|
+
function makeTimeoutError(cause: Error, csotEnabled: boolean): Error {
|
|
920
|
+
// Async APIs know how to cancel themselves and might return CSOT error
|
|
921
|
+
if (cause instanceof MongoOperationTimeoutError) {
|
|
922
|
+
return cause;
|
|
923
|
+
}
|
|
924
|
+
if (csotEnabled) {
|
|
925
|
+
const timeoutError = new MongoOperationTimeoutError('Timed out during withTransaction', {
|
|
926
|
+
cause
|
|
927
|
+
});
|
|
928
|
+
if (cause instanceof MongoError) {
|
|
929
|
+
for (const label of cause.errorLabels) {
|
|
930
|
+
timeoutError.addErrorLabel(label);
|
|
931
|
+
}
|
|
932
|
+
}
|
|
933
|
+
return timeoutError;
|
|
934
|
+
}
|
|
935
|
+
return cause;
|
|
936
|
+
}
|
|
937
|
+
|
|
906
938
|
const NON_DETERMINISTIC_WRITE_CONCERN_ERRORS = new Set([
|
|
907
939
|
'CannotSatisfyWriteConcern',
|
|
908
940
|
'UnknownReplWriteConcern',
|
|
@@ -1018,7 +1050,7 @@ export class ServerSession {
|
|
|
1018
1050
|
/** @internal */
|
|
1019
1051
|
constructor(cloned?: ServerSession | null) {
|
|
1020
1052
|
if (cloned != null) {
|
|
1021
|
-
const idBytes =
|
|
1053
|
+
const idBytes = ByteUtils.allocateUnsafe(16);
|
|
1022
1054
|
idBytes.set(cloned.id.id.buffer);
|
|
1023
1055
|
this.id = { id: new Binary(idBytes, cloned.id.id.sub_type) };
|
|
1024
1056
|
this.lastUse = cloned.lastUse;
|
|
@@ -1203,7 +1235,6 @@ export function applySession(
|
|
|
1203
1235
|
command.autocommit = false;
|
|
1204
1236
|
|
|
1205
1237
|
if (session.transaction.state === TxnState.STARTING_TRANSACTION) {
|
|
1206
|
-
session.transaction.transition(TxnState.TRANSACTION_IN_PROGRESS);
|
|
1207
1238
|
command.startTransaction = true;
|
|
1208
1239
|
|
|
1209
1240
|
const readConcern =
|
|
@@ -1241,4 +1272,17 @@ export function updateSessionFromResponse(session: ClientSession, document: Mong
|
|
|
1241
1272
|
session.snapshotTime = atClusterTime;
|
|
1242
1273
|
}
|
|
1243
1274
|
}
|
|
1275
|
+
|
|
1276
|
+
if (session.transaction.state === TxnState.STARTING_TRANSACTION) {
|
|
1277
|
+
if (document.ok === 1) {
|
|
1278
|
+
session.transaction.transition(TxnState.TRANSACTION_IN_PROGRESS);
|
|
1279
|
+
} else {
|
|
1280
|
+
const error = new MongoServerError(document.toObject());
|
|
1281
|
+
const isRetryableError = error.hasErrorLabel(MongoErrorLabel.RetryableError);
|
|
1282
|
+
|
|
1283
|
+
if (!isRetryableError) {
|
|
1284
|
+
session.transaction.transition(TxnState.TRANSACTION_IN_PROGRESS);
|
|
1285
|
+
}
|
|
1286
|
+
}
|
|
1287
|
+
}
|
|
1244
1288
|
}
|
package/src/utils.ts
CHANGED
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import * as crypto from 'crypto';
|
|
2
1
|
import type { SrvRecord } from 'dns';
|
|
3
2
|
import { type EventEmitter } from 'events';
|
|
4
3
|
import { promises as fs } from 'fs';
|
|
@@ -6,7 +5,14 @@ import * as http from 'http';
|
|
|
6
5
|
import * as process from 'process';
|
|
7
6
|
import { clearTimeout, setTimeout } from 'timers';
|
|
8
7
|
|
|
9
|
-
import {
|
|
8
|
+
import {
|
|
9
|
+
ByteUtils,
|
|
10
|
+
deserialize,
|
|
11
|
+
type Document,
|
|
12
|
+
NumberUtils,
|
|
13
|
+
ObjectId,
|
|
14
|
+
resolveBSONOptions
|
|
15
|
+
} from './bson';
|
|
10
16
|
import type { Connection } from './cmap/connection';
|
|
11
17
|
import { MAX_SUPPORTED_WIRE_VERSION } from './cmap/wire_protocol/constants';
|
|
12
18
|
import type { Collection } from './collection';
|
|
@@ -44,26 +50,6 @@ export type Callback<T = any> = (error?: AnyError, result?: T) => void;
|
|
|
44
50
|
|
|
45
51
|
export type AnyOptions = Document;
|
|
46
52
|
|
|
47
|
-
export const ByteUtils = {
|
|
48
|
-
toLocalBufferType(this: void, buffer: Buffer | Uint8Array): Buffer {
|
|
49
|
-
return Buffer.isBuffer(buffer)
|
|
50
|
-
? buffer
|
|
51
|
-
: Buffer.from(buffer.buffer, buffer.byteOffset, buffer.byteLength);
|
|
52
|
-
},
|
|
53
|
-
|
|
54
|
-
equals(this: void, seqA: Uint8Array, seqB: Uint8Array) {
|
|
55
|
-
return ByteUtils.toLocalBufferType(seqA).equals(seqB);
|
|
56
|
-
},
|
|
57
|
-
|
|
58
|
-
compare(this: void, seqA: Uint8Array, seqB: Uint8Array) {
|
|
59
|
-
return ByteUtils.toLocalBufferType(seqA).compare(seqB);
|
|
60
|
-
},
|
|
61
|
-
|
|
62
|
-
toBase64(this: void, uint8array: Uint8Array) {
|
|
63
|
-
return ByteUtils.toLocalBufferType(uint8array).toString('base64');
|
|
64
|
-
}
|
|
65
|
-
};
|
|
66
|
-
|
|
67
53
|
/**
|
|
68
54
|
* Returns true if value is a Uint8Array or a Buffer
|
|
69
55
|
* @param value - any value that may be a Uint8Array
|
|
@@ -83,13 +69,27 @@ export function isUint8Array(value: unknown): value is Uint8Array {
|
|
|
83
69
|
*/
|
|
84
70
|
export function hostMatchesWildcards(host: string, wildcards: string[]): boolean {
|
|
85
71
|
for (const wildcard of wildcards) {
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
(wildcard.startsWith('*.') && host?.endsWith(wildcard.substring(2, wildcard.length))) ||
|
|
89
|
-
(wildcard.startsWith('*/') && host?.endsWith(wildcard.substring(2, wildcard.length)))
|
|
90
|
-
) {
|
|
72
|
+
// Exact match always wins
|
|
73
|
+
if (host === wildcard) {
|
|
91
74
|
return true;
|
|
92
75
|
}
|
|
76
|
+
|
|
77
|
+
// Wildcard match with leading *.
|
|
78
|
+
if (wildcard.startsWith('*.')) {
|
|
79
|
+
const suffix = wildcard.substring(2);
|
|
80
|
+
// Exact match or strict subdomain match
|
|
81
|
+
if (host === suffix || host.endsWith(`.${suffix}`)) {
|
|
82
|
+
return true;
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
// Wildcard match with leading */
|
|
86
|
+
if (wildcard.startsWith('*/')) {
|
|
87
|
+
const suffix = wildcard.substring(2);
|
|
88
|
+
// Exact match or strict subpath match
|
|
89
|
+
if (host === suffix || host.endsWith(`/${suffix}`)) {
|
|
90
|
+
return true;
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
93
|
}
|
|
94
94
|
return false;
|
|
95
95
|
}
|
|
@@ -318,8 +318,8 @@ export function* makeCounter(seed = 0): Generator<number> {
|
|
|
318
318
|
* Synchronously Generate a UUIDv4
|
|
319
319
|
* @internal
|
|
320
320
|
*/
|
|
321
|
-
export function uuidV4():
|
|
322
|
-
const result = crypto.
|
|
321
|
+
export function uuidV4(): Uint8Array {
|
|
322
|
+
const result = crypto.getRandomValues(new Uint8Array(16));
|
|
323
323
|
result[6] = (result[6] & 0x0f) | 0x40;
|
|
324
324
|
result[8] = (result[8] & 0x3f) | 0x80;
|
|
325
325
|
return result;
|
|
@@ -793,7 +793,7 @@ export class List<T = unknown> {
|
|
|
793
793
|
* @internal
|
|
794
794
|
*/
|
|
795
795
|
export class BufferPool {
|
|
796
|
-
private buffers: List<
|
|
796
|
+
private buffers: List<Uint8Array>;
|
|
797
797
|
private totalByteLength: number;
|
|
798
798
|
|
|
799
799
|
constructor() {
|
|
@@ -806,7 +806,7 @@ export class BufferPool {
|
|
|
806
806
|
}
|
|
807
807
|
|
|
808
808
|
/** Adds a buffer to the internal buffer pool list */
|
|
809
|
-
append(buffer:
|
|
809
|
+
append(buffer: Uint8Array): void {
|
|
810
810
|
this.buffers.push(buffer);
|
|
811
811
|
this.totalByteLength += buffer.length;
|
|
812
812
|
}
|
|
@@ -821,13 +821,13 @@ export class BufferPool {
|
|
|
821
821
|
}
|
|
822
822
|
const firstBuffer = this.buffers.first();
|
|
823
823
|
if (firstBuffer != null && firstBuffer.byteLength >= 4) {
|
|
824
|
-
return
|
|
824
|
+
return NumberUtils.getInt32LE(firstBuffer, 0);
|
|
825
825
|
}
|
|
826
826
|
|
|
827
827
|
// Unlikely case: an int32 is split across buffers.
|
|
828
828
|
// Use read and put the returned buffer back on top
|
|
829
829
|
const top4Bytes = this.read(4);
|
|
830
|
-
const value =
|
|
830
|
+
const value = NumberUtils.getInt32LE(top4Bytes, 0);
|
|
831
831
|
|
|
832
832
|
// Put it back.
|
|
833
833
|
this.totalByteLength += 4;
|
|
@@ -837,19 +837,19 @@ export class BufferPool {
|
|
|
837
837
|
}
|
|
838
838
|
|
|
839
839
|
/** Reads the requested number of bytes, optionally consuming them */
|
|
840
|
-
read(size: number):
|
|
840
|
+
read(size: number): Uint8Array {
|
|
841
841
|
if (typeof size !== 'number' || size < 0) {
|
|
842
842
|
throw new MongoInvalidArgumentError('Argument "size" must be a non-negative number');
|
|
843
843
|
}
|
|
844
844
|
|
|
845
845
|
// oversized request returns empty buffer
|
|
846
846
|
if (size > this.totalByteLength) {
|
|
847
|
-
return
|
|
847
|
+
return ByteUtils.allocate(0);
|
|
848
848
|
}
|
|
849
849
|
|
|
850
850
|
// We know we have enough, we just don't know how it is spread across chunks
|
|
851
851
|
// TODO(NODE-4732): alloc API should change based on raw option
|
|
852
|
-
const result =
|
|
852
|
+
const result = ByteUtils.allocateUnsafe(size);
|
|
853
853
|
|
|
854
854
|
for (let bytesRead = 0; bytesRead < size; ) {
|
|
855
855
|
const buffer = this.buffers.shift();
|
|
@@ -1239,13 +1239,8 @@ export function squashError(_error: unknown) {
|
|
|
1239
1239
|
return;
|
|
1240
1240
|
}
|
|
1241
1241
|
|
|
1242
|
-
export const randomBytes = (size: number) => {
|
|
1243
|
-
return
|
|
1244
|
-
crypto.randomBytes(size, (error: Error | null, buf: Buffer) => {
|
|
1245
|
-
if (error) return reject(error);
|
|
1246
|
-
resolve(buf);
|
|
1247
|
-
});
|
|
1248
|
-
});
|
|
1242
|
+
export const randomBytes = (size: number): Promise<Uint8Array> => {
|
|
1243
|
+
return Promise.resolve(crypto.getRandomValues(new Uint8Array(size)));
|
|
1249
1244
|
};
|
|
1250
1245
|
|
|
1251
1246
|
/**
|
|
@@ -1331,10 +1326,10 @@ export function decorateDecryptionResult(
|
|
|
1331
1326
|
): void {
|
|
1332
1327
|
if (isTopLevelDecorateCall) {
|
|
1333
1328
|
// The original value could have been either a JS object or a BSON buffer
|
|
1334
|
-
if (
|
|
1329
|
+
if (ByteUtils.isUint8Array(original)) {
|
|
1335
1330
|
original = deserialize(original);
|
|
1336
1331
|
}
|
|
1337
|
-
if (
|
|
1332
|
+
if (ByteUtils.isUint8Array(decrypted)) {
|
|
1338
1333
|
throw new MongoRuntimeError('Expected result of decryption to be deserialized BSON object');
|
|
1339
1334
|
}
|
|
1340
1335
|
}
|
package/tsconfig.json
CHANGED