From 242d8f0deb7979305b6440a3e63be338e76e7573 Mon Sep 17 00:00:00 2001
From: Daniel Bruce <djbruce@google.com>
Date: Wed, 13 Sep 2023 10:50:33 -0400
Subject: [PATCH 001/129] Add a unit test for commit as a non-tx

The fact that using a transaction object to do a commit results in a non-transaction should be documented so that if we decide to introduce a change later where this behaves differently then it is well documented.

# Conflicts:
#	test/transaction.ts
---
 test/transaction.ts | 87 ++++++++++++++++++++++++++++++++++++++++++---
 1 file changed, 83 insertions(+), 4 deletions(-)

diff --git a/test/transaction.ts b/test/transaction.ts
index 06faf3421..889fdaf32 100644
--- a/test/transaction.ts
+++ b/test/transaction.ts
@@ -21,6 +21,7 @@ import * as proxyquire from 'proxyquire';
 import {
   Datastore,
   DatastoreOptions,
+  DatastoreClient,
   DatastoreRequest,
   Query,
   TransactionOptions,
@@ -28,6 +29,7 @@ import {
 import {Entity} from '../src/entity';
 import * as tsTypes from '../src/transaction';
 import * as sinon from 'sinon';
+import {ClientStub} from 'google-gax';
 import {RequestConfig} from '../src/request';
 import {SECOND_DATABASE_ID} from './index';
 const async = require('async');
@@ -147,10 +149,87 @@ async.each(
         });
       });
 
-      describe('commit', () => {
-        beforeEach(() => {
-          transaction.id = TRANSACTION_ID;
-        });
+  describe('commit without setting up transaction id', () => {
+    const namespace = 'commit-without-mock';
+    const projectId = 'project-id';
+    const options = {
+      projectId,
+      namespace,
+    };
+    const datastore = new Datastore(options);
+    const transactionWithoutMock = datastore.transaction();
+    const dataClientName = 'DatastoreClient';
+    let dataClient: ClientStub | undefined;
+    let originalCommitMethod: Function;
+
+    beforeEach(async () => {
+      const gapic = Object.freeze({
+        v1: require('../src/v1'),
+      });
+      // Datastore Gapic clients haven't been initialized yet so we initialize them here.
+      datastore.clients_.set(
+        dataClientName,
+        new gapic.v1[dataClientName](options)
+      );
+      dataClient = datastore.clients_.get(dataClientName);
+      if (dataClient && dataClient.commit) {
+        originalCommitMethod = dataClient.commit;
+      }
+    });
+
+    afterEach(() => {
+      // Commit has likely been mocked out in these tests.
+      // We should reassign commit back to its original value for the rest of the tests.
+      if (dataClient && originalCommitMethod) {
+        dataClient.commit = originalCommitMethod;
+      }
+    });
+
+    it('should execute as a non-transaction', async () => {
+      const kind = 'Product';
+      const id = 123;
+      const url = 'www.google.com.sample';
+      const path = [kind, id];
+      const obj = {url};
+      const localKey = datastore.key(path);
+      // Mock out commit and show value that commit receives
+      if (dataClient) {
+        dataClient.commit = (
+          request: any,
+          options: any,
+          callback: (err?: unknown) => void
+        ) => {
+          try {
+            assert.deepStrictEqual(request, {
+              projectId,
+              mode: 'NON_TRANSACTIONAL', // Even though a transaction object is used, this runs as a non-transaction.
+              mutations: [
+                {
+                  upsert: {
+                    key: {
+                      path: [{kind, id}],
+                      partitionId: {namespaceId: namespace},
+                    },
+                    properties: {url: {stringValue: url}},
+                  },
+                },
+              ],
+            });
+          } catch (e) {
+            callback(e);
+          }
+          callback();
+        };
+      }
+      transactionWithoutMock.save({key: localKey, data: obj});
+      await transactionWithoutMock.commit();
+    });
+  });
+
+  describe('commit', () => {
+    beforeEach(() => {
+      transaction.id = TRANSACTION_ID;
+    });
 
         afterEach(() => {
           sinon.restore();

From 8a7b0eaca2f6ef6c51a58ea8fa2d84ff390d0ee0 Mon Sep 17 00:00:00 2001
From: Daniel Bruce <djbruce@google.com>
Date: Wed, 1 Nov 2023 10:18:58 -0400
Subject: [PATCH 002/129] use linter

---
 test/transaction.ts | 152 ++++++++++++++++++++++----------------------
 1 file changed, 76 insertions(+), 76 deletions(-)

diff --git a/test/transaction.ts b/test/transaction.ts
index 889fdaf32..895510822 100644
--- a/test/transaction.ts
+++ b/test/transaction.ts
@@ -149,87 +149,87 @@ async.each(
         });
       });
 
-  describe('commit without setting up transaction id', () => {
-    const namespace = 'commit-without-mock';
-    const projectId = 'project-id';
-    const options = {
-      projectId,
-      namespace,
-    };
-    const datastore = new Datastore(options);
-    const transactionWithoutMock = datastore.transaction();
-    const dataClientName = 'DatastoreClient';
-    let dataClient: ClientStub | undefined;
-    let originalCommitMethod: Function;
-
-    beforeEach(async () => {
-      const gapic = Object.freeze({
-        v1: require('../src/v1'),
-      });
-      // Datastore Gapic clients haven't been initialized yet so we initialize them here.
-      datastore.clients_.set(
-        dataClientName,
-        new gapic.v1[dataClientName](options)
-      );
-      dataClient = datastore.clients_.get(dataClientName);
-      if (dataClient && dataClient.commit) {
-        originalCommitMethod = dataClient.commit;
-      }
-    });
+      describe('commit without setting up transaction id', () => {
+        const namespace = 'commit-without-mock';
+        const projectId = 'project-id';
+        const options = {
+          projectId,
+          namespace,
+        };
+        const datastore = new Datastore(options);
+        const transactionWithoutMock = datastore.transaction();
+        const dataClientName = 'DatastoreClient';
+        let dataClient: ClientStub | undefined;
+        let originalCommitMethod: Function;
+
+        beforeEach(async () => {
+          const gapic = Object.freeze({
+            v1: require('../src/v1'),
+          });
+          // Datastore Gapic clients haven't been initialized yet so we initialize them here.
+          datastore.clients_.set(
+            dataClientName,
+            new gapic.v1[dataClientName](options)
+          );
+          dataClient = datastore.clients_.get(dataClientName);
+          if (dataClient && dataClient.commit) {
+            originalCommitMethod = dataClient.commit;
+          }
+        });
 
-    afterEach(() => {
-      // Commit has likely been mocked out in these tests.
-      // We should reassign commit back to its original value for the rest of the tests.
-      if (dataClient && originalCommitMethod) {
-        dataClient.commit = originalCommitMethod;
-      }
-    });
+        afterEach(() => {
+          // Commit has likely been mocked out in these tests.
+          // We should reassign commit back to its original value for the rest of the tests.
+          if (dataClient && originalCommitMethod) {
+            dataClient.commit = originalCommitMethod;
+          }
+        });
 
-    it('should execute as a non-transaction', async () => {
-      const kind = 'Product';
-      const id = 123;
-      const url = 'www.google.com.sample';
-      const path = [kind, id];
-      const obj = {url};
-      const localKey = datastore.key(path);
-      // Mock out commit and show value that commit receives
-      if (dataClient) {
-        dataClient.commit = (
-          request: any,
-          options: any,
-          callback: (err?: unknown) => void
-        ) => {
-          try {
-            assert.deepStrictEqual(request, {
-              projectId,
-              mode: 'NON_TRANSACTIONAL', // Even though a transaction object is used, this runs as a non-transaction.
-              mutations: [
-                {
-                  upsert: {
-                    key: {
-                      path: [{kind, id}],
-                      partitionId: {namespaceId: namespace},
+        it('should execute as a non-transaction', async () => {
+          const kind = 'Product';
+          const id = 123;
+          const url = 'www.google.com.sample';
+          const path = [kind, id];
+          const obj = {url};
+          const localKey = datastore.key(path);
+          // Mock out commit and show value that commit receives
+          if (dataClient) {
+            dataClient.commit = (
+              request: any,
+              options: any,
+              callback: (err?: unknown) => void
+            ) => {
+              try {
+                assert.deepStrictEqual(request, {
+                  projectId,
+                  mode: 'NON_TRANSACTIONAL', // Even though a transaction object is used, this runs as a non-transaction.
+                  mutations: [
+                    {
+                      upsert: {
+                        key: {
+                          path: [{kind, id}],
+                          partitionId: {namespaceId: namespace},
+                        },
+                        properties: {url: {stringValue: url}},
+                      },
                     },
-                    properties: {url: {stringValue: url}},
-                  },
-                },
-              ],
-            });
-          } catch (e) {
-            callback(e);
+                  ],
+                });
+              } catch (e) {
+                callback(e);
+              }
+              callback();
+            };
           }
-          callback();
-        };
-      }
-      transactionWithoutMock.save({key: localKey, data: obj});
-      await transactionWithoutMock.commit();
-    });
-  });
+          transactionWithoutMock.save({key: localKey, data: obj});
+          await transactionWithoutMock.commit();
+        });
+      });
 
-  describe('commit', () => {
-    beforeEach(() => {
-      transaction.id = TRANSACTION_ID;
-    });
+      describe('commit', () => {
+        beforeEach(() => {
+          transaction.id = TRANSACTION_ID;
+        });
 
         afterEach(() => {
           sinon.restore();

From d0cd928819cfd363e1230776105765923464e5ca Mon Sep 17 00:00:00 2001
From: Daniel Bruce <djbruce@google.com>
Date: Wed, 1 Nov 2023 12:00:44 -0400
Subject: [PATCH 003/129] Write tests to capture current behavior of error

When begin transaction sends back an error, we want some tests to capture what the behavior is so that when we make changes to the run function then behavior is preserved.
---
 test/transaction.ts | 84 ++++++++++++++++++++++++++++++++++++++++++++-
 1 file changed, 83 insertions(+), 1 deletion(-)

diff --git a/test/transaction.ts b/test/transaction.ts
index 895510822..1818fa5d1 100644
--- a/test/transaction.ts
+++ b/test/transaction.ts
@@ -24,7 +24,7 @@ import {
   DatastoreClient,
   DatastoreRequest,
   Query,
-  TransactionOptions,
+  TransactionOptions, Transaction,
 } from '../src';
 import {Entity} from '../src/entity';
 import * as tsTypes from '../src/transaction';
@@ -32,6 +32,8 @@ import * as sinon from 'sinon';
 import {ClientStub} from 'google-gax';
 import {RequestConfig} from '../src/request';
 import {SECOND_DATABASE_ID} from './index';
+import {google} from '../protos/protos';
+import {RunCallback} from '../src/transaction';
 const async = require('async');
 
 // eslint-disable-next-line @typescript-eslint/no-explicit-any
@@ -226,6 +228,86 @@ async.each(
         });
       });
 
+      describe.only('run without setting up transaction id', () => {
+        const testResp = {transaction: 'some-response'};
+        const namespace = 'run-without-mock';
+        const projectId = 'project-id';
+        const testErrorMessage = 'test-error';
+        const options = {
+          projectId,
+          namespace,
+        };
+        const datastore = new Datastore(options);
+        const transactionWithoutMock = datastore.transaction();
+        const dataClientName = 'DatastoreClient';
+        let dataClient: ClientStub | undefined;
+        let originalBeginTransactionMethod: Function;
+
+        beforeEach(async () => {
+          const gapic = Object.freeze({
+            v1: require('../src/v1'),
+          });
+          // Datastore Gapic clients haven't been initialized yet so we initialize them here.
+          datastore.clients_.set(
+            dataClientName,
+            new gapic.v1[dataClientName](options)
+          );
+          dataClient = datastore.clients_.get(dataClientName);
+          if (dataClient && dataClient.beginTransaction) {
+            originalBeginTransactionMethod = dataClient.beginTransaction;
+          }
+        });
+
+        afterEach(() => {
+          // Commit has likely been mocked out in these tests.
+          // We should reassign commit back to its original value for the rest of the tests.
+          if (dataClient && originalBeginTransactionMethod) {
+            dataClient.beginTransaction = originalBeginTransactionMethod;
+          }
+        });
+
+        // Cases: send error back - with callback, with promise
+        // Case: send run response back
+        describe('should pass error back to the user', async () => {
+          beforeEach(() => {
+            // Mock out begin transaction and show value that begin transaction receives
+            if (dataClient) {
+              dataClient.beginTransaction = (
+                request: any,
+                options: any,
+                callback: (err: unknown, resp: any) => void
+              ) => {
+                callback(new Error(testErrorMessage), testResp);
+              };
+            }
+          });
+
+          it('should send back the error when awaiting a promise', async () => {
+            try {
+              await transactionWithoutMock.run();
+              assert.fail('The run call should have failed.');
+            } catch (error: any) {
+              // TODO: Substitute type any
+              assert.strictEqual(error['message'], testErrorMessage);
+            }
+          });
+          it('should send back the error when using a callback', done => {
+            const runCallback: RunCallback = (
+              error: Error | null,
+              transaction: Transaction | null,
+              response?: google.datastore.v1.IBeginTransactionResponse
+            ) => {
+              assert(error);
+              assert.strictEqual(error.message, testErrorMessage);
+              assert.strictEqual(transaction, null);
+              assert.strictEqual(response, testResp);
+              done();
+            };
+            transactionWithoutMock.run({}, runCallback);
+          });
+        });
+      });
+
       describe('commit', () => {
         beforeEach(() => {
           transaction.id = TRANSACTION_ID;

From ed5a301665280395bb6224c70373e56af53e1180 Mon Sep 17 00:00:00 2001
From: Daniel Bruce <djbruce@google.com>
Date: Wed, 1 Nov 2023 13:36:18 -0400
Subject: [PATCH 004/129] Add tests for passing a response

A response should reach the user the right way. Add tests to make sure behavior is preserved.
---
 test/transaction.ts | 40 ++++++++++++++++++++++++++++++++++++++--
 1 file changed, 38 insertions(+), 2 deletions(-)

diff --git a/test/transaction.ts b/test/transaction.ts
index 1818fa5d1..0bec46a7c 100644
--- a/test/transaction.ts
+++ b/test/transaction.ts
@@ -228,8 +228,12 @@ async.each(
         });
       });
 
-      describe.only('run without setting up transaction id', () => {
-        const testResp = {transaction: 'some-response'};
+      describe('run without setting up transaction id', () => {
+        // These tests were created so that when transaction.run is restructured we
+        // can be confident that it works the same way as before.
+        const testResp = {
+          transaction: Buffer.from(Array.from(Array(100).keys())),
+        };
         const namespace = 'run-without-mock';
         const projectId = 'project-id';
         const testErrorMessage = 'test-error';
@@ -306,6 +310,38 @@ async.each(
             transactionWithoutMock.run({}, runCallback);
           });
         });
+        describe('should pass response back to the user', async () => {
+          beforeEach(() => {
+            // Mock out begin transaction and show value that begin transaction receives
+            if (dataClient) {
+              dataClient.beginTransaction = (
+                request: any,
+                options: any,
+                callback: (err: unknown, resp: any) => void
+              ) => {
+                callback(null, testResp);
+              };
+            }
+          });
+          it('should send back the response when awaiting a promise', async () => {
+            const [transaction, resp] = await transactionWithoutMock.run();
+            assert.strictEqual(transaction, transactionWithoutMock);
+            assert.strictEqual(resp, testResp);
+          });
+          it('should send back the response when using a callback', done => {
+            const runCallback: RunCallback = (
+              error: Error | null,
+              transaction: Transaction | null,
+              response?: google.datastore.v1.IBeginTransactionResponse
+            ) => {
+              assert.strictEqual(error, null);
+              assert.strictEqual(response, testResp);
+              assert.strictEqual(transaction, transactionWithoutMock);
+              done();
+            };
+            transactionWithoutMock.run({}, runCallback);
+          });
+        });
       });
 
       describe('commit', () => {

From b3b13b41e1044c61f5c6b7142ac0fb86b5e92af1 Mon Sep 17 00:00:00 2001
From: Daniel Bruce <djbruce@google.com>
Date: Wed, 1 Nov 2023 14:46:57 -0400
Subject: [PATCH 005/129] run async close to working

In the run function delegate calls to runAsync and use run async to make promise calls
---
 src/request.ts      |  1 +
 src/transaction.ts  | 59 +++++++++++++++++++++++++++++++++------------
 test/transaction.ts |  5 ++--
 3 files changed, 48 insertions(+), 17 deletions(-)

diff --git a/src/request.ts b/src/request.ts
index d39f572ef..4c75335ba 100644
--- a/src/request.ts
+++ b/src/request.ts
@@ -1144,6 +1144,7 @@ export interface RequestCallback {
     b?: any
   ): void;
 }
+
 export interface RequestConfig {
   client: string;
   gaxOpts?: CallOptions;
diff --git a/src/transaction.ts b/src/transaction.ts
index 9bc760dd3..20ee69850 100644
--- a/src/transaction.ts
+++ b/src/transaction.ts
@@ -32,6 +32,15 @@ import {
 } from './request';
 import {AggregateQuery} from './aggregate';
 
+// RequestPromiseReturnType should line up with the types in RequestCallback
+interface RequestPromiseReturnType {
+  err?: Error | null;
+  resp: any;
+}
+interface RequestAsPromise {
+  (resolve: RequestPromiseReturnType): void;
+}
+
 /**
  * A transaction is a set of Datastore operations on one or more entities. Each
  * transaction is guaranteed to be atomic, which means that transactions are
@@ -544,7 +553,20 @@ class Transaction extends DatastoreRequest {
       typeof optionsOrCallback === 'object' ? optionsOrCallback : {};
     const callback =
       typeof optionsOrCallback === 'function' ? optionsOrCallback : cb!;
+    // eslint-disable-next-line @typescript-eslint/no-this-alias
+    this.runAsync(options).then((response: any) => {
+      const err = response.err;
+      const resp = response.resp;
+      if (err) {
+        callback(err, null, resp);
+        return;
+      }
+      this.id = resp!.transaction;
+      callback(null, this, resp);
+    });
+  }
 
+  private async runAsync(options: RunOptions) {
     const reqOpts = {
       transactionOptions: {},
     } as RequestOptions;
@@ -563,22 +585,28 @@ class Transaction extends DatastoreRequest {
       reqOpts.transactionOptions = options.transactionOptions;
     }
 
-    this.request_(
-      {
-        client: 'DatastoreClient',
-        method: 'beginTransaction',
-        reqOpts,
-        gaxOpts: options.gaxOptions,
-      },
-      (err, resp) => {
-        if (err) {
-          callback(err, null, resp);
-          return;
+    // TODO: Change this later to remove self
+    // eslint-disable-next-line @typescript-eslint/no-this-alias
+    const self = this;
+    const promiseFunction = (resolve: any) => {
+      self.request_(
+        {
+          client: 'DatastoreClient',
+          method: 'beginTransaction',
+          reqOpts,
+          gaxOpts: options.gaxOptions,
+        },
+        // In original functionality sometimes a response is provided when an error is also provided
+        // reject only allows us to pass back an error so use resolve for both error and non-error cases.
+        (err, resp) => {
+          resolve({
+            err,
+            resp,
+          });
         }
-        this.id = resp!.transaction;
-        callback(null, this, resp);
-      }
-    );
+      );
+    };
+    return new Promise(promiseFunction);
   }
 
   /**
@@ -810,6 +838,7 @@ promisifyAll(Transaction, {
     'createQuery',
     'delete',
     'insert',
+    'runAsync',
     'save',
     'update',
     'upsert',
diff --git a/test/transaction.ts b/test/transaction.ts
index 0bec46a7c..bb965dad0 100644
--- a/test/transaction.ts
+++ b/test/transaction.ts
@@ -24,7 +24,8 @@ import {
   DatastoreClient,
   DatastoreRequest,
   Query,
-  TransactionOptions, Transaction,
+  TransactionOptions,
+  Transaction,
 } from '../src';
 import {Entity} from '../src/entity';
 import * as tsTypes from '../src/transaction';
@@ -228,7 +229,7 @@ async.each(
         });
       });
 
-      describe('run without setting up transaction id', () => {
+      describe.only('run without setting up transaction id', () => {
         // These tests were created so that when transaction.run is restructured we
         // can be confident that it works the same way as before.
         const testResp = {

From 85ec536f89497da5539678f3e0d096ad7c70b46a Mon Sep 17 00:00:00 2001
From: Daniel Bruce <djbruce@google.com>
Date: Wed, 1 Nov 2023 14:53:46 -0400
Subject: [PATCH 006/129] Add runAsync to promise excludes

This allows this function to return a promise instead of a promise wrapped in a promise. This makes the tests pass and behave the way they should.
---
 test/transaction.ts | 3 ++-
 1 file changed, 2 insertions(+), 1 deletion(-)

diff --git a/test/transaction.ts b/test/transaction.ts
index bb965dad0..a58fd41b7 100644
--- a/test/transaction.ts
+++ b/test/transaction.ts
@@ -56,6 +56,7 @@ const fakePfy = Object.assign({}, pfy, {
       'createQuery',
       'delete',
       'insert',
+      'runAsync',
       'save',
       'update',
       'upsert',
@@ -229,7 +230,7 @@ async.each(
         });
       });
 
-      describe.only('run without setting up transaction id', () => {
+      describe('run without setting up transaction id', () => {
         // These tests were created so that when transaction.run is restructured we
         // can be confident that it works the same way as before.
         const testResp = {

From d26ebd3d9e2114f847dbc984baadd1e1f0bea822 Mon Sep 17 00:00:00 2001
From: Daniel Bruce <djbruce@google.com>
Date: Wed, 1 Nov 2023 15:14:00 -0400
Subject: [PATCH 007/129] Remove space

---
 src/request.ts | 1 -
 1 file changed, 1 deletion(-)

diff --git a/src/request.ts b/src/request.ts
index 4c75335ba..d39f572ef 100644
--- a/src/request.ts
+++ b/src/request.ts
@@ -1144,7 +1144,6 @@ export interface RequestCallback {
     b?: any
   ): void;
 }
-
 export interface RequestConfig {
   client: string;
   gaxOpts?: CallOptions;

From 2d13f56556ef90d6dd1647ae025325051fab5f7f Mon Sep 17 00:00:00 2001
From: Daniel Bruce <djbruce@google.com>
Date: Wed, 1 Nov 2023 15:16:50 -0400
Subject: [PATCH 008/129] Change to use this instead of self

Do not call request from self
---
 src/transaction.ts | 6 +-----
 1 file changed, 1 insertion(+), 5 deletions(-)

diff --git a/src/transaction.ts b/src/transaction.ts
index 20ee69850..733f42c6d 100644
--- a/src/transaction.ts
+++ b/src/transaction.ts
@@ -584,12 +584,8 @@ class Transaction extends DatastoreRequest {
     if (options.transactionOptions) {
       reqOpts.transactionOptions = options.transactionOptions;
     }
-
-    // TODO: Change this later to remove self
-    // eslint-disable-next-line @typescript-eslint/no-this-alias
-    const self = this;
     const promiseFunction = (resolve: any) => {
-      self.request_(
+      this.request_(
         {
           client: 'DatastoreClient',
           method: 'beginTransaction',

From 3f66406553aaaff7730b77784b5f182509e5e360 Mon Sep 17 00:00:00 2001
From: Daniel Bruce <djbruce@google.com>
Date: Wed, 1 Nov 2023 15:18:37 -0400
Subject: [PATCH 009/129] Eliminate unused comments

---
 test/transaction.ts | 4 +---
 1 file changed, 1 insertion(+), 3 deletions(-)

diff --git a/test/transaction.ts b/test/transaction.ts
index a58fd41b7..254980c6b 100644
--- a/test/transaction.ts
+++ b/test/transaction.ts
@@ -271,9 +271,7 @@ async.each(
             dataClient.beginTransaction = originalBeginTransactionMethod;
           }
         });
-
-        // Cases: send error back - with callback, with promise
-        // Case: send run response back
+        
         describe('should pass error back to the user', async () => {
           beforeEach(() => {
             // Mock out begin transaction and show value that begin transaction receives

From ff76a6dd00c68d4606b9f17fe676607a6de6290d Mon Sep 17 00:00:00 2001
From: Daniel Bruce <djbruce@google.com>
Date: Wed, 1 Nov 2023 15:24:34 -0400
Subject: [PATCH 010/129] Add two comments

Comments should actually explain what is being done
---
 test/transaction.ts | 8 +++++---
 1 file changed, 5 insertions(+), 3 deletions(-)

diff --git a/test/transaction.ts b/test/transaction.ts
index 254980c6b..8f2124032 100644
--- a/test/transaction.ts
+++ b/test/transaction.ts
@@ -271,10 +271,11 @@ async.each(
             dataClient.beginTransaction = originalBeginTransactionMethod;
           }
         });
-        
+
         describe('should pass error back to the user', async () => {
           beforeEach(() => {
-            // Mock out begin transaction and show value that begin transaction receives
+            // Mock out begin transaction and send error back to the user
+            // from the Gapic layer.
             if (dataClient) {
               dataClient.beginTransaction = (
                 request: any,
@@ -312,7 +313,8 @@ async.each(
         });
         describe('should pass response back to the user', async () => {
           beforeEach(() => {
-            // Mock out begin transaction and show value that begin transaction receives
+            // Mock out begin transaction and send a response
+            // back to the user from the Gapic layer.
             if (dataClient) {
               dataClient.beginTransaction = (
                 request: any,

From d7806bddd7fef217bbe7a49cde86c77a9aae6c7f Mon Sep 17 00:00:00 2001
From: Daniel Bruce <djbruce@google.com>
Date: Wed, 1 Nov 2023 15:27:42 -0400
Subject: [PATCH 011/129] Remove the commit test for this PR

The commit test for this PR should be removed because it is not really relevant for the async run functionality.
---
 test/transaction.ts | 77 ---------------------------------------------
 1 file changed, 77 deletions(-)

diff --git a/test/transaction.ts b/test/transaction.ts
index 8f2124032..c0581c9ca 100644
--- a/test/transaction.ts
+++ b/test/transaction.ts
@@ -153,83 +153,6 @@ async.each(
         });
       });
 
-      describe('commit without setting up transaction id', () => {
-        const namespace = 'commit-without-mock';
-        const projectId = 'project-id';
-        const options = {
-          projectId,
-          namespace,
-        };
-        const datastore = new Datastore(options);
-        const transactionWithoutMock = datastore.transaction();
-        const dataClientName = 'DatastoreClient';
-        let dataClient: ClientStub | undefined;
-        let originalCommitMethod: Function;
-
-        beforeEach(async () => {
-          const gapic = Object.freeze({
-            v1: require('../src/v1'),
-          });
-          // Datastore Gapic clients haven't been initialized yet so we initialize them here.
-          datastore.clients_.set(
-            dataClientName,
-            new gapic.v1[dataClientName](options)
-          );
-          dataClient = datastore.clients_.get(dataClientName);
-          if (dataClient && dataClient.commit) {
-            originalCommitMethod = dataClient.commit;
-          }
-        });
-
-        afterEach(() => {
-          // Commit has likely been mocked out in these tests.
-          // We should reassign commit back to its original value for the rest of the tests.
-          if (dataClient && originalCommitMethod) {
-            dataClient.commit = originalCommitMethod;
-          }
-        });
-
-        it('should execute as a non-transaction', async () => {
-          const kind = 'Product';
-          const id = 123;
-          const url = 'www.google.com.sample';
-          const path = [kind, id];
-          const obj = {url};
-          const localKey = datastore.key(path);
-          // Mock out commit and show value that commit receives
-          if (dataClient) {
-            dataClient.commit = (
-              request: any,
-              options: any,
-              callback: (err?: unknown) => void
-            ) => {
-              try {
-                assert.deepStrictEqual(request, {
-                  projectId,
-                  mode: 'NON_TRANSACTIONAL', // Even though a transaction object is used, this runs as a non-transaction.
-                  mutations: [
-                    {
-                      upsert: {
-                        key: {
-                          path: [{kind, id}],
-                          partitionId: {namespaceId: namespace},
-                        },
-                        properties: {url: {stringValue: url}},
-                      },
-                    },
-                  ],
-                });
-              } catch (e) {
-                callback(e);
-              }
-              callback();
-            };
-          }
-          transactionWithoutMock.save({key: localKey, data: obj});
-          await transactionWithoutMock.commit();
-        });
-      });
-
       describe('run without setting up transaction id', () => {
         // These tests were created so that when transaction.run is restructured we
         // can be confident that it works the same way as before.

From 01d78377d9fd0b6590ce47f0efb6faee8a4cf6af Mon Sep 17 00:00:00 2001
From: Daniel Bruce <djbruce@google.com>
Date: Wed, 1 Nov 2023 15:46:04 -0400
Subject: [PATCH 012/129] Clarify types throughout the function
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

The types used should be very specific so that reading the code isn’t confusing.
---
 src/transaction.ts | 13 +++++++++----
 1 file changed, 9 insertions(+), 4 deletions(-)

diff --git a/src/transaction.ts b/src/transaction.ts
index 733f42c6d..e548b8332 100644
--- a/src/transaction.ts
+++ b/src/transaction.ts
@@ -37,8 +37,11 @@ interface RequestPromiseReturnType {
   err?: Error | null;
   resp: any;
 }
+interface RequestResolveFunction {
+  (callbackData: RequestPromiseReturnType): void;
+}
 interface RequestAsPromise {
-  (resolve: RequestPromiseReturnType): void;
+  (resolve: RequestResolveFunction): void;
 }
 
 /**
@@ -554,7 +557,7 @@ class Transaction extends DatastoreRequest {
     const callback =
       typeof optionsOrCallback === 'function' ? optionsOrCallback : cb!;
     // eslint-disable-next-line @typescript-eslint/no-this-alias
-    this.runAsync(options).then((response: any) => {
+    this.runAsync(options).then((response: RequestPromiseReturnType) => {
       const err = response.err;
       const resp = response.resp;
       if (err) {
@@ -566,7 +569,9 @@ class Transaction extends DatastoreRequest {
     });
   }
 
-  private async runAsync(options: RunOptions) {
+  private async runAsync(
+    options: RunOptions
+  ): Promise<RequestPromiseReturnType> {
     const reqOpts = {
       transactionOptions: {},
     } as RequestOptions;
@@ -584,7 +589,7 @@ class Transaction extends DatastoreRequest {
     if (options.transactionOptions) {
       reqOpts.transactionOptions = options.transactionOptions;
     }
-    const promiseFunction = (resolve: any) => {
+    const promiseFunction: RequestAsPromise = resolve => {
       this.request_(
         {
           client: 'DatastoreClient',

From dff31833a83ea1d0e9470e4aaf195eb758a2e0f7 Mon Sep 17 00:00:00 2001
From: Daniel Bruce <djbruce@google.com>
Date: Wed, 1 Nov 2023 15:59:03 -0400
Subject: [PATCH 013/129] Add a bit more typing for clarity

Add a type to the resolve function just to introduce more clarity
---
 src/transaction.ts | 10 ++++++----
 1 file changed, 6 insertions(+), 4 deletions(-)

diff --git a/src/transaction.ts b/src/transaction.ts
index e548b8332..2db87cc0d 100644
--- a/src/transaction.ts
+++ b/src/transaction.ts
@@ -35,7 +35,7 @@ import {AggregateQuery} from './aggregate';
 // RequestPromiseReturnType should line up with the types in RequestCallback
 interface RequestPromiseReturnType {
   err?: Error | null;
-  resp: any;
+  resp: any; // TODO: Replace with google.datastore.v1.IBeginTransactionResponse and address downstream issues
 }
 interface RequestResolveFunction {
   (callbackData: RequestPromiseReturnType): void;
@@ -572,9 +572,9 @@ class Transaction extends DatastoreRequest {
   private async runAsync(
     options: RunOptions
   ): Promise<RequestPromiseReturnType> {
-    const reqOpts = {
+    const reqOpts: RequestOptions = {
       transactionOptions: {},
-    } as RequestOptions;
+    };
 
     if (options.readOnly || this.readOnly) {
       reqOpts.transactionOptions!.readOnly = {};
@@ -589,7 +589,9 @@ class Transaction extends DatastoreRequest {
     if (options.transactionOptions) {
       reqOpts.transactionOptions = options.transactionOptions;
     }
-    const promiseFunction: RequestAsPromise = resolve => {
+    const promiseFunction: RequestAsPromise = (
+      resolve: RequestResolveFunction
+    ) => {
       this.request_(
         {
           client: 'DatastoreClient',

From f4e05bdabb52d21b6c8da18561b0914773a93ae2 Mon Sep 17 00:00:00 2001
From: Daniel Bruce <djbruce@google.com>
Date: Thu, 2 Nov 2023 09:49:13 -0400
Subject: [PATCH 014/129] Change types used in the data client callback

Make the types more specific in the data client callback so that it is easier to track down the signature and match against the begin transaction function.
---
 test/transaction.ts | 27 ++++++++++++++++++++-------
 1 file changed, 20 insertions(+), 7 deletions(-)

diff --git a/test/transaction.ts b/test/transaction.ts
index c0581c9ca..8b8b6c8a4 100644
--- a/test/transaction.ts
+++ b/test/transaction.ts
@@ -30,11 +30,12 @@ import {
 import {Entity} from '../src/entity';
 import * as tsTypes from '../src/transaction';
 import * as sinon from 'sinon';
-import {ClientStub} from 'google-gax';
+import {Callback, CallOptions, ClientStub} from 'google-gax';
 import {RequestConfig} from '../src/request';
 import {SECOND_DATABASE_ID} from './index';
 import {google} from '../protos/protos';
 import {RunCallback} from '../src/transaction';
+import * as protos from '../protos/protos';
 const async = require('async');
 
 // eslint-disable-next-line @typescript-eslint/no-explicit-any
@@ -201,9 +202,15 @@ async.each(
             // from the Gapic layer.
             if (dataClient) {
               dataClient.beginTransaction = (
-                request: any,
-                options: any,
-                callback: (err: unknown, resp: any) => void
+                request: protos.google.datastore.v1.IBeginTransactionRequest,
+                options: CallOptions,
+                callback: Callback<
+                  protos.google.datastore.v1.IBeginTransactionResponse,
+                  | protos.google.datastore.v1.IBeginTransactionRequest
+                  | null
+                  | undefined,
+                  {} | null | undefined
+                >
               ) => {
                 callback(new Error(testErrorMessage), testResp);
               };
@@ -240,9 +247,15 @@ async.each(
             // back to the user from the Gapic layer.
             if (dataClient) {
               dataClient.beginTransaction = (
-                request: any,
-                options: any,
-                callback: (err: unknown, resp: any) => void
+                  request: protos.google.datastore.v1.IBeginTransactionRequest,
+                  options: CallOptions,
+                  callback: Callback<
+                      protos.google.datastore.v1.IBeginTransactionResponse,
+                      | protos.google.datastore.v1.IBeginTransactionRequest
+                      | null
+                      | undefined,
+                      {} | null | undefined
+                  >
               ) => {
                 callback(null, testResp);
               };

From 6d50e945d6302db23038135b70a95a9f7e2daa56 Mon Sep 17 00:00:00 2001
From: Daniel Bruce <djbruce@google.com>
Date: Thu, 2 Nov 2023 09:51:18 -0400
Subject: [PATCH 015/129] run the linter

---
 test/transaction.ts | 18 +++++++++---------
 1 file changed, 9 insertions(+), 9 deletions(-)

diff --git a/test/transaction.ts b/test/transaction.ts
index 8b8b6c8a4..43130570f 100644
--- a/test/transaction.ts
+++ b/test/transaction.ts
@@ -247,15 +247,15 @@ async.each(
             // back to the user from the Gapic layer.
             if (dataClient) {
               dataClient.beginTransaction = (
-                  request: protos.google.datastore.v1.IBeginTransactionRequest,
-                  options: CallOptions,
-                  callback: Callback<
-                      protos.google.datastore.v1.IBeginTransactionResponse,
-                      | protos.google.datastore.v1.IBeginTransactionRequest
-                      | null
-                      | undefined,
-                      {} | null | undefined
-                  >
+                request: protos.google.datastore.v1.IBeginTransactionRequest,
+                options: CallOptions,
+                callback: Callback<
+                  protos.google.datastore.v1.IBeginTransactionResponse,
+                  | protos.google.datastore.v1.IBeginTransactionRequest
+                  | null
+                  | undefined,
+                  {} | null | undefined
+                >
               ) => {
                 callback(null, testResp);
               };

From 875bf4b7165bb6628163cae6461814a0cd7012be Mon Sep 17 00:00:00 2001
From: Daniel Bruce <djbruce@google.com>
Date: Thu, 2 Nov 2023 09:56:17 -0400
Subject: [PATCH 016/129] Add comments to clarify PR

---
 test/transaction.ts | 7 +++++--
 1 file changed, 5 insertions(+), 2 deletions(-)

diff --git a/test/transaction.ts b/test/transaction.ts
index 43130570f..f1a942b05 100644
--- a/test/transaction.ts
+++ b/test/transaction.ts
@@ -174,6 +174,9 @@ async.each(
         let originalBeginTransactionMethod: Function;
 
         beforeEach(async () => {
+          // In this before hook, save the original beginTransaction method in a variable.
+          // After tests are finished, reassign beginTransaction to the variable.
+          // This way, mocking beginTransaction in this block doesn't affect other tests.
           const gapic = Object.freeze({
             v1: require('../src/v1'),
           });
@@ -189,8 +192,8 @@ async.each(
         });
 
         afterEach(() => {
-          // Commit has likely been mocked out in these tests.
-          // We should reassign commit back to its original value for the rest of the tests.
+          // beginTransaction has likely been mocked out in these tests.
+          // We should reassign beginTransaction back to its original value for tests outside this block.
           if (dataClient && originalBeginTransactionMethod) {
             dataClient.beginTransaction = originalBeginTransactionMethod;
           }

From ea80c65ce9a7e1a5ec711f72ea4a4f946aa1fb10 Mon Sep 17 00:00:00 2001
From: Daniel Bruce <djbruce@google.com>
Date: Thu, 2 Nov 2023 10:11:29 -0400
Subject: [PATCH 017/129] Refactor the parsing logic out of run

The parsing logic is going to be needed elsewhere so taking it apart now.
---
 src/transaction.ts | 25 ++++++++++++++++---------
 1 file changed, 16 insertions(+), 9 deletions(-)

diff --git a/src/transaction.ts b/src/transaction.ts
index 2db87cc0d..c659c9fb4 100644
--- a/src/transaction.ts
+++ b/src/transaction.ts
@@ -556,19 +556,26 @@ class Transaction extends DatastoreRequest {
       typeof optionsOrCallback === 'object' ? optionsOrCallback : {};
     const callback =
       typeof optionsOrCallback === 'function' ? optionsOrCallback : cb!;
-    // eslint-disable-next-line @typescript-eslint/no-this-alias
     this.runAsync(options).then((response: RequestPromiseReturnType) => {
-      const err = response.err;
-      const resp = response.resp;
-      if (err) {
-        callback(err, null, resp);
-        return;
-      }
-      this.id = resp!.transaction;
-      callback(null, this, resp);
+      this.parseRunAsync(response, callback);
     });
   }
 
+  // TODO: Replace with #parseRunAsync when pack and play error is gone
+  private parseRunAsync(
+    response: RequestPromiseReturnType,
+    callback: RunCallback
+  ): void {
+    const err = response.err;
+    const resp = response.resp;
+    if (err) {
+      callback(err, null, resp);
+      return;
+    }
+    this.id = resp!.transaction;
+    callback(null, this, resp);
+  }
+  // TODO: Replace with #runAsync when pack and play error is gone
   private async runAsync(
     options: RunOptions
   ): Promise<RequestPromiseReturnType> {

From 14af87eb91d1a3ae837213bdafbb9aa64fa3950a Mon Sep 17 00:00:00 2001
From: Daniel Bruce <djbruce@google.com>
Date: Thu, 2 Nov 2023 10:14:57 -0400
Subject: [PATCH 018/129] Change interface of request promise callback

The interface name should be changed so that it matches what it is. It is the callback used to form a promise.
---
 src/transaction.ts | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/src/transaction.ts b/src/transaction.ts
index c659c9fb4..816db0379 100644
--- a/src/transaction.ts
+++ b/src/transaction.ts
@@ -40,7 +40,7 @@ interface RequestPromiseReturnType {
 interface RequestResolveFunction {
   (callbackData: RequestPromiseReturnType): void;
 }
-interface RequestAsPromise {
+interface RequestAsPromiseCallback {
   (resolve: RequestResolveFunction): void;
 }
 
@@ -596,7 +596,7 @@ class Transaction extends DatastoreRequest {
     if (options.transactionOptions) {
       reqOpts.transactionOptions = options.transactionOptions;
     }
-    const promiseFunction: RequestAsPromise = (
+    const promiseFunction: RequestAsPromiseCallback = (
       resolve: RequestResolveFunction
     ) => {
       this.request_(

From 3a28892a580aeff0c9e5cb5ab5015e7012ca7258 Mon Sep 17 00:00:00 2001
From: Daniel Bruce <djbruce@google.com>
Date: Thu, 2 Nov 2023 13:23:57 -0400
Subject: [PATCH 019/129] Move commit functionality to a new function

The internals of commit should be moved to a new function. This way we can sandwich a commit async call between commit and runCommitAsync.
---
 src/transaction.ts | 228 +++++++++++++++++++++++----------------------
 1 file changed, 117 insertions(+), 111 deletions(-)

diff --git a/src/transaction.ts b/src/transaction.ts
index 816db0379..8dcdca1ec 100644
--- a/src/transaction.ts
+++ b/src/transaction.ts
@@ -161,117 +161,7 @@ class Transaction extends DatastoreRequest {
         : () => {};
     const gaxOptions =
       typeof gaxOptionsOrCallback === 'object' ? gaxOptionsOrCallback : {};
-
-    if (this.skipCommit) {
-      setImmediate(callback);
-      return;
-    }
-
-    const keys: Entities = {};
-
-    this.modifiedEntities_
-      // Reverse the order of the queue to respect the "last queued request
-      // wins" behavior.
-      .reverse()
-      // Limit the operations we're going to send through to only the most
-      // recently queued operations. E.g., if a user tries to save with the
-      // same key they just asked to be deleted, the delete request will be
-      // ignored, giving preference to the save operation.
-      .filter((modifiedEntity: Entity) => {
-        const key = modifiedEntity.entity.key;
-
-        if (!entity.isKeyComplete(key)) return true;
-
-        const stringifiedKey = JSON.stringify(modifiedEntity.entity.key);
-
-        if (!keys[stringifiedKey]) {
-          keys[stringifiedKey] = true;
-          return true;
-        }
-
-        return false;
-      })
-      // Group entities together by method: `save` mutations, then `delete`.
-      // Note: `save` mutations being first is required to maintain order when
-      // assigning IDs to incomplete keys.
-      .sort((a, b) => {
-        return a.method < b.method ? 1 : a.method > b.method ? -1 : 0;
-      })
-      // Group arguments together so that we only make one call to each
-      // method. This is important for `DatastoreRequest.save`, especially, as
-      // that method handles assigning auto-generated IDs to the original keys
-      // passed in. When we eventually execute the `save` method's API
-      // callback, having all the keys together is necessary to maintain
-      // order.
-      .reduce((acc: Entities, entityObject: Entity) => {
-        const lastEntityObject = acc[acc.length - 1];
-        const sameMethod =
-          lastEntityObject && entityObject.method === lastEntityObject.method;
-
-        if (!lastEntityObject || !sameMethod) {
-          acc.push(entityObject);
-        } else {
-          lastEntityObject.args = lastEntityObject.args.concat(
-            entityObject.args
-          );
-        }
-
-        return acc;
-      }, [])
-      // Call each of the mutational methods (DatastoreRequest[save,delete])
-      // to build up a `req` array on this instance. This will also build up a
-      // `callbacks` array, that is the same callback that would run if we
-      // were using `save` and `delete` outside of a transaction, to process
-      // the response from the API.
-      .forEach(
-        (modifiedEntity: {method: string; args: {reverse: () => void}}) => {
-          const method = modifiedEntity.method;
-          const args = modifiedEntity.args.reverse();
-          Datastore.prototype[method].call(this, args, () => {});
-        }
-      );
-
-    // Take the `req` array built previously, and merge them into one request to
-    // send as the final transactional commit.
-    const reqOpts = {
-      mutations: this.requests_
-        .map((x: {mutations: google.datastore.v1.Mutation}) => x.mutations)
-        .reduce(
-          (a: {concat: (arg0: Entity) => void}, b: Entity) => a.concat(b),
-          []
-        ),
-    };
-
-    this.request_(
-      {
-        client: 'DatastoreClient',
-        method: 'commit',
-        reqOpts,
-        gaxOpts: gaxOptions || {},
-      },
-      (err, resp) => {
-        if (err) {
-          // Rollback automatically for the user.
-          this.rollback(() => {
-            // Provide the error & API response from the failed commit to the
-            // user. Even a failed rollback should be transparent. RE:
-            // https://github.com/GoogleCloudPlatform/google-cloud-node/pull/1369#discussion_r66833976
-            callback(err, resp);
-          });
-          return;
-        }
-
-        // The `callbacks` array was built previously. These are the callbacks
-        // that handle the API response normally when using the
-        // DatastoreRequest.save and .delete methods.
-        this.requestCallbacks_.forEach(
-          (cb: (arg0: null, arg1: Entity) => void) => {
-            cb(null, resp);
-          }
-        );
-        callback(null, resp);
-      }
-    );
+    this.runCommit(gaxOptions, callback);
   }
 
   /**
@@ -561,6 +451,122 @@ class Transaction extends DatastoreRequest {
     });
   }
 
+  private runCommit(
+    gaxOptions: CallOptions,
+    callback: CommitCallback
+  ): void | Promise<CommitResponse> {
+    if (this.skipCommit) {
+      setImmediate(callback);
+      return;
+    }
+
+    const keys: Entities = {};
+
+    this.modifiedEntities_
+      // Reverse the order of the queue to respect the "last queued request
+      // wins" behavior.
+      .reverse()
+      // Limit the operations we're going to send through to only the most
+      // recently queued operations. E.g., if a user tries to save with the
+      // same key they just asked to be deleted, the delete request will be
+      // ignored, giving preference to the save operation.
+      .filter((modifiedEntity: Entity) => {
+        const key = modifiedEntity.entity.key;
+
+        if (!entity.isKeyComplete(key)) return true;
+
+        const stringifiedKey = JSON.stringify(modifiedEntity.entity.key);
+
+        if (!keys[stringifiedKey]) {
+          keys[stringifiedKey] = true;
+          return true;
+        }
+
+        return false;
+      })
+      // Group entities together by method: `save` mutations, then `delete`.
+      // Note: `save` mutations being first is required to maintain order when
+      // assigning IDs to incomplete keys.
+      .sort((a, b) => {
+        return a.method < b.method ? 1 : a.method > b.method ? -1 : 0;
+      })
+      // Group arguments together so that we only make one call to each
+      // method. This is important for `DatastoreRequest.save`, especially, as
+      // that method handles assigning auto-generated IDs to the original keys
+      // passed in. When we eventually execute the `save` method's API
+      // callback, having all the keys together is necessary to maintain
+      // order.
+      .reduce((acc: Entities, entityObject: Entity) => {
+        const lastEntityObject = acc[acc.length - 1];
+        const sameMethod =
+          lastEntityObject && entityObject.method === lastEntityObject.method;
+
+        if (!lastEntityObject || !sameMethod) {
+          acc.push(entityObject);
+        } else {
+          lastEntityObject.args = lastEntityObject.args.concat(
+            entityObject.args
+          );
+        }
+
+        return acc;
+      }, [])
+      // Call each of the mutational methods (DatastoreRequest[save,delete])
+      // to build up a `req` array on this instance. This will also build up a
+      // `callbacks` array, that is the same callback that would run if we
+      // were using `save` and `delete` outside of a transaction, to process
+      // the response from the API.
+      .forEach(
+        (modifiedEntity: {method: string; args: {reverse: () => void}}) => {
+          const method = modifiedEntity.method;
+          const args = modifiedEntity.args.reverse();
+          Datastore.prototype[method].call(this, args, () => {});
+        }
+      );
+
+    // Take the `req` array built previously, and merge them into one request to
+    // send as the final transactional commit.
+    const reqOpts = {
+      mutations: this.requests_
+        .map((x: {mutations: google.datastore.v1.Mutation}) => x.mutations)
+        .reduce(
+          (a: {concat: (arg0: Entity) => void}, b: Entity) => a.concat(b),
+          []
+        ),
+    };
+
+    this.request_(
+      {
+        client: 'DatastoreClient',
+        method: 'commit',
+        reqOpts,
+        gaxOpts: gaxOptions || {},
+      },
+      (err, resp) => {
+        if (err) {
+          // Rollback automatically for the user.
+          this.rollback(() => {
+            // Provide the error & API response from the failed commit to the
+            // user. Even a failed rollback should be transparent. RE:
+            // https://github.com/GoogleCloudPlatform/google-cloud-node/pull/1369#discussion_r66833976
+            callback(err, resp);
+          });
+          return;
+        }
+
+        // The `callbacks` array was built previously. These are the callbacks
+        // that handle the API response normally when using the
+        // DatastoreRequest.save and .delete methods.
+        this.requestCallbacks_.forEach(
+          (cb: (arg0: null, arg1: Entity) => void) => {
+            cb(null, resp);
+          }
+        );
+        callback(null, resp);
+      }
+    );
+  }
+
   // TODO: Replace with #parseRunAsync when pack and play error is gone
   private parseRunAsync(
     response: RequestPromiseReturnType,

From 5de45453fe4cf4c8cf977a8df196ea7d64416cad Mon Sep 17 00:00:00 2001
From: Daniel Bruce <djbruce@google.com>
Date: Thu, 2 Nov 2023 17:39:10 -0400
Subject: [PATCH 020/129] Add the mutex and transaction state etc.

Still an issue with system tests. commit still needs tweaks to work with async.
---
 package.json        |   1 +
 src/request.ts      |  21 +++++++
 src/transaction.ts  |  92 +++++++++++++++++++++++++++--
 test/transaction.ts | 141 ++++++++++++++++++++++++++++++++++++++++++++
 4 files changed, 249 insertions(+), 6 deletions(-)

diff --git a/package.json b/package.json
index 95c436c5b..5956f1969 100644
--- a/package.json
+++ b/package.json
@@ -43,6 +43,7 @@
   "dependencies": {
     "@google-cloud/promisify": "^4.0.0",
     "arrify": "^2.0.1",
+    "async-mutex": "^0.4.0",
     "concat-stream": "^2.0.0",
     "extend": "^3.0.2",
     "google-gax": "^4.0.5",
diff --git a/src/request.ts b/src/request.ts
index d39f572ef..6ba6993dc 100644
--- a/src/request.ts
+++ b/src/request.ts
@@ -57,6 +57,7 @@ import {
 import {Datastore} from '.';
 import ITimestamp = google.protobuf.ITimestamp;
 import {AggregateQuery} from './aggregate';
+import {Mutex} from 'async-mutex';
 
 /**
  * A map of read consistency values to proto codes.
@@ -69,6 +70,23 @@ const CONSISTENCY_PROTO_CODE: ConsistencyProtoCode = {
   strong: 1,
 };
 
+// TODO: Typescript had difficulty working with enums before.
+// TODO: Try to get enums working instead of using static properties.
+
+export class TransactionState {
+  static NOT_TRANSACTION = Symbol('NON_TRANSACTION');
+  static NOT_STARTED = Symbol('NOT_STARTED');
+  // IN_PROGRESS currently tracks the expired state as well
+  static IN_PROGRESS = Symbol('IN_PROGRESS');
+}
+
+/*
+export enum TransactionState {
+  NOT_TRANSACTION,
+  NOT_STARTED,
+  IN_PROGRESS
+}
+ */
 /**
  * Handle logic for Datastore API operations. Handles request logic for
  * Datastore.
@@ -89,6 +107,9 @@ class DatastoreRequest {
     | Array<(err: Error | null, resp: Entity | null) => void>
     | Entity;
   datastore!: Datastore;
+  // TODO: Replace protected with a symbol for better data hiding.
+  protected mutex = new Mutex();
+  protected state: Symbol = TransactionState.NOT_TRANSACTION;
   [key: string]: Entity;
 
   /**
diff --git a/src/transaction.ts b/src/transaction.ts
index 8dcdca1ec..4662cf097 100644
--- a/src/transaction.ts
+++ b/src/transaction.ts
@@ -29,6 +29,7 @@ import {
   DatastoreRequest,
   RequestOptions,
   PrepareEntityObjectResponse,
+  TransactionState,
 } from './request';
 import {AggregateQuery} from './aggregate';
 
@@ -44,6 +45,12 @@ interface RequestAsPromiseCallback {
   (resolve: RequestResolveFunction): void;
 }
 
+// Data types in CommitPromiseReturnType should line up with CommitCallback
+interface CommitPromiseReturnType {
+  err?: Error | null;
+  resp?: google.datastore.v1.ICommitResponse;
+}
+
 /**
  * A transaction is a set of Datastore operations on one or more entities. Each
  * transaction is guaranteed to be atomic, which means that transactions are
@@ -99,6 +106,7 @@ class Transaction extends DatastoreRequest {
 
     // Queue the requests to make when we send the transactional commit.
     this.requests_ = [];
+    this.state = TransactionState.NOT_STARTED;
   }
 
   /*! Developer Documentation
@@ -162,6 +170,47 @@ class Transaction extends DatastoreRequest {
     const gaxOptions =
       typeof gaxOptionsOrCallback === 'object' ? gaxOptionsOrCallback : {};
     this.runCommit(gaxOptions, callback);
+    // TODO: Add call to commitAsync here and handle result in the .then hook
+    /*
+    this.commitAsync(gaxOptions).then((response: CommitPromiseReturnType) => {
+      callback(response.err, response.resp);
+    });
+     */
+  }
+
+  // The promise that commitAsync uses should always resolve and never reject.
+  // The data it resolves with will contain response and error information.
+  private async commitAsync(
+    gaxOptions: CallOptions
+  ): Promise<CommitPromiseReturnType> {
+    if (this.state === TransactionState.NOT_STARTED) {
+      const release = await this.mutex.acquire();
+      try {
+        try {
+          if (this.state === TransactionState.NOT_STARTED) {
+            const runResults = await this.runAsync({gaxOptions});
+            this.parseRunSuccess(runResults);
+          }
+        } finally {
+          // TODO: Check that error actually reaches user
+          release(); // TODO: Be sure to release the mutex in the error state
+        }
+      } catch (err: any) {
+        return await new Promise(resolve => {
+          // resp from the beginTransaction call never reaches the user
+          // if we allowed it to then the return type of commit would need to change
+          resolve({err});
+        });
+      }
+    }
+    return await new Promise(resolve => {
+      this.runCommit(
+        gaxOptions,
+        (err?: Error | null, resp?: google.datastore.v1.ICommitResponse) => {
+          resolve({err, resp});
+        }
+      );
+    });
   }
 
   /**
@@ -446,15 +495,37 @@ class Transaction extends DatastoreRequest {
       typeof optionsOrCallback === 'object' ? optionsOrCallback : {};
     const callback =
       typeof optionsOrCallback === 'function' ? optionsOrCallback : cb!;
-    this.runAsync(options).then((response: RequestPromiseReturnType) => {
-      this.parseRunAsync(response, callback);
-    });
+    if (this.state === TransactionState.NOT_STARTED) {
+      this.mutex.acquire().then(release => {
+        this.runAsync(options).then((response: RequestPromiseReturnType) => {
+          // TODO: Probably release the mutex after the id is recorded, but likely doesn't matter since node is single threaded.
+          release();
+          this.parseRunAsync(response, callback);
+        });
+      });
+    } else {
+      process.emitWarning(
+        'run has already been called and should not be called again.'
+      );
+    }
   }
 
+  private runCommit(gaxOptions?: CallOptions): Promise<CommitResponse>;
+  private runCommit(callback: CommitCallback): void;
+  private runCommit(gaxOptions: CallOptions, callback: CommitCallback): void;
   private runCommit(
-    gaxOptions: CallOptions,
-    callback: CommitCallback
+    gaxOptionsOrCallback?: CallOptions | CommitCallback,
+    cb?: CommitCallback
   ): void | Promise<CommitResponse> {
+    const callback =
+      typeof gaxOptionsOrCallback === 'function'
+        ? gaxOptionsOrCallback
+        : typeof cb === 'function'
+        ? cb
+        : () => {};
+    const gaxOptions =
+      typeof gaxOptionsOrCallback === 'object' ? gaxOptionsOrCallback : {};
+
     if (this.skipCommit) {
       setImmediate(callback);
       return;
@@ -578,9 +649,16 @@ class Transaction extends DatastoreRequest {
       callback(err, null, resp);
       return;
     }
-    this.id = resp!.transaction;
+    this.parseRunSuccess(response);
     callback(null, this, resp);
   }
+
+  private parseRunSuccess(response: RequestPromiseReturnType) {
+    const resp = response.resp;
+    this.id = resp!.transaction;
+    this.state = TransactionState.IN_PROGRESS;
+  }
+
   // TODO: Replace with #runAsync when pack and play error is gone
   private async runAsync(
     options: RunOptions
@@ -855,6 +933,8 @@ promisifyAll(Transaction, {
     'delete',
     'insert',
     'runAsync',
+    'parseRunAsync',
+    'parseTransactionResponse',
     'save',
     'update',
     'upsert',
diff --git a/test/transaction.ts b/test/transaction.ts
index f1a942b05..7c14563bd 100644
--- a/test/transaction.ts
+++ b/test/transaction.ts
@@ -154,6 +154,147 @@ async.each(
         });
       });
 
+      /*
+      describe('commit without setting up transaction id', () => {
+        // These tests were created so that when transaction.run is restructured we
+        // can be confident that it works the same way as before.
+        const testResp = {
+          mutationResults: [
+            {
+              key: {
+                path: [
+                  {
+                    kind: 'some-kind',
+                  },
+                ],
+              },
+            },
+          ],
+        };
+        const namespace = 'run-without-mock';
+        const projectId = 'project-id';
+        const testErrorMessage = 'test-error';
+        const options = {
+          projectId,
+          namespace,
+        };
+        const datastore = new Datastore(options);
+        const transactionWithoutMock = datastore.transaction();
+        const dataClientName = 'DatastoreClient';
+        let dataClient: ClientStub | undefined;
+        let originalCommitMethod: Function;
+
+        beforeEach(async () => {
+          // In this before hook, save the original beginTransaction method in a variable.
+          // After tests are finished, reassign beginTransaction to the variable.
+          // This way, mocking beginTransaction in this block doesn't affect other tests.
+          const gapic = Object.freeze({
+            v1: require('../src/v1'),
+          });
+          // Datastore Gapic clients haven't been initialized yet so we initialize them here.
+          datastore.clients_.set(
+            dataClientName,
+            new gapic.v1[dataClientName](options)
+          );
+          dataClient = datastore.clients_.get(dataClientName);
+          if (dataClient && dataClient.commit) {
+            originalCommitMethod = dataClient.commit;
+          }
+        });
+
+        afterEach(() => {
+          // beginTransaction has likely been mocked out in these tests.
+          // We should reassign beginTransaction back to its original value for tests outside this block.
+          if (dataClient && originalCommitMethod) {
+            dataClient.commit = originalCommitMethod;
+          }
+        });
+
+        describe('should pass error back to the user', async () => {
+          beforeEach(() => {
+            // Mock out begin transaction and send error back to the user
+            // from the Gapic layer.
+            if (dataClient) {
+              dataClient.commit = (
+                request: protos.google.datastore.v1.ICommitRequest,
+                options: CallOptions,
+                callback: Callback<
+                  protos.google.datastore.v1.ICommitResponse,
+                  protos.google.datastore.v1.ICommitRequest | null | undefined,
+                  {} | null | undefined
+                >
+              ) => {
+                callback(new Error(testErrorMessage), testResp);
+              };
+            }
+          });
+
+          it('should send back the error when awaiting a promise',async () => {
+            try {
+              await transactionWithoutMock.run();
+              await transactionWithoutMock.commit();
+              assert.fail('The run call should have failed.');
+            } catch (error: any) {
+              // TODO: Substitute type any
+              assert.strictEqual(error['message'], testErrorMessage);
+            }
+          });
+          it('should send back the error when using a callback', done => {
+            const runCallback: RunCallback = (
+              error: Error | null,
+              transaction: Transaction | null,
+              response?: google.datastore.v1.IBeginTransactionResponse
+            ) => {
+              assert(error);
+              assert.strictEqual(error.message, testErrorMessage);
+              assert.strictEqual(transaction, null);
+              assert.strictEqual(response, testResp);
+              done();
+            };
+            transactionWithoutMock.run();
+          });
+        });
+        describe('should pass response back to the user', async () => {
+          beforeEach(() => {
+            // Mock out begin transaction and send a response
+            // back to the user from the Gapic layer.
+            if (dataClient) {
+              dataClient.beginTransaction = (
+                request: protos.google.datastore.v1.IBeginTransactionRequest,
+                options: CallOptions,
+                callback: Callback<
+                  protos.google.datastore.v1.IBeginTransactionResponse,
+                  | protos.google.datastore.v1.IBeginTransactionRequest
+                  | null
+                  | undefined,
+                  {} | null | undefined
+                >
+              ) => {
+                callback(null, testResp);
+              };
+            }
+          });
+          it('should send back the response when awaiting a promise', async () => {
+            const [transaction, resp] = await transactionWithoutMock.run();
+            assert.strictEqual(transaction, transactionWithoutMock);
+            assert.strictEqual(resp, testResp);
+          });
+          it('should send back the response when using a callback', done => {
+            const runCallback: RunCallback = (
+              error: Error | null,
+              transaction: Transaction | null,
+              response?: google.datastore.v1.IBeginTransactionResponse
+            ) => {
+              assert.strictEqual(error, null);
+              assert.strictEqual(response, testResp);
+              assert.strictEqual(transaction, transactionWithoutMock);
+              done();
+            };
+            transactionWithoutMock.run({}, runCallback);
+          });
+        });
+      });
+      */
       describe('run without setting up transaction id', () => {
         // These tests were created so that when transaction.run is restructured we
         // can be confident that it works the same way as before.

From 1ccacec8bf90130adcbe27cec29ce867133d2e6e Mon Sep 17 00:00:00 2001
From: Daniel Bruce <djbruce@google.com>
Date: Fri, 3 Nov 2023 10:18:59 -0400
Subject: [PATCH 021/129] Remove no-op, get commit tests working

Remove the no-op, get commit tests in place.
---
 src/transaction.ts  |  21 ++--
 test/transaction.ts | 301 +++++++++++++++++++++++---------------------
 2 files changed, 170 insertions(+), 152 deletions(-)

diff --git a/src/transaction.ts b/src/transaction.ts
index 4662cf097..ab3ec360f 100644
--- a/src/transaction.ts
+++ b/src/transaction.ts
@@ -495,19 +495,20 @@ class Transaction extends DatastoreRequest {
       typeof optionsOrCallback === 'object' ? optionsOrCallback : {};
     const callback =
       typeof optionsOrCallback === 'function' ? optionsOrCallback : cb!;
-    if (this.state === TransactionState.NOT_STARTED) {
-      this.mutex.acquire().then(release => {
-        this.runAsync(options).then((response: RequestPromiseReturnType) => {
-          // TODO: Probably release the mutex after the id is recorded, but likely doesn't matter since node is single threaded.
-          release();
-          this.parseRunAsync(response, callback);
-        });
-      });
-    } else {
+    // TODO: Whenever run is called a second time and a warning is emitted then do nothing.
+    // TODO: This means rewriting many tests so that they don't use the same transaction object.
+    if (this.state !== TransactionState.NOT_STARTED) {
       process.emitWarning(
         'run has already been called and should not be called again.'
       );
     }
+    this.mutex.acquire().then(release => {
+      this.runAsync(options).then((response: RequestPromiseReturnType) => {
+        // TODO: Probably release the mutex after the id is recorded, but likely doesn't matter since node is single threaded.
+        release();
+        this.parseRunAsync(response, callback);
+      });
+    });
   }
 
   private runCommit(gaxOptions?: CallOptions): Promise<CommitResponse>;
@@ -932,9 +933,9 @@ promisifyAll(Transaction, {
     'createQuery',
     'delete',
     'insert',
-    'runAsync',
     'parseRunAsync',
     'parseTransactionResponse',
+    'runAsync',
     'save',
     'update',
     'upsert',
diff --git a/test/transaction.ts b/test/transaction.ts
index 7c14563bd..dcdd95b1b 100644
--- a/test/transaction.ts
+++ b/test/transaction.ts
@@ -31,7 +31,7 @@ import {Entity} from '../src/entity';
 import * as tsTypes from '../src/transaction';
 import * as sinon from 'sinon';
 import {Callback, CallOptions, ClientStub} from 'google-gax';
-import {RequestConfig} from '../src/request';
+import {CommitCallback, RequestConfig} from '../src/request';
 import {SECOND_DATABASE_ID} from './index';
 import {google} from '../protos/protos';
 import {RunCallback} from '../src/transaction';
@@ -57,6 +57,8 @@ const fakePfy = Object.assign({}, pfy, {
       'createQuery',
       'delete',
       'insert',
+      'parseRunAsync',
+      'parseTransactionResponse',
       'runAsync',
       'save',
       'update',
@@ -154,147 +156,6 @@ async.each(
         });
       });
 
-      /*
-      describe('commit without setting up transaction id', () => {
-        // These tests were created so that when transaction.run is restructured we
-        // can be confident that it works the same way as before.
-        const testResp = {
-          mutationResults: [
-            {
-              key: {
-                path: [
-                  {
-                    kind: 'some-kind',
-                  },
-                ],
-              },
-            },
-          ],
-        };
-        const namespace = 'run-without-mock';
-        const projectId = 'project-id';
-        const testErrorMessage = 'test-error';
-        const options = {
-          projectId,
-          namespace,
-        };
-        const datastore = new Datastore(options);
-        const transactionWithoutMock = datastore.transaction();
-        const dataClientName = 'DatastoreClient';
-        let dataClient: ClientStub | undefined;
-        let originalCommitMethod: Function;
-
-        beforeEach(async () => {
-          // In this before hook, save the original beginTransaction method in a variable.
-          // After tests are finished, reassign beginTransaction to the variable.
-          // This way, mocking beginTransaction in this block doesn't affect other tests.
-          const gapic = Object.freeze({
-            v1: require('../src/v1'),
-          });
-          // Datastore Gapic clients haven't been initialized yet so we initialize them here.
-          datastore.clients_.set(
-            dataClientName,
-            new gapic.v1[dataClientName](options)
-          );
-          dataClient = datastore.clients_.get(dataClientName);
-          if (dataClient && dataClient.commit) {
-            originalCommitMethod = dataClient.commit;
-          }
-        });
-
-        afterEach(() => {
-          // beginTransaction has likely been mocked out in these tests.
-          // We should reassign beginTransaction back to its original value for tests outside this block.
-          if (dataClient && originalCommitMethod) {
-            dataClient.commit = originalCommitMethod;
-          }
-        });
-
-        describe('should pass error back to the user', async () => {
-          beforeEach(() => {
-            // Mock out begin transaction and send error back to the user
-            // from the Gapic layer.
-            if (dataClient) {
-              dataClient.commit = (
-                request: protos.google.datastore.v1.ICommitRequest,
-                options: CallOptions,
-                callback: Callback<
-                  protos.google.datastore.v1.ICommitResponse,
-                  protos.google.datastore.v1.ICommitRequest | null | undefined,
-                  {} | null | undefined
-                >
-              ) => {
-                callback(new Error(testErrorMessage), testResp);
-              };
-            }
-          });
-
-          it('should send back the error when awaiting a promise',async () => {
-            try {
-              await transactionWithoutMock.run();
-              await transactionWithoutMock.commit();
-              assert.fail('The run call should have failed.');
-            } catch (error: any) {
-              // TODO: Substitute type any
-              assert.strictEqual(error['message'], testErrorMessage);
-            }
-          });
-          it('should send back the error when using a callback', done => {
-            const runCallback: RunCallback = (
-              error: Error | null,
-              transaction: Transaction | null,
-              response?: google.datastore.v1.IBeginTransactionResponse
-            ) => {
-              assert(error);
-              assert.strictEqual(error.message, testErrorMessage);
-              assert.strictEqual(transaction, null);
-              assert.strictEqual(response, testResp);
-              done();
-            };
-            transactionWithoutMock.run();
-          });
-        });
-        describe('should pass response back to the user', async () => {
-          beforeEach(() => {
-            // Mock out begin transaction and send a response
-            // back to the user from the Gapic layer.
-            if (dataClient) {
-              dataClient.beginTransaction = (
-                request: protos.google.datastore.v1.IBeginTransactionRequest,
-                options: CallOptions,
-                callback: Callback<
-                  protos.google.datastore.v1.IBeginTransactionResponse,
-                  | protos.google.datastore.v1.IBeginTransactionRequest
-                  | null
-                  | undefined,
-                  {} | null | undefined
-                >
-              ) => {
-                callback(null, testResp);
-              };
-            }
-          });
-          it('should send back the response when awaiting a promise', async () => {
-            const [transaction, resp] = await transactionWithoutMock.run();
-            assert.strictEqual(transaction, transactionWithoutMock);
-            assert.strictEqual(resp, testResp);
-          });
-          it('should send back the response when using a callback', done => {
-            const runCallback: RunCallback = (
-              error: Error | null,
-              transaction: Transaction | null,
-              response?: google.datastore.v1.IBeginTransactionResponse
-            ) => {
-              assert.strictEqual(error, null);
-              assert.strictEqual(response, testResp);
-              assert.strictEqual(transaction, transactionWithoutMock);
-              done();
-            };
-            transactionWithoutMock.run({}, runCallback);
-          });
-        });
-      });
-      */
       describe('run without setting up transaction id', () => {
         // These tests were created so that when transaction.run is restructured we
         // can be confident that it works the same way as before.
@@ -423,6 +284,162 @@ async.each(
             };
             transactionWithoutMock.run({}, runCallback);
           });
+          // TODO: Add a test here for calling commit
+          describe('commit without setting up transaction id when run returns a response', () => {
+            // These tests were created so that when transaction.commit is restructured we
+            // can be confident that it works the same way as before.
+            const testCommitResp = {
+              mutationResults: [
+                {
+                  key: {
+                    path: [
+                      {
+                        kind: 'some-kind',
+                      },
+                    ],
+                  },
+                },
+              ],
+            };
+            const namespace = 'run-without-mock';
+            const projectId = 'project-id';
+            const testErrorMessage = 'test-commit-error';
+            const options = {
+              projectId,
+              namespace,
+            };
+            const datastore = new Datastore(options);
+            const transactionWithoutMock = datastore.transaction();
+            const dataClientName = 'DatastoreClient';
+            let dataClient: ClientStub | undefined;
+            let originalCommitMethod: Function;
+
+            beforeEach(async () => {
+              // In this before hook, save the original beginTransaction method in a variable.
+              // After tests are finished, reassign beginTransaction to the variable.
+              // This way, mocking beginTransaction in this block doesn't affect other tests.
+              const gapic = Object.freeze({
+                v1: require('../src/v1'),
+              });
+              // Datastore Gapic clients haven't been initialized yet so we initialize them here.
+              datastore.clients_.set(
+                dataClientName,
+                new gapic.v1[dataClientName](options)
+              );
+              dataClient = datastore.clients_.get(dataClientName);
+              if (dataClient && dataClient.commit) {
+                originalCommitMethod = dataClient.commit;
+              }
+            });
+
+            afterEach(() => {
+              // beginTransaction has likely been mocked out in these tests.
+              // We should reassign beginTransaction back to its original value for tests outside this block.
+              if (dataClient && originalCommitMethod) {
+                dataClient.commit = originalCommitMethod;
+              }
+            });
+
+            describe('should pass error back to the user', async () => {
+              beforeEach(() => {
+                // Mock out begin transaction and send error back to the user
+                // from the Gapic layer.
+                if (dataClient) {
+                  dataClient.commit = (
+                    request: protos.google.datastore.v1.ICommitRequest,
+                    options: CallOptions,
+                    callback: Callback<
+                      protos.google.datastore.v1.ICommitResponse,
+                      | protos.google.datastore.v1.ICommitRequest
+                      | null
+                      | undefined,
+                      {} | null | undefined
+                    >
+                  ) => {
+                    callback(new Error(testErrorMessage), testCommitResp);
+                  };
+                }
+              });
+
+              it('should send back the error when awaiting a promise', async () => {
+                try {
+                  await transactionWithoutMock.run();
+                  await transactionWithoutMock.commit();
+                  assert.fail('The run call should have failed.');
+                } catch (error: any) {
+                  // TODO: Substitute type any
+                  assert.strictEqual(error['message'], testErrorMessage);
+                }
+              });
+              it('should send back the error when using a callback', done => {
+                const commitCallback: CommitCallback = (
+                  error: Error | null | undefined,
+                  response?: google.datastore.v1.ICommitResponse
+                ) => {
+                  assert(error);
+                  assert.strictEqual(error.message, testErrorMessage);
+                  assert.strictEqual(transaction, null);
+                  assert.strictEqual(response, testCommitResp);
+                  done();
+                };
+                transactionWithoutMock.run(
+                  (
+                    error: Error | null,
+                    transaction: Transaction | null,
+                    response?: google.datastore.v1.IBeginTransactionResponse
+                  ) => {
+                    transactionWithoutMock.commit(commitCallback);
+                  }
+                );
+              });
+            });
+            describe('should pass response back to the user', async () => {
+              beforeEach(() => {
+                // Mock out begin transaction and send a response
+                // back to the user from the Gapic layer.
+                if (dataClient) {
+                  dataClient.commit = (
+                    request: protos.google.datastore.v1.ICommitRequest,
+                    options: CallOptions,
+                    callback: Callback<
+                      protos.google.datastore.v1.ICommitResponse,
+                      | protos.google.datastore.v1.ICommitRequest
+                      | null
+                      | undefined,
+                      {} | null | undefined
+                    >
+                  ) => {
+                    callback(null, testCommitResp);
+                  };
+                }
+              });
+              it('should send back the response when awaiting a promise', async () => {
+                await transactionWithoutMock.run();
+                const commitResults = await transactionWithoutMock.commit();
+                assert.strictEqual(commitResults, testCommitResp);
+              });
+              it('should send back the response when using a callback', done => {
+                const commitCallback: CommitCallback = (
+                  error: Error | null | undefined,
+                  response?: google.datastore.v1.ICommitResponse
+                ) => {
+                  assert(error);
+                  assert.strictEqual(error, null);
+                  assert.strictEqual(response, testCommitResp);
+                  done();
+                };
+                transactionWithoutMock.run(
+                  (
+                    error: Error | null,
+                    transaction: Transaction | null,
+                    response?: google.datastore.v1.IBeginTransactionResponse
+                  ) => {
+                    transactionWithoutMock.commit(commitCallback);
+                  }
+                );
+              });
+            });
+          });
         });
       });
 

From 37918d76e3a8f502cc23a1b3f6602e02e87be759 Mon Sep 17 00:00:00 2001
From: Daniel Bruce <djbruce@google.com>
Date: Fri, 3 Nov 2023 10:49:26 -0400
Subject: [PATCH 022/129] Add mocks and additional debugging

Mocks and additional debugging hooks to introspect what is going on.
---
 src/transaction.ts  | 15 ++++++++++-----
 test/transaction.ts | 27 +++++++++++++++++++++++++++
 2 files changed, 37 insertions(+), 5 deletions(-)

diff --git a/src/transaction.ts b/src/transaction.ts
index ab3ec360f..d7b561357 100644
--- a/src/transaction.ts
+++ b/src/transaction.ts
@@ -503,11 +503,16 @@ class Transaction extends DatastoreRequest {
       );
     }
     this.mutex.acquire().then(release => {
-      this.runAsync(options).then((response: RequestPromiseReturnType) => {
-        // TODO: Probably release the mutex after the id is recorded, but likely doesn't matter since node is single threaded.
-        release();
-        this.parseRunAsync(response, callback);
-      });
+      this.runAsync(options)
+        .then((response: RequestPromiseReturnType) => {
+          // TODO: Probably release the mutex after the id is recorded, but likely doesn't matter since node is single threaded.
+          release();
+          this.parseRunAsync(response, callback);
+        })
+        .catch((err: any) => {
+          // TODO: Remove this catch block
+          callback(Error('The error should always be caught by then'), this);
+        });
     });
   }
 
diff --git a/test/transaction.ts b/test/transaction.ts
index dcdd95b1b..1994f8c0c 100644
--- a/test/transaction.ts
+++ b/test/transaction.ts
@@ -358,6 +358,19 @@ async.each(
                   ) => {
                     callback(new Error(testErrorMessage), testCommitResp);
                   };
+                  dataClient.beginTransaction = (
+                    request: protos.google.datastore.v1.IBeginTransactionRequest,
+                    options: CallOptions,
+                    callback: Callback<
+                      protos.google.datastore.v1.IBeginTransactionResponse,
+                      | protos.google.datastore.v1.IBeginTransactionRequest
+                      | null
+                      | undefined,
+                      {} | null | undefined
+                    >
+                  ) => {
+                    callback(null, testResp);
+                  };
                 }
               });
 
@@ -411,6 +424,20 @@ async.each(
                   ) => {
                     callback(null, testCommitResp);
                   };
+                  // TODO: See if eliminating this mock will fix the problem
+                  dataClient.beginTransaction = (
+                    request: protos.google.datastore.v1.IBeginTransactionRequest,
+                    options: CallOptions,
+                    callback: Callback<
+                      protos.google.datastore.v1.IBeginTransactionResponse,
+                      | protos.google.datastore.v1.IBeginTransactionRequest
+                      | null
+                      | undefined,
+                      {} | null | undefined
+                    >
+                  ) => {
+                    callback(null, testResp);
+                  };
                 }
               });
               it('should send back the response when awaiting a promise', async () => {

From a02d293976cb25ea4d599d29f8836835b619ba6a Mon Sep 17 00:00:00 2001
From: Daniel Bruce <djbruce@google.com>
Date: Fri, 3 Nov 2023 15:11:29 -0400
Subject: [PATCH 023/129] Add tests for commit

Make sure commit behaves the same way as before.
---
 test/transaction.ts | 187 +++++++++++++++++++++++++++++++++++++++++++-
 1 file changed, 185 insertions(+), 2 deletions(-)

diff --git a/test/transaction.ts b/test/transaction.ts
index f1a942b05..bd8a85f03 100644
--- a/test/transaction.ts
+++ b/test/transaction.ts
@@ -31,7 +31,7 @@ import {Entity} from '../src/entity';
 import * as tsTypes from '../src/transaction';
 import * as sinon from 'sinon';
 import {Callback, CallOptions, ClientStub} from 'google-gax';
-import {RequestConfig} from '../src/request';
+import {CommitCallback, RequestConfig} from '../src/request';
 import {SECOND_DATABASE_ID} from './index';
 import {google} from '../protos/protos';
 import {RunCallback} from '../src/transaction';
@@ -154,7 +154,7 @@ async.each(
         });
       });
 
-      describe('run without setting up transaction id', () => {
+      describe.only('run without setting up transaction id', () => {
         // These tests were created so that when transaction.run is restructured we
         // can be confident that it works the same way as before.
         const testResp = {
@@ -282,6 +282,189 @@ async.each(
             };
             transactionWithoutMock.run({}, runCallback);
           });
+          // TODO: Add a test here for calling commit
+          describe('commit without setting up transaction id when run returns a response', () => {
+            // These tests were created so that when transaction.commit is restructured we
+            // can be confident that it works the same way as before.
+            const testCommitResp = {
+              mutationResults: [
+                {
+                  key: {
+                    path: [
+                      {
+                        kind: 'some-kind',
+                      },
+                    ],
+                  },
+                },
+              ],
+            };
+            const namespace = 'run-without-mock';
+            const projectId = 'project-id';
+            const testErrorMessage = 'test-commit-error';
+            const options = {
+              projectId,
+              namespace,
+            };
+            const datastore = new Datastore(options);
+            const transactionWithoutMock = datastore.transaction();
+            const dataClientName = 'DatastoreClient';
+            let dataClient: ClientStub | undefined;
+            let originalCommitMethod: Function;
+
+            beforeEach(async () => {
+              // In this before hook, save the original beginTransaction method in a variable.
+              // After tests are finished, reassign beginTransaction to the variable.
+              // This way, mocking beginTransaction in this block doesn't affect other tests.
+              const gapic = Object.freeze({
+                v1: require('../src/v1'),
+              });
+              // Datastore Gapic clients haven't been initialized yet so we initialize them here.
+              datastore.clients_.set(
+                dataClientName,
+                new gapic.v1[dataClientName](options)
+              );
+              dataClient = datastore.clients_.get(dataClientName);
+              if (dataClient && dataClient.commit) {
+                originalCommitMethod = dataClient.commit;
+              }
+            });
+
+            afterEach(() => {
+              // beginTransaction has likely been mocked out in these tests.
+              // We should reassign beginTransaction back to its original value for tests outside this block.
+              if (dataClient && originalCommitMethod) {
+                dataClient.commit = originalCommitMethod;
+              }
+            });
+
+            describe('should pass error back to the user', async () => {
+              beforeEach(() => {
+                // Mock out begin transaction and send error back to the user
+                // from the Gapic layer.
+                if (dataClient) {
+                  dataClient.commit = (
+                    request: protos.google.datastore.v1.ICommitRequest,
+                    options: CallOptions,
+                    callback: Callback<
+                      protos.google.datastore.v1.ICommitResponse,
+                      | protos.google.datastore.v1.ICommitRequest
+                      | null
+                      | undefined,
+                      {} | null | undefined
+                    >
+                  ) => {
+                    callback(new Error(testErrorMessage), testCommitResp);
+                  };
+                  dataClient.beginTransaction = (
+                    request: protos.google.datastore.v1.IBeginTransactionRequest,
+                    options: CallOptions,
+                    callback: Callback<
+                      protos.google.datastore.v1.IBeginTransactionResponse,
+                      | protos.google.datastore.v1.IBeginTransactionRequest
+                      | null
+                      | undefined,
+                      {} | null | undefined
+                    >
+                  ) => {
+                    callback(null, testResp);
+                  };
+                }
+              });
+
+              it('should send back the error when awaiting a promise', async () => {
+                try {
+                  await transactionWithoutMock.run();
+                  await transactionWithoutMock.commit();
+                  assert.fail('The run call should have failed.');
+                } catch (error: any) {
+                  // TODO: Substitute type any
+                  assert.strictEqual(error['message'], testErrorMessage);
+                }
+              });
+              it('should send back the error when using a callback', done => {
+                const commitCallback: CommitCallback = (
+                  error: Error | null | undefined,
+                  response?: google.datastore.v1.ICommitResponse
+                ) => {
+                  assert(error);
+                  assert.strictEqual(error.message, testErrorMessage);
+                  assert.strictEqual(transaction, null);
+                  assert.strictEqual(response, testCommitResp);
+                  done();
+                };
+                transactionWithoutMock.run(
+                  (
+                    error: Error | null,
+                    transaction: Transaction | null,
+                    response?: google.datastore.v1.IBeginTransactionResponse
+                  ) => {
+                    transactionWithoutMock.commit(commitCallback);
+                  }
+                );
+              });
+            });
+            describe('should pass response back to the user', async () => {
+              beforeEach(() => {
+                // Mock out begin transaction and send a response
+                // back to the user from the Gapic layer.
+                if (dataClient) {
+                  dataClient.commit = (
+                    request: protos.google.datastore.v1.ICommitRequest,
+                    options: CallOptions,
+                    callback: Callback<
+                      protos.google.datastore.v1.ICommitResponse,
+                      | protos.google.datastore.v1.ICommitRequest
+                      | null
+                      | undefined,
+                      {} | null | undefined
+                    >
+                  ) => {
+                    callback(null, testCommitResp);
+                  };
+                  // TODO: See if eliminating this mock will fix the problem
+                  dataClient.beginTransaction = (
+                    request: protos.google.datastore.v1.IBeginTransactionRequest,
+                    options: CallOptions,
+                    callback: Callback<
+                      protos.google.datastore.v1.IBeginTransactionResponse,
+                      | protos.google.datastore.v1.IBeginTransactionRequest
+                      | null
+                      | undefined,
+                      {} | null | undefined
+                    >
+                  ) => {
+                    callback(null, testResp);
+                  };
+                }
+              });
+              it('should send back the response when awaiting a promise', async () => {
+                await transactionWithoutMock.run();
+                const commitResults = await transactionWithoutMock.commit();
+                assert.strictEqual(commitResults, testCommitResp);
+              });
+              it('should send back the response when using a callback', done => {
+                const commitCallback: CommitCallback = (
+                  error: Error | null | undefined,
+                  response?: google.datastore.v1.ICommitResponse
+                ) => {
+                  assert(error);
+                  assert.strictEqual(error, null);
+                  assert.strictEqual(response, testCommitResp);
+                  done();
+                };
+                transactionWithoutMock.run(
+                  (
+                    error: Error | null,
+                    transaction: Transaction | null,
+                    response?: google.datastore.v1.IBeginTransactionResponse
+                  ) => {
+                    transactionWithoutMock.commit(commitCallback);
+                  }
+                );
+              });
+            });
+          });
         });
       });
 

From 3293a9ca1f91c32a8ac6dd4045da14a843fa7913 Mon Sep 17 00:00:00 2001
From: Daniel Bruce <djbruce@google.com>
Date: Fri, 3 Nov 2023 15:18:35 -0400
Subject: [PATCH 024/129] Add the commit tests

Commit tests added to ensure that commit behaves the same way as before.
---
 test/transaction.ts | 185 +++++++++++++++++++++++++++++++++++++++++++-
 1 file changed, 184 insertions(+), 1 deletion(-)

diff --git a/test/transaction.ts b/test/transaction.ts
index f1a942b05..bb2ee150e 100644
--- a/test/transaction.ts
+++ b/test/transaction.ts
@@ -31,7 +31,7 @@ import {Entity} from '../src/entity';
 import * as tsTypes from '../src/transaction';
 import * as sinon from 'sinon';
 import {Callback, CallOptions, ClientStub} from 'google-gax';
-import {RequestConfig} from '../src/request';
+import {CommitCallback, RequestConfig} from '../src/request';
 import {SECOND_DATABASE_ID} from './index';
 import {google} from '../protos/protos';
 import {RunCallback} from '../src/transaction';
@@ -282,6 +282,189 @@ async.each(
             };
             transactionWithoutMock.run({}, runCallback);
           });
+          // TODO: Add a test here for calling commit
+          describe('commit without setting up transaction id when run returns a response', () => {
+            // These tests were created so that when transaction.commit is restructured we
+            // can be confident that it works the same way as before.
+            const testCommitResp = {
+              mutationResults: [
+                {
+                  key: {
+                    path: [
+                      {
+                        kind: 'some-kind',
+                      },
+                    ],
+                  },
+                },
+              ],
+            };
+            const namespace = 'run-without-mock';
+            const projectId = 'project-id';
+            const testErrorMessage = 'test-commit-error';
+            const options = {
+              projectId,
+              namespace,
+            };
+            const datastore = new Datastore(options);
+            const transactionWithoutMock = datastore.transaction();
+            const dataClientName = 'DatastoreClient';
+            let dataClient: ClientStub | undefined;
+            let originalCommitMethod: Function;
+
+            beforeEach(async () => {
+              // In this before hook, save the original beginTransaction method in a variable.
+              // After tests are finished, reassign beginTransaction to the variable.
+              // This way, mocking beginTransaction in this block doesn't affect other tests.
+              const gapic = Object.freeze({
+                v1: require('../src/v1'),
+              });
+              // Datastore Gapic clients haven't been initialized yet so we initialize them here.
+              datastore.clients_.set(
+                dataClientName,
+                new gapic.v1[dataClientName](options)
+              );
+              dataClient = datastore.clients_.get(dataClientName);
+              if (dataClient && dataClient.commit) {
+                originalCommitMethod = dataClient.commit;
+              }
+            });
+
+            afterEach(() => {
+              // beginTransaction has likely been mocked out in these tests.
+              // We should reassign beginTransaction back to its original value for tests outside this block.
+              if (dataClient && originalCommitMethod) {
+                dataClient.commit = originalCommitMethod;
+              }
+            });
+
+            describe('should pass error back to the user', async () => {
+              beforeEach(() => {
+                // Mock out begin transaction and send error back to the user
+                // from the Gapic layer.
+                if (dataClient) {
+                  dataClient.commit = (
+                    request: protos.google.datastore.v1.ICommitRequest,
+                    options: CallOptions,
+                    callback: Callback<
+                      protos.google.datastore.v1.ICommitResponse,
+                      | protos.google.datastore.v1.ICommitRequest
+                      | null
+                      | undefined,
+                      {} | null | undefined
+                    >
+                  ) => {
+                    callback(new Error(testErrorMessage), testCommitResp);
+                  };
+                  dataClient.beginTransaction = (
+                    request: protos.google.datastore.v1.IBeginTransactionRequest,
+                    options: CallOptions,
+                    callback: Callback<
+                      protos.google.datastore.v1.IBeginTransactionResponse,
+                      | protos.google.datastore.v1.IBeginTransactionRequest
+                      | null
+                      | undefined,
+                      {} | null | undefined
+                    >
+                  ) => {
+                    callback(null, testResp);
+                  };
+                }
+              });
+
+              it('should send back the error when awaiting a promise', async () => {
+                try {
+                  await transactionWithoutMock.run();
+                  await transactionWithoutMock.commit();
+                  assert.fail('The run call should have failed.');
+                } catch (error: any) {
+                  // TODO: Substitute type any
+                  assert.strictEqual(error['message'], testErrorMessage);
+                }
+              });
+              it('should send back the error when using a callback', done => {
+                const commitCallback: CommitCallback = (
+                  error: Error | null | undefined,
+                  response?: google.datastore.v1.ICommitResponse
+                ) => {
+                  assert(error);
+                  assert.strictEqual(error.message, testErrorMessage);
+                  assert.strictEqual(transaction, null);
+                  assert.strictEqual(response, testCommitResp);
+                  done();
+                };
+                transactionWithoutMock.run(
+                  (
+                    error: Error | null,
+                    transaction: Transaction | null,
+                    response?: google.datastore.v1.IBeginTransactionResponse
+                  ) => {
+                    transactionWithoutMock.commit(commitCallback);
+                  }
+                );
+              });
+            });
+            describe('should pass response back to the user', async () => {
+              beforeEach(() => {
+                // Mock out begin transaction and send a response
+                // back to the user from the Gapic layer.
+                if (dataClient) {
+                  dataClient.commit = (
+                    request: protos.google.datastore.v1.ICommitRequest,
+                    options: CallOptions,
+                    callback: Callback<
+                      protos.google.datastore.v1.ICommitResponse,
+                      | protos.google.datastore.v1.ICommitRequest
+                      | null
+                      | undefined,
+                      {} | null | undefined
+                    >
+                  ) => {
+                    callback(null, testCommitResp);
+                  };
+                  // TODO: See if eliminating this mock will fix the problem
+                  dataClient.beginTransaction = (
+                    request: protos.google.datastore.v1.IBeginTransactionRequest,
+                    options: CallOptions,
+                    callback: Callback<
+                      protos.google.datastore.v1.IBeginTransactionResponse,
+                      | protos.google.datastore.v1.IBeginTransactionRequest
+                      | null
+                      | undefined,
+                      {} | null | undefined
+                    >
+                  ) => {
+                    callback(null, testResp);
+                  };
+                }
+              });
+              it('should send back the response when awaiting a promise', async () => {
+                await transactionWithoutMock.run();
+                const commitResults = await transactionWithoutMock.commit();
+                assert.strictEqual(commitResults, testCommitResp);
+              });
+              it('should send back the response when using a callback', done => {
+                const commitCallback: CommitCallback = (
+                  error: Error | null | undefined,
+                  response?: google.datastore.v1.ICommitResponse
+                ) => {
+                  assert(error);
+                  assert.strictEqual(error, null);
+                  assert.strictEqual(response, testCommitResp);
+                  done();
+                };
+                transactionWithoutMock.run(
+                  (
+                    error: Error | null,
+                    transaction: Transaction | null,
+                    response?: google.datastore.v1.IBeginTransactionResponse
+                  ) => {
+                    transactionWithoutMock.commit(commitCallback);
+                  }
+                );
+              });
+            });
+          });
         });
       });
 

From f4a41d66e21d3b845fe06a1a48c799e2de047d99 Mon Sep 17 00:00:00 2001
From: Daniel Bruce <djbruce@google.com>
Date: Fri, 3 Nov 2023 15:58:12 -0400
Subject: [PATCH 025/129] Fix the tests so that they pass on commit

The tests should pass before we make changes to commit.
---
 test/transaction.ts | 8 ++++----
 1 file changed, 4 insertions(+), 4 deletions(-)

diff --git a/test/transaction.ts b/test/transaction.ts
index bb2ee150e..d1e3d91db 100644
--- a/test/transaction.ts
+++ b/test/transaction.ts
@@ -307,12 +307,14 @@ async.each(
               namespace,
             };
             const datastore = new Datastore(options);
-            const transactionWithoutMock = datastore.transaction();
+            let transactionWithoutMock: Transaction;
             const dataClientName = 'DatastoreClient';
             let dataClient: ClientStub | undefined;
             let originalCommitMethod: Function;
 
             beforeEach(async () => {
+              // Create a fresh transaction for each test because transaction state changes after a commit.
+              transactionWithoutMock = datastore.transaction();
               // In this before hook, save the original beginTransaction method in a variable.
               // After tests are finished, reassign beginTransaction to the variable.
               // This way, mocking beginTransaction in this block doesn't affect other tests.
@@ -389,7 +391,6 @@ async.each(
                 ) => {
                   assert(error);
                   assert.strictEqual(error.message, testErrorMessage);
-                  assert.strictEqual(transaction, null);
                   assert.strictEqual(response, testCommitResp);
                   done();
                 };
@@ -440,7 +441,7 @@ async.each(
               });
               it('should send back the response when awaiting a promise', async () => {
                 await transactionWithoutMock.run();
-                const commitResults = await transactionWithoutMock.commit();
+                const [commitResults] = await transactionWithoutMock.commit();
                 assert.strictEqual(commitResults, testCommitResp);
               });
               it('should send back the response when using a callback', done => {
@@ -448,7 +449,6 @@ async.each(
                   error: Error | null | undefined,
                   response?: google.datastore.v1.ICommitResponse
                 ) => {
-                  assert(error);
                   assert.strictEqual(error, null);
                   assert.strictEqual(response, testCommitResp);
                   done();

From 1d8b3749db56a25bca2e10ccd441ada2994a1989 Mon Sep 17 00:00:00 2001
From: Daniel Bruce <djbruce@google.com>
Date: Fri, 3 Nov 2023 16:21:02 -0400
Subject: [PATCH 026/129] refactor one of the mocks

One of the mocks does not need to be written twice
---
 test/transaction.ts | 42 +++++++++++++++---------------------------
 1 file changed, 15 insertions(+), 27 deletions(-)

diff --git a/test/transaction.ts b/test/transaction.ts
index d1e3d91db..7b5ed19d6 100644
--- a/test/transaction.ts
+++ b/test/transaction.ts
@@ -330,6 +330,21 @@ async.each(
               if (dataClient && dataClient.commit) {
                 originalCommitMethod = dataClient.commit;
               }
+              if (dataClient && dataClient.beginTransaction) {
+                dataClient.beginTransaction = (
+                  request: protos.google.datastore.v1.IBeginTransactionRequest,
+                  options: CallOptions,
+                  callback: Callback<
+                    protos.google.datastore.v1.IBeginTransactionResponse,
+                    | protos.google.datastore.v1.IBeginTransactionRequest
+                    | null
+                    | undefined,
+                    {} | null | undefined
+                  >
+                ) => {
+                  callback(null, testResp);
+                };
+              }
             });
 
             afterEach(() => {
@@ -358,19 +373,6 @@ async.each(
                   ) => {
                     callback(new Error(testErrorMessage), testCommitResp);
                   };
-                  dataClient.beginTransaction = (
-                    request: protos.google.datastore.v1.IBeginTransactionRequest,
-                    options: CallOptions,
-                    callback: Callback<
-                      protos.google.datastore.v1.IBeginTransactionResponse,
-                      | protos.google.datastore.v1.IBeginTransactionRequest
-                      | null
-                      | undefined,
-                      {} | null | undefined
-                    >
-                  ) => {
-                    callback(null, testResp);
-                  };
                 }
               });
 
@@ -423,20 +425,6 @@ async.each(
                   ) => {
                     callback(null, testCommitResp);
                   };
-                  // TODO: See if eliminating this mock will fix the problem
-                  dataClient.beginTransaction = (
-                    request: protos.google.datastore.v1.IBeginTransactionRequest,
-                    options: CallOptions,
-                    callback: Callback<
-                      protos.google.datastore.v1.IBeginTransactionResponse,
-                      | protos.google.datastore.v1.IBeginTransactionRequest
-                      | null
-                      | undefined,
-                      {} | null | undefined
-                    >
-                  ) => {
-                    callback(null, testResp);
-                  };
                 }
               });
               it('should send back the response when awaiting a promise', async () => {

From 81f2ec0aed40e79d13f8f79520b94b11e887cc09 Mon Sep 17 00:00:00 2001
From: Daniel Bruce <djbruce@google.com>
Date: Fri, 3 Nov 2023 16:50:50 -0400
Subject: [PATCH 027/129] reverting changes to add new test on transaction

---
 test/transaction.ts | 325 +-------------------------------------------
 1 file changed, 1 insertion(+), 324 deletions(-)

diff --git a/test/transaction.ts b/test/transaction.ts
index 1994f8c0c..06faf3421 100644
--- a/test/transaction.ts
+++ b/test/transaction.ts
@@ -21,21 +21,15 @@ import * as proxyquire from 'proxyquire';
 import {
   Datastore,
   DatastoreOptions,
-  DatastoreClient,
   DatastoreRequest,
   Query,
   TransactionOptions,
-  Transaction,
 } from '../src';
 import {Entity} from '../src/entity';
 import * as tsTypes from '../src/transaction';
 import * as sinon from 'sinon';
-import {Callback, CallOptions, ClientStub} from 'google-gax';
-import {CommitCallback, RequestConfig} from '../src/request';
+import {RequestConfig} from '../src/request';
 import {SECOND_DATABASE_ID} from './index';
-import {google} from '../protos/protos';
-import {RunCallback} from '../src/transaction';
-import * as protos from '../protos/protos';
 const async = require('async');
 
 // eslint-disable-next-line @typescript-eslint/no-explicit-any
@@ -57,9 +51,6 @@ const fakePfy = Object.assign({}, pfy, {
       'createQuery',
       'delete',
       'insert',
-      'parseRunAsync',
-      'parseTransactionResponse',
-      'runAsync',
       'save',
       'update',
       'upsert',
@@ -156,320 +147,6 @@ async.each(
         });
       });
 
-      describe('run without setting up transaction id', () => {
-        // These tests were created so that when transaction.run is restructured we
-        // can be confident that it works the same way as before.
-        const testResp = {
-          transaction: Buffer.from(Array.from(Array(100).keys())),
-        };
-        const namespace = 'run-without-mock';
-        const projectId = 'project-id';
-        const testErrorMessage = 'test-error';
-        const options = {
-          projectId,
-          namespace,
-        };
-        const datastore = new Datastore(options);
-        const transactionWithoutMock = datastore.transaction();
-        const dataClientName = 'DatastoreClient';
-        let dataClient: ClientStub | undefined;
-        let originalBeginTransactionMethod: Function;
-
-        beforeEach(async () => {
-          // In this before hook, save the original beginTransaction method in a variable.
-          // After tests are finished, reassign beginTransaction to the variable.
-          // This way, mocking beginTransaction in this block doesn't affect other tests.
-          const gapic = Object.freeze({
-            v1: require('../src/v1'),
-          });
-          // Datastore Gapic clients haven't been initialized yet so we initialize them here.
-          datastore.clients_.set(
-            dataClientName,
-            new gapic.v1[dataClientName](options)
-          );
-          dataClient = datastore.clients_.get(dataClientName);
-          if (dataClient && dataClient.beginTransaction) {
-            originalBeginTransactionMethod = dataClient.beginTransaction;
-          }
-        });
-
-        afterEach(() => {
-          // beginTransaction has likely been mocked out in these tests.
-          // We should reassign beginTransaction back to its original value for tests outside this block.
-          if (dataClient && originalBeginTransactionMethod) {
-            dataClient.beginTransaction = originalBeginTransactionMethod;
-          }
-        });
-
-        describe('should pass error back to the user', async () => {
-          beforeEach(() => {
-            // Mock out begin transaction and send error back to the user
-            // from the Gapic layer.
-            if (dataClient) {
-              dataClient.beginTransaction = (
-                request: protos.google.datastore.v1.IBeginTransactionRequest,
-                options: CallOptions,
-                callback: Callback<
-                  protos.google.datastore.v1.IBeginTransactionResponse,
-                  | protos.google.datastore.v1.IBeginTransactionRequest
-                  | null
-                  | undefined,
-                  {} | null | undefined
-                >
-              ) => {
-                callback(new Error(testErrorMessage), testResp);
-              };
-            }
-          });
-
-          it('should send back the error when awaiting a promise', async () => {
-            try {
-              await transactionWithoutMock.run();
-              assert.fail('The run call should have failed.');
-            } catch (error: any) {
-              // TODO: Substitute type any
-              assert.strictEqual(error['message'], testErrorMessage);
-            }
-          });
-          it('should send back the error when using a callback', done => {
-            const runCallback: RunCallback = (
-              error: Error | null,
-              transaction: Transaction | null,
-              response?: google.datastore.v1.IBeginTransactionResponse
-            ) => {
-              assert(error);
-              assert.strictEqual(error.message, testErrorMessage);
-              assert.strictEqual(transaction, null);
-              assert.strictEqual(response, testResp);
-              done();
-            };
-            transactionWithoutMock.run({}, runCallback);
-          });
-        });
-        describe('should pass response back to the user', async () => {
-          beforeEach(() => {
-            // Mock out begin transaction and send a response
-            // back to the user from the Gapic layer.
-            if (dataClient) {
-              dataClient.beginTransaction = (
-                request: protos.google.datastore.v1.IBeginTransactionRequest,
-                options: CallOptions,
-                callback: Callback<
-                  protos.google.datastore.v1.IBeginTransactionResponse,
-                  | protos.google.datastore.v1.IBeginTransactionRequest
-                  | null
-                  | undefined,
-                  {} | null | undefined
-                >
-              ) => {
-                callback(null, testResp);
-              };
-            }
-          });
-          it('should send back the response when awaiting a promise', async () => {
-            const [transaction, resp] = await transactionWithoutMock.run();
-            assert.strictEqual(transaction, transactionWithoutMock);
-            assert.strictEqual(resp, testResp);
-          });
-          it('should send back the response when using a callback', done => {
-            const runCallback: RunCallback = (
-              error: Error | null,
-              transaction: Transaction | null,
-              response?: google.datastore.v1.IBeginTransactionResponse
-            ) => {
-              assert.strictEqual(error, null);
-              assert.strictEqual(response, testResp);
-              assert.strictEqual(transaction, transactionWithoutMock);
-              done();
-            };
-            transactionWithoutMock.run({}, runCallback);
-          });
-          // TODO: Add a test here for calling commit
-          describe('commit without setting up transaction id when run returns a response', () => {
-            // These tests were created so that when transaction.commit is restructured we
-            // can be confident that it works the same way as before.
-            const testCommitResp = {
-              mutationResults: [
-                {
-                  key: {
-                    path: [
-                      {
-                        kind: 'some-kind',
-                      },
-                    ],
-                  },
-                },
-              ],
-            };
-            const namespace = 'run-without-mock';
-            const projectId = 'project-id';
-            const testErrorMessage = 'test-commit-error';
-            const options = {
-              projectId,
-              namespace,
-            };
-            const datastore = new Datastore(options);
-            const transactionWithoutMock = datastore.transaction();
-            const dataClientName = 'DatastoreClient';
-            let dataClient: ClientStub | undefined;
-            let originalCommitMethod: Function;
-
-            beforeEach(async () => {
-              // In this before hook, save the original beginTransaction method in a variable.
-              // After tests are finished, reassign beginTransaction to the variable.
-              // This way, mocking beginTransaction in this block doesn't affect other tests.
-              const gapic = Object.freeze({
-                v1: require('../src/v1'),
-              });
-              // Datastore Gapic clients haven't been initialized yet so we initialize them here.
-              datastore.clients_.set(
-                dataClientName,
-                new gapic.v1[dataClientName](options)
-              );
-              dataClient = datastore.clients_.get(dataClientName);
-              if (dataClient && dataClient.commit) {
-                originalCommitMethod = dataClient.commit;
-              }
-            });
-
-            afterEach(() => {
-              // beginTransaction has likely been mocked out in these tests.
-              // We should reassign beginTransaction back to its original value for tests outside this block.
-              if (dataClient && originalCommitMethod) {
-                dataClient.commit = originalCommitMethod;
-              }
-            });
-
-            describe('should pass error back to the user', async () => {
-              beforeEach(() => {
-                // Mock out begin transaction and send error back to the user
-                // from the Gapic layer.
-                if (dataClient) {
-                  dataClient.commit = (
-                    request: protos.google.datastore.v1.ICommitRequest,
-                    options: CallOptions,
-                    callback: Callback<
-                      protos.google.datastore.v1.ICommitResponse,
-                      | protos.google.datastore.v1.ICommitRequest
-                      | null
-                      | undefined,
-                      {} | null | undefined
-                    >
-                  ) => {
-                    callback(new Error(testErrorMessage), testCommitResp);
-                  };
-                  dataClient.beginTransaction = (
-                    request: protos.google.datastore.v1.IBeginTransactionRequest,
-                    options: CallOptions,
-                    callback: Callback<
-                      protos.google.datastore.v1.IBeginTransactionResponse,
-                      | protos.google.datastore.v1.IBeginTransactionRequest
-                      | null
-                      | undefined,
-                      {} | null | undefined
-                    >
-                  ) => {
-                    callback(null, testResp);
-                  };
-                }
-              });
-
-              it('should send back the error when awaiting a promise', async () => {
-                try {
-                  await transactionWithoutMock.run();
-                  await transactionWithoutMock.commit();
-                  assert.fail('The run call should have failed.');
-                } catch (error: any) {
-                  // TODO: Substitute type any
-                  assert.strictEqual(error['message'], testErrorMessage);
-                }
-              });
-              it('should send back the error when using a callback', done => {
-                const commitCallback: CommitCallback = (
-                  error: Error | null | undefined,
-                  response?: google.datastore.v1.ICommitResponse
-                ) => {
-                  assert(error);
-                  assert.strictEqual(error.message, testErrorMessage);
-                  assert.strictEqual(transaction, null);
-                  assert.strictEqual(response, testCommitResp);
-                  done();
-                };
-                transactionWithoutMock.run(
-                  (
-                    error: Error | null,
-                    transaction: Transaction | null,
-                    response?: google.datastore.v1.IBeginTransactionResponse
-                  ) => {
-                    transactionWithoutMock.commit(commitCallback);
-                  }
-                );
-              });
-            });
-            describe('should pass response back to the user', async () => {
-              beforeEach(() => {
-                // Mock out begin transaction and send a response
-                // back to the user from the Gapic layer.
-                if (dataClient) {
-                  dataClient.commit = (
-                    request: protos.google.datastore.v1.ICommitRequest,
-                    options: CallOptions,
-                    callback: Callback<
-                      protos.google.datastore.v1.ICommitResponse,
-                      | protos.google.datastore.v1.ICommitRequest
-                      | null
-                      | undefined,
-                      {} | null | undefined
-                    >
-                  ) => {
-                    callback(null, testCommitResp);
-                  };
-                  // TODO: See if eliminating this mock will fix the problem
-                  dataClient.beginTransaction = (
-                    request: protos.google.datastore.v1.IBeginTransactionRequest,
-                    options: CallOptions,
-                    callback: Callback<
-                      protos.google.datastore.v1.IBeginTransactionResponse,
-                      | protos.google.datastore.v1.IBeginTransactionRequest
-                      | null
-                      | undefined,
-                      {} | null | undefined
-                    >
-                  ) => {
-                    callback(null, testResp);
-                  };
-                }
-              });
-              it('should send back the response when awaiting a promise', async () => {
-                await transactionWithoutMock.run();
-                const commitResults = await transactionWithoutMock.commit();
-                assert.strictEqual(commitResults, testCommitResp);
-              });
-              it('should send back the response when using a callback', done => {
-                const commitCallback: CommitCallback = (
-                  error: Error | null | undefined,
-                  response?: google.datastore.v1.ICommitResponse
-                ) => {
-                  assert(error);
-                  assert.strictEqual(error, null);
-                  assert.strictEqual(response, testCommitResp);
-                  done();
-                };
-                transactionWithoutMock.run(
-                  (
-                    error: Error | null,
-                    transaction: Transaction | null,
-                    response?: google.datastore.v1.IBeginTransactionResponse
-                  ) => {
-                    transactionWithoutMock.commit(commitCallback);
-                  }
-                );
-              });
-            });
-          });
-        });
-      });
-
       describe('commit', () => {
         beforeEach(() => {
           transaction.id = TRANSACTION_ID;

From d5f5f4bffaf0ad16103988add984add5614adc23 Mon Sep 17 00:00:00 2001
From: Daniel Bruce <djbruce@google.com>
Date: Fri, 3 Nov 2023 17:11:46 -0400
Subject: [PATCH 028/129] Change the promise

Make the promise simpler. Change the tests to exclude functions with promisify.
---
 src/transaction.ts  | 8 +-------
 test/transaction.ts | 2 ++
 2 files changed, 3 insertions(+), 7 deletions(-)

diff --git a/src/transaction.ts b/src/transaction.ts
index d7b561357..1a1b0c0bd 100644
--- a/src/transaction.ts
+++ b/src/transaction.ts
@@ -171,11 +171,9 @@ class Transaction extends DatastoreRequest {
       typeof gaxOptionsOrCallback === 'object' ? gaxOptionsOrCallback : {};
     this.runCommit(gaxOptions, callback);
     // TODO: Add call to commitAsync here and handle result in the .then hook
-    /*
     this.commitAsync(gaxOptions).then((response: CommitPromiseReturnType) => {
       callback(response.err, response.resp);
     });
-     */
   }
 
   // The promise that commitAsync uses should always resolve and never reject.
@@ -196,11 +194,7 @@ class Transaction extends DatastoreRequest {
           release(); // TODO: Be sure to release the mutex in the error state
         }
       } catch (err: any) {
-        return await new Promise(resolve => {
-          // resp from the beginTransaction call never reaches the user
-          // if we allowed it to then the return type of commit would need to change
-          resolve({err});
-        });
+        return {err};
       }
     }
     return await new Promise(resolve => {
diff --git a/test/transaction.ts b/test/transaction.ts
index 7b5ed19d6..1b0c248a7 100644
--- a/test/transaction.ts
+++ b/test/transaction.ts
@@ -57,6 +57,8 @@ const fakePfy = Object.assign({}, pfy, {
       'createQuery',
       'delete',
       'insert',
+      'parseRunAsync',
+      'parseTransactionResponse',
       'runAsync',
       'save',
       'update',

From ba363e05fc5bbf69bcfb76f5fe8818f7f9f509ad Mon Sep 17 00:00:00 2001
From: Daniel Bruce <djbruce@google.com>
Date: Mon, 6 Nov 2023 10:30:24 -0500
Subject: [PATCH 029/129] Hide data completely

Change accessors to hide data completely instead of using the private modifier
---
 src/transaction.ts | 15 ++++++---------
 1 file changed, 6 insertions(+), 9 deletions(-)

diff --git a/src/transaction.ts b/src/transaction.ts
index 816db0379..c312b73a6 100644
--- a/src/transaction.ts
+++ b/src/transaction.ts
@@ -556,13 +556,12 @@ class Transaction extends DatastoreRequest {
       typeof optionsOrCallback === 'object' ? optionsOrCallback : {};
     const callback =
       typeof optionsOrCallback === 'function' ? optionsOrCallback : cb!;
-    this.runAsync(options).then((response: RequestPromiseReturnType) => {
-      this.parseRunAsync(response, callback);
+    this.#runAsync(options).then((response: RequestPromiseReturnType) => {
+      this.#parseRunAsync(response, callback);
     });
   }
 
-  // TODO: Replace with #parseRunAsync when pack and play error is gone
-  private parseRunAsync(
+  #parseRunAsync(
     response: RequestPromiseReturnType,
     callback: RunCallback
   ): void {
@@ -575,10 +574,8 @@ class Transaction extends DatastoreRequest {
     this.id = resp!.transaction;
     callback(null, this, resp);
   }
-  // TODO: Replace with #runAsync when pack and play error is gone
-  private async runAsync(
-    options: RunOptions
-  ): Promise<RequestPromiseReturnType> {
+
+  async #runAsync(options: RunOptions): Promise<RequestPromiseReturnType> {
     const reqOpts: RequestOptions = {
       transactionOptions: {},
     };
@@ -848,7 +845,7 @@ promisifyAll(Transaction, {
     'createQuery',
     'delete',
     'insert',
-    'runAsync',
+    '#runAsync',
     'save',
     'update',
     'upsert',

From 9f50cbad792dd787c2ea25ad1690e51a50776b78 Mon Sep 17 00:00:00 2001
From: Daniel Bruce <djbruce@google.com>
Date: Mon, 6 Nov 2023 10:35:14 -0500
Subject: [PATCH 030/129] PR use if/else block

Eliminate the early return as suggested in the PR
---
 src/transaction.ts | 6 +++---
 1 file changed, 3 insertions(+), 3 deletions(-)

diff --git a/src/transaction.ts b/src/transaction.ts
index c312b73a6..cc8bb77fa 100644
--- a/src/transaction.ts
+++ b/src/transaction.ts
@@ -569,10 +569,10 @@ class Transaction extends DatastoreRequest {
     const resp = response.resp;
     if (err) {
       callback(err, null, resp);
-      return;
+    } else {
+      this.id = resp!.transaction;
+      callback(null, this, resp);
     }
-    this.id = resp!.transaction;
-    callback(null, this, resp);
   }
 
   async #runAsync(options: RunOptions): Promise<RequestPromiseReturnType> {

From f96471dde70055007d04c57a756f142565e4d5c6 Mon Sep 17 00:00:00 2001
From: Daniel Bruce <djbruce@google.com>
Date: Mon, 6 Nov 2023 10:55:46 -0500
Subject: [PATCH 031/129] Add comments to document the new functions

The comments capture the parameters and return type.
---
 src/transaction.ts | 17 +++++++++++++++++
 1 file changed, 17 insertions(+)

diff --git a/src/transaction.ts b/src/transaction.ts
index cc8bb77fa..89017cd7e 100644
--- a/src/transaction.ts
+++ b/src/transaction.ts
@@ -561,6 +561,15 @@ class Transaction extends DatastoreRequest {
     });
   }
 
+  /**
+   * This function parses results from a beginTransaction call
+   *
+   * @param {RequestPromiseReturnType} response The response from a call to
+   * begin a transaction.
+   * @param {RunCallback} callback A callback that accepts an error and a
+   * response as arguments.
+   *
+   **/
   #parseRunAsync(
     response: RequestPromiseReturnType,
     callback: RunCallback
@@ -575,6 +584,14 @@ class Transaction extends DatastoreRequest {
     }
   }
 
+  /**
+   * This async function makes a beginTransaction call and returns a promise with
+   * the information returned from the call that was made.
+   *
+   * @param {RunOptions} options The options used for a beginTransaction call.
+   *
+   *
+   **/
   async #runAsync(options: RunOptions): Promise<RequestPromiseReturnType> {
     const reqOpts: RequestOptions = {
       transactionOptions: {},

From 59ee72a4f8116c7b4de2fc95f620bba120f921ba Mon Sep 17 00:00:00 2001
From: Daniel Bruce <djbruce@google.com>
Date: Mon, 6 Nov 2023 10:57:58 -0500
Subject: [PATCH 032/129] Update return type in docs

---
 src/transaction.ts | 1 +
 1 file changed, 1 insertion(+)

diff --git a/src/transaction.ts b/src/transaction.ts
index 89017cd7e..8fef1df4e 100644
--- a/src/transaction.ts
+++ b/src/transaction.ts
@@ -589,6 +589,7 @@ class Transaction extends DatastoreRequest {
    * the information returned from the call that was made.
    *
    * @param {RunOptions} options The options used for a beginTransaction call.
+   * @returns {Promise<RequestPromiseReturnType>}
    *
    *
    **/

From 34e961e9cf290cec443d92283bdf1fed2f788cdc Mon Sep 17 00:00:00 2001
From: Daniel Bruce <djbruce@google.com>
Date: Mon, 6 Nov 2023 11:56:22 -0500
Subject: [PATCH 033/129] Update the tests to include runAsync

runAsync should be in promisfy excludes
---
 test/transaction.ts | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/test/transaction.ts b/test/transaction.ts
index f1a942b05..0535ae022 100644
--- a/test/transaction.ts
+++ b/test/transaction.ts
@@ -57,7 +57,7 @@ const fakePfy = Object.assign({}, pfy, {
       'createQuery',
       'delete',
       'insert',
-      'runAsync',
+      '#runAsync',
       'save',
       'update',
       'upsert',

From 38cd2efacc45562c47f9dbe777b3a2fdf4d43b91 Mon Sep 17 00:00:00 2001
From: Daniel Bruce <djbruce@google.com>
Date: Mon, 6 Nov 2023 11:56:22 -0500
Subject: [PATCH 034/129] refactor: Break transaction.run into smaller pieces
 for use with async functions and other read/write calls

---
 test/transaction.ts | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/test/transaction.ts b/test/transaction.ts
index f1a942b05..0535ae022 100644
--- a/test/transaction.ts
+++ b/test/transaction.ts
@@ -57,7 +57,7 @@ const fakePfy = Object.assign({}, pfy, {
       'createQuery',
       'delete',
       'insert',
-      'runAsync',
+      '#runAsync',
       'save',
       'update',
       'upsert',

From ad39a90be711e97536b74dab6c2e6c48e8df2977 Mon Sep 17 00:00:00 2001
From: Daniel Bruce <djbruce@google.com>
Date: Mon, 6 Nov 2023 13:15:09 -0500
Subject: [PATCH 035/129] Rename a function to be more descriptive

Make sure it is explicit that we are parsing begin results.
---
 src/transaction.ts | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/src/transaction.ts b/src/transaction.ts
index 8fef1df4e..39a2c5558 100644
--- a/src/transaction.ts
+++ b/src/transaction.ts
@@ -557,7 +557,7 @@ class Transaction extends DatastoreRequest {
     const callback =
       typeof optionsOrCallback === 'function' ? optionsOrCallback : cb!;
     this.#runAsync(options).then((response: RequestPromiseReturnType) => {
-      this.#parseRunAsync(response, callback);
+      this.#processBeginResults(response, callback);
     });
   }
 
@@ -570,7 +570,7 @@ class Transaction extends DatastoreRequest {
    * response as arguments.
    *
    **/
-  #parseRunAsync(
+  #processBeginResults(
     response: RequestPromiseReturnType,
     callback: RunCallback
   ): void {

From c9e8e9509ce32ca62c6435d989caf60a39c941c8 Mon Sep 17 00:00:00 2001
From: Daniel Bruce <djbruce@google.com>
Date: Mon, 6 Nov 2023 14:24:22 -0500
Subject: [PATCH 036/129] Move the mutex and the state down to derived class

The mutex and state should be moved down to the derived class. We are going to override get/runQuery/runAggregateQuery there.
---
 src/request.ts     | 18 +-------------
 src/transaction.ts | 62 ++++++++++++++++++++++++++++++++++++----------
 2 files changed, 50 insertions(+), 30 deletions(-)

diff --git a/src/request.ts b/src/request.ts
index 6ba6993dc..cad0751b3 100644
--- a/src/request.ts
+++ b/src/request.ts
@@ -57,7 +57,7 @@ import {
 import {Datastore} from '.';
 import ITimestamp = google.protobuf.ITimestamp;
 import {AggregateQuery} from './aggregate';
-import {Mutex} from 'async-mutex';
+
 
 /**
  * A map of read consistency values to proto codes.
@@ -73,20 +73,6 @@ const CONSISTENCY_PROTO_CODE: ConsistencyProtoCode = {
 // TODO: Typescript had difficulty working with enums before.
 // TODO: Try to get enums working instead of using static properties.
 
-export class TransactionState {
-  static NOT_TRANSACTION = Symbol('NON_TRANSACTION');
-  static NOT_STARTED = Symbol('NOT_STARTED');
-  // IN_PROGRESS currently tracks the expired state as well
-  static IN_PROGRESS = Symbol('IN_PROGRESS');
-}
-
-/*
-export enum TransactionState {
-  NOT_TRANSACTION,
-  NOT_STARTED,
-  IN_PROGRESS
-}
- */
 /**
  * Handle logic for Datastore API operations. Handles request logic for
  * Datastore.
@@ -108,8 +94,6 @@ class DatastoreRequest {
     | Entity;
   datastore!: Datastore;
   // TODO: Replace protected with a symbol for better data hiding.
-  protected mutex = new Mutex();
-  protected state: Symbol = TransactionState.NOT_TRANSACTION;
   [key: string]: Entity;
 
   /**
diff --git a/src/transaction.ts b/src/transaction.ts
index 1a1b0c0bd..a7fd035cc 100644
--- a/src/transaction.ts
+++ b/src/transaction.ts
@@ -29,9 +29,12 @@ import {
   DatastoreRequest,
   RequestOptions,
   PrepareEntityObjectResponse,
-  TransactionState,
+  CreateReadStreamOptions,
+  GetResponse,
+  GetCallback,
 } from './request';
 import {AggregateQuery} from './aggregate';
+import {Mutex} from 'async-mutex';
 
 // RequestPromiseReturnType should line up with the types in RequestCallback
 interface RequestPromiseReturnType {
@@ -51,6 +54,13 @@ interface CommitPromiseReturnType {
   resp?: google.datastore.v1.ICommitResponse;
 }
 
+class TransactionState {
+  static NOT_TRANSACTION = Symbol('NON_TRANSACTION');
+  static NOT_STARTED = Symbol('NOT_STARTED');
+  // IN_PROGRESS currently tracks the expired state as well
+  static IN_PROGRESS = Symbol('IN_PROGRESS');
+}
+
 /**
  * A transaction is a set of Datastore operations on one or more entities. Each
  * transaction is guaranteed to be atomic, which means that transactions are
@@ -77,6 +87,8 @@ class Transaction extends DatastoreRequest {
   request: Function;
   modifiedEntities_: ModifiedEntities;
   skipCommit?: boolean;
+  #mutex = new Mutex();
+  #state: Symbol = TransactionState.NOT_TRANSACTION;
   constructor(datastore: Datastore, options?: TransactionOptions) {
     super();
     /**
@@ -106,7 +118,7 @@ class Transaction extends DatastoreRequest {
 
     // Queue the requests to make when we send the transactional commit.
     this.requests_ = [];
-    this.state = TransactionState.NOT_STARTED;
+    this.#state = TransactionState.NOT_STARTED;
   }
 
   /*! Developer Documentation
@@ -181,13 +193,13 @@ class Transaction extends DatastoreRequest {
   private async commitAsync(
     gaxOptions: CallOptions
   ): Promise<CommitPromiseReturnType> {
-    if (this.state === TransactionState.NOT_STARTED) {
-      const release = await this.mutex.acquire();
+    if (this.#state === TransactionState.NOT_STARTED) {
+      const release = await this.#mutex.acquire();
       try {
         try {
-          if (this.state === TransactionState.NOT_STARTED) {
+          if (this.#state === TransactionState.NOT_STARTED) {
             const runResults = await this.runAsync({gaxOptions});
-            this.parseRunSuccess(runResults);
+            this.#parseRunSuccess(runResults);
           }
         } finally {
           // TODO: Check that error actually reaches user
@@ -343,6 +355,30 @@ class Transaction extends DatastoreRequest {
     });
   }
 
+  get(
+    keys: entity.Key | entity.Key[],
+    options?: CreateReadStreamOptions
+  ): Promise<GetResponse>;
+  get(keys: entity.Key | entity.Key[], callback: GetCallback): void;
+  get(
+    keys: entity.Key | entity.Key[],
+    options: CreateReadStreamOptions,
+    callback: GetCallback
+  ): void;
+  get(
+    keys: entity.Key | entity.Key[],
+    optionsOrCallback?: CreateReadStreamOptions | GetCallback,
+    cb?: GetCallback
+  ): void | Promise<GetResponse> {
+    const options =
+      typeof optionsOrCallback === 'object' && optionsOrCallback
+        ? optionsOrCallback
+        : {};
+    const callback =
+      typeof optionsOrCallback === 'function' ? optionsOrCallback : cb!;
+    super.get(keys, options, callback);
+  }
+
   /**
    * Maps to {@link https://cloud.google.com/nodejs/docs/reference/datastore/latest/datastore/transaction#_google_cloud_datastore_Transaction_save_member_1_|Datastore#save}, forcing the method to be `insert`.
    *
@@ -491,17 +527,17 @@ class Transaction extends DatastoreRequest {
       typeof optionsOrCallback === 'function' ? optionsOrCallback : cb!;
     // TODO: Whenever run is called a second time and a warning is emitted then do nothing.
     // TODO: This means rewriting many tests so that they don't use the same transaction object.
-    if (this.state !== TransactionState.NOT_STARTED) {
+    if (this.#state !== TransactionState.NOT_STARTED) {
       process.emitWarning(
         'run has already been called and should not be called again.'
       );
     }
-    this.mutex.acquire().then(release => {
+    this.#mutex.acquire().then(release => {
       this.runAsync(options)
         .then((response: RequestPromiseReturnType) => {
           // TODO: Probably release the mutex after the id is recorded, but likely doesn't matter since node is single threaded.
           release();
-          this.parseRunAsync(response, callback);
+          this.#parseRunAsync(response, callback);
         })
         .catch((err: any) => {
           // TODO: Remove this catch block
@@ -639,7 +675,7 @@ class Transaction extends DatastoreRequest {
   }
 
   // TODO: Replace with #parseRunAsync when pack and play error is gone
-  private parseRunAsync(
+  #parseRunAsync(
     response: RequestPromiseReturnType,
     callback: RunCallback
   ): void {
@@ -649,14 +685,14 @@ class Transaction extends DatastoreRequest {
       callback(err, null, resp);
       return;
     }
-    this.parseRunSuccess(response);
+    this.#parseRunSuccess(response);
     callback(null, this, resp);
   }
 
-  private parseRunSuccess(response: RequestPromiseReturnType) {
+  #parseRunSuccess(response: RequestPromiseReturnType) {
     const resp = response.resp;
     this.id = resp!.transaction;
-    this.state = TransactionState.IN_PROGRESS;
+    this.#state = TransactionState.IN_PROGRESS;
   }
 
   // TODO: Replace with #runAsync when pack and play error is gone

From 071d04b94b92802fb9ca2a6dfdc9ffa11e9b3f5d Mon Sep 17 00:00:00 2001
From: Daniel Bruce <djbruce@google.com>
Date: Mon, 6 Nov 2023 14:45:34 -0500
Subject: [PATCH 037/129] Add hook to call run before commit

Add the hook to call run before calling commit in existing tests.
---
 src/transaction.ts  | 2 +-
 test/transaction.ts | 9 ++++++++-
 2 files changed, 9 insertions(+), 2 deletions(-)

diff --git a/src/transaction.ts b/src/transaction.ts
index a7fd035cc..f86cb9d09 100644
--- a/src/transaction.ts
+++ b/src/transaction.ts
@@ -181,7 +181,7 @@ class Transaction extends DatastoreRequest {
         : () => {};
     const gaxOptions =
       typeof gaxOptionsOrCallback === 'object' ? gaxOptionsOrCallback : {};
-    this.runCommit(gaxOptions, callback);
+    // this.runCommit(gaxOptions, callback);
     // TODO: Add call to commitAsync here and handle result in the .then hook
     this.commitAsync(gaxOptions).then((response: CommitPromiseReturnType) => {
       callback(response.err, response.resp);
diff --git a/test/transaction.ts b/test/transaction.ts
index 1b0c248a7..12aa463f7 100644
--- a/test/transaction.ts
+++ b/test/transaction.ts
@@ -459,8 +459,15 @@ async.each(
       });
 
       describe('commit', () => {
-        beforeEach(() => {
+        beforeEach(done => {
           transaction.id = TRANSACTION_ID;
+          transaction.request_ = (config, callback) => {
+            done();
+            callback(null, {
+              transaction: Buffer.from(Array.from(Array(100).keys())),
+            });
+          };
+          transaction.run();
         });
 
         afterEach(() => {

From 3ff33ccaa87a0363f39a2cc047236e73cddb0bd7 Mon Sep 17 00:00:00 2001
From: Daniel Bruce <djbruce@google.com>
Date: Mon, 6 Nov 2023 15:11:25 -0500
Subject: [PATCH 038/129] Add commitAsync to promisify excludes

commitAsync should be resolved and then() function should be called as it was not being called before
---
 src/transaction.ts  | 7 +++++++
 test/transaction.ts | 3 ++-
 2 files changed, 9 insertions(+), 1 deletion(-)

diff --git a/src/transaction.ts b/src/transaction.ts
index f86cb9d09..2387d7de6 100644
--- a/src/transaction.ts
+++ b/src/transaction.ts
@@ -184,6 +184,8 @@ class Transaction extends DatastoreRequest {
     // this.runCommit(gaxOptions, callback);
     // TODO: Add call to commitAsync here and handle result in the .then hook
     this.commitAsync(gaxOptions).then((response: CommitPromiseReturnType) => {
+      console.log(response.err);
+      console.log(response.resp);
       callback(response.err, response.resp);
     });
   }
@@ -193,6 +195,7 @@ class Transaction extends DatastoreRequest {
   private async commitAsync(
     gaxOptions: CallOptions
   ): Promise<CommitPromiseReturnType> {
+    console.log('test');
     if (this.#state === TransactionState.NOT_STARTED) {
       const release = await this.#mutex.acquire();
       try {
@@ -213,6 +216,7 @@ class Transaction extends DatastoreRequest {
       this.runCommit(
         gaxOptions,
         (err?: Error | null, resp?: google.datastore.v1.ICommitResponse) => {
+          console.log('resolving');
           resolve({err, resp});
         }
       );
@@ -652,10 +656,12 @@ class Transaction extends DatastoreRequest {
       (err, resp) => {
         if (err) {
           // Rollback automatically for the user.
+          console.log('rolling back');
           this.rollback(() => {
             // Provide the error & API response from the failed commit to the
             // user. Even a failed rollback should be transparent. RE:
             // https://github.com/GoogleCloudPlatform/google-cloud-node/pull/1369#discussion_r66833976
+            console.log('rolled back');
             callback(err, resp);
           });
           return;
@@ -965,6 +971,7 @@ export interface RunOptions {
 promisifyAll(Transaction, {
   exclude: [
     'createAggregationQuery',
+    'commitAsync',
     'createQuery',
     'delete',
     'insert',
diff --git a/test/transaction.ts b/test/transaction.ts
index 12aa463f7..07246b7c6 100644
--- a/test/transaction.ts
+++ b/test/transaction.ts
@@ -54,6 +54,7 @@ const fakePfy = Object.assign({}, pfy, {
     promisified = true;
     assert.deepStrictEqual(options.exclude, [
       'createAggregationQuery',
+      'commitAsync',
       'createQuery',
       'delete',
       'insert',
@@ -156,7 +157,7 @@ async.each(
         });
       });
 
-      describe('run without setting up transaction id', () => {
+      describe.only('run without setting up transaction id', () => {
         // These tests were created so that when transaction.run is restructured we
         // can be confident that it works the same way as before.
         const testResp = {

From bddc35537ecf2c52df93b1baed540920b5f5a78e Mon Sep 17 00:00:00 2001
From: Daniel Bruce <djbruce@google.com>
Date: Mon, 6 Nov 2023 15:14:54 -0500
Subject: [PATCH 039/129] remove the console logs

---
 src/transaction.ts | 6 ------
 1 file changed, 6 deletions(-)

diff --git a/src/transaction.ts b/src/transaction.ts
index 2387d7de6..038ac947f 100644
--- a/src/transaction.ts
+++ b/src/transaction.ts
@@ -184,8 +184,6 @@ class Transaction extends DatastoreRequest {
     // this.runCommit(gaxOptions, callback);
     // TODO: Add call to commitAsync here and handle result in the .then hook
     this.commitAsync(gaxOptions).then((response: CommitPromiseReturnType) => {
-      console.log(response.err);
-      console.log(response.resp);
       callback(response.err, response.resp);
     });
   }
@@ -195,7 +193,6 @@ class Transaction extends DatastoreRequest {
   private async commitAsync(
     gaxOptions: CallOptions
   ): Promise<CommitPromiseReturnType> {
-    console.log('test');
     if (this.#state === TransactionState.NOT_STARTED) {
       const release = await this.#mutex.acquire();
       try {
@@ -216,7 +213,6 @@ class Transaction extends DatastoreRequest {
       this.runCommit(
         gaxOptions,
         (err?: Error | null, resp?: google.datastore.v1.ICommitResponse) => {
-          console.log('resolving');
           resolve({err, resp});
         }
       );
@@ -656,12 +652,10 @@ class Transaction extends DatastoreRequest {
       (err, resp) => {
         if (err) {
           // Rollback automatically for the user.
-          console.log('rolling back');
           this.rollback(() => {
             // Provide the error & API response from the failed commit to the
             // user. Even a failed rollback should be transparent. RE:
             // https://github.com/GoogleCloudPlatform/google-cloud-node/pull/1369#discussion_r66833976
-            console.log('rolled back');
             callback(err, resp);
           });
           return;

From b33d59380518ccaca960e63c32d87ef0032101a3 Mon Sep 17 00:00:00 2001
From: Daniel Bruce <djbruce@google.com>
Date: Mon, 6 Nov 2023 15:37:01 -0500
Subject: [PATCH 040/129] Delete run commit

---
 src/transaction.ts | 2 --
 1 file changed, 2 deletions(-)

diff --git a/src/transaction.ts b/src/transaction.ts
index 038ac947f..d7d6af1b4 100644
--- a/src/transaction.ts
+++ b/src/transaction.ts
@@ -181,8 +181,6 @@ class Transaction extends DatastoreRequest {
         : () => {};
     const gaxOptions =
       typeof gaxOptionsOrCallback === 'object' ? gaxOptionsOrCallback : {};
-    // this.runCommit(gaxOptions, callback);
-    // TODO: Add call to commitAsync here and handle result in the .then hook
     this.commitAsync(gaxOptions).then((response: CommitPromiseReturnType) => {
       callback(response.err, response.resp);
     });

From b055e3ddbfdfd168ddbb7f40044412fcae4437f9 Mon Sep 17 00:00:00 2001
From: Daniel Bruce <djbruce@google.com>
Date: Mon, 6 Nov 2023 15:42:23 -0500
Subject: [PATCH 041/129] Remove the private identifier

Use the private modifier instead to hide data
---
 src/transaction.ts | 16 ++++++++--------
 1 file changed, 8 insertions(+), 8 deletions(-)

diff --git a/src/transaction.ts b/src/transaction.ts
index d7d6af1b4..bc5122399 100644
--- a/src/transaction.ts
+++ b/src/transaction.ts
@@ -181,14 +181,14 @@ class Transaction extends DatastoreRequest {
         : () => {};
     const gaxOptions =
       typeof gaxOptionsOrCallback === 'object' ? gaxOptionsOrCallback : {};
-    this.commitAsync(gaxOptions).then((response: CommitPromiseReturnType) => {
+    this.#commitAsync(gaxOptions).then((response: CommitPromiseReturnType) => {
       callback(response.err, response.resp);
     });
   }
 
   // The promise that commitAsync uses should always resolve and never reject.
   // The data it resolves with will contain response and error information.
-  private async commitAsync(
+  async #commitAsync(
     gaxOptions: CallOptions
   ): Promise<CommitPromiseReturnType> {
     if (this.#state === TransactionState.NOT_STARTED) {
@@ -208,7 +208,7 @@ class Transaction extends DatastoreRequest {
       }
     }
     return await new Promise(resolve => {
-      this.runCommit(
+      this.#runCommit(
         gaxOptions,
         (err?: Error | null, resp?: google.datastore.v1.ICommitResponse) => {
           resolve({err, resp});
@@ -544,10 +544,10 @@ class Transaction extends DatastoreRequest {
     });
   }
 
-  private runCommit(gaxOptions?: CallOptions): Promise<CommitResponse>;
-  private runCommit(callback: CommitCallback): void;
-  private runCommit(gaxOptions: CallOptions, callback: CommitCallback): void;
-  private runCommit(
+  #runCommit(gaxOptions?: CallOptions): Promise<CommitResponse>;
+  #runCommit(callback: CommitCallback): void;
+  #runCommit(gaxOptions: CallOptions, callback: CommitCallback): void;
+  #runCommit(
     gaxOptionsOrCallback?: CallOptions | CommitCallback,
     cb?: CommitCallback
   ): void | Promise<CommitResponse> {
@@ -963,7 +963,7 @@ export interface RunOptions {
 promisifyAll(Transaction, {
   exclude: [
     'createAggregationQuery',
-    'commitAsync',
+    '#commitAsync',
     'createQuery',
     'delete',
     'insert',

From f3c0e1daad51f064f3cc35b5df9f05addd94f504 Mon Sep 17 00:00:00 2001
From: Daniel Bruce <djbruce@google.com>
Date: Mon, 6 Nov 2023 16:17:03 -0500
Subject: [PATCH 042/129] Add withBeginTransaction

withBeginTransaction will be used with all calls that begin transactions and then intend to use the mutex for locking when the begin transaction call is made.
---
 src/transaction.ts  | 48 +++++++++++++++++++++++++++++++++++++++++++--
 test/transaction.ts |  2 +-
 2 files changed, 47 insertions(+), 3 deletions(-)

diff --git a/src/transaction.ts b/src/transaction.ts
index bc5122399..22dda1d66 100644
--- a/src/transaction.ts
+++ b/src/transaction.ts
@@ -53,6 +53,10 @@ interface CommitPromiseReturnType {
   err?: Error | null;
   resp?: google.datastore.v1.ICommitResponse;
 }
+interface PassThroughReturnType<T> {
+  err?: Error | null;
+  resp?: T;
+}
 
 class TransactionState {
   static NOT_TRANSACTION = Symbol('NON_TRANSACTION');
@@ -181,9 +185,21 @@ class Transaction extends DatastoreRequest {
         : () => {};
     const gaxOptions =
       typeof gaxOptionsOrCallback === 'object' ? gaxOptionsOrCallback : {};
-    this.#commitAsync(gaxOptions).then((response: CommitPromiseReturnType) => {
-      callback(response.err, response.resp);
+    type commitPromiseType =
+      PassThroughReturnType<google.datastore.v1.ICommitResponse>;
+    const promise: Promise<commitPromiseType> = new Promise(resolve => {
+      this.#runCommit(
+        gaxOptions,
+        (err?: Error | null, resp?: google.datastore.v1.ICommitResponse) => {
+          resolve({err, resp});
+        }
+      );
     });
+    this.#withBeginTransaction(gaxOptions, promise).then(
+      (response: commitPromiseType) => {
+        callback(response.err, response.resp);
+      }
+    );
   }
 
   // The promise that commitAsync uses should always resolve and never reject.
@@ -217,6 +233,29 @@ class Transaction extends DatastoreRequest {
     });
   }
 
+  async #withBeginTransaction<T>(
+    gaxOptions: CallOptions,
+    promise: Promise<PassThroughReturnType<T>>
+  ): Promise<PassThroughReturnType<T>> {
+    if (this.#state === TransactionState.NOT_STARTED) {
+      const release = await this.#mutex.acquire();
+      try {
+        try {
+          if (this.#state === TransactionState.NOT_STARTED) {
+            const runResults = await this.runAsync({gaxOptions});
+            this.#parseRunSuccess(runResults);
+          }
+        } finally {
+          // TODO: Check that error actually reaches user
+          release(); // TODO: Be sure to release the mutex in the error state
+        }
+      } catch (err: any) {
+        return {err};
+      }
+    }
+    return await promise;
+  }
+
   /**
    * Create a query for the specified kind. See {module:datastore/query} for all
    * of the available methods.
@@ -353,6 +392,7 @@ class Transaction extends DatastoreRequest {
     });
   }
 
+  /*
   get(
     keys: entity.Key | entity.Key[],
     options?: CreateReadStreamOptions
@@ -374,8 +414,12 @@ class Transaction extends DatastoreRequest {
         : {};
     const callback =
       typeof optionsOrCallback === 'function' ? optionsOrCallback : cb!;
+    const promise = new Promise(resolve => {
+      super.get(keys, options, callback);
+    });
     super.get(keys, options, callback);
   }
+   */
 
   /**
    * Maps to {@link https://cloud.google.com/nodejs/docs/reference/datastore/latest/datastore/transaction#_google_cloud_datastore_Transaction_save_member_1_|Datastore#save}, forcing the method to be `insert`.
diff --git a/test/transaction.ts b/test/transaction.ts
index 07246b7c6..9b72140c3 100644
--- a/test/transaction.ts
+++ b/test/transaction.ts
@@ -54,7 +54,7 @@ const fakePfy = Object.assign({}, pfy, {
     promisified = true;
     assert.deepStrictEqual(options.exclude, [
       'createAggregationQuery',
-      'commitAsync',
+      '#commitAsync',
       'createQuery',
       'delete',
       'insert',

From cebc155cb9fcaaf20f42991e80684b36bd1f1ba9 Mon Sep 17 00:00:00 2001
From: Daniel Bruce <djbruce@google.com>
Date: Mon, 6 Nov 2023 16:37:26 -0500
Subject: [PATCH 043/129] Add another level of abstraction

Add #beginWithCallback so that all the read calls can be made with one line of code.
---
 src/transaction.ts | 18 +++++++++++++-----
 1 file changed, 13 insertions(+), 5 deletions(-)

diff --git a/src/transaction.ts b/src/transaction.ts
index 22dda1d66..5b136ffe2 100644
--- a/src/transaction.ts
+++ b/src/transaction.ts
@@ -135,6 +135,18 @@ class Transaction extends DatastoreRequest {
    *      the final commit request with.
    */
 
+  #beginWithCallback<T>(
+    gaxOptions: CallOptions,
+    promise: Promise<PassThroughReturnType<T>>,
+    callback: (err?: Error | null, resp?: T) => void
+  ) {
+    this.#withBeginTransaction(gaxOptions, promise).then(
+      (response: PassThroughReturnType<T>) => {
+        callback(response.err, response.resp);
+      }
+    );
+  }
+
   /**
    * Commit the remote transaction and finalize the current transaction
    * instance.
@@ -195,11 +207,7 @@ class Transaction extends DatastoreRequest {
         }
       );
     });
-    this.#withBeginTransaction(gaxOptions, promise).then(
-      (response: commitPromiseType) => {
-        callback(response.err, response.resp);
-      }
-    );
+    this.#beginWithCallback(gaxOptions, promise, callback);
   }
 
   // The promise that commitAsync uses should always resolve and never reject.

From fe78cc6e0c8d9edc8490733181f134980f597b95 Mon Sep 17 00:00:00 2001
From: Daniel Bruce <djbruce@google.com>
Date: Mon, 6 Nov 2023 17:01:53 -0500
Subject: [PATCH 044/129] commit async is not needed anymore

---
 src/transaction.ts | 37 ++-----------------------------------
 1 file changed, 2 insertions(+), 35 deletions(-)

diff --git a/src/transaction.ts b/src/transaction.ts
index 5b136ffe2..17b087e95 100644
--- a/src/transaction.ts
+++ b/src/transaction.ts
@@ -210,37 +210,6 @@ class Transaction extends DatastoreRequest {
     this.#beginWithCallback(gaxOptions, promise, callback);
   }
 
-  // The promise that commitAsync uses should always resolve and never reject.
-  // The data it resolves with will contain response and error information.
-  async #commitAsync(
-    gaxOptions: CallOptions
-  ): Promise<CommitPromiseReturnType> {
-    if (this.#state === TransactionState.NOT_STARTED) {
-      const release = await this.#mutex.acquire();
-      try {
-        try {
-          if (this.#state === TransactionState.NOT_STARTED) {
-            const runResults = await this.runAsync({gaxOptions});
-            this.#parseRunSuccess(runResults);
-          }
-        } finally {
-          // TODO: Check that error actually reaches user
-          release(); // TODO: Be sure to release the mutex in the error state
-        }
-      } catch (err: any) {
-        return {err};
-      }
-    }
-    return await new Promise(resolve => {
-      this.#runCommit(
-        gaxOptions,
-        (err?: Error | null, resp?: google.datastore.v1.ICommitResponse) => {
-          resolve({err, resp});
-        }
-      );
-    });
-  }
-
   async #withBeginTransaction<T>(
     gaxOptions: CallOptions,
     promise: Promise<PassThroughReturnType<T>>
@@ -400,7 +369,6 @@ class Transaction extends DatastoreRequest {
     });
   }
 
-  /*
   get(
     keys: entity.Key | entity.Key[],
     options?: CreateReadStreamOptions
@@ -423,11 +391,10 @@ class Transaction extends DatastoreRequest {
     const callback =
       typeof optionsOrCallback === 'function' ? optionsOrCallback : cb!;
     const promise = new Promise(resolve => {
-      super.get(keys, options, callback);
+      super.get(keys, options, ());
     });
-    super.get(keys, options, callback);
+
   }
-   */
 
   /**
    * Maps to {@link https://cloud.google.com/nodejs/docs/reference/datastore/latest/datastore/transaction#_google_cloud_datastore_Transaction_save_member_1_|Datastore#save}, forcing the method to be `insert`.

From 36360a7f72bf57c7092d11b8720535e0986b192f Mon Sep 17 00:00:00 2001
From: Daniel Bruce <djbruce@google.com>
Date: Mon, 6 Nov 2023 17:02:28 -0500
Subject: [PATCH 045/129] This data structure is not needed anymore

---
 src/transaction.ts | 5 -----
 1 file changed, 5 deletions(-)

diff --git a/src/transaction.ts b/src/transaction.ts
index 17b087e95..ea342870a 100644
--- a/src/transaction.ts
+++ b/src/transaction.ts
@@ -48,11 +48,6 @@ interface RequestAsPromiseCallback {
   (resolve: RequestResolveFunction): void;
 }
 
-// Data types in CommitPromiseReturnType should line up with CommitCallback
-interface CommitPromiseReturnType {
-  err?: Error | null;
-  resp?: google.datastore.v1.ICommitResponse;
-}
 interface PassThroughReturnType<T> {
   err?: Error | null;
   resp?: T;

From 33e09ac43c53365bdd0d6b3c9cdfa4c95665bd93 Mon Sep 17 00:00:00 2001
From: Daniel Bruce <djbruce@google.com>
Date: Mon, 6 Nov 2023 17:33:29 -0500
Subject: [PATCH 046/129] Replace types associated with run to use generics

The generic parameter should be used for types with run.
---
 src/transaction.ts | 51 ++++++++++++++++++++++++----------------------
 1 file changed, 27 insertions(+), 24 deletions(-)

diff --git a/src/transaction.ts b/src/transaction.ts
index ea342870a..9c905a81a 100644
--- a/src/transaction.ts
+++ b/src/transaction.ts
@@ -36,16 +36,11 @@ import {
 import {AggregateQuery} from './aggregate';
 import {Mutex} from 'async-mutex';
 
-// RequestPromiseReturnType should line up with the types in RequestCallback
-interface RequestPromiseReturnType {
-  err?: Error | null;
-  resp: any; // TODO: Replace with google.datastore.v1.IBeginTransactionResponse and address downstream issues
-}
-interface RequestResolveFunction {
-  (callbackData: RequestPromiseReturnType): void;
+interface RequestResolveFunction<T> {
+  (callbackData: PassThroughReturnType<T>): void;
 }
-interface RequestAsPromiseCallback {
-  (resolve: RequestResolveFunction): void;
+interface RequestAsPromiseCallback<T> {
+  (resolve: RequestResolveFunction<T>): void;
 }
 
 interface PassThroughReturnType<T> {
@@ -131,7 +126,7 @@ class Transaction extends DatastoreRequest {
    */
 
   #beginWithCallback<T>(
-    gaxOptions: CallOptions,
+    gaxOptions: CallOptions | undefined,
     promise: Promise<PassThroughReturnType<T>>,
     callback: (err?: Error | null, resp?: T) => void
   ) {
@@ -206,7 +201,7 @@ class Transaction extends DatastoreRequest {
   }
 
   async #withBeginTransaction<T>(
-    gaxOptions: CallOptions,
+    gaxOptions: CallOptions | undefined,
     promise: Promise<PassThroughReturnType<T>>
   ): Promise<PassThroughReturnType<T>> {
     if (this.#state === TransactionState.NOT_STARTED) {
@@ -385,10 +380,13 @@ class Transaction extends DatastoreRequest {
         : {};
     const callback =
       typeof optionsOrCallback === 'function' ? optionsOrCallback : cb!;
-    const promise = new Promise(resolve => {
-      super.get(keys, options, ());
+    type getPromiseType = PassThroughReturnType<GetResponse>;
+    const promise: Promise<getPromiseType> = new Promise(resolve => {
+      super.get(keys, options, (err?: Error | null, resp?: GetResponse) => {
+        resolve({err, resp});
+      });
     });
-
+    this.#beginWithCallback(options.gaxOptions, promise, callback);
   }
 
   /**
@@ -546,11 +544,16 @@ class Transaction extends DatastoreRequest {
     }
     this.#mutex.acquire().then(release => {
       this.runAsync(options)
-        .then((response: RequestPromiseReturnType) => {
-          // TODO: Probably release the mutex after the id is recorded, but likely doesn't matter since node is single threaded.
-          release();
-          this.#parseRunAsync(response, callback);
-        })
+        // TODO: Replace type with google.datastore.v1.IBeginTransactionResponse and address downstream issues
+        .then(
+          (
+            response: PassThroughReturnType<google.datastore.v1.IBeginTransactionResponse>
+          ) => {
+            // TODO: Probably release the mutex after the id is recorded, but likely doesn't matter since node is single threaded.
+            release();
+            this.#parseRunAsync(response, callback);
+          }
+        )
         .catch((err: any) => {
           // TODO: Remove this catch block
           callback(Error('The error should always be caught by then'), this);
@@ -688,7 +691,7 @@ class Transaction extends DatastoreRequest {
 
   // TODO: Replace with #parseRunAsync when pack and play error is gone
   #parseRunAsync(
-    response: RequestPromiseReturnType,
+    response: PassThroughReturnType<any>,
     callback: RunCallback
   ): void {
     const err = response.err;
@@ -701,7 +704,7 @@ class Transaction extends DatastoreRequest {
     callback(null, this, resp);
   }
 
-  #parseRunSuccess(response: RequestPromiseReturnType) {
+  #parseRunSuccess(response: PassThroughReturnType<any>) {
     const resp = response.resp;
     this.id = resp!.transaction;
     this.#state = TransactionState.IN_PROGRESS;
@@ -710,7 +713,7 @@ class Transaction extends DatastoreRequest {
   // TODO: Replace with #runAsync when pack and play error is gone
   private async runAsync(
     options: RunOptions
-  ): Promise<RequestPromiseReturnType> {
+  ): Promise<PassThroughReturnType<any>> {
     const reqOpts: RequestOptions = {
       transactionOptions: {},
     };
@@ -728,8 +731,8 @@ class Transaction extends DatastoreRequest {
     if (options.transactionOptions) {
       reqOpts.transactionOptions = options.transactionOptions;
     }
-    const promiseFunction: RequestAsPromiseCallback = (
-      resolve: RequestResolveFunction
+    const promiseFunction: RequestAsPromiseCallback<any> = (
+      resolve: RequestResolveFunction<any>
     ) => {
       this.request_(
         {

From 8e8f5456cba623f834d6daf3f83090b437f728ea Mon Sep 17 00:00:00 2001
From: Daniel Bruce <djbruce@google.com>
Date: Mon, 6 Nov 2023 17:36:00 -0500
Subject: [PATCH 047/129] Make response type more specific for parseRunAsync

More specific types are better and it make code easier to read
---
 src/transaction.ts | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/src/transaction.ts b/src/transaction.ts
index 9c905a81a..e046652e8 100644
--- a/src/transaction.ts
+++ b/src/transaction.ts
@@ -691,7 +691,7 @@ class Transaction extends DatastoreRequest {
 
   // TODO: Replace with #parseRunAsync when pack and play error is gone
   #parseRunAsync(
-    response: PassThroughReturnType<any>,
+    response: PassThroughReturnType<google.datastore.v1.IBeginTransactionResponse>,
     callback: RunCallback
   ): void {
     const err = response.err;

From c129bd29050144de040b99cdef97b9528bd5479f Mon Sep 17 00:00:00 2001
From: Daniel Bruce <djbruce@google.com>
Date: Tue, 7 Nov 2023 09:24:42 -0500
Subject: [PATCH 048/129] Modify comment
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Modify comment so that it doesn’t reference the way the code was before.
---
 src/transaction.ts | 3 +--
 1 file changed, 1 insertion(+), 2 deletions(-)

diff --git a/src/transaction.ts b/src/transaction.ts
index 39a2c5558..914442e4e 100644
--- a/src/transaction.ts
+++ b/src/transaction.ts
@@ -621,8 +621,7 @@ class Transaction extends DatastoreRequest {
           reqOpts,
           gaxOpts: options.gaxOptions,
         },
-        // In original functionality sometimes a response is provided when an error is also provided
-        // reject only allows us to pass back an error so use resolve for both error and non-error cases.
+        // Always use resolve because then this function can return both the error and the response
         (err, resp) => {
           resolve({
             err,

From d4b40e86712e77f0eca13a7ba7bc45e6970e5353 Mon Sep 17 00:00:00 2001
From: Daniel Bruce <djbruce@google.com>
Date: Tue, 7 Nov 2023 09:57:02 -0500
Subject: [PATCH 049/129] Add implementation for runQuery
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

runQuery should make the call to begin the transaction first if that hasn’t already happened yet.
---
 src/transaction.ts | 52 +++++++++++++++++++++++++++++++++++++++++++---
 1 file changed, 49 insertions(+), 3 deletions(-)

diff --git a/src/transaction.ts b/src/transaction.ts
index e046652e8..2d4befce8 100644
--- a/src/transaction.ts
+++ b/src/transaction.ts
@@ -22,7 +22,13 @@ import {google} from '../protos/protos';
 
 import {Datastore, TransactionOptions} from '.';
 import {entity, Entity, Entities} from './entity';
-import {Query} from './query';
+import {
+  Query,
+  RunQueryCallback,
+  RunQueryInfo,
+  RunQueryOptions,
+  RunQueryResponse,
+} from './query';
 import {
   CommitCallback,
   CommitResponse,
@@ -36,6 +42,10 @@ import {
 import {AggregateQuery} from './aggregate';
 import {Mutex} from 'async-mutex';
 
+type RunQueryResponseOptional = [
+  Entity[] | undefined,
+  RunQueryInfo | undefined,
+];
 interface RequestResolveFunction<T> {
   (callbackData: PassThroughReturnType<T>): void;
 }
@@ -380,8 +390,8 @@ class Transaction extends DatastoreRequest {
         : {};
     const callback =
       typeof optionsOrCallback === 'function' ? optionsOrCallback : cb!;
-    type getPromiseType = PassThroughReturnType<GetResponse>;
-    const promise: Promise<getPromiseType> = new Promise(resolve => {
+    type promiseType = PassThroughReturnType<GetResponse>;
+    const promise: Promise<promiseType> = new Promise(resolve => {
       super.get(keys, options, (err?: Error | null, resp?: GetResponse) => {
         resolve({err, resp});
       });
@@ -754,6 +764,42 @@ class Transaction extends DatastoreRequest {
     return new Promise(promiseFunction);
   }
 
+  runQuery(query: Query, options?: RunQueryOptions): Promise<RunQueryResponse>;
+  runQuery(
+    query: Query,
+    options: RunQueryOptions,
+    callback: RunQueryCallback
+  ): void;
+  runQuery(query: Query, callback: RunQueryCallback): void;
+  runQuery(
+    query: Query,
+    optionsOrCallback?: RunQueryOptions | RunQueryCallback,
+    cb?: RunQueryCallback
+  ): void | Promise<RunQueryResponse> {
+    const options =
+      typeof optionsOrCallback === 'object' && optionsOrCallback
+        ? optionsOrCallback
+        : {};
+    const callback =
+      typeof optionsOrCallback === 'function' ? optionsOrCallback : cb!;
+    type promiseType = PassThroughReturnType<RunQueryResponseOptional>;
+    const promise: Promise<promiseType> = new Promise(resolve => {
+      super.runQuery(
+        query,
+        options,
+        (err: Error | null, entities?: Entity[], info?: RunQueryInfo) => {
+          resolve({err, resp: [entities, info]});
+        }
+      );
+    });
+    this.#withBeginTransaction(options.gaxOptions, promise).then(
+      (response: PassThroughReturnType<RunQueryResponseOptional>) => {
+        const error = response.err ? response.err : null;
+        callback(error, response.resp);
+      }
+    );
+  }
+
   /**
    * Insert or update the specified object(s) in the current transaction. If a
    * key is incomplete, its associated object is inserted and the original Key

From 6c7a147a252a7ba14e689804101ee9c4f6ab25b8 Mon Sep 17 00:00:00 2001
From: Daniel Bruce <djbruce@google.com>
Date: Tue, 7 Nov 2023 14:39:41 -0500
Subject: [PATCH 050/129] Making fixes for runAggregationQuery

Fixes for run aggregation query. Still getting undefined results.
---
 src/request.ts           |   6 ++-
 src/transaction.ts       | 106 ++++++++++++++++++++++++++++++++++++++-
 system-test/datastore.ts |  20 +++-----
 test/transaction.ts      |   5 +-
 4 files changed, 121 insertions(+), 16 deletions(-)

diff --git a/src/request.ts b/src/request.ts
index cad0751b3..522c8f97a 100644
--- a/src/request.ts
+++ b/src/request.ts
@@ -58,7 +58,6 @@ import {Datastore} from '.';
 import ITimestamp = google.protobuf.ITimestamp;
 import {AggregateQuery} from './aggregate';
 
-
 /**
  * A map of read consistency values to proto codes.
  *
@@ -589,6 +588,7 @@ class DatastoreRequest {
     const reqOpts: RunAggregationQueryRequest = Object.assign(sharedQueryOpts, {
       aggregationQuery: aggregationQueryOptions,
     });
+    console.log('Making run aggregation query call');
     this.request_(
       {
         client: 'DatastoreClient',
@@ -597,6 +597,9 @@ class DatastoreRequest {
         gaxOpts: options.gaxOptions,
       },
       (err, res) => {
+        console.log('Run aggregation response');
+        console.log(err);
+        console.log(res);
         if (res && res.batch) {
           const results = res.batch.aggregationResults;
           const finalResults = results
@@ -613,6 +616,7 @@ class DatastoreRequest {
                 )
               )
             );
+          console.log('calling callback');
           callback(err, finalResults);
         } else {
           callback(err, res);
diff --git a/src/transaction.ts b/src/transaction.ts
index 2d4befce8..df5228d77 100644
--- a/src/transaction.ts
+++ b/src/transaction.ts
@@ -38,6 +38,7 @@ import {
   CreateReadStreamOptions,
   GetResponse,
   GetCallback,
+  RequestCallback,
 } from './request';
 import {AggregateQuery} from './aggregate';
 import {Mutex} from 'async-mutex';
@@ -233,6 +234,35 @@ class Transaction extends DatastoreRequest {
     return await promise;
   }
 
+  // TODO: Simplify the generics here: remove PassThroughReturnType
+  async #someFunction<T>(
+    gaxOptions: CallOptions | undefined,
+    resolver: (
+      resolve: (
+        value: PassThroughReturnType<T> | PromiseLike<PassThroughReturnType<T>>
+      ) => void
+    ) => void
+  ): Promise<PassThroughReturnType<T>> {
+    if (this.#state === TransactionState.NOT_STARTED) {
+      const release = await this.#mutex.acquire();
+      try {
+        try {
+          if (this.#state === TransactionState.NOT_STARTED) {
+            const runResults = await this.runAsync({gaxOptions});
+            this.#parseRunSuccess(runResults);
+          }
+        } finally {
+          // TODO: Check that error actually reaches user
+          release(); // TODO: Be sure to release the mutex in the error state
+        }
+      } catch (err: any) {
+        return {err};
+      }
+    }
+    const promiseResults = await new Promise(resolver);
+    return promiseResults;
+  }
+
   /**
    * Create a query for the specified kind. See {module:datastore/query} for all
    * of the available methods.
@@ -764,6 +794,76 @@ class Transaction extends DatastoreRequest {
     return new Promise(promiseFunction);
   }
 
+  runAggregationQuery(
+    query: AggregateQuery,
+    options?: RunQueryOptions
+  ): Promise<RunQueryResponse>;
+  runAggregationQuery(
+    query: AggregateQuery,
+    options: RunQueryOptions,
+    callback: RequestCallback
+  ): void;
+  runAggregationQuery(query: AggregateQuery, callback: RequestCallback): void;
+  runAggregationQuery(
+    query: AggregateQuery,
+    optionsOrCallback?: RunQueryOptions | RequestCallback,
+    cb?: RequestCallback
+  ): void | Promise<RunQueryResponse> {
+    const options =
+      typeof optionsOrCallback === 'object' && optionsOrCallback
+        ? optionsOrCallback
+        : {};
+    const a = 7;
+    const callback =
+      typeof optionsOrCallback === 'function' ? optionsOrCallback : cb!;
+    type promiseType = PassThroughReturnType<PassThroughReturnType<any>>;
+    const someOtherPromise = new Promise(resolve => {
+      console.log('some other');
+      resolve('something');
+    });
+    type resolverType = (
+      resolve: (
+        value:
+          | PassThroughReturnType<any>
+          | PromiseLike<PassThroughReturnType<any>>
+      ) => void
+    ) => void;
+    console.log('call runAggregationQuery');
+    const resolver: resolverType = resolve => {
+      console.log('resolving');
+      super.runAggregationQuery(
+        query,
+        options,
+        (err?: Error | null, resp?: any) => {
+          console.log('resolved');
+          resolve({err, resp});
+        }
+      );
+    };
+    console.log('some-function');
+    this.#someFunction(options.gaxOptions, resolver).then(
+      (response: promiseType) => {
+        console.log('passing into callback');
+        const error = response.err ? response.err : null;
+        console.log('passing into callback 2');
+        console.log(error);
+        console.log(response.resp);
+        // callback(error, response.resp);
+        callback(null, 'some-response');
+      }
+    );
+    //const promise: Promise<promiseType> = new Promise();
+    // console.log('starting withBeginTransaction');
+    /*
+    this.#withBeginTransaction(options.gaxOptions, promise).then(
+      (response: promiseType) => {
+        const error = response.err ? response.err : null;
+        callback(error, response.resp);
+      }
+    );
+    */
+  }
+
   runQuery(query: Query, options?: RunQueryOptions): Promise<RunQueryResponse>;
   runQuery(
     query: Query,
@@ -793,7 +893,7 @@ class Transaction extends DatastoreRequest {
       );
     });
     this.#withBeginTransaction(options.gaxOptions, promise).then(
-      (response: PassThroughReturnType<RunQueryResponseOptional>) => {
+      (response: promiseType) => {
         const error = response.err ? response.err : null;
         callback(error, response.resp);
       }
@@ -1029,11 +1129,15 @@ promisifyAll(Transaction, {
     '#commitAsync',
     'createQuery',
     'delete',
+    'get',
     'insert',
     'parseRunAsync',
     'parseTransactionResponse',
+    'runAggregationQuery',
     'runAsync',
+    'runQuery',
     'save',
+    '#someFunction',
     'update',
     'upsert',
   ],
diff --git a/system-test/datastore.ts b/system-test/datastore.ts
index 2d1e6bdc1..1fa89cdfc 100644
--- a/system-test/datastore.ts
+++ b/system-test/datastore.ts
@@ -1749,7 +1749,7 @@ async.each(
           assert.deepStrictEqual(results, [{property_1: 4}]);
         });
       });
-      describe('transactions', () => {
+      describe.only('transactions', () => {
         it('should run in a transaction', async () => {
           const key = datastore.key(['Company', 'Google']);
           const obj = {
@@ -1862,6 +1862,7 @@ async.each(
             );
           });
           it('should aggregate query within a count transaction', async () => {
+            console.log('running test of interest');
             const transaction = datastore.transaction();
             await transaction.run();
             const query = transaction.createQuery('Company');
@@ -1870,12 +1871,11 @@ async.each(
               .count('total');
             let result;
             try {
+              const allResults = await aggregateQuery.run();
               [result] = await aggregateQuery.run();
             } catch (e) {
               await transaction.rollback();
-              assert.fail(
-                'The aggregation query run should have been successful'
-              );
+              throw e;
             }
             assert.deepStrictEqual(result, [{total: 2}]);
             await transaction.commit();
@@ -1892,9 +1892,7 @@ async.each(
               [result] = await aggregateQuery.run();
             } catch (e) {
               await transaction.rollback();
-              assert.fail(
-                'The aggregation query run should have been successful'
-              );
+              throw e;
             }
             assert.deepStrictEqual(result, [{'total rating': 200}]);
             await transaction.commit();
@@ -1911,9 +1909,7 @@ async.each(
               [result] = await aggregateQuery.run();
             } catch (e) {
               await transaction.rollback();
-              assert.fail(
-                'The aggregation query run should have been successful'
-              );
+              throw e;
             }
             assert.deepStrictEqual(result, [{'average rating': 100}]);
             await transaction.commit();
@@ -1929,9 +1925,7 @@ async.each(
                 [result] = await aggregateQuery.run();
               } catch (e) {
                 await transaction.rollback();
-                assert.fail(
-                  'The aggregation query run should have been successful'
-                );
+                throw e;
               }
               return result;
             }
diff --git a/test/transaction.ts b/test/transaction.ts
index 9b72140c3..2f0313993 100644
--- a/test/transaction.ts
+++ b/test/transaction.ts
@@ -57,10 +57,13 @@ const fakePfy = Object.assign({}, pfy, {
       '#commitAsync',
       'createQuery',
       'delete',
+      'get',
       'insert',
       'parseRunAsync',
       'parseTransactionResponse',
+      'runAggregationQuery',
       'runAsync',
+      'runQuery',
       'save',
       'update',
       'upsert',
@@ -157,7 +160,7 @@ async.each(
         });
       });
 
-      describe.only('run without setting up transaction id', () => {
+      describe('run without setting up transaction id', () => {
         // These tests were created so that when transaction.run is restructured we
         // can be confident that it works the same way as before.
         const testResp = {

From 4c3cb5c0ba9fd9ffe10717ed2a87752b484135a3 Mon Sep 17 00:00:00 2001
From: Daniel Bruce <djbruce@google.com>
Date: Tue, 7 Nov 2023 16:38:35 -0500
Subject: [PATCH 051/129] Write tests for runAggregateQuery

Make sure that runAggregateQuery return results make it back to the user.
---
 src/request.ts      |   1 +
 test/transaction.ts | 218 ++++++++++++++++++++++++++++++++++++++++++--
 2 files changed, 211 insertions(+), 8 deletions(-)

diff --git a/src/request.ts b/src/request.ts
index 522c8f97a..4a5a4abe0 100644
--- a/src/request.ts
+++ b/src/request.ts
@@ -600,6 +600,7 @@ class DatastoreRequest {
         console.log('Run aggregation response');
         console.log(err);
         console.log(res);
+        console.log(JSON.stringify(res));
         if (res && res.batch) {
           const results = res.batch.aggregationResults;
           const finalResults = results
diff --git a/test/transaction.ts b/test/transaction.ts
index 2f0313993..7e68b6687 100644
--- a/test/transaction.ts
+++ b/test/transaction.ts
@@ -26,12 +26,13 @@ import {
   Query,
   TransactionOptions,
   Transaction,
+  AggregateField,
 } from '../src';
 import {Entity} from '../src/entity';
 import * as tsTypes from '../src/transaction';
 import * as sinon from 'sinon';
 import {Callback, CallOptions, ClientStub} from 'google-gax';
-import {CommitCallback, RequestConfig} from '../src/request';
+import {CommitCallback, RequestCallback, RequestConfig} from '../src/request';
 import {SECOND_DATABASE_ID} from './index';
 import {google} from '../protos/protos';
 import {RunCallback} from '../src/transaction';
@@ -163,7 +164,7 @@ async.each(
       describe('run without setting up transaction id', () => {
         // These tests were created so that when transaction.run is restructured we
         // can be confident that it works the same way as before.
-        const testResp = {
+        const testRunResp = {
           transaction: Buffer.from(Array.from(Array(100).keys())),
         };
         const namespace = 'run-without-mock';
@@ -221,7 +222,7 @@ async.each(
                   {} | null | undefined
                 >
               ) => {
-                callback(new Error(testErrorMessage), testResp);
+                callback(new Error(testErrorMessage), testRunResp);
               };
             }
           });
@@ -244,7 +245,7 @@ async.each(
               assert(error);
               assert.strictEqual(error.message, testErrorMessage);
               assert.strictEqual(transaction, null);
-              assert.strictEqual(response, testResp);
+              assert.strictEqual(response, testRunResp);
               done();
             };
             transactionWithoutMock.run({}, runCallback);
@@ -266,14 +267,14 @@ async.each(
                   {} | null | undefined
                 >
               ) => {
-                callback(null, testResp);
+                callback(null, testRunResp);
               };
             }
           });
           it('should send back the response when awaiting a promise', async () => {
             const [transaction, resp] = await transactionWithoutMock.run();
             assert.strictEqual(transaction, transactionWithoutMock);
-            assert.strictEqual(resp, testResp);
+            assert.strictEqual(resp, testRunResp);
           });
           it('should send back the response when using a callback', done => {
             const runCallback: RunCallback = (
@@ -282,7 +283,7 @@ async.each(
               response?: google.datastore.v1.IBeginTransactionResponse
             ) => {
               assert.strictEqual(error, null);
-              assert.strictEqual(response, testResp);
+              assert.strictEqual(response, testRunResp);
               assert.strictEqual(transaction, transactionWithoutMock);
               done();
             };
@@ -348,7 +349,7 @@ async.each(
                     {} | null | undefined
                   >
                 ) => {
-                  callback(null, testResp);
+                  callback(null, testRunResp);
                 };
               }
             });
@@ -459,6 +460,207 @@ async.each(
               });
             });
           });
+
+          describe('runAggregationQuery without setting up transaction id when run returns a response', () => {
+            // These tests were created so that when transaction.runAggregateQuery is restructured we
+            // can be confident that it works the same way as before.
+
+            const runAggregationQueryResp = {
+              batch: {
+                aggregationResults: [
+                  {
+                    aggregateProperties: {
+                      'average rating': {
+                        meaning: 0,
+                        excludeFromIndexes: false,
+                        doubleValue: 100,
+                        valueType: 'doubleValue',
+                      },
+                    },
+                  },
+                ],
+                moreResults:
+                  google.datastore.v1.QueryResultBatch.MoreResultsType
+                    .NO_MORE_RESULTS,
+                readTime: {seconds: '1699390681', nanos: 961667000},
+              },
+              query: null,
+              transaction: testRunResp.transaction,
+            };
+            const namespace = 'run-without-mock';
+            const projectId = 'project-id';
+            const testErrorMessage = 'test-run-Aggregate-Query-error';
+            const options = {
+              projectId,
+              namespace,
+            };
+            const datastore = new Datastore(options);
+            const q = datastore.createQuery('Character');
+            const aggregate = datastore
+              .createAggregationQuery(q)
+              .addAggregation(AggregateField.average('appearances'));
+            let transactionWithoutMock: Transaction;
+            const dataClientName = 'DatastoreClient';
+            let dataClient: ClientStub | undefined;
+            let originalRunAggregateQueryMethod: Function;
+
+            beforeEach(async () => {
+              // Create a fresh transaction for each test because transaction state changes after a commit.
+              transactionWithoutMock = datastore.transaction();
+              // In this before hook, save the original beginTransaction method in a variable.
+              // After tests are finished, reassign beginTransaction to the variable.
+              // This way, mocking beginTransaction in this block doesn't affect other tests.
+              const gapic = Object.freeze({
+                v1: require('../src/v1'),
+              });
+              // Datastore Gapic clients haven't been initialized yet so we initialize them here.
+              datastore.clients_.set(
+                dataClientName,
+                new gapic.v1[dataClientName](options)
+              );
+              dataClient = datastore.clients_.get(dataClientName);
+              if (dataClient && dataClient.runAggregationQuery) {
+                originalRunAggregateQueryMethod =
+                  dataClient.runAggregationQuery;
+              }
+              if (dataClient && dataClient.beginTransaction) {
+                dataClient.beginTransaction = (
+                  request: protos.google.datastore.v1.IBeginTransactionRequest,
+                  options: CallOptions,
+                  callback: Callback<
+                    protos.google.datastore.v1.IBeginTransactionResponse,
+                    | protos.google.datastore.v1.IBeginTransactionRequest
+                    | null
+                    | undefined,
+                    {} | null | undefined
+                  >
+                ) => {
+                  callback(null, testRunResp);
+                };
+              }
+            });
+
+            afterEach(() => {
+              // beginTransaction has likely been mocked out in these tests.
+              // We should reassign beginTransaction back to its original value for tests outside this block.
+              if (dataClient && originalRunAggregateQueryMethod) {
+                dataClient.runAggregationQuery =
+                  originalRunAggregateQueryMethod;
+              }
+            });
+
+            describe('should pass error back to the user', async () => {
+              beforeEach(() => {
+                // Mock out begin transaction and send error back to the user
+                // from the Gapic layer.
+                if (dataClient) {
+                  dataClient.runAggregationQuery = (
+                    request: protos.google.datastore.v1.IRunAggregationQueryRequest,
+                    options: CallOptions,
+                    callback: Callback<
+                      protos.google.datastore.v1.IRunAggregationQueryResponse,
+                      | protos.google.datastore.v1.IRunAggregationQueryRequest
+                      | null
+                      | undefined,
+                      {} | null | undefined
+                    >
+                  ) => {
+                    callback(
+                      new Error(testErrorMessage),
+                      runAggregationQueryResp
+                    );
+                  };
+                }
+              });
+
+              it('should send back the error when awaiting a promise', async () => {
+                try {
+                  await transactionWithoutMock.run();
+                  await transactionWithoutMock.runAggregationQuery(aggregate);
+                  assert.fail('The run call should have failed.');
+                } catch (error: any) {
+                  // TODO: Substitute type any
+                  assert.strictEqual(error['message'], testErrorMessage);
+                }
+              });
+              it('should send back the error when using a callback', done => {
+                const runAggregateQueryCallback: RequestCallback = (
+                  error: Error | null | undefined,
+                  response?: any
+                ) => {
+                  assert(error);
+                  assert.strictEqual(error.message, testErrorMessage);
+                  assert.strictEqual(response, runAggregationQueryResp);
+                  done();
+                };
+                transactionWithoutMock.run(
+                  (
+                    error: Error | null,
+                    transaction: Transaction | null,
+                    response?: google.datastore.v1.IBeginTransactionResponse
+                  ) => {
+                    transactionWithoutMock.runAggregationQuery(
+                      aggregate,
+                      runAggregateQueryCallback
+                    );
+                  }
+                );
+              });
+            });
+            describe('should pass response back to the user', async () => {
+              beforeEach(() => {
+                // Mock out begin transaction and send a response
+                // back to the user from the Gapic layer.
+                if (dataClient) {
+                  dataClient.runAggregationQuery = (
+                    request: protos.google.datastore.v1.IRunAggregationQueryRequest,
+                    options: CallOptions,
+                    callback: Callback<
+                      protos.google.datastore.v1.IRunAggregationQueryResponse,
+                      | protos.google.datastore.v1.IRunAggregationQueryRequest
+                      | null
+                      | undefined,
+                      {} | null | undefined
+                    >
+                  ) => {
+                    callback(null, runAggregationQueryResp);
+                  };
+                }
+              });
+              it('should send back the response when awaiting a promise', async () => {
+                await transactionWithoutMock.run();
+                const allResults =
+                  await transactionWithoutMock.runAggregationQuery(aggregate);
+                const [runAggregateQueryResults] = allResults;
+                assert.strictEqual(
+                  runAggregateQueryResults,
+                  runAggregationQueryResp
+                );
+              });
+              it('should send back the response when using a callback', done => {
+                const runAggregateQueryCallback: RequestCallback = (
+                  error: Error | null | undefined,
+                  response?: any
+                ) => {
+                  assert.strictEqual(error, null);
+                  assert.strictEqual(response, runAggregationQueryResp);
+                  done();
+                };
+                transactionWithoutMock.run(
+                  (
+                    error: Error | null,
+                    transaction: Transaction | null,
+                    response?: google.datastore.v1.IBeginTransactionResponse
+                  ) => {
+                    transactionWithoutMock.runAggregationQuery(
+                      aggregate,
+                      runAggregateQueryCallback
+                    );
+                  }
+                );
+              });
+            });
+          });
         });
       });
 

From d5072295d2f174c6c78721efe46f6d98b2072b13 Mon Sep 17 00:00:00 2001
From: Daniel Bruce <djbruce@google.com>
Date: Tue, 7 Nov 2023 17:01:45 -0500
Subject: [PATCH 052/129] Get one test case passing for runAggregateQuery

runAggregateQuery should not be excluded by promisify. Otherwise it will not return a promise, but we want it to return a promise.
---
 src/transaction.ts  | 8 +++-----
 test/transaction.ts | 7 +++----
 2 files changed, 6 insertions(+), 9 deletions(-)

diff --git a/src/transaction.ts b/src/transaction.ts
index df5228d77..65d141320 100644
--- a/src/transaction.ts
+++ b/src/transaction.ts
@@ -260,6 +260,8 @@ class Transaction extends DatastoreRequest {
       }
     }
     const promiseResults = await new Promise(resolver);
+    console.log('Promise results');
+    console.log(promiseResults);
     return promiseResults;
   }
 
@@ -848,8 +850,7 @@ class Transaction extends DatastoreRequest {
         console.log('passing into callback 2');
         console.log(error);
         console.log(response.resp);
-        // callback(error, response.resp);
-        callback(null, 'some-response');
+        callback(error, response.resp);
       }
     );
     //const promise: Promise<promiseType> = new Promise();
@@ -1129,13 +1130,10 @@ promisifyAll(Transaction, {
     '#commitAsync',
     'createQuery',
     'delete',
-    'get',
     'insert',
     'parseRunAsync',
     'parseTransactionResponse',
-    'runAggregationQuery',
     'runAsync',
-    'runQuery',
     'save',
     '#someFunction',
     'update',
diff --git a/test/transaction.ts b/test/transaction.ts
index 7e68b6687..17b90f3ae 100644
--- a/test/transaction.ts
+++ b/test/transaction.ts
@@ -58,14 +58,12 @@ const fakePfy = Object.assign({}, pfy, {
       '#commitAsync',
       'createQuery',
       'delete',
-      'get',
       'insert',
       'parseRunAsync',
       'parseTransactionResponse',
-      'runAggregationQuery',
       'runAsync',
-      'runQuery',
       'save',
+      '#someFunction',
       'update',
       'upsert',
     ]);
@@ -576,7 +574,8 @@ async.each(
               it('should send back the error when awaiting a promise', async () => {
                 try {
                   await transactionWithoutMock.run();
-                  await transactionWithoutMock.runAggregationQuery(aggregate);
+                  const results =
+                    await transactionWithoutMock.runAggregationQuery(aggregate);
                   assert.fail('The run call should have failed.');
                 } catch (error: any) {
                   // TODO: Substitute type any

From 8afa968f193d164e07794473ef2a7971d8b068a2 Mon Sep 17 00:00:00 2001
From: Daniel Bruce <djbruce@google.com>
Date: Tue, 7 Nov 2023 17:03:52 -0500
Subject: [PATCH 053/129] remove console log clutter

---
 src/request.ts     |  5 -----
 src/transaction.ts | 14 --------------
 2 files changed, 19 deletions(-)

diff --git a/src/request.ts b/src/request.ts
index 4a5a4abe0..6c3833b38 100644
--- a/src/request.ts
+++ b/src/request.ts
@@ -597,10 +597,6 @@ class DatastoreRequest {
         gaxOpts: options.gaxOptions,
       },
       (err, res) => {
-        console.log('Run aggregation response');
-        console.log(err);
-        console.log(res);
-        console.log(JSON.stringify(res));
         if (res && res.batch) {
           const results = res.batch.aggregationResults;
           const finalResults = results
@@ -617,7 +613,6 @@ class DatastoreRequest {
                 )
               )
             );
-          console.log('calling callback');
           callback(err, finalResults);
         } else {
           callback(err, res);
diff --git a/src/transaction.ts b/src/transaction.ts
index 65d141320..72930ee05 100644
--- a/src/transaction.ts
+++ b/src/transaction.ts
@@ -260,8 +260,6 @@ class Transaction extends DatastoreRequest {
       }
     }
     const promiseResults = await new Promise(resolver);
-    console.log('Promise results');
-    console.log(promiseResults);
     return promiseResults;
   }
 
@@ -819,10 +817,6 @@ class Transaction extends DatastoreRequest {
     const callback =
       typeof optionsOrCallback === 'function' ? optionsOrCallback : cb!;
     type promiseType = PassThroughReturnType<PassThroughReturnType<any>>;
-    const someOtherPromise = new Promise(resolve => {
-      console.log('some other');
-      resolve('something');
-    });
     type resolverType = (
       resolve: (
         value:
@@ -830,26 +824,18 @@ class Transaction extends DatastoreRequest {
           | PromiseLike<PassThroughReturnType<any>>
       ) => void
     ) => void;
-    console.log('call runAggregationQuery');
     const resolver: resolverType = resolve => {
-      console.log('resolving');
       super.runAggregationQuery(
         query,
         options,
         (err?: Error | null, resp?: any) => {
-          console.log('resolved');
           resolve({err, resp});
         }
       );
     };
-    console.log('some-function');
     this.#someFunction(options.gaxOptions, resolver).then(
       (response: promiseType) => {
-        console.log('passing into callback');
         const error = response.err ? response.err : null;
-        console.log('passing into callback 2');
-        console.log(error);
-        console.log(response.resp);
         callback(error, response.resp);
       }
     );

From db48cb30e497b13d1131f08f3b9715439e0dea1e Mon Sep 17 00:00:00 2001
From: Daniel Bruce <djbruce@google.com>
Date: Tue, 7 Nov 2023 17:18:36 -0500
Subject: [PATCH 054/129] Change tests for runAggregationQuery

Change the test to use deep strict equal since the objects being compared will not be reference equal.
---
 src/request.ts      | 1 -
 test/transaction.ts | 9 +++++----
 2 files changed, 5 insertions(+), 5 deletions(-)

diff --git a/src/request.ts b/src/request.ts
index 6c3833b38..1397a09f1 100644
--- a/src/request.ts
+++ b/src/request.ts
@@ -588,7 +588,6 @@ class DatastoreRequest {
     const reqOpts: RunAggregationQueryRequest = Object.assign(sharedQueryOpts, {
       aggregationQuery: aggregationQueryOptions,
     });
-    console.log('Making run aggregation query call');
     this.request_(
       {
         client: 'DatastoreClient',
diff --git a/test/transaction.ts b/test/transaction.ts
index 17b90f3ae..dd9a30e78 100644
--- a/test/transaction.ts
+++ b/test/transaction.ts
@@ -463,6 +463,7 @@ async.each(
             // These tests were created so that when transaction.runAggregateQuery is restructured we
             // can be confident that it works the same way as before.
 
+            const runAggregationQueryUserResp = [{'average rating': 100}];
             const runAggregationQueryResp = {
               batch: {
                 aggregationResults: [
@@ -589,7 +590,7 @@ async.each(
                 ) => {
                   assert(error);
                   assert.strictEqual(error.message, testErrorMessage);
-                  assert.strictEqual(response, runAggregationQueryResp);
+                  assert.deepStrictEqual(response, runAggregationQueryUserResp);
                   done();
                 };
                 transactionWithoutMock.run(
@@ -631,9 +632,9 @@ async.each(
                 const allResults =
                   await transactionWithoutMock.runAggregationQuery(aggregate);
                 const [runAggregateQueryResults] = allResults;
-                assert.strictEqual(
+                assert.deepStrictEqual(
                   runAggregateQueryResults,
-                  runAggregationQueryResp
+                  runAggregationQueryUserResp
                 );
               });
               it('should send back the response when using a callback', done => {
@@ -642,7 +643,7 @@ async.each(
                   response?: any
                 ) => {
                   assert.strictEqual(error, null);
-                  assert.strictEqual(response, runAggregationQueryResp);
+                  assert.deepStrictEqual(response, runAggregationQueryUserResp);
                   done();
                 };
                 transactionWithoutMock.run(

From 69cd491f80a6369213b0e889a6af71b57475ef68 Mon Sep 17 00:00:00 2001
From: Daniel Bruce <djbruce@google.com>
Date: Tue, 7 Nov 2023 17:33:01 -0500
Subject: [PATCH 055/129] Add resolver type

Eliminate some unused code
---
 src/transaction.ts | 34 +++++++++++-----------------------
 1 file changed, 11 insertions(+), 23 deletions(-)

diff --git a/src/transaction.ts b/src/transaction.ts
index 72930ee05..7da98be37 100644
--- a/src/transaction.ts
+++ b/src/transaction.ts
@@ -47,16 +47,22 @@ type RunQueryResponseOptional = [
   Entity[] | undefined,
   RunQueryInfo | undefined,
 ];
+interface PassThroughReturnType<T> {
+  err?: Error | null;
+  resp?: T;
+}
 interface RequestResolveFunction<T> {
   (callbackData: PassThroughReturnType<T>): void;
 }
 interface RequestAsPromiseCallback<T> {
   (resolve: RequestResolveFunction<T>): void;
 }
-
-interface PassThroughReturnType<T> {
-  err?: Error | null;
-  resp?: T;
+interface ResolverType<T> {
+  (
+    resolve: (
+      value: PassThroughReturnType<T> | PromiseLike<PassThroughReturnType<T>>
+    ) => void
+  ): void;
 }
 
 class TransactionState {
@@ -813,18 +819,10 @@ class Transaction extends DatastoreRequest {
       typeof optionsOrCallback === 'object' && optionsOrCallback
         ? optionsOrCallback
         : {};
-    const a = 7;
     const callback =
       typeof optionsOrCallback === 'function' ? optionsOrCallback : cb!;
     type promiseType = PassThroughReturnType<PassThroughReturnType<any>>;
-    type resolverType = (
-      resolve: (
-        value:
-          | PassThroughReturnType<any>
-          | PromiseLike<PassThroughReturnType<any>>
-      ) => void
-    ) => void;
-    const resolver: resolverType = resolve => {
+    const resolver: ResolverType<any> = resolve => {
       super.runAggregationQuery(
         query,
         options,
@@ -839,16 +837,6 @@ class Transaction extends DatastoreRequest {
         callback(error, response.resp);
       }
     );
-    //const promise: Promise<promiseType> = new Promise();
-    // console.log('starting withBeginTransaction');
-    /*
-    this.#withBeginTransaction(options.gaxOptions, promise).then(
-      (response: promiseType) => {
-        const error = response.err ? response.err : null;
-        callback(error, response.resp);
-      }
-    );
-    */
   }
 
   runQuery(query: Query, options?: RunQueryOptions): Promise<RunQueryResponse>;

From c31193e91540498f73397ffbb0ba491eedba7ba3 Mon Sep 17 00:00:00 2001
From: Daniel Bruce <djbruce@google.com>
Date: Wed, 8 Nov 2023 11:19:40 -0500
Subject: [PATCH 056/129] Create a transaction wrapper class for testing

This is going to be a useful test for mocking out various layers to make sure they work the same way as before.
---
 test/transaction.ts | 407 +++++++++++++++++++++++++++++++++++++++++++-
 1 file changed, 398 insertions(+), 9 deletions(-)

diff --git a/test/transaction.ts b/test/transaction.ts
index 7b5ed19d6..d82d4f1ec 100644
--- a/test/transaction.ts
+++ b/test/transaction.ts
@@ -26,12 +26,13 @@ import {
   Query,
   TransactionOptions,
   Transaction,
+  AggregateField,
 } from '../src';
 import {Entity} from '../src/entity';
 import * as tsTypes from '../src/transaction';
 import * as sinon from 'sinon';
 import {Callback, CallOptions, ClientStub} from 'google-gax';
-import {CommitCallback, RequestConfig} from '../src/request';
+import {CommitCallback, RequestCallback, RequestConfig} from '../src/request';
 import {SECOND_DATABASE_ID} from './index';
 import {google} from '../protos/protos';
 import {RunCallback} from '../src/transaction';
@@ -157,7 +158,7 @@ async.each(
       describe('run without setting up transaction id', () => {
         // These tests were created so that when transaction.run is restructured we
         // can be confident that it works the same way as before.
-        const testResp = {
+        const testRunResp = {
           transaction: Buffer.from(Array.from(Array(100).keys())),
         };
         const namespace = 'run-without-mock';
@@ -215,7 +216,7 @@ async.each(
                   {} | null | undefined
                 >
               ) => {
-                callback(new Error(testErrorMessage), testResp);
+                callback(new Error(testErrorMessage), testRunResp);
               };
             }
           });
@@ -238,7 +239,7 @@ async.each(
               assert(error);
               assert.strictEqual(error.message, testErrorMessage);
               assert.strictEqual(transaction, null);
-              assert.strictEqual(response, testResp);
+              assert.strictEqual(response, testRunResp);
               done();
             };
             transactionWithoutMock.run({}, runCallback);
@@ -260,14 +261,14 @@ async.each(
                   {} | null | undefined
                 >
               ) => {
-                callback(null, testResp);
+                callback(null, testRunResp);
               };
             }
           });
           it('should send back the response when awaiting a promise', async () => {
             const [transaction, resp] = await transactionWithoutMock.run();
             assert.strictEqual(transaction, transactionWithoutMock);
-            assert.strictEqual(resp, testResp);
+            assert.strictEqual(resp, testRunResp);
           });
           it('should send back the response when using a callback', done => {
             const runCallback: RunCallback = (
@@ -276,13 +277,12 @@ async.each(
               response?: google.datastore.v1.IBeginTransactionResponse
             ) => {
               assert.strictEqual(error, null);
-              assert.strictEqual(response, testResp);
+              assert.strictEqual(response, testRunResp);
               assert.strictEqual(transaction, transactionWithoutMock);
               done();
             };
             transactionWithoutMock.run({}, runCallback);
           });
-          // TODO: Add a test here for calling commit
           describe('commit without setting up transaction id when run returns a response', () => {
             // These tests were created so that when transaction.commit is restructured we
             // can be confident that it works the same way as before.
@@ -342,7 +342,7 @@ async.each(
                     {} | null | undefined
                   >
                 ) => {
-                  callback(null, testResp);
+                  callback(null, testRunResp);
                 };
               }
             });
@@ -453,9 +453,398 @@ async.each(
               });
             });
           });
+          describe('runAggregationQuery without setting up transaction id when run returns a response', () => {
+            // These tests were created so that when transaction.runAggregateQuery is restructured we
+            // can be confident that it works the same way as before.
+
+            const runAggregationQueryUserResp = [{'average rating': 100}];
+            const runAggregationQueryResp = {
+              batch: {
+                aggregationResults: [
+                  {
+                    aggregateProperties: {
+                      'average rating': {
+                        meaning: 0,
+                        excludeFromIndexes: false,
+                        doubleValue: 100,
+                        valueType: 'doubleValue',
+                      },
+                    },
+                  },
+                ],
+                moreResults:
+                  google.datastore.v1.QueryResultBatch.MoreResultsType
+                    .NO_MORE_RESULTS,
+                readTime: {seconds: '1699390681', nanos: 961667000},
+              },
+              query: null,
+              transaction: testRunResp.transaction,
+            };
+            const namespace = 'run-without-mock';
+            const projectId = 'project-id';
+            const testErrorMessage = 'test-run-Aggregate-Query-error';
+            const options = {
+              projectId,
+              namespace,
+            };
+            const datastore = new Datastore(options);
+            const q = datastore.createQuery('Character');
+            const aggregate = datastore
+              .createAggregationQuery(q)
+              .addAggregation(AggregateField.average('appearances'));
+            let transactionWithoutMock: Transaction;
+            const dataClientName = 'DatastoreClient';
+            let dataClient: ClientStub | undefined;
+            let originalRunAggregateQueryMethod: Function;
+
+            beforeEach(async () => {
+              // Create a fresh transaction for each test because transaction state changes after a commit.
+              transactionWithoutMock = datastore.transaction();
+              // In this before hook, save the original beginTransaction method in a variable.
+              // After tests are finished, reassign beginTransaction to the variable.
+              // This way, mocking beginTransaction in this block doesn't affect other tests.
+              const gapic = Object.freeze({
+                v1: require('../src/v1'),
+              });
+              // Datastore Gapic clients haven't been initialized yet so we initialize them here.
+              datastore.clients_.set(
+                dataClientName,
+                new gapic.v1[dataClientName](options)
+              );
+              dataClient = datastore.clients_.get(dataClientName);
+              if (dataClient && dataClient.runAggregationQuery) {
+                originalRunAggregateQueryMethod =
+                  dataClient.runAggregationQuery;
+              }
+              if (dataClient && dataClient.beginTransaction) {
+                dataClient.beginTransaction = (
+                  request: protos.google.datastore.v1.IBeginTransactionRequest,
+                  options: CallOptions,
+                  callback: Callback<
+                    protos.google.datastore.v1.IBeginTransactionResponse,
+                    | protos.google.datastore.v1.IBeginTransactionRequest
+                    | null
+                    | undefined,
+                    {} | null | undefined
+                  >
+                ) => {
+                  callback(null, testRunResp);
+                };
+              }
+            });
+
+            afterEach(() => {
+              // beginTransaction has likely been mocked out in these tests.
+              // We should reassign beginTransaction back to its original value for tests outside this block.
+              if (dataClient && originalRunAggregateQueryMethod) {
+                dataClient.runAggregationQuery =
+                  originalRunAggregateQueryMethod;
+              }
+            });
+
+            describe('should pass error back to the user', async () => {
+              beforeEach(() => {
+                // Mock out begin transaction and send error back to the user
+                // from the Gapic layer.
+                if (dataClient) {
+                  dataClient.runAggregationQuery = (
+                    request: protos.google.datastore.v1.IRunAggregationQueryRequest,
+                    options: CallOptions,
+                    callback: Callback<
+                      protos.google.datastore.v1.IRunAggregationQueryResponse,
+                      | protos.google.datastore.v1.IRunAggregationQueryRequest
+                      | null
+                      | undefined,
+                      {} | null | undefined
+                    >
+                  ) => {
+                    callback(
+                      new Error(testErrorMessage),
+                      runAggregationQueryResp
+                    );
+                  };
+                }
+              });
+
+              it('should send back the error when awaiting a promise', async () => {
+                try {
+                  await transactionWithoutMock.run();
+                  const results =
+                    await transactionWithoutMock.runAggregationQuery(aggregate);
+                  assert.fail('The run call should have failed.');
+                } catch (error: any) {
+                  // TODO: Substitute type any
+                  assert.strictEqual(error['message'], testErrorMessage);
+                }
+              });
+              it('should send back the error when using a callback', done => {
+                const runAggregateQueryCallback: RequestCallback = (
+                  error: Error | null | undefined,
+                  response?: any
+                ) => {
+                  assert(error);
+                  assert.strictEqual(error.message, testErrorMessage);
+                  assert.deepStrictEqual(response, runAggregationQueryUserResp);
+                  done();
+                };
+                transactionWithoutMock.run(
+                  (
+                    error: Error | null,
+                    transaction: Transaction | null,
+                    response?: google.datastore.v1.IBeginTransactionResponse
+                  ) => {
+                    transactionWithoutMock.runAggregationQuery(
+                      aggregate,
+                      runAggregateQueryCallback
+                    );
+                  }
+                );
+              });
+            });
+            describe('should pass response back to the user', async () => {
+              beforeEach(() => {
+                // Mock out begin transaction and send a response
+                // back to the user from the Gapic layer.
+                if (dataClient) {
+                  dataClient.runAggregationQuery = (
+                    request: protos.google.datastore.v1.IRunAggregationQueryRequest,
+                    options: CallOptions,
+                    callback: Callback<
+                      protos.google.datastore.v1.IRunAggregationQueryResponse,
+                      | protos.google.datastore.v1.IRunAggregationQueryRequest
+                      | null
+                      | undefined,
+                      {} | null | undefined
+                    >
+                  ) => {
+                    callback(null, runAggregationQueryResp);
+                  };
+                }
+              });
+              it('should send back the response when awaiting a promise', async () => {
+                await transactionWithoutMock.run();
+                const allResults =
+                  await transactionWithoutMock.runAggregationQuery(aggregate);
+                const [runAggregateQueryResults] = allResults;
+                assert.deepStrictEqual(
+                  runAggregateQueryResults,
+                  runAggregationQueryUserResp
+                );
+              });
+              it('should send back the response when using a callback', done => {
+                const runAggregateQueryCallback: RequestCallback = (
+                  error: Error | null | undefined,
+                  response?: any
+                ) => {
+                  assert.strictEqual(error, null);
+                  assert.deepStrictEqual(response, runAggregationQueryUserResp);
+                  done();
+                };
+                transactionWithoutMock.run(
+                  (
+                    error: Error | null,
+                    transaction: Transaction | null,
+                    response?: google.datastore.v1.IBeginTransactionResponse
+                  ) => {
+                    transactionWithoutMock.runAggregationQuery(
+                      aggregate,
+                      runAggregateQueryCallback
+                    );
+                  }
+                );
+              });
+            });
+          });
         });
       });
 
+      // TODO: Add a test here for calling commit
+      describe('various functions without setting up transaction id when run returns a response', () => {
+        // These tests were created so that when transaction.run is restructured we
+        // can be confident that it works the same way as before.
+        const testRunResp = {
+          transaction: Buffer.from(Array.from(Array(100).keys())),
+        };
+        // These tests were created so that when transaction.commit is restructured we
+        // can be confident that it works the same way as before.
+        const testCommitResp = {
+          mutationResults: [
+            {
+              key: {
+                path: [
+                  {
+                    kind: 'some-kind',
+                  },
+                ],
+              },
+            },
+          ],
+        };
+        const testErrorMessage = 'test-commit-error';
+
+        class MockedTransactionWrapper {
+          transaction: Transaction;
+          dataClient: any; // TODO: replace with data client type
+          mockedBeginTransaction: any;
+          mockedFunction: any; // TODO: replace with type
+
+          constructor() {
+            const namespace = 'run-without-mock';
+            const projectId = 'project-id';
+            const options = {
+              projectId,
+              namespace,
+            };
+            const datastore = new Datastore(options);
+            const dataClientName = 'DatastoreClient';
+            // Create a fresh transaction for each test because transaction state changes after a commit.
+            this.transaction = datastore.transaction();
+            // In this before hook, save the original beginTransaction method in a variable.
+            // After tests are finished, reassign beginTransaction to the variable.
+            // This way, mocking beginTransaction in this block doesn't affect other tests.
+            const gapic = Object.freeze({
+              v1: require('../src/v1'),
+            });
+            // Datastore Gapic clients haven't been initialized yet so we initialize them here.
+            datastore.clients_.set(
+              dataClientName,
+              new gapic.v1[dataClientName](options)
+            );
+            const dataClient = datastore.clients_.get(dataClientName);
+            // Mock begin transaction
+            if (dataClient && dataClient.beginTransaction) {
+              this.mockedBeginTransaction = dataClient.beginTransaction;
+            }
+            if (dataClient && dataClient.beginTransaction) {
+              dataClient.beginTransaction = (
+                request: protos.google.datastore.v1.IBeginTransactionRequest,
+                options: CallOptions,
+                callback: Callback<
+                  protos.google.datastore.v1.IBeginTransactionResponse,
+                  | protos.google.datastore.v1.IBeginTransactionRequest
+                  | null
+                  | undefined,
+                  {} | null | undefined
+                >
+              ) => {
+                callback(null, testRunResp);
+              };
+            }
+            this.dataClient = dataClient;
+          }
+          mockGapicFunction<ResponseType>(
+            functionName: string,
+            response: ResponseType,
+            error: Error | null
+          ) {
+            const dataClient = this.dataClient;
+            if (dataClient && dataClient[functionName]) {
+              this.mockedFunction = dataClient[functionName];
+            }
+            if (dataClient && dataClient[functionName]) {
+              dataClient[functionName] = (
+                request: any, // RequestType
+                options: CallOptions,
+                callback: Callback<
+                  ResponseType,
+                  | any // RequestType
+                  | null
+                  | undefined,
+                  {} | null | undefined
+                >
+              ) => {
+                callback(error, response);
+              };
+            }
+          }
+
+          resetBeginTransaction() {
+            if (this.dataClient && this.dataClient.beginTransaction) {
+              this.dataClient.beginTransaction = this.mockedBeginTransaction;
+            }
+          }
+          // TODO: Allow several functions to be mocked, eliminate string parameter
+          resetGapicFunctions(functionName: string) {
+            this.dataClient[functionName] = this.mockedFunction;
+          }
+        }
+
+        describe('commit', () => {
+          let transactionWrapper: MockedTransactionWrapper;
+
+          beforeEach(async () => {
+            transactionWrapper = new MockedTransactionWrapper();
+          });
+
+          afterEach(() => {
+            transactionWrapper.resetBeginTransaction();
+            transactionWrapper.resetGapicFunctions('commit');
+          });
+
+          describe('should pass error back to the user', async () => {
+            beforeEach(() => {
+              transactionWrapper.mockGapicFunction(
+                'commit',
+                testCommitResp,
+                new Error(testErrorMessage)
+              );
+            });
+
+            it('should send back the error when awaiting a promise', async () => {
+              try {
+                await transactionWrapper.transaction.run();
+                await transactionWrapper.transaction.commit();
+                assert.fail('The run call should have failed.');
+              } catch (error: any) {
+                // TODO: Substitute type any
+                assert.strictEqual(error['message'], testErrorMessage);
+              }
+            });
+            it('should send back the error when using a callback', done => {
+              const commitCallback: CommitCallback = (
+                error: Error | null | undefined,
+                response?: google.datastore.v1.ICommitResponse
+              ) => {
+                assert(error);
+                assert.strictEqual(error.message, testErrorMessage);
+                assert.strictEqual(response, testCommitResp);
+                done();
+              };
+              transactionWrapper.transaction.run(() => {
+                transactionWrapper.transaction.commit(commitCallback);
+              });
+            });
+          });
+          describe('should pass response back to the user', async () => {
+            beforeEach(() => {
+              transactionWrapper.mockGapicFunction(
+                'commit',
+                testCommitResp,
+                null
+              );
+            });
+            it('should send back the response when awaiting a promise', async () => {
+              await transactionWrapper.transaction.run();
+              const [commitResults] =
+                await transactionWrapper.transaction.commit();
+              assert.strictEqual(commitResults, testCommitResp);
+            });
+            it('should send back the response when using a callback', done => {
+              const commitCallback: CommitCallback = (
+                error: Error | null | undefined,
+                response?: google.datastore.v1.ICommitResponse
+              ) => {
+                assert.strictEqual(error, null);
+                assert.strictEqual(response, testCommitResp);
+                done();
+              };
+              transactionWrapper.transaction.run(() => {
+                transactionWrapper.transaction.commit(commitCallback);
+              });
+            });
+          });
+        });
+      });
       describe('commit', () => {
         beforeEach(() => {
           transaction.id = TRANSACTION_ID;

From a608589c088da92d422874575c53ab4344be6c66 Mon Sep 17 00:00:00 2001
From: Daniel Bruce <djbruce@google.com>
Date: Wed, 8 Nov 2023 11:34:48 -0500
Subject: [PATCH 057/129] Clean up mocked transaction wrapper

The mocked transaction wrapper should reset all mocked gapic functions and not have to be told which ones to reset.
---
 test/transaction.ts | 16 +++++++++++++---
 1 file changed, 13 insertions(+), 3 deletions(-)

diff --git a/test/transaction.ts b/test/transaction.ts
index d82d4f1ec..8976e025f 100644
--- a/test/transaction.ts
+++ b/test/transaction.ts
@@ -687,6 +687,7 @@ async.each(
           dataClient: any; // TODO: replace with data client type
           mockedBeginTransaction: any;
           mockedFunction: any; // TODO: replace with type
+          functionsMocked: {name: string; mockedFunction: any}[];
 
           constructor() {
             const namespace = 'run-without-mock';
@@ -731,6 +732,7 @@ async.each(
               };
             }
             this.dataClient = dataClient;
+            this.functionsMocked = [];
           }
           mockGapicFunction<ResponseType>(
             functionName: string,
@@ -738,7 +740,12 @@ async.each(
             error: Error | null
           ) {
             const dataClient = this.dataClient;
+            // TODO: Check here that function hasn't been mocked out already
             if (dataClient && dataClient[functionName]) {
+              this.functionsMocked.push({
+                name: functionName,
+                mockedFunction: dataClient[functionName],
+              });
               this.mockedFunction = dataClient[functionName];
             }
             if (dataClient && dataClient[functionName]) {
@@ -764,8 +771,11 @@ async.each(
             }
           }
           // TODO: Allow several functions to be mocked, eliminate string parameter
-          resetGapicFunctions(functionName: string) {
-            this.dataClient[functionName] = this.mockedFunction;
+          resetGapicFunctions() {
+            this.functionsMocked.forEach(functionMocked => {
+              this.dataClient[functionMocked.name] =
+                functionMocked.mockedFunction;
+            });
           }
         }
 
@@ -778,7 +788,7 @@ async.each(
 
           afterEach(() => {
             transactionWrapper.resetBeginTransaction();
-            transactionWrapper.resetGapicFunctions('commit');
+            transactionWrapper.resetGapicFunctions();
           });
 
           describe('should pass error back to the user', async () => {

From 2d23d07e49640fffe55cd8d2b86ab0bdb49e8149 Mon Sep 17 00:00:00 2001
From: Daniel Bruce <djbruce@google.com>
Date: Wed, 8 Nov 2023 11:37:17 -0500
Subject: [PATCH 058/129] Move test information for commit into commit block

---
 test/transaction.ts | 32 ++++++++++++++++----------------
 1 file changed, 16 insertions(+), 16 deletions(-)

diff --git a/test/transaction.ts b/test/transaction.ts
index 8976e025f..fe7e20a29 100644
--- a/test/transaction.ts
+++ b/test/transaction.ts
@@ -665,22 +665,6 @@ async.each(
         const testRunResp = {
           transaction: Buffer.from(Array.from(Array(100).keys())),
         };
-        // These tests were created so that when transaction.commit is restructured we
-        // can be confident that it works the same way as before.
-        const testCommitResp = {
-          mutationResults: [
-            {
-              key: {
-                path: [
-                  {
-                    kind: 'some-kind',
-                  },
-                ],
-              },
-            },
-          ],
-        };
-        const testErrorMessage = 'test-commit-error';
 
         class MockedTransactionWrapper {
           transaction: Transaction;
@@ -780,6 +764,22 @@ async.each(
         }
 
         describe('commit', () => {
+          // These tests were created so that when transaction.commit is restructured we
+          // can be confident that it works the same way as before.
+          const testCommitResp = {
+            mutationResults: [
+              {
+                key: {
+                  path: [
+                    {
+                      kind: 'some-kind',
+                    },
+                  ],
+                },
+              },
+            ],
+          };
+          const testErrorMessage = 'test-commit-error';
           let transactionWrapper: MockedTransactionWrapper;
 
           beforeEach(async () => {

From e09ab3614527db61cfbe54cb2e4cfe0cf7ac7620 Mon Sep 17 00:00:00 2001
From: Daniel Bruce <djbruce@google.com>
Date: Wed, 8 Nov 2023 13:04:50 -0500
Subject: [PATCH 059/129] Add gapic mocked tests for aggregation query

Gapic mocked tests for run aggregation query need to be written that use the mock transaction object.
---
 test/transaction.ts | 123 +++++++++++++++++++++++++++++++++++++++++++-
 1 file changed, 121 insertions(+), 2 deletions(-)

diff --git a/test/transaction.ts b/test/transaction.ts
index fe7e20a29..e573dc818 100644
--- a/test/transaction.ts
+++ b/test/transaction.ts
@@ -37,6 +37,7 @@ import {SECOND_DATABASE_ID} from './index';
 import {google} from '../protos/protos';
 import {RunCallback} from '../src/transaction';
 import * as protos from '../protos/protos';
+import {AggregateQuery} from '../src/aggregate';
 const async = require('async');
 
 // eslint-disable-next-line @typescript-eslint/no-explicit-any
@@ -667,6 +668,7 @@ async.each(
         };
 
         class MockedTransactionWrapper {
+          datastore: Datastore;
           transaction: Transaction;
           dataClient: any; // TODO: replace with data client type
           mockedBeginTransaction: any;
@@ -717,6 +719,7 @@ async.each(
             }
             this.dataClient = dataClient;
             this.functionsMocked = [];
+            this.datastore = datastore;
           }
           mockGapicFunction<ResponseType>(
             functionName: string,
@@ -764,8 +767,6 @@ async.each(
         }
 
         describe('commit', () => {
-          // These tests were created so that when transaction.commit is restructured we
-          // can be confident that it works the same way as before.
           const testCommitResp = {
             mutationResults: [
               {
@@ -854,6 +855,124 @@ async.each(
             });
           });
         });
+        describe('runAggregationQuery', () => {
+          // These tests were created so that when transaction.runAggregationQuery is restructured we
+          // can be confident that it works the same way as before.
+          const runAggregationQueryUserResp = [{'average rating': 100}];
+          const runAggregationQueryResp = {
+            batch: {
+              aggregationResults: [
+                {
+                  aggregateProperties: {
+                    'average rating': {
+                      meaning: 0,
+                      excludeFromIndexes: false,
+                      doubleValue: 100,
+                      valueType: 'doubleValue',
+                    },
+                  },
+                },
+              ],
+              moreResults:
+                google.datastore.v1.QueryResultBatch.MoreResultsType
+                  .NO_MORE_RESULTS,
+              readTime: {seconds: '1699390681', nanos: 961667000},
+            },
+            query: null,
+            transaction: testRunResp.transaction,
+          };
+          const testErrorMessage = 'test-run-Aggregate-Query-error';
+          let transactionWrapper: MockedTransactionWrapper;
+          let transaction: Transaction;
+          let aggregate: AggregateQuery;
+
+          beforeEach(async () => {
+            transactionWrapper = new MockedTransactionWrapper();
+            transaction = transactionWrapper.transaction;
+            const q = transactionWrapper.datastore.createQuery('Character');
+            aggregate = transactionWrapper.datastore
+              .createAggregationQuery(q)
+              .addAggregation(AggregateField.average('appearances'));
+          });
+
+          afterEach(() => {
+            transactionWrapper.resetBeginTransaction();
+            transactionWrapper.resetGapicFunctions();
+          });
+
+          describe('should pass error back to the user', async () => {
+            beforeEach(() => {
+              transactionWrapper.mockGapicFunction(
+                'runAggregationQuery',
+                runAggregationQueryResp,
+                new Error(testErrorMessage)
+              );
+            });
+
+            it('should send back the error when awaiting a promise', async () => {
+              try {
+                await transaction.run();
+                await transaction.runAggregationQuery(aggregate);
+                assert.fail('The run call should have failed.');
+              } catch (error: any) {
+                // TODO: Substitute type any
+                assert.strictEqual(error['message'], testErrorMessage);
+              }
+            });
+            it('should send back the error when using a callback', done => {
+              const runAggregateQueryCallback: RequestCallback = (
+                error: Error | null | undefined,
+                response?: any
+              ) => {
+                assert(error);
+                assert.strictEqual(error.message, testErrorMessage);
+                assert.deepStrictEqual(response, runAggregationQueryUserResp);
+                done();
+              };
+              transaction.run(() => {
+                transaction.runAggregationQuery(
+                  aggregate,
+                  runAggregateQueryCallback
+                );
+              });
+            });
+          });
+          describe('should pass response back to the user', async () => {
+            beforeEach(() => {
+              transactionWrapper.mockGapicFunction(
+                'runAggregationQuery',
+                runAggregationQueryResp,
+                null
+              );
+            });
+            it('should send back the response when awaiting a promise', async () => {
+              await transaction.run();
+              const allResults =
+                await transaction.runAggregationQuery(aggregate);
+              const [runAggregateQueryResults] = allResults;
+              assert.deepStrictEqual(
+                runAggregateQueryResults,
+                runAggregationQueryUserResp
+              );
+            });
+            it('should send back the response when using a callback', done => {
+              const runAggregateQueryCallback: CommitCallback = (
+                error: Error | null | undefined,
+                response?: any
+              ) => {
+                assert.strictEqual(error, null);
+                assert.deepStrictEqual(response, runAggregationQueryUserResp);
+                done();
+              };
+              transaction.run(() => {
+                transaction.runAggregationQuery(
+                  aggregate,
+                  runAggregateQueryCallback
+                );
+              });
+            });
+          });
+        });
       });
       describe('commit', () => {
         beforeEach(() => {

From fbbb0d83efc992e2bb6c8e879155e00b69a60e3f Mon Sep 17 00:00:00 2001
From: Daniel Bruce <djbruce@google.com>
Date: Wed, 8 Nov 2023 13:48:09 -0500
Subject: [PATCH 060/129] Fixing up the runQuery test

---
 test/transaction.ts | 92 +++++++++++++++++++++++++++++++++++++++++++++
 1 file changed, 92 insertions(+)

diff --git a/test/transaction.ts b/test/transaction.ts
index e573dc818..bc612d48a 100644
--- a/test/transaction.ts
+++ b/test/transaction.ts
@@ -38,6 +38,7 @@ import {google} from '../protos/protos';
 import {RunCallback} from '../src/transaction';
 import * as protos from '../protos/protos';
 import {AggregateQuery} from '../src/aggregate';
+import {RunQueryCallback, RunQueryResponse} from '../src/query';
 const async = require('async');
 
 // eslint-disable-next-line @typescript-eslint/no-explicit-any
@@ -973,6 +974,97 @@ async.each(
             });
           });
         });
+        describe.only('runQuery', () => {
+          // These tests were created so that when transaction.runAggregationQuery is restructured we
+          // can be confident that it works the same way as before.
+          const runQueryResp = {
+            batch: {
+              entityResults: [],
+            },
+          };
+          const runQueryUserResp: Entity[] = [];
+          const testErrorMessage = 'test-run-Query-error';
+          let transactionWrapper: MockedTransactionWrapper;
+          let transaction: Transaction;
+          let q: Query;
+
+          beforeEach(async () => {
+            transactionWrapper = new MockedTransactionWrapper();
+            transaction = transactionWrapper.transaction;
+            q = transactionWrapper.datastore.createQuery('Character');
+          });
+
+          afterEach(() => {
+            transactionWrapper.resetBeginTransaction();
+            transactionWrapper.resetGapicFunctions();
+          });
+
+          describe('should pass error back to the user', async () => {
+            beforeEach(() => {
+              transactionWrapper.mockGapicFunction(
+                'runQuery',
+                runQueryResp,
+                new Error(testErrorMessage)
+              );
+            });
+
+            it('should send back the error when awaiting a promise', async () => {
+              try {
+                await transaction.run();
+                await transaction.runQuery(q);
+                assert.fail('The run call should have failed.');
+              } catch (error: any) {
+                // TODO: Substitute type any
+                assert.strictEqual(error['message'], testErrorMessage);
+              }
+            });
+            it('should send back the error when using a callback', done => {
+              const callback: RunQueryCallback = (
+                error: Error | null | undefined,
+                response?: any
+              ) => {
+                assert(error);
+                assert.strictEqual(error.message, testErrorMessage);
+                assert.deepStrictEqual(response, undefined);
+                done();
+              };
+              transaction.run(() => {
+                transaction.runQuery(q, callback);
+              });
+            });
+          });
+          describe('should pass response back to the user', async () => {
+            beforeEach(() => {
+              transactionWrapper.mockGapicFunction(
+                'runQuery',
+                runQueryUserResp,
+                null
+              );
+            });
+            it('should send back the response when awaiting a promise', async () => {
+              await transaction.run();
+              const allResults = await transaction.runQuery(q);
+              const [runAggregateQueryResults] = allResults;
+              assert.deepStrictEqual(
+                runAggregateQueryResults,
+                runQueryUserResp
+              );
+            });
+            it('should send back the response when using a callback', done => {
+              const callback: RunQueryCallback = (
+                error: Error | null | undefined,
+                response?: any
+              ) => {
+                assert.strictEqual(error, null);
+                assert.deepStrictEqual(response, runQueryUserResp);
+                done();
+              };
+              transaction.run(() => {
+                transaction.runQuery(q, callback);
+              });
+            });
+          });
+        });
       });
       describe('commit', () => {
         beforeEach(() => {

From cf85fd0e63f5ac6f744e0b615c743be78c4b7ce1 Mon Sep 17 00:00:00 2001
From: Daniel Bruce <djbruce@google.com>
Date: Wed, 8 Nov 2023 14:02:34 -0500
Subject: [PATCH 061/129] Finished the runQuery tests

The runQuery tests are finished so we can use this to take apart the function from end to end.
---
 test/transaction.ts | 6 +++++-
 1 file changed, 5 insertions(+), 1 deletion(-)

diff --git a/test/transaction.ts b/test/transaction.ts
index bc612d48a..6210ada17 100644
--- a/test/transaction.ts
+++ b/test/transaction.ts
@@ -980,6 +980,10 @@ async.each(
           const runQueryResp = {
             batch: {
               entityResults: [],
+              endCursor: {
+                type: 'Buffer',
+                data: Buffer.from(Array.from(Array(100).keys())),
+              },
             },
           };
           const runQueryUserResp: Entity[] = [];
@@ -1037,7 +1041,7 @@ async.each(
             beforeEach(() => {
               transactionWrapper.mockGapicFunction(
                 'runQuery',
-                runQueryUserResp,
+                runQueryResp,
                 null
               );
             });

From 39b9f2482cd9e4eaa707d12dac3bde81a4a2eed2 Mon Sep 17 00:00:00 2001
From: Daniel Bruce <djbruce@google.com>
Date: Wed, 8 Nov 2023 15:05:49 -0500
Subject: [PATCH 062/129] Finished the get tests

The get tests now make sure that the data coming back from the gapic layer results in the same values for users.
---
 test/transaction.ts | 124 ++++++++++++++++++++++++++++++++++++++++++--
 1 file changed, 121 insertions(+), 3 deletions(-)

diff --git a/test/transaction.ts b/test/transaction.ts
index 6210ada17..d975c3baa 100644
--- a/test/transaction.ts
+++ b/test/transaction.ts
@@ -32,7 +32,12 @@ import {Entity} from '../src/entity';
 import * as tsTypes from '../src/transaction';
 import * as sinon from 'sinon';
 import {Callback, CallOptions, ClientStub} from 'google-gax';
-import {CommitCallback, RequestCallback, RequestConfig} from '../src/request';
+import {
+  CommitCallback,
+  GetCallback,
+  RequestCallback,
+  RequestConfig,
+} from '../src/request';
 import {SECOND_DATABASE_ID} from './index';
 import {google} from '../protos/protos';
 import {RunCallback} from '../src/transaction';
@@ -974,8 +979,8 @@ async.each(
             });
           });
         });
-        describe.only('runQuery', () => {
-          // These tests were created so that when transaction.runAggregationQuery is restructured we
+        describe('runQuery', () => {
+          // These tests were created so that when transaction.runQuery is restructured we
           // can be confident that it works the same way as before.
           const runQueryResp = {
             batch: {
@@ -1069,6 +1074,119 @@ async.each(
             });
           });
         });
+        describe.only('get', () => {
+          // These tests were created so that when transaction.get is restructured we
+          // can be confident that it works the same way as before.
+          const getResp = {
+            found: [
+              {
+                entity: {
+                  key: {
+                    path: [
+                      {
+                        kind: 'Post',
+                        name: 'post1',
+                        idType: 'name',
+                      },
+                    ],
+                    partitionId: {
+                      projectId: 'projectId',
+                      databaseId: 'databaseId',
+                      namespaceId: 'namespaceId',
+                    },
+                  },
+                  excludeFromIndexes: false,
+                  properties: {},
+                },
+              },
+            ],
+            missing: [],
+            deferred: [],
+            transaction: testRunResp.transaction,
+            readTime: {
+              seconds: '1699470605',
+              nanos: 201398000,
+            },
+          };
+          const getUserResp: string = 'post1';
+          const testErrorMessage = 'test-run-Query-error';
+          let transactionWrapper: MockedTransactionWrapper;
+          let transaction: Transaction;
+          let q: Query;
+          let key: any; // TODO: Replace with key type
+
+          beforeEach(async () => {
+            transactionWrapper = new MockedTransactionWrapper();
+            transaction = transactionWrapper.transaction;
+            q = transactionWrapper.datastore.createQuery('Character');
+            key = transactionWrapper.datastore.key(['Company', 'Google']);
+          });
+
+          afterEach(() => {
+            transactionWrapper.resetBeginTransaction();
+            transactionWrapper.resetGapicFunctions();
+          });
+
+          describe('should pass error back to the user', async () => {
+            beforeEach(() => {
+              transactionWrapper.mockGapicFunction(
+                'lookup',
+                getResp,
+                new Error(testErrorMessage)
+              );
+            });
+
+            it('should send back the error when awaiting a promise', async () => {
+              try {
+                await transaction.run();
+                await transaction.get(key);
+                assert.fail('The run call should have failed.');
+              } catch (error: any) {
+                // TODO: Substitute type any
+                assert.strictEqual(error['message'], testErrorMessage);
+              }
+            });
+            it('should send back the error when using a callback', done => {
+              const callback: GetCallback = (
+                error: Error | null | undefined,
+                response?: any
+              ) => {
+                assert(error);
+                assert.strictEqual(error.message, testErrorMessage);
+                assert.deepStrictEqual(response, undefined);
+                done();
+              };
+              transaction.run(() => {
+                transaction.get(key, callback);
+              });
+            });
+          });
+          describe('should pass response back to the user', async () => {
+            beforeEach(() => {
+              transactionWrapper.mockGapicFunction('lookup', getResp, null);
+            });
+            it('should send back the response when awaiting a promise', async () => {
+              await transaction.run();
+              const [results] = await transaction.get(key);
+              const result = results[transactionWrapper.datastore.KEY];
+              assert.deepStrictEqual(result.name, getUserResp);
+            });
+            it('should send back the response when using a callback', done => {
+              const callback: GetCallback = (
+                error: Error | null | undefined,
+                response?: any
+              ) => {
+                const result = response[transactionWrapper.datastore.KEY];
+                assert.strictEqual(error, null);
+                assert.deepStrictEqual(result.name, getUserResp);
+                done();
+              };
+              transaction.run(() => {
+                transaction.get(key, callback);
+              });
+            });
+          });
+        });
       });
       describe('commit', () => {
         beforeEach(() => {

From dce083682ab2a11bccd5f4b23200f7265f19d749 Mon Sep 17 00:00:00 2001
From: Daniel Bruce <djbruce@google.com>
Date: Wed, 8 Nov 2023 15:07:02 -0500
Subject: [PATCH 063/129] remove only

---
 test/transaction.ts | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/test/transaction.ts b/test/transaction.ts
index d975c3baa..c19b49181 100644
--- a/test/transaction.ts
+++ b/test/transaction.ts
@@ -1074,7 +1074,7 @@ async.each(
             });
           });
         });
-        describe.only('get', () => {
+        describe('get', () => {
           // These tests were created so that when transaction.get is restructured we
           // can be confident that it works the same way as before.
           const getResp = {

From 2391ca84f099c127bcddc418cc452ff2e886bf8e Mon Sep 17 00:00:00 2001
From: Daniel Bruce <djbruce@google.com>
Date: Wed, 8 Nov 2023 15:20:01 -0500
Subject: [PATCH 064/129] remove only and console log

---
 system-test/datastore.ts | 3 +--
 1 file changed, 1 insertion(+), 2 deletions(-)

diff --git a/system-test/datastore.ts b/system-test/datastore.ts
index 1fa89cdfc..cbda61361 100644
--- a/system-test/datastore.ts
+++ b/system-test/datastore.ts
@@ -1749,7 +1749,7 @@ async.each(
           assert.deepStrictEqual(results, [{property_1: 4}]);
         });
       });
-      describe.only('transactions', () => {
+      describe('transactions', () => {
         it('should run in a transaction', async () => {
           const key = datastore.key(['Company', 'Google']);
           const obj = {
@@ -1862,7 +1862,6 @@ async.each(
             );
           });
           it('should aggregate query within a count transaction', async () => {
-            console.log('running test of interest');
             const transaction = datastore.transaction();
             await transaction.run();
             const query = transaction.createQuery('Company');

From 905c7daf90cb652e3faa4b19af69ec154df63293 Mon Sep 17 00:00:00 2001
From: Daniel Bruce <djbruce@google.com>
Date: Wed, 8 Nov 2023 15:29:05 -0500
Subject: [PATCH 065/129] Try modifications to runQuery

Add modifications to runQuery to use some function to return values
---
 src/transaction.ts | 8 +++-----
 1 file changed, 3 insertions(+), 5 deletions(-)

diff --git a/src/transaction.ts b/src/transaction.ts
index b3babd286..0c4b5b7de 100644
--- a/src/transaction.ts
+++ b/src/transaction.ts
@@ -839,7 +839,6 @@ class Transaction extends DatastoreRequest {
     );
   }
 
-  /*
   runQuery(query: Query, options?: RunQueryOptions): Promise<RunQueryResponse>;
   runQuery(
     query: Query,
@@ -859,7 +858,7 @@ class Transaction extends DatastoreRequest {
     const callback =
       typeof optionsOrCallback === 'function' ? optionsOrCallback : cb!;
     type promiseType = PassThroughReturnType<RunQueryResponseOptional>;
-    const promise: Promise<promiseType> = new Promise(resolve => {
+    const resolver: ResolverType<RunQueryResponseOptional> = resolve => {
       super.runQuery(
         query,
         options,
@@ -867,15 +866,14 @@ class Transaction extends DatastoreRequest {
           resolve({err, resp: [entities, info]});
         }
       );
-    });
-    this.#withBeginTransaction(options.gaxOptions, promise).then(
+    };
+    this.#someFunction(options.gaxOptions, resolver).then(
       (response: promiseType) => {
         const error = response.err ? response.err : null;
         callback(error, response.resp);
       }
     );
   }
-  */
 
   /**
    * Insert or update the specified object(s) in the current transaction. If a

From 5074de09d28de078ffc903aff87a590fe9a093ce Mon Sep 17 00:00:00 2001
From: Daniel Bruce <djbruce@google.com>
Date: Wed, 8 Nov 2023 15:47:43 -0500
Subject: [PATCH 066/129] Add try catch blocks to handle errors in the tests

try/catch logic in the test suite is added so that errors bubble up to the test runner and it is easier to see why tests failed
---
 test/transaction.ts | 92 ++++++++++++++++++++++++++++++---------------
 1 file changed, 62 insertions(+), 30 deletions(-)

diff --git a/test/transaction.ts b/test/transaction.ts
index c19b49181..43b37c511 100644
--- a/test/transaction.ts
+++ b/test/transaction.ts
@@ -822,10 +822,14 @@ async.each(
                 error: Error | null | undefined,
                 response?: google.datastore.v1.ICommitResponse
               ) => {
-                assert(error);
-                assert.strictEqual(error.message, testErrorMessage);
-                assert.strictEqual(response, testCommitResp);
-                done();
+                try {
+                  assert(error);
+                  assert.strictEqual(error.message, testErrorMessage);
+                  assert.strictEqual(response, testCommitResp);
+                  done();
+                } catch (e) {
+                  done(e);
+                }
               };
               transactionWrapper.transaction.run(() => {
                 transactionWrapper.transaction.commit(commitCallback);
@@ -851,9 +855,13 @@ async.each(
                 error: Error | null | undefined,
                 response?: google.datastore.v1.ICommitResponse
               ) => {
-                assert.strictEqual(error, null);
-                assert.strictEqual(response, testCommitResp);
-                done();
+                try {
+                  assert.strictEqual(error, null);
+                  assert.strictEqual(response, testCommitResp);
+                  done();
+                } catch (e) {
+                  done(e);
+                }
               };
               transactionWrapper.transaction.run(() => {
                 transactionWrapper.transaction.commit(commitCallback);
@@ -930,10 +938,14 @@ async.each(
                 error: Error | null | undefined,
                 response?: any
               ) => {
-                assert(error);
-                assert.strictEqual(error.message, testErrorMessage);
-                assert.deepStrictEqual(response, runAggregationQueryUserResp);
-                done();
+                try {
+                  assert(error);
+                  assert.strictEqual(error.message, testErrorMessage);
+                  assert.deepStrictEqual(response, runAggregationQueryUserResp);
+                  done();
+                } catch (e) {
+                  done(e);
+                }
               };
               transaction.run(() => {
                 transaction.runAggregationQuery(
@@ -966,9 +978,13 @@ async.each(
                 error: Error | null | undefined,
                 response?: any
               ) => {
-                assert.strictEqual(error, null);
-                assert.deepStrictEqual(response, runAggregationQueryUserResp);
-                done();
+                try {
+                  assert.strictEqual(error, null);
+                  assert.deepStrictEqual(response, runAggregationQueryUserResp);
+                  done();
+                } catch (e) {
+                  done(e);
+                }
               };
               transaction.run(() => {
                 transaction.runAggregationQuery(
@@ -1032,10 +1048,14 @@ async.each(
                 error: Error | null | undefined,
                 response?: any
               ) => {
-                assert(error);
-                assert.strictEqual(error.message, testErrorMessage);
-                assert.deepStrictEqual(response, undefined);
-                done();
+                try {
+                  assert(error);
+                  assert.strictEqual(error.message, testErrorMessage);
+                  assert.deepStrictEqual(response, undefined);
+                  done();
+                } catch (e) {
+                  done(e);
+                }
               };
               transaction.run(() => {
                 transaction.runQuery(q, callback);
@@ -1064,9 +1084,13 @@ async.each(
                 error: Error | null | undefined,
                 response?: any
               ) => {
-                assert.strictEqual(error, null);
-                assert.deepStrictEqual(response, runQueryUserResp);
-                done();
+                try {
+                  assert.strictEqual(error, null);
+                  assert.deepStrictEqual(response, runQueryUserResp);
+                  done();
+                } catch (e) {
+                  done(e);
+                }
               };
               transaction.run(() => {
                 transaction.runQuery(q, callback);
@@ -1108,7 +1132,7 @@ async.each(
               nanos: 201398000,
             },
           };
-          const getUserResp: string = 'post1';
+          const getUserResp = 'post1';
           const testErrorMessage = 'test-run-Query-error';
           let transactionWrapper: MockedTransactionWrapper;
           let transaction: Transaction;
@@ -1151,10 +1175,14 @@ async.each(
                 error: Error | null | undefined,
                 response?: any
               ) => {
-                assert(error);
-                assert.strictEqual(error.message, testErrorMessage);
-                assert.deepStrictEqual(response, undefined);
-                done();
+                try {
+                  assert(error);
+                  assert.strictEqual(error.message, testErrorMessage);
+                  assert.deepStrictEqual(response, undefined);
+                  done();
+                } catch (e) {
+                  done(e);
+                }
               };
               transaction.run(() => {
                 transaction.get(key, callback);
@@ -1176,10 +1204,14 @@ async.each(
                 error: Error | null | undefined,
                 response?: any
               ) => {
-                const result = response[transactionWrapper.datastore.KEY];
-                assert.strictEqual(error, null);
-                assert.deepStrictEqual(result.name, getUserResp);
-                done();
+                try {
+                  const result = response[transactionWrapper.datastore.KEY];
+                  assert.strictEqual(error, null);
+                  assert.deepStrictEqual(result.name, getUserResp);
+                  done();
+                } catch (e) {
+                  done(e);
+                }
               };
               transaction.run(() => {
                 transaction.get(key, callback);

From d78210a49b544bcf9cae206edced27fb11ca3f94 Mon Sep 17 00:00:00 2001
From: Daniel Bruce <djbruce@google.com>
Date: Wed, 8 Nov 2023 16:32:13 -0500
Subject: [PATCH 067/129] =?UTF-8?q?Modify=20commit=20so=20it=20doesn?=
 =?UTF-8?q?=E2=80=99t=20run=20early?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Don’t start commit with a promise or the promise will run early.
---
 src/transaction.ts | 16 ++++++++++++----
 1 file changed, 12 insertions(+), 4 deletions(-)

diff --git a/src/transaction.ts b/src/transaction.ts
index 0c4b5b7de..a9bd051fb 100644
--- a/src/transaction.ts
+++ b/src/transaction.ts
@@ -206,15 +206,21 @@ class Transaction extends DatastoreRequest {
       typeof gaxOptionsOrCallback === 'object' ? gaxOptionsOrCallback : {};
     type commitPromiseType =
       PassThroughReturnType<google.datastore.v1.ICommitResponse>;
-    const promise: Promise<commitPromiseType> = new Promise(resolve => {
+    const resolver: ResolverType<
+      google.datastore.v1.ICommitResponse
+    > = resolve => {
       this.#runCommit(
         gaxOptions,
         (err?: Error | null, resp?: google.datastore.v1.ICommitResponse) => {
           resolve({err, resp});
         }
       );
-    });
-    this.#beginWithCallback(gaxOptions, promise, callback);
+    };
+    this.#someFunction(gaxOptions, resolver).then(
+      (response: commitPromiseType) => {
+        callback(response.err, response.resp);
+      }
+    );
   }
 
   async #withBeginTransaction<T>(
@@ -870,7 +876,9 @@ class Transaction extends DatastoreRequest {
     this.#someFunction(options.gaxOptions, resolver).then(
       (response: promiseType) => {
         const error = response.err ? response.err : null;
-        callback(error, response.resp);
+        const entities = response.resp ? response.resp[0] : undefined;
+        const info = response.resp ? response.resp[1] : undefined;
+        callback(error, entities, info);
       }
     );
   }

From 13f9b0937ff505764c3fff05a8ac1a170e35a06b Mon Sep 17 00:00:00 2001
From: Daniel Bruce <djbruce@google.com>
Date: Wed, 8 Nov 2023 16:40:45 -0500
Subject: [PATCH 068/129] Update get with resolver

get should use the same pattern as runQuery and runAggregationQuery to use a resolver for the mutex business logic
---
 src/transaction.ts | 10 +++++++---
 1 file changed, 7 insertions(+), 3 deletions(-)

diff --git a/src/transaction.ts b/src/transaction.ts
index a9bd051fb..a24c7271d 100644
--- a/src/transaction.ts
+++ b/src/transaction.ts
@@ -433,12 +433,16 @@ class Transaction extends DatastoreRequest {
     const callback =
       typeof optionsOrCallback === 'function' ? optionsOrCallback : cb!;
     type promiseType = PassThroughReturnType<GetResponse>;
-    const promise: Promise<promiseType> = new Promise(resolve => {
+    const resolver: ResolverType<GetResponse> = resolve => {
       super.get(keys, options, (err?: Error | null, resp?: GetResponse) => {
         resolve({err, resp});
       });
-    });
-    this.#beginWithCallback(options.gaxOptions, promise, callback);
+    };
+    this.#someFunction(options.gaxOptions, resolver).then(
+      (response: promiseType) => {
+        callback(response.err, response.resp);
+      }
+    );
   }
 
   /**

From 14dc7430fe4bc1604cdbeda840679e40aae6f9c5 Mon Sep 17 00:00:00 2001
From: Daniel Bruce <djbruce@google.com>
Date: Wed, 8 Nov 2023 16:41:37 -0500
Subject: [PATCH 069/129] remove #beginWithCallback

#beginWithCallback is no longer used so remove it
---
 src/transaction.ts | 12 ------------
 1 file changed, 12 deletions(-)

diff --git a/src/transaction.ts b/src/transaction.ts
index a24c7271d..f09bdf43c 100644
--- a/src/transaction.ts
+++ b/src/transaction.ts
@@ -142,18 +142,6 @@ class Transaction extends DatastoreRequest {
    *      the final commit request with.
    */
 
-  #beginWithCallback<T>(
-    gaxOptions: CallOptions | undefined,
-    promise: Promise<PassThroughReturnType<T>>,
-    callback: (err?: Error | null, resp?: T) => void
-  ) {
-    this.#withBeginTransaction(gaxOptions, promise).then(
-      (response: PassThroughReturnType<T>) => {
-        callback(response.err, response.resp);
-      }
-    );
-  }
-
   /**
    * Commit the remote transaction and finalize the current transaction
    * instance.

From 898df4261ab76990a9de74a6d9ff39cd5930e745 Mon Sep 17 00:00:00 2001
From: Daniel Bruce <djbruce@google.com>
Date: Wed, 8 Nov 2023 16:42:48 -0500
Subject: [PATCH 070/129] Remove #withBeginTransaction

This function is no longer used
---
 test/transaction.ts | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/test/transaction.ts b/test/transaction.ts
index ee2ccf4b9..a03cd56e5 100644
--- a/test/transaction.ts
+++ b/test/transaction.ts
@@ -670,7 +670,7 @@ async.each(
       });
 
       // TODO: Add a test here for calling commit
-      describe('various functions without setting up transaction id when run returns a response', () => {
+      describe.only('various functions without setting up transaction id when run returns a response', () => {
         // These tests were created so that when transaction.run is restructured we
         // can be confident that it works the same way as before.
         const testRunResp = {

From c20974e2ed2d56f3f306f3edea0add0a0dcee195 Mon Sep 17 00:00:00 2001
From: Daniel Bruce <djbruce@google.com>
Date: Wed, 8 Nov 2023 16:45:14 -0500
Subject: [PATCH 071/129] Rename #someFunction with begin transaction

#someFunction should be named differently
---
 src/transaction.ts | 35 ++++++-----------------------------
 1 file changed, 6 insertions(+), 29 deletions(-)

diff --git a/src/transaction.ts b/src/transaction.ts
index f09bdf43c..3c7f1c9ce 100644
--- a/src/transaction.ts
+++ b/src/transaction.ts
@@ -204,38 +204,15 @@ class Transaction extends DatastoreRequest {
         }
       );
     };
-    this.#someFunction(gaxOptions, resolver).then(
+    this.#withBeginTransaction(gaxOptions, resolver).then(
       (response: commitPromiseType) => {
         callback(response.err, response.resp);
       }
     );
   }
 
-  async #withBeginTransaction<T>(
-    gaxOptions: CallOptions | undefined,
-    promise: Promise<PassThroughReturnType<T>>
-  ): Promise<PassThroughReturnType<T>> {
-    if (this.#state === TransactionState.NOT_STARTED) {
-      const release = await this.#mutex.acquire();
-      try {
-        try {
-          if (this.#state === TransactionState.NOT_STARTED) {
-            const runResults = await this.runAsync({gaxOptions});
-            this.#parseRunSuccess(runResults);
-          }
-        } finally {
-          // TODO: Check that error actually reaches user
-          release(); // TODO: Be sure to release the mutex in the error state
-        }
-      } catch (err: any) {
-        return {err};
-      }
-    }
-    return await promise;
-  }
-
   // TODO: Simplify the generics here: remove PassThroughReturnType
-  async #someFunction<T>(
+  async #withBeginTransaction<T>(
     gaxOptions: CallOptions | undefined,
     resolver: (
       resolve: (
@@ -426,7 +403,7 @@ class Transaction extends DatastoreRequest {
         resolve({err, resp});
       });
     };
-    this.#someFunction(options.gaxOptions, resolver).then(
+    this.#withBeginTransaction(options.gaxOptions, resolver).then(
       (response: promiseType) => {
         callback(response.err, response.resp);
       }
@@ -829,7 +806,7 @@ class Transaction extends DatastoreRequest {
         }
       );
     };
-    this.#someFunction(options.gaxOptions, resolver).then(
+    this.#withBeginTransaction(options.gaxOptions, resolver).then(
       (response: promiseType) => {
         const error = response.err ? response.err : null;
         callback(error, response.resp);
@@ -865,7 +842,7 @@ class Transaction extends DatastoreRequest {
         }
       );
     };
-    this.#someFunction(options.gaxOptions, resolver).then(
+    this.#withBeginTransaction(options.gaxOptions, resolver).then(
       (response: promiseType) => {
         const error = response.err ? response.err : null;
         const entities = response.resp ? response.resp[0] : undefined;
@@ -1109,7 +1086,7 @@ promisifyAll(Transaction, {
     'parseTransactionResponse',
     'runAsync',
     'save',
-    '#someFunction',
+    '#withBeginTransaction',
     'update',
     'upsert',
   ],

From ce852d133e2161108c9721d5fc6ecad94712b47a Mon Sep 17 00:00:00 2001
From: Daniel Bruce <djbruce@google.com>
Date: Wed, 8 Nov 2023 16:58:54 -0500
Subject: [PATCH 072/129] Fix promisify. Change to a deepStrictEqual check.

A deepStrictEqual check is all that is needed in this test that currently reuses a transaction.
---
 src/transaction.ts  | 36 +++++++++++++++++++-----------------
 test/transaction.ts |  6 +++---
 2 files changed, 22 insertions(+), 20 deletions(-)

diff --git a/src/transaction.ts b/src/transaction.ts
index 3c7f1c9ce..cdad7c238 100644
--- a/src/transaction.ts
+++ b/src/transaction.ts
@@ -562,24 +562,26 @@ class Transaction extends DatastoreRequest {
       process.emitWarning(
         'run has already been called and should not be called again.'
       );
+      callback(null, this, {transaction: this.id});
+    } else {
+      this.#mutex.acquire().then(release => {
+        this.runAsync(options)
+          // TODO: Replace type with google.datastore.v1.IBeginTransactionResponse and address downstream issues
+          .then(
+            (
+              response: PassThroughReturnType<google.datastore.v1.IBeginTransactionResponse>
+            ) => {
+              // TODO: Probably release the mutex after the id is recorded, but likely doesn't matter since node is single threaded.
+              release();
+              this.#parseRunAsync(response, callback);
+            }
+          )
+          .catch((err: any) => {
+            // TODO: Remove this catch block
+            callback(Error('The error should always be caught by then'), this);
+          });
+      });
     }
-    this.#mutex.acquire().then(release => {
-      this.runAsync(options)
-        // TODO: Replace type with google.datastore.v1.IBeginTransactionResponse and address downstream issues
-        .then(
-          (
-            response: PassThroughReturnType<google.datastore.v1.IBeginTransactionResponse>
-          ) => {
-            // TODO: Probably release the mutex after the id is recorded, but likely doesn't matter since node is single threaded.
-            release();
-            this.#parseRunAsync(response, callback);
-          }
-        )
-        .catch((err: any) => {
-          // TODO: Remove this catch block
-          callback(Error('The error should always be caught by then'), this);
-        });
-    });
   }
 
   #runCommit(gaxOptions?: CallOptions): Promise<CommitResponse>;
diff --git a/test/transaction.ts b/test/transaction.ts
index a03cd56e5..0e2243b69 100644
--- a/test/transaction.ts
+++ b/test/transaction.ts
@@ -70,7 +70,7 @@ const fakePfy = Object.assign({}, pfy, {
       'parseTransactionResponse',
       'runAsync',
       'save',
-      '#someFunction',
+      '#withBeginTransaction',
       'update',
       'upsert',
     ]);
@@ -288,7 +288,7 @@ async.each(
               response?: google.datastore.v1.IBeginTransactionResponse
             ) => {
               assert.strictEqual(error, null);
-              assert.strictEqual(response, testRunResp);
+              assert.deepStrictEqual(response, testRunResp);
               assert.strictEqual(transaction, transactionWithoutMock);
               done();
             };
@@ -670,7 +670,7 @@ async.each(
       });
 
       // TODO: Add a test here for calling commit
-      describe.only('various functions without setting up transaction id when run returns a response', () => {
+      describe('various functions without setting up transaction id when run returns a response', () => {
         // These tests were created so that when transaction.run is restructured we
         // can be confident that it works the same way as before.
         const testRunResp = {

From b0fe8f45bd54c10225d0fe7a2f3924b1bbb180e6 Mon Sep 17 00:00:00 2001
From: Daniel Bruce <djbruce@google.com>
Date: Thu, 9 Nov 2023 11:49:11 -0500
Subject: [PATCH 073/129] Add setImmediate to the tests

If we add a delay to the tests then the mutex has the opportunity to unlock before running the tests.
---
 test/transaction.ts | 5 ++++-
 1 file changed, 4 insertions(+), 1 deletion(-)

diff --git a/test/transaction.ts b/test/transaction.ts
index 0e2243b69..bfbeb5edb 100644
--- a/test/transaction.ts
+++ b/test/transaction.ts
@@ -1231,7 +1231,10 @@ async.each(
             callback(null, {
               transaction: Buffer.from(Array.from(Array(100).keys())),
             });
-            done();
+            // Delay to give the transaction mutex the opportunity to unlock before running tests.
+            setImmediate(() => {
+              done();
+            });
           };
           transaction.run();
         });

From 3c528425e9381ac05b9db55e61a1528ed7ba0cf8 Mon Sep 17 00:00:00 2001
From: Daniel Bruce <djbruce@google.com>
Date: Thu, 9 Nov 2023 14:48:43 -0500
Subject: [PATCH 074/129] Added some tests for call ordering

We want to make sure that the calls get delivered and received in order
---
 src/transaction.ts  |  6 ++++
 test/transaction.ts | 83 ++++++++++++++++++++++++++++++++++++++++++++-
 2 files changed, 88 insertions(+), 1 deletion(-)

diff --git a/src/transaction.ts b/src/transaction.ts
index cdad7c238..18579dd5d 100644
--- a/src/transaction.ts
+++ b/src/transaction.ts
@@ -221,7 +221,9 @@ class Transaction extends DatastoreRequest {
     ) => void
   ): Promise<PassThroughReturnType<T>> {
     if (this.#state === TransactionState.NOT_STARTED) {
+      console.log('acquiring mutex');
       const release = await this.#mutex.acquire();
+      console.log('acquired mutex');
       try {
         try {
           if (this.#state === TransactionState.NOT_STARTED) {
@@ -565,6 +567,7 @@ class Transaction extends DatastoreRequest {
       callback(null, this, {transaction: this.id});
     } else {
       this.#mutex.acquire().then(release => {
+        // TODO: Check for not-started here?
         this.runAsync(options)
           // TODO: Replace type with google.datastore.v1.IBeginTransactionResponse and address downstream issues
           .then(
@@ -573,6 +576,8 @@ class Transaction extends DatastoreRequest {
             ) => {
               // TODO: Probably release the mutex after the id is recorded, but likely doesn't matter since node is single threaded.
               release();
+              console.log('Locked');
+              console.log(this.#mutex.isLocked());
               this.#parseRunAsync(response, callback);
             }
           )
@@ -728,6 +733,7 @@ class Transaction extends DatastoreRequest {
   }
 
   #parseRunSuccess(response: PassThroughReturnType<any>) {
+    console.log('saving id');
     const resp = response.resp;
     this.id = resp!.transaction;
     this.#state = TransactionState.IN_PROGRESS;
diff --git a/test/transaction.ts b/test/transaction.ts
index bfbeb5edb..51124f156 100644
--- a/test/transaction.ts
+++ b/test/transaction.ts
@@ -684,8 +684,11 @@ async.each(
           mockedBeginTransaction: any;
           mockedFunction: any; // TODO: replace with type
           functionsMocked: {name: string; mockedFunction: any}[];
+          callBackSignaler: (callbackReached: string) => void;
 
-          constructor() {
+          constructor(callBackSignaler?: (callbackReached: string) => void) {
+            const defaultCallback = () => {};
+            this.callBackSignaler = callBackSignaler ?? defaultCallback;
             const namespace = 'run-without-mock';
             const projectId = 'project-id';
             const options = {
@@ -724,6 +727,7 @@ async.each(
                   {} | null | undefined
                 >
               ) => {
+                this.callBackSignaler('beginTransaction called');
                 callback(null, testRunResp);
               };
             }
@@ -757,6 +761,7 @@ async.each(
                   {} | null | undefined
                 >
               ) => {
+                this.callBackSignaler(`${functionName} called`);
                 callback(error, response);
               };
             }
@@ -1223,6 +1228,82 @@ async.each(
             });
           });
         });
+        describe('concurrency', async () => {
+          const testCommitResp = {
+            mutationResults: [
+              {
+                key: {
+                  path: [
+                    {
+                      kind: 'some-kind',
+                    },
+                  ],
+                },
+              },
+            ],
+          };
+          let transactionWrapper: MockedTransactionWrapper;
+
+          beforeEach(async () => {
+            transactionWrapper = new MockedTransactionWrapper();
+          });
+
+          afterEach(() => {
+            transactionWrapper.resetBeginTransaction();
+            transactionWrapper.resetGapicFunctions();
+          });
+
+          describe('should pass response back to the user', async () => {
+            beforeEach(() => {
+              transactionWrapper.mockGapicFunction(
+                'commit',
+                testCommitResp,
+                null
+              );
+            });
+
+            it('should call the callbacks in the proper order', done => {
+              const transaction = transactionWrapper.transaction;
+              const callbackOrder: string[] = [];
+              function checkForCompletion() {
+                if (callbackOrder.length >= 5) {
+                  // TODO: assertion check here
+                  assert.deepStrictEqual(callbackOrder, [
+                    'functions called',
+                    'beginTransaction called',
+                    'run callback',
+                    'commit called',
+                    'commit callback',
+                  ]);
+                  done();
+                }
+              }
+              function gapicCallHandler(call: string): void {
+                callbackOrder.push(call);
+                checkForCompletion();
+              }
+              transactionWrapper.callBackSignaler = gapicCallHandler;
+              const runCallback: RunCallback = (
+                error: Error | null | undefined,
+                response?: any
+              ) => {
+                callbackOrder.push('run callback');
+                checkForCompletion();
+              };
+              const commitCallback: CommitCallback = (
+                error: Error | null | undefined,
+                response?: google.datastore.v1.ICommitResponse
+              ) => {
+                callbackOrder.push('commit callback');
+                checkForCompletion();
+              };
+              transaction.run(runCallback);
+              transaction.commit(commitCallback);
+              callbackOrder.push('functions called');
+              checkForCompletion();
+            });
+          });
+        });
       });
       describe('commit', () => {
         beforeEach(done => {

From 816ab0cecfc50af7a21ef31095a073fffb3346cb Mon Sep 17 00:00:00 2001
From: Daniel Bruce <djbruce@google.com>
Date: Thu, 9 Nov 2023 15:35:19 -0500
Subject: [PATCH 075/129] Pack testing tool into an object

Put the testing tool in an object so that we can explore more orderings and possibilities.
---
 src/transaction.ts  |   2 +
 test/transaction.ts | 119 +++++++++++++++++++++++++++++---------------
 2 files changed, 82 insertions(+), 39 deletions(-)

diff --git a/src/transaction.ts b/src/transaction.ts
index 18579dd5d..c947802db 100644
--- a/src/transaction.ts
+++ b/src/transaction.ts
@@ -566,7 +566,9 @@ class Transaction extends DatastoreRequest {
       );
       callback(null, this, {transaction: this.id});
     } else {
+      console.log('acquiring run mutex');
       this.#mutex.acquire().then(release => {
+        console.log('run mutex acquired');
         // TODO: Check for not-started here?
         this.runAsync(options)
           // TODO: Replace type with google.datastore.v1.IBeginTransactionResponse and address downstream issues
diff --git a/test/transaction.ts b/test/transaction.ts
index 51124f156..838fd6f1d 100644
--- a/test/transaction.ts
+++ b/test/transaction.ts
@@ -727,7 +727,9 @@ async.each(
                   {} | null | undefined
                 >
               ) => {
+                console.log('beginTransaction called');
                 this.callBackSignaler('beginTransaction called');
+                console.log('callback signaller called');
                 callback(null, testRunResp);
               };
             }
@@ -1228,7 +1230,7 @@ async.each(
             });
           });
         });
-        describe('concurrency', async () => {
+        describe.only('concurrency', async () => {
           const testCommitResp = {
             mutationResults: [
               {
@@ -1253,54 +1255,93 @@ async.each(
             transactionWrapper.resetGapicFunctions();
           });
 
-          describe('should pass response back to the user', async () => {
-            beforeEach(() => {
-              transactionWrapper.mockGapicFunction(
-                'commit',
-                testCommitResp,
-                null
-              );
-            });
-
-            it('should call the callbacks in the proper order', done => {
-              const transaction = transactionWrapper.transaction;
-              const callbackOrder: string[] = [];
-              function checkForCompletion() {
-                if (callbackOrder.length >= 5) {
+          class TransactionOrderTester {
+            callbackOrder: string[] = [];
+            transactionWrapper: MockedTransactionWrapper;
+            done: (err?: any) => void;
+            checkForCompletion() {
+              if (this.callbackOrder.length >= 5) {
+                try {
                   // TODO: assertion check here
-                  assert.deepStrictEqual(callbackOrder, [
+                  assert.deepStrictEqual(this.callbackOrder, [
                     'functions called',
                     'beginTransaction called',
                     'run callback',
                     'commit called',
                     'commit callback',
                   ]);
-                  done();
+                  this.done();
+                } catch (e) {
+                  this.done(e);
                 }
               }
-              function gapicCallHandler(call: string): void {
-                callbackOrder.push(call);
-                checkForCompletion();
-              }
-              transactionWrapper.callBackSignaler = gapicCallHandler;
-              const runCallback: RunCallback = (
-                error: Error | null | undefined,
-                response?: any
-              ) => {
-                callbackOrder.push('run callback');
-                checkForCompletion();
-              };
-              const commitCallback: CommitCallback = (
-                error: Error | null | undefined,
-                response?: google.datastore.v1.ICommitResponse
-              ) => {
-                callbackOrder.push('commit callback');
-                checkForCompletion();
+            }
+
+            runCallback: RunCallback = (
+              error: Error | null | undefined,
+              response?: any
+            ) => {
+              console.log('calling run callback');
+              this.callbackOrder.push('run callback');
+              this.checkForCompletion();
+            };
+            commitCallback: CommitCallback = (
+              error: Error | null | undefined,
+              response?: google.datastore.v1.ICommitResponse
+            ) => {
+              console.log('calling commit callback');
+              this.callbackOrder.push('commit callback');
+              this.checkForCompletion();
+            };
+
+            constructor(
+              transactionWrapper: MockedTransactionWrapper,
+              done: (err?: any) => void
+            ) {
+              const gapicCallHandler = (call: string) => {
+                try {
+                  this.callbackOrder.push(call);
+                  this.checkForCompletion();
+                } catch (e) {
+                  this.done(e);
+                }
               };
-              transaction.run(runCallback);
-              transaction.commit(commitCallback);
-              callbackOrder.push('functions called');
-              checkForCompletion();
+              this.done = done;
+              transactionWrapper.callBackSignaler = gapicCallHandler;
+              this.transactionWrapper = transactionWrapper;
+            }
+
+            callRun() {
+              this.transactionWrapper.transaction.run(this.runCallback);
+            }
+
+            callCommit() {
+              this.transactionWrapper.transaction.commit(this.commitCallback);
+            }
+
+            pushString(callbackPushed: string) {
+              this.callbackOrder.push(callbackPushed);
+              this.checkForCompletion();
+            }
+          }
+
+          describe('should pass response back to the user', async () => {
+            beforeEach(() => {
+              transactionWrapper.mockGapicFunction(
+                'commit',
+                testCommitResp,
+                null
+              );
+            });
+
+            it('should call the callbacks in the proper order', done => {
+              const transactionOrderTester = new TransactionOrderTester(
+                transactionWrapper,
+                done
+              );
+              transactionOrderTester.callRun();
+              transactionOrderTester.callCommit();
+              transactionOrderTester.pushString('functions called');
             });
           });
         });

From 21f3afdbc72f1345cfd287d1d420dbe386e704ab Mon Sep 17 00:00:00 2001
From: Daniel Bruce <djbruce@google.com>
Date: Thu, 9 Nov 2023 15:42:57 -0500
Subject: [PATCH 076/129] Eliminate console logs, use expected order

Allow expected order to be passed into the tester. This will make the object reusable.
---
 src/transaction.ts  |  7 -------
 test/transaction.ts | 30 ++++++++++++++++--------------
 2 files changed, 16 insertions(+), 21 deletions(-)

diff --git a/src/transaction.ts b/src/transaction.ts
index c947802db..c743f73d9 100644
--- a/src/transaction.ts
+++ b/src/transaction.ts
@@ -221,9 +221,7 @@ class Transaction extends DatastoreRequest {
     ) => void
   ): Promise<PassThroughReturnType<T>> {
     if (this.#state === TransactionState.NOT_STARTED) {
-      console.log('acquiring mutex');
       const release = await this.#mutex.acquire();
-      console.log('acquired mutex');
       try {
         try {
           if (this.#state === TransactionState.NOT_STARTED) {
@@ -566,9 +564,7 @@ class Transaction extends DatastoreRequest {
       );
       callback(null, this, {transaction: this.id});
     } else {
-      console.log('acquiring run mutex');
       this.#mutex.acquire().then(release => {
-        console.log('run mutex acquired');
         // TODO: Check for not-started here?
         this.runAsync(options)
           // TODO: Replace type with google.datastore.v1.IBeginTransactionResponse and address downstream issues
@@ -578,8 +574,6 @@ class Transaction extends DatastoreRequest {
             ) => {
               // TODO: Probably release the mutex after the id is recorded, but likely doesn't matter since node is single threaded.
               release();
-              console.log('Locked');
-              console.log(this.#mutex.isLocked());
               this.#parseRunAsync(response, callback);
             }
           )
@@ -735,7 +729,6 @@ class Transaction extends DatastoreRequest {
   }
 
   #parseRunSuccess(response: PassThroughReturnType<any>) {
-    console.log('saving id');
     const resp = response.resp;
     this.id = resp!.transaction;
     this.#state = TransactionState.IN_PROGRESS;
diff --git a/test/transaction.ts b/test/transaction.ts
index 838fd6f1d..b258b2b88 100644
--- a/test/transaction.ts
+++ b/test/transaction.ts
@@ -727,9 +727,7 @@ async.each(
                   {} | null | undefined
                 >
               ) => {
-                console.log('beginTransaction called');
                 this.callBackSignaler('beginTransaction called');
-                console.log('callback signaller called');
                 callback(null, testRunResp);
               };
             }
@@ -1255,7 +1253,10 @@ async.each(
             transactionWrapper.resetGapicFunctions();
           });
 
+          // This object is used for testing the order that different events occur
+          // The events can include
           class TransactionOrderTester {
+            expectedOrder: string[] = [];
             callbackOrder: string[] = [];
             transactionWrapper: MockedTransactionWrapper;
             done: (err?: any) => void;
@@ -1263,13 +1264,7 @@ async.each(
               if (this.callbackOrder.length >= 5) {
                 try {
                   // TODO: assertion check here
-                  assert.deepStrictEqual(this.callbackOrder, [
-                    'functions called',
-                    'beginTransaction called',
-                    'run callback',
-                    'commit called',
-                    'commit callback',
-                  ]);
+                  assert.deepStrictEqual(this.callbackOrder, this.expectedOrder);
                   this.done();
                 } catch (e) {
                   this.done(e);
@@ -1281,7 +1276,6 @@ async.each(
               error: Error | null | undefined,
               response?: any
             ) => {
-              console.log('calling run callback');
               this.callbackOrder.push('run callback');
               this.checkForCompletion();
             };
@@ -1289,21 +1283,22 @@ async.each(
               error: Error | null | undefined,
               response?: google.datastore.v1.ICommitResponse
             ) => {
-              console.log('calling commit callback');
               this.callbackOrder.push('commit callback');
               this.checkForCompletion();
             };
 
             constructor(
               transactionWrapper: MockedTransactionWrapper,
-              done: (err?: any) => void
+              done: (err?: any) => void,
+              expectedOrder: string[]
             ) {
+              this.expectedOrder = expectedOrder;
               const gapicCallHandler = (call: string) => {
                 try {
                   this.callbackOrder.push(call);
                   this.checkForCompletion();
                 } catch (e) {
-                  this.done(e);
+                  done(e);
                 }
               };
               this.done = done;
@@ -1337,7 +1332,14 @@ async.each(
             it('should call the callbacks in the proper order', done => {
               const transactionOrderTester = new TransactionOrderTester(
                 transactionWrapper,
-                done
+                done,
+                [
+                  'functions called',
+                  'beginTransaction called',
+                  'run callback',
+                  'commit called',
+                  'commit callback',
+                ]
               );
               transactionOrderTester.callRun();
               transactionOrderTester.callCommit();

From 7677400aeb874a02a5f09725a6281d08a39a9ef5 Mon Sep 17 00:00:00 2001
From: Daniel Bruce <djbruce@google.com>
Date: Thu, 9 Nov 2023 16:23:21 -0500
Subject: [PATCH 077/129] Add a check for transaction not started yet

Need a check to be sure that the transaction is not in progress if making another beginTransaction call.
---
 src/transaction.ts  | 46 ++++++++++++++++++++++++++-------------------
 test/transaction.ts | 38 ++++++++++++++++++++++++++++++++++---
 2 files changed, 62 insertions(+), 22 deletions(-)

diff --git a/src/transaction.ts b/src/transaction.ts
index c743f73d9..b0ef5bb66 100644
--- a/src/transaction.ts
+++ b/src/transaction.ts
@@ -558,29 +558,37 @@ class Transaction extends DatastoreRequest {
       typeof optionsOrCallback === 'function' ? optionsOrCallback : cb!;
     // TODO: Whenever run is called a second time and a warning is emitted then do nothing.
     // TODO: This means rewriting many tests so that they don't use the same transaction object.
+    const warningMessage =
+      'run has already been called and should not be called again.';
     if (this.#state !== TransactionState.NOT_STARTED) {
-      process.emitWarning(
-        'run has already been called and should not be called again.'
-      );
+      process.emitWarning(warningMessage);
       callback(null, this, {transaction: this.id});
     } else {
       this.#mutex.acquire().then(release => {
-        // TODO: Check for not-started here?
-        this.runAsync(options)
-          // TODO: Replace type with google.datastore.v1.IBeginTransactionResponse and address downstream issues
-          .then(
-            (
-              response: PassThroughReturnType<google.datastore.v1.IBeginTransactionResponse>
-            ) => {
-              // TODO: Probably release the mutex after the id is recorded, but likely doesn't matter since node is single threaded.
-              release();
-              this.#parseRunAsync(response, callback);
-            }
-          )
-          .catch((err: any) => {
-            // TODO: Remove this catch block
-            callback(Error('The error should always be caught by then'), this);
-          });
+        if (this.#state === TransactionState.NOT_STARTED) {
+          this.runAsync(options)
+            // TODO: Replace type with google.datastore.v1.IBeginTransactionResponse and address downstream issues
+            .then(
+              (
+                response: PassThroughReturnType<google.datastore.v1.IBeginTransactionResponse>
+              ) => {
+                // TODO: Probably release the mutex after the id is recorded, but likely doesn't matter since node is single threaded.
+                release();
+                this.#parseRunAsync(response, callback);
+              }
+            )
+            .catch((err: any) => {
+              // TODO: Remove this catch block
+              callback(
+                Error('The error should always be caught by then'),
+                this
+              );
+            });
+        } else {
+          release();
+          process.emitWarning(warningMessage);
+          callback(null, this, {transaction: this.id});
+        }
       });
     }
   }
diff --git a/test/transaction.ts b/test/transaction.ts
index b258b2b88..75b0a23f4 100644
--- a/test/transaction.ts
+++ b/test/transaction.ts
@@ -1261,10 +1261,13 @@ async.each(
             transactionWrapper: MockedTransactionWrapper;
             done: (err?: any) => void;
             checkForCompletion() {
-              if (this.callbackOrder.length >= 5) {
+              if (this.callbackOrder.length >= this.expectedOrder.length) {
                 try {
                   // TODO: assertion check here
-                  assert.deepStrictEqual(this.callbackOrder, this.expectedOrder);
+                  assert.deepStrictEqual(
+                    this.callbackOrder,
+                    this.expectedOrder
+                  );
                   this.done();
                 } catch (e) {
                   this.done(e);
@@ -1329,7 +1332,7 @@ async.each(
               );
             });
 
-            it('should call the callbacks in the proper order', done => {
+            it('should call the callbacks in the proper order with run and commit', done => {
               const transactionOrderTester = new TransactionOrderTester(
                 transactionWrapper,
                 done,
@@ -1345,6 +1348,35 @@ async.each(
               transactionOrderTester.callCommit();
               transactionOrderTester.pushString('functions called');
             });
+            it('should call the callbacks in the proper order with commit', done => {
+              const transactionOrderTester = new TransactionOrderTester(
+                transactionWrapper,
+                done,
+                [
+                  'functions called',
+                  'beginTransaction called',
+                  'commit called',
+                  'commit callback',
+                ]
+              );
+              transactionOrderTester.callCommit();
+              transactionOrderTester.pushString('functions called');
+            });
+            it('should call the callbacks in the proper order with two run calls', done => {
+              const transactionOrderTester = new TransactionOrderTester(
+                transactionWrapper,
+                done,
+                [
+                  'functions called',
+                  'beginTransaction called',
+                  'run callback',
+                  'run callback',
+                ]
+              );
+              transactionOrderTester.callRun();
+              transactionOrderTester.callRun();
+              transactionOrderTester.pushString('functions called');
+            });
           });
         });
       });

From 34903bd8798bd3ae9da224160618dc49d3b81af1 Mon Sep 17 00:00:00 2001
From: Daniel Bruce <djbruce@google.com>
Date: Fri, 10 Nov 2023 10:15:05 -0500
Subject: [PATCH 078/129] Remove NOT_TRANSACTION

Remove the NOT_TRANSACTION and default to NOT_STARTED
---
 src/transaction.ts | 4 +---
 1 file changed, 1 insertion(+), 3 deletions(-)

diff --git a/src/transaction.ts b/src/transaction.ts
index b0ef5bb66..016551aa9 100644
--- a/src/transaction.ts
+++ b/src/transaction.ts
@@ -66,7 +66,6 @@ interface ResolverType<T> {
 }
 
 class TransactionState {
-  static NOT_TRANSACTION = Symbol('NON_TRANSACTION');
   static NOT_STARTED = Symbol('NOT_STARTED');
   // IN_PROGRESS currently tracks the expired state as well
   static IN_PROGRESS = Symbol('IN_PROGRESS');
@@ -99,7 +98,7 @@ class Transaction extends DatastoreRequest {
   modifiedEntities_: ModifiedEntities;
   skipCommit?: boolean;
   #mutex = new Mutex();
-  #state: Symbol = TransactionState.NOT_TRANSACTION;
+  #state = TransactionState.NOT_STARTED;
   constructor(datastore: Datastore, options?: TransactionOptions) {
     super();
     /**
@@ -129,7 +128,6 @@ class Transaction extends DatastoreRequest {
 
     // Queue the requests to make when we send the transactional commit.
     this.requests_ = [];
-    this.#state = TransactionState.NOT_STARTED;
   }
 
   /*! Developer Documentation

From 390d5ee8502f9632d376e628b9c05ee99998a83f Mon Sep 17 00:00:00 2001
From: Daniel Bruce <djbruce@google.com>
Date: Fri, 10 Nov 2023 10:16:22 -0500
Subject: [PATCH 079/129] Remove only

Remove only and let all unit tests run
---
 test/transaction.ts | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/test/transaction.ts b/test/transaction.ts
index 75b0a23f4..6e9b13a84 100644
--- a/test/transaction.ts
+++ b/test/transaction.ts
@@ -1228,7 +1228,7 @@ async.each(
             });
           });
         });
-        describe.only('concurrency', async () => {
+        describe('concurrency', async () => {
           const testCommitResp = {
             mutationResults: [
               {

From c8b5c49a6b18634674f85ace71dc16050773523c Mon Sep 17 00:00:00 2001
From: Daniel Bruce <djbruce@google.com>
Date: Fri, 10 Nov 2023 10:21:44 -0500
Subject: [PATCH 080/129] Use an enum instead of static class members

Use an enum to track transaction state.
---
 src/transaction.ts | 10 ++++------
 1 file changed, 4 insertions(+), 6 deletions(-)

diff --git a/src/transaction.ts b/src/transaction.ts
index 016551aa9..fb257ce77 100644
--- a/src/transaction.ts
+++ b/src/transaction.ts
@@ -65,10 +65,9 @@ interface ResolverType<T> {
   ): void;
 }
 
-class TransactionState {
-  static NOT_STARTED = Symbol('NOT_STARTED');
-  // IN_PROGRESS currently tracks the expired state as well
-  static IN_PROGRESS = Symbol('IN_PROGRESS');
+enum TransactionState {
+  NOT_STARTED,
+  IN_PROGRESS, // IN_PROGRESS currently tracks the expired state as well
 }
 
 /**
@@ -227,8 +226,7 @@ class Transaction extends DatastoreRequest {
             this.#parseRunSuccess(runResults);
           }
         } finally {
-          // TODO: Check that error actually reaches user
-          release(); // TODO: Be sure to release the mutex in the error state
+          release();
         }
       } catch (err: any) {
         return {err};

From 0bdfa0b1ed6ab3d9968432d9989a9e6c682f1bca Mon Sep 17 00:00:00 2001
From: Daniel Bruce <djbruce@google.com>
Date: Fri, 10 Nov 2023 10:26:39 -0500
Subject: [PATCH 081/129] TODOs are done

---
 src/request.ts | 4 ----
 1 file changed, 4 deletions(-)

diff --git a/src/request.ts b/src/request.ts
index 1397a09f1..d39f572ef 100644
--- a/src/request.ts
+++ b/src/request.ts
@@ -69,9 +69,6 @@ const CONSISTENCY_PROTO_CODE: ConsistencyProtoCode = {
   strong: 1,
 };
 
-// TODO: Typescript had difficulty working with enums before.
-// TODO: Try to get enums working instead of using static properties.
-
 /**
  * Handle logic for Datastore API operations. Handles request logic for
  * Datastore.
@@ -92,7 +89,6 @@ class DatastoreRequest {
     | Array<(err: Error | null, resp: Entity | null) => void>
     | Entity;
   datastore!: Datastore;
-  // TODO: Replace protected with a symbol for better data hiding.
   [key: string]: Entity;
 
   /**

From 623bb76b0bc1e0f20909543fef3138d38b92ac31 Mon Sep 17 00:00:00 2001
From: Daniel Bruce <djbruce@google.com>
Date: Fri, 10 Nov 2023 10:43:13 -0500
Subject: [PATCH 082/129] Move excludes to proper position

Excludes should contain the right functions in the right order.
---
 src/transaction.ts  | 3 +--
 test/transaction.ts | 3 +--
 2 files changed, 2 insertions(+), 4 deletions(-)

diff --git a/src/transaction.ts b/src/transaction.ts
index fb257ce77..6474f2d46 100644
--- a/src/transaction.ts
+++ b/src/transaction.ts
@@ -1085,7 +1085,6 @@ export interface RunOptions {
 promisifyAll(Transaction, {
   exclude: [
     'createAggregationQuery',
-    '#commitAsync',
     'createQuery',
     'delete',
     'insert',
@@ -1093,9 +1092,9 @@ promisifyAll(Transaction, {
     'parseTransactionResponse',
     'runAsync',
     'save',
-    '#withBeginTransaction',
     'update',
     'upsert',
+    '#withBeginTransaction',
   ],
 });
 
diff --git a/test/transaction.ts b/test/transaction.ts
index 6e9b13a84..ace0e3a1c 100644
--- a/test/transaction.ts
+++ b/test/transaction.ts
@@ -62,7 +62,6 @@ const fakePfy = Object.assign({}, pfy, {
     promisified = true;
     assert.deepStrictEqual(options.exclude, [
       'createAggregationQuery',
-      '#commitAsync',
       'createQuery',
       'delete',
       'insert',
@@ -70,9 +69,9 @@ const fakePfy = Object.assign({}, pfy, {
       'parseTransactionResponse',
       'runAsync',
       'save',
-      '#withBeginTransaction',
       'update',
       'upsert',
+      '#withBeginTransaction',
     ]);
   },
 });

From 521f11c496240ffb98a1e45996cbd1bffb7c5d98 Mon Sep 17 00:00:00 2001
From: Daniel Bruce <djbruce@google.com>
Date: Fri, 10 Nov 2023 11:07:57 -0500
Subject: [PATCH 083/129] Simplify the run function a little bit

Regroup functionality to simplify the run function
---
 src/transaction.ts | 43 ++++++++++++++++---------------------------
 1 file changed, 16 insertions(+), 27 deletions(-)

diff --git a/src/transaction.ts b/src/transaction.ts
index 6474f2d46..7644367d1 100644
--- a/src/transaction.ts
+++ b/src/transaction.ts
@@ -208,7 +208,6 @@ class Transaction extends DatastoreRequest {
     );
   }
 
-  // TODO: Simplify the generics here: remove PassThroughReturnType
   async #withBeginTransaction<T>(
     gaxOptions: CallOptions | undefined,
     resolver: (
@@ -552,38 +551,28 @@ class Transaction extends DatastoreRequest {
       typeof optionsOrCallback === 'object' ? optionsOrCallback : {};
     const callback =
       typeof optionsOrCallback === 'function' ? optionsOrCallback : cb!;
-    // TODO: Whenever run is called a second time and a warning is emitted then do nothing.
-    // TODO: This means rewriting many tests so that they don't use the same transaction object.
-    const warningMessage =
-      'run has already been called and should not be called again.';
-    if (this.#state !== TransactionState.NOT_STARTED) {
-      process.emitWarning(warningMessage);
+    const runIfStarted = () => {
+      process.emitWarning(
+        'run has already been called and should not be called again.'
+      );
       callback(null, this, {transaction: this.id});
+    };
+    if (this.#state !== TransactionState.NOT_STARTED) {
+      runIfStarted();
     } else {
       this.#mutex.acquire().then(release => {
         if (this.#state === TransactionState.NOT_STARTED) {
-          this.runAsync(options)
-            // TODO: Replace type with google.datastore.v1.IBeginTransactionResponse and address downstream issues
-            .then(
-              (
-                response: PassThroughReturnType<google.datastore.v1.IBeginTransactionResponse>
-              ) => {
-                // TODO: Probably release the mutex after the id is recorded, but likely doesn't matter since node is single threaded.
-                release();
-                this.#parseRunAsync(response, callback);
-              }
-            )
-            .catch((err: any) => {
-              // TODO: Remove this catch block
-              callback(
-                Error('The error should always be caught by then'),
-                this
-              );
-            });
+          this.runAsync(options).then(
+            (
+              response: PassThroughReturnType<google.datastore.v1.IBeginTransactionResponse>
+            ) => {
+              release();
+              this.#parseRunAsync(response, callback);
+            }
+          );
         } else {
           release();
-          process.emitWarning(warningMessage);
-          callback(null, this, {transaction: this.id});
+          runIfStarted();
         }
       });
     }

From 32d40d2568ed8ec3e26761755b8f0ef7656495a2 Mon Sep 17 00:00:00 2001
From: Daniel Bruce <djbruce@google.com>
Date: Fri, 10 Nov 2023 13:46:03 -0500
Subject: [PATCH 084/129] Add comments to tests

Explain purpose of testing objects
---
 test/transaction.ts | 22 +++++++++++++++++++---
 1 file changed, 19 insertions(+), 3 deletions(-)

diff --git a/test/transaction.ts b/test/transaction.ts
index b8cc49015..915a55082 100644
--- a/test/transaction.ts
+++ b/test/transaction.ts
@@ -163,7 +163,7 @@ async.each(
         });
       });
 
-      describe.only('run without setting up transaction id', () => {
+      describe('run without setting up transaction id', () => {
         // These tests were created so that when transaction.run is restructured we
         // can be confident that it works the same way as before.
         const testRunResp = {
@@ -674,6 +674,11 @@ async.each(
           transaction: Buffer.from(Array.from(Array(100).keys())),
         };
 
+        /*
+          MockedTransactionWrapper is a helper class for mocking out various
+          Gapic functions and ensuring that responses and errors actually make it
+          back to the user.
+         */
         class MockedTransactionWrapper {
           datastore: Datastore;
           transaction: Transaction;
@@ -738,7 +743,14 @@ async.each(
             error: Error | null
           ) {
             const dataClient = this.dataClient;
-            // TODO: Check here that function hasn't been mocked out already
+            // Check here that function hasn't been mocked out already
+            // Ensures that this mocking object is not being misused.
+            this.functionsMocked.forEach(fn => {
+              if (fn.name === functionName) {
+                console.log('test');
+                // throw Error('${functionName} has already been mocked out');
+              }
+            });
             if (dataClient && dataClient[functionName]) {
               this.functionsMocked.push({
                 name: functionName,
@@ -764,12 +776,16 @@ async.each(
             }
           }
 
+          // This resets beginTransaction from the Gapic layer to what it originally was.
+          // Resetting beginTransaction ensures other tests don't use the beginTransaction mock.
           resetBeginTransaction() {
             if (this.dataClient && this.dataClient.beginTransaction) {
               this.dataClient.beginTransaction = this.mockedBeginTransaction;
             }
           }
-          // TODO: Allow several functions to be mocked, eliminate string parameter
+
+          // This resets Gapic functions mocked out by the tests to what they originally were.
+          // Resetting mocked out Gapic functions ensures other tests don't use these mocks.
           resetGapicFunctions() {
             this.functionsMocked.forEach(functionMocked => {
               this.dataClient[functionMocked.name] =

From 04496764750ea724ca6dc6237b0e1200cd9ff28d Mon Sep 17 00:00:00 2001
From: Daniel Bruce <djbruce@google.com>
Date: Fri, 10 Nov 2023 14:14:12 -0500
Subject: [PATCH 085/129] Modify tests and fix a bug from the merge
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

The merge added a bug because the pound sign wasn’t used and it should have been.
---
 src/transaction.ts  |  2 +-
 test/transaction.ts | 19 +++++++++++++------
 2 files changed, 14 insertions(+), 7 deletions(-)

diff --git a/src/transaction.ts b/src/transaction.ts
index 263a24d75..935bfe59b 100644
--- a/src/transaction.ts
+++ b/src/transaction.ts
@@ -221,7 +221,7 @@ class Transaction extends DatastoreRequest {
       try {
         try {
           if (this.#state === TransactionState.NOT_STARTED) {
-            const runResults = await this.runAsync({gaxOptions});
+            const runResults = await this.#runAsync({gaxOptions});
             this.#parseRunSuccess(runResults);
           }
         } finally {
diff --git a/test/transaction.ts b/test/transaction.ts
index 915a55082..f56488908 100644
--- a/test/transaction.ts
+++ b/test/transaction.ts
@@ -747,8 +747,7 @@ async.each(
             // Ensures that this mocking object is not being misused.
             this.functionsMocked.forEach(fn => {
               if (fn.name === functionName) {
-                console.log('test');
-                // throw Error('${functionName} has already been mocked out');
+                throw Error('${functionName} has already been mocked out');
               }
             });
             if (dataClient && dataClient[functionName]) {
@@ -1292,15 +1291,23 @@ async.each(
               error: Error | null | undefined,
               response?: any
             ) => {
-              this.callbackOrder.push('run callback');
-              this.checkForCompletion();
+              try {
+                this.callbackOrder.push('run callback');
+                this.checkForCompletion();
+              } catch (e) {
+                this.done(e);
+              }
             };
             commitCallback: CommitCallback = (
               error: Error | null | undefined,
               response?: google.datastore.v1.ICommitResponse
             ) => {
-              this.callbackOrder.push('commit callback');
-              this.checkForCompletion();
+              try {
+                this.callbackOrder.push('commit callback');
+                this.checkForCompletion();
+              } catch (e) {
+                this.done(e);
+              }
             };
 
             constructor(

From 36189dea38ceead3690d970f81829e640f1fd73c Mon Sep 17 00:00:00 2001
From: Daniel Bruce <djbruce@google.com>
Date: Fri, 10 Nov 2023 14:22:20 -0500
Subject: [PATCH 086/129] Fix error message

---
 test/transaction.ts | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/test/transaction.ts b/test/transaction.ts
index f56488908..f795a3a66 100644
--- a/test/transaction.ts
+++ b/test/transaction.ts
@@ -747,7 +747,7 @@ async.each(
             // Ensures that this mocking object is not being misused.
             this.functionsMocked.forEach(fn => {
               if (fn.name === functionName) {
-                throw Error('${functionName} has already been mocked out');
+                throw Error(`${functionName} has already been mocked out`);
               }
             });
             if (dataClient && dataClient[functionName]) {

From 1fb109b2f9c9299013ddb42007baff2508ed2a29 Mon Sep 17 00:00:00 2001
From: Daniel Bruce <djbruce@google.com>
Date: Fri, 10 Nov 2023 14:49:15 -0500
Subject: [PATCH 087/129] Comments and general cleanup

Remove constructor parameter that is not used. Add comments to functions so that the tests are more readable.
---
 test/transaction.ts | 20 ++++++++++----------
 1 file changed, 10 insertions(+), 10 deletions(-)

diff --git a/test/transaction.ts b/test/transaction.ts
index f795a3a66..2551aafe1 100644
--- a/test/transaction.ts
+++ b/test/transaction.ts
@@ -666,7 +666,6 @@ async.each(
         });
       });
 
-      // TODO: Add a test here for calling commit
       describe('various functions without setting up transaction id when run returns a response', () => {
         // These tests were created so that when transaction.run is restructured we
         // can be confident that it works the same way as before.
@@ -674,11 +673,9 @@ async.each(
           transaction: Buffer.from(Array.from(Array(100).keys())),
         };
 
-        /*
-          MockedTransactionWrapper is a helper class for mocking out various
-          Gapic functions and ensuring that responses and errors actually make it
-          back to the user.
-         */
+        // MockedTransactionWrapper is a helper class for mocking out various
+        // Gapic functions and ensuring that responses and errors actually make it
+        // back to the user.
         class MockedTransactionWrapper {
           datastore: Datastore;
           transaction: Transaction;
@@ -686,11 +683,9 @@ async.each(
           mockedBeginTransaction: any;
           mockedFunction: any; // TODO: replace with type
           functionsMocked: {name: string; mockedFunction: any}[];
-          callBackSignaler: (callbackReached: string) => void;
+          callBackSignaler: (callbackReached: string) => void = () => {};
 
-          constructor(callBackSignaler?: (callbackReached: string) => void) {
-            const defaultCallback = () => {};
-            this.callBackSignaler = callBackSignaler ?? defaultCallback;
+          constructor() {
             const namespace = 'run-without-mock';
             const projectId = 'project-id';
             const options = {
@@ -729,6 +724,8 @@ async.each(
                   {} | null | undefined
                 >
               ) => {
+                // Calls a user provided function that will receive this string
+                // Usually used to track when this code was reached relative to other code
                 this.callBackSignaler('beginTransaction called');
                 callback(null, testRunResp);
               };
@@ -737,6 +734,9 @@ async.each(
             this.functionsMocked = [];
             this.datastore = datastore;
           }
+
+          // This mocks out a gapic function to just call the callback received in the Gapic function.
+          // The callback will send back the error and response arguments provided as parameters.
           mockGapicFunction<ResponseType>(
             functionName: string,
             response: ResponseType,

From f09b6f8e64bbc86b6911d0a0ecacc2ded5ad3ba8 Mon Sep 17 00:00:00 2001
From: Daniel Bruce <djbruce@google.com>
Date: Fri, 10 Nov 2023 15:34:55 -0500
Subject: [PATCH 088/129] Added comments for functions in transaction.ts

Functions in transaction.ts need JSdoc comments.
---
 src/transaction.ts | 11 +++++++++--
 1 file changed, 9 insertions(+), 2 deletions(-)

diff --git a/src/transaction.ts b/src/transaction.ts
index 935bfe59b..24f02ddcf 100644
--- a/src/transaction.ts
+++ b/src/transaction.ts
@@ -709,9 +709,9 @@ class Transaction extends DatastoreRequest {
   /**
    * This function parses results from a beginTransaction call
    *
-   * @param {RequestPromiseReturnType} response The response from a call to
+   * @param {RequestPromiseReturnType} [response] The response from a call to
    * begin a transaction.
-   * @param {RunCallback} callback A callback that accepts an error and a
+   * @param {RunCallback} [callback] A callback that accepts an error and a
    * response as arguments.
    *
    **/
@@ -729,6 +729,13 @@ class Transaction extends DatastoreRequest {
     }
   }
 
+  /**
+   * This function saves results from a successful beginTransaction call.
+   *
+   * @param {PassThroughReturnType<any>} [response] The response from a call to
+   * begin a transaction that completed successfully.
+   *
+   **/
   #parseRunSuccess(response: PassThroughReturnType<any>) {
     const resp = response.resp;
     this.id = resp!.transaction;

From 6b73bbf234c01b5de95c5eee1ff1fe39e8e180b3 Mon Sep 17 00:00:00 2001
From: Daniel Bruce <djbruce@google.com>
Date: Fri, 10 Nov 2023 15:49:40 -0500
Subject: [PATCH 089/129] Add a comment for aggregate queries

Add JSdoc comment
---
 src/request.ts | 12 ++++++++++++
 1 file changed, 12 insertions(+)

diff --git a/src/request.ts b/src/request.ts
index d39f572ef..1231e595a 100644
--- a/src/request.ts
+++ b/src/request.ts
@@ -546,6 +546,18 @@ class DatastoreRequest {
       );
   }
 
+  /**
+   * Datastore allows you to run aggregate queries by aggregate field.
+   *
+   * The query is run, and the results are returned as the second argument to
+   * your callback.
+   *
+   * @param {AggregateQuery} query AggregateQuery object.
+   * @param {RunQueryOptions} options Optional configuration
+   * @param {function} [callback] The callback function. If omitted, a promise is
+   * returned.
+   *
+   **/
   runAggregationQuery(
     query: AggregateQuery,
     options?: RunQueryOptions

From 9b4715d5877758056420aca54df9a7ce5ebea24c Mon Sep 17 00:00:00 2001
From: Daniel Bruce <djbruce@google.com>
Date: Fri, 10 Nov 2023 16:25:19 -0500
Subject: [PATCH 090/129] Add an assertion to test

The assertion should make sure the first read returns undefined.
---
 system-test/datastore.ts | 3 ++-
 1 file changed, 2 insertions(+), 1 deletion(-)

diff --git a/system-test/datastore.ts b/system-test/datastore.ts
index cbda61361..05e5c571b 100644
--- a/system-test/datastore.ts
+++ b/system-test/datastore.ts
@@ -1757,7 +1757,8 @@ async.each(
           };
           const transaction = datastore.transaction();
           await transaction.run();
-          await transaction.get(key);
+          const [firstRead] = await transaction.get(key);
+          assert(!firstRead);
           transaction.save({key, data: obj});
           await transaction.commit();
           const [entity] = await datastore.get(key);

From 6e737717973f37acfb9e4da26d7e590d245ee587 Mon Sep 17 00:00:00 2001
From: Daniel Bruce <djbruce@google.com>
Date: Fri, 10 Nov 2023 16:57:42 -0500
Subject: [PATCH 091/129] Add tests for lookup, put, commit

For calls with run and without run, do lookup, put, commit. Make sure to clean up after the tests too.
---
 system-test/datastore.ts | 35 +++++++++++++++++++++++++----------
 1 file changed, 25 insertions(+), 10 deletions(-)

diff --git a/system-test/datastore.ts b/system-test/datastore.ts
index 05e5c571b..e5e70dc38 100644
--- a/system-test/datastore.ts
+++ b/system-test/datastore.ts
@@ -1750,20 +1750,35 @@ async.each(
         });
       });
       describe('transactions', () => {
-        it('should run in a transaction', async () => {
+        describe('lookup, put, commit', () => {
           const key = datastore.key(['Company', 'Google']);
           const obj = {
             url: 'www.google.com',
           };
-          const transaction = datastore.transaction();
-          await transaction.run();
-          const [firstRead] = await transaction.get(key);
-          assert(!firstRead);
-          transaction.save({key, data: obj});
-          await transaction.commit();
-          const [entity] = await datastore.get(key);
-          delete entity[datastore.KEY];
-          assert.deepStrictEqual(entity, obj);
+          afterEach(async () => {
+            await datastore.delete(key);
+          });
+          it('should run in a transaction', async () => {
+            const transaction = datastore.transaction();
+            await transaction.run();
+            const [firstRead] = await transaction.get(key);
+            assert(!firstRead);
+            transaction.save({key, data: obj});
+            await transaction.commit();
+            const [entity] = await datastore.get(key);
+            delete entity[datastore.KEY];
+            assert.deepStrictEqual(entity, obj);
+          });
+          it('should run in a transaction without run', async () => {
+            const transaction = datastore.transaction();
+            const [firstRead] = await transaction.get(key);
+            assert(!firstRead);
+            transaction.save({key, data: obj});
+            await transaction.commit();
+            const [entity] = await datastore.get(key);
+            delete entity[datastore.KEY];
+            assert.deepStrictEqual(entity, obj);
+          });
         });
 
         it('should commit all saves and deletes at the end', async () => {

From 74097f31442461aa6f9b54500b25d95f13170def Mon Sep 17 00:00:00 2001
From: Daniel Bruce <djbruce@google.com>
Date: Fri, 10 Nov 2023 17:04:29 -0500
Subject: [PATCH 092/129] refactor the test

Test does lookup, put, commit. This is done in an async function.
---
 system-test/datastore.ts | 27 +++++++++++++++++++++++++++
 1 file changed, 27 insertions(+)

diff --git a/system-test/datastore.ts b/system-test/datastore.ts
index e5e70dc38..2e024d994 100644
--- a/system-test/datastore.ts
+++ b/system-test/datastore.ts
@@ -1751,6 +1751,33 @@ async.each(
       });
       describe('transactions', () => {
         describe('lookup, put, commit', () => {
+          const key = datastore.key(['Company', 'Google']);
+          const obj = {
+            url: 'www.google.com',
+          };
+          afterEach(async () => {
+            await datastore.delete(key);
+          });
+          async function doLookupPutCommit(transaction: Transaction) {
+            const [firstRead] = await transaction.get(key);
+            assert(!firstRead);
+            transaction.save({key, data: obj});
+            await transaction.commit();
+            const [entity] = await datastore.get(key);
+            delete entity[datastore.KEY];
+            assert.deepStrictEqual(entity, obj);
+          }
+          it('should run in a transaction', async () => {
+            const transaction = datastore.transaction();
+            await transaction.run();
+            await doLookupPutCommit(transaction);
+          });
+          it('should run in a transaction without run', async () => {
+            const transaction = datastore.transaction();
+            await doLookupPutCommit(transaction);
+          });
+        });
+        describe('put, lookup, commit', () => {
           const key = datastore.key(['Company', 'Google']);
           const obj = {
             url: 'www.google.com',

From 41fc42be100cc41abd96e893231bf22dc8a54d22 Mon Sep 17 00:00:00 2001
From: Daniel Bruce <djbruce@google.com>
Date: Fri, 10 Nov 2023 17:20:48 -0500
Subject: [PATCH 093/129] Add tests for put, lookup, commit

A group of tests should be added for put, lookup, commit. One with run and one without run.
---
 system-test/datastore.ts | 19 ++++++++-----------
 1 file changed, 8 insertions(+), 11 deletions(-)

diff --git a/system-test/datastore.ts b/system-test/datastore.ts
index 2e024d994..19e0db0ab 100644
--- a/system-test/datastore.ts
+++ b/system-test/datastore.ts
@@ -1785,26 +1785,23 @@ async.each(
           afterEach(async () => {
             await datastore.delete(key);
           });
-          it('should run in a transaction', async () => {
-            const transaction = datastore.transaction();
-            await transaction.run();
+          async function doPutLookupCommit(transaction: Transaction) {
+            transaction.save({key, data: obj});
             const [firstRead] = await transaction.get(key);
             assert(!firstRead);
-            transaction.save({key, data: obj});
             await transaction.commit();
             const [entity] = await datastore.get(key);
             delete entity[datastore.KEY];
             assert.deepStrictEqual(entity, obj);
+          }
+          it('should run in a transaction', async () => {
+            const transaction = datastore.transaction();
+            await transaction.run();
+            await doPutLookupCommit(transaction);
           });
           it('should run in a transaction without run', async () => {
             const transaction = datastore.transaction();
-            const [firstRead] = await transaction.get(key);
-            assert(!firstRead);
-            transaction.save({key, data: obj});
-            await transaction.commit();
-            const [entity] = await datastore.get(key);
-            delete entity[datastore.KEY];
-            assert.deepStrictEqual(entity, obj);
+            await doPutLookupCommit(transaction);
           });
         });
 

From 1cfaa6c17d96da2c9d731483a182826c85e9c966 Mon Sep 17 00:00:00 2001
From: Daniel Bruce <djbruce@google.com>
Date: Tue, 14 Nov 2023 15:36:47 -0500
Subject: [PATCH 094/129] Add comments and general cleanup

Cleanup includes adding comments so that the tests are more readable.
---
 test/transaction.ts | 28 ++++++++++++++++++++++++----
 1 file changed, 24 insertions(+), 4 deletions(-)

diff --git a/test/transaction.ts b/test/transaction.ts
index 23919d022..6f08d72e0 100644
--- a/test/transaction.ts
+++ b/test/transaction.ts
@@ -284,7 +284,7 @@ async.each(
               response?: google.datastore.v1.IBeginTransactionResponse
             ) => {
               assert.strictEqual(error, null);
-              assert.strictEqual(response, testRunResp);
+              assert.deepStrictEqual(response, testRunResp);
               assert.strictEqual(transaction, transactionWithoutMock);
               done();
             };
@@ -665,7 +665,6 @@ async.each(
         });
       });
 
-      // TODO: Add a test here for calling commit
       describe('various functions without setting up transaction id when run returns a response', () => {
         // These tests were created so that when transaction.run is restructured we
         // can be confident that it works the same way as before.
@@ -673,6 +672,9 @@ async.each(
           transaction: Buffer.from(Array.from(Array(100).keys())),
         };
 
+        // MockedTransactionWrapper is a helper class for mocking out various
+        // Gapic functions and ensuring that responses and errors actually make it
+        // back to the user.
         class MockedTransactionWrapper {
           datastore: Datastore;
           transaction: Transaction;
@@ -680,6 +682,7 @@ async.each(
           mockedBeginTransaction: any;
           mockedFunction: any; // TODO: replace with type
           functionsMocked: {name: string; mockedFunction: any}[];
+          callBackSignaler: (callbackReached: string) => void = () => {};
 
           constructor() {
             const namespace = 'run-without-mock';
@@ -720,6 +723,9 @@ async.each(
                   {} | null | undefined
                 >
               ) => {
+                // Calls a user provided function that will receive this string
+                // Usually used to track when this code was reached relative to other code
+                this.callBackSignaler('beginTransaction called');
                 callback(null, testRunResp);
               };
             }
@@ -727,13 +733,22 @@ async.each(
             this.functionsMocked = [];
             this.datastore = datastore;
           }
+
+          // This mocks out a gapic function to just call the callback received in the Gapic function.
+          // The callback will send back the error and response arguments provided as parameters.
           mockGapicFunction<ResponseType>(
             functionName: string,
             response: ResponseType,
             error: Error | null
           ) {
             const dataClient = this.dataClient;
-            // TODO: Check here that function hasn't been mocked out already
+            // Check here that function hasn't been mocked out already
+            // Ensures that this mocking object is not being misused.
+            this.functionsMocked.forEach(fn => {
+              if (fn.name === functionName) {
+                throw Error(`${functionName} has already been mocked out`);
+              }
+            });
             if (dataClient && dataClient[functionName]) {
               this.functionsMocked.push({
                 name: functionName,
@@ -753,17 +768,22 @@ async.each(
                   {} | null | undefined
                 >
               ) => {
+                this.callBackSignaler(`${functionName} called`);
                 callback(error, response);
               };
             }
           }
 
+          // This resets beginTransaction from the Gapic layer to what it originally was.
+          // Resetting beginTransaction ensures other tests don't use the beginTransaction mock.
           resetBeginTransaction() {
             if (this.dataClient && this.dataClient.beginTransaction) {
               this.dataClient.beginTransaction = this.mockedBeginTransaction;
             }
           }
-          // TODO: Allow several functions to be mocked, eliminate string parameter
+
+          // This resets Gapic functions mocked out by the tests to what they originally were.
+          // Resetting mocked out Gapic functions ensures other tests don't use these mocks.
           resetGapicFunctions() {
             this.functionsMocked.forEach(functionMocked => {
               this.dataClient[functionMocked.name] =

From 2ff43b869eafc665b3f1090731aca7e6e7d9f7bc Mon Sep 17 00:00:00 2001
From: Daniel Bruce <djbruce@google.com>
Date: Tue, 14 Nov 2023 15:40:42 -0500
Subject: [PATCH 095/129] Remove some redundant tests

Some of these tests are captured in the various functions describe block.
---
 test/transaction.ts | 372 --------------------------------------------
 1 file changed, 372 deletions(-)

diff --git a/test/transaction.ts b/test/transaction.ts
index 6f08d72e0..9777fa7a0 100644
--- a/test/transaction.ts
+++ b/test/transaction.ts
@@ -290,378 +290,6 @@ async.each(
             };
             transactionWithoutMock.run({}, runCallback);
           });
-          describe('commit without setting up transaction id when run returns a response', () => {
-            // These tests were created so that when transaction.commit is restructured we
-            // can be confident that it works the same way as before.
-            const testCommitResp = {
-              mutationResults: [
-                {
-                  key: {
-                    path: [
-                      {
-                        kind: 'some-kind',
-                      },
-                    ],
-                  },
-                },
-              ],
-            };
-            const namespace = 'run-without-mock';
-            const projectId = 'project-id';
-            const testErrorMessage = 'test-commit-error';
-            const options = {
-              projectId,
-              namespace,
-            };
-            const datastore = new Datastore(options);
-            let transactionWithoutMock: Transaction;
-            const dataClientName = 'DatastoreClient';
-            let dataClient: ClientStub | undefined;
-            let originalCommitMethod: Function;
-
-            beforeEach(async () => {
-              // Create a fresh transaction for each test because transaction state changes after a commit.
-              transactionWithoutMock = datastore.transaction();
-              // In this before hook, save the original beginTransaction method in a variable.
-              // After tests are finished, reassign beginTransaction to the variable.
-              // This way, mocking beginTransaction in this block doesn't affect other tests.
-              const gapic = Object.freeze({
-                v1: require('../src/v1'),
-              });
-              // Datastore Gapic clients haven't been initialized yet so we initialize them here.
-              datastore.clients_.set(
-                dataClientName,
-                new gapic.v1[dataClientName](options)
-              );
-              dataClient = datastore.clients_.get(dataClientName);
-              if (dataClient && dataClient.commit) {
-                originalCommitMethod = dataClient.commit;
-              }
-              if (dataClient && dataClient.beginTransaction) {
-                dataClient.beginTransaction = (
-                  request: protos.google.datastore.v1.IBeginTransactionRequest,
-                  options: CallOptions,
-                  callback: Callback<
-                    protos.google.datastore.v1.IBeginTransactionResponse,
-                    | protos.google.datastore.v1.IBeginTransactionRequest
-                    | null
-                    | undefined,
-                    {} | null | undefined
-                  >
-                ) => {
-                  callback(null, testRunResp);
-                };
-              }
-            });
-
-            afterEach(() => {
-              // beginTransaction has likely been mocked out in these tests.
-              // We should reassign beginTransaction back to its original value for tests outside this block.
-              if (dataClient && originalCommitMethod) {
-                dataClient.commit = originalCommitMethod;
-              }
-            });
-
-            describe('should pass error back to the user', async () => {
-              beforeEach(() => {
-                // Mock out begin transaction and send error back to the user
-                // from the Gapic layer.
-                if (dataClient) {
-                  dataClient.commit = (
-                    request: protos.google.datastore.v1.ICommitRequest,
-                    options: CallOptions,
-                    callback: Callback<
-                      protos.google.datastore.v1.ICommitResponse,
-                      | protos.google.datastore.v1.ICommitRequest
-                      | null
-                      | undefined,
-                      {} | null | undefined
-                    >
-                  ) => {
-                    callback(new Error(testErrorMessage), testCommitResp);
-                  };
-                }
-              });
-
-              it('should send back the error when awaiting a promise', async () => {
-                try {
-                  await transactionWithoutMock.run();
-                  await transactionWithoutMock.commit();
-                  assert.fail('The run call should have failed.');
-                } catch (error: any) {
-                  // TODO: Substitute type any
-                  assert.strictEqual(error['message'], testErrorMessage);
-                }
-              });
-              it('should send back the error when using a callback', done => {
-                const commitCallback: CommitCallback = (
-                  error: Error | null | undefined,
-                  response?: google.datastore.v1.ICommitResponse
-                ) => {
-                  assert(error);
-                  assert.strictEqual(error.message, testErrorMessage);
-                  assert.strictEqual(response, testCommitResp);
-                  done();
-                };
-                transactionWithoutMock.run(
-                  (
-                    error: Error | null,
-                    transaction: Transaction | null,
-                    response?: google.datastore.v1.IBeginTransactionResponse
-                  ) => {
-                    transactionWithoutMock.commit(commitCallback);
-                  }
-                );
-              });
-            });
-            describe('should pass response back to the user', async () => {
-              beforeEach(() => {
-                // Mock out begin transaction and send a response
-                // back to the user from the Gapic layer.
-                if (dataClient) {
-                  dataClient.commit = (
-                    request: protos.google.datastore.v1.ICommitRequest,
-                    options: CallOptions,
-                    callback: Callback<
-                      protos.google.datastore.v1.ICommitResponse,
-                      | protos.google.datastore.v1.ICommitRequest
-                      | null
-                      | undefined,
-                      {} | null | undefined
-                    >
-                  ) => {
-                    callback(null, testCommitResp);
-                  };
-                }
-              });
-              it('should send back the response when awaiting a promise', async () => {
-                await transactionWithoutMock.run();
-                const [commitResults] = await transactionWithoutMock.commit();
-                assert.strictEqual(commitResults, testCommitResp);
-              });
-              it('should send back the response when using a callback', done => {
-                const commitCallback: CommitCallback = (
-                  error: Error | null | undefined,
-                  response?: google.datastore.v1.ICommitResponse
-                ) => {
-                  assert.strictEqual(error, null);
-                  assert.strictEqual(response, testCommitResp);
-                  done();
-                };
-                transactionWithoutMock.run(
-                  (
-                    error: Error | null,
-                    transaction: Transaction | null,
-                    response?: google.datastore.v1.IBeginTransactionResponse
-                  ) => {
-                    transactionWithoutMock.commit(commitCallback);
-                  }
-                );
-              });
-            });
-          });
-          describe('runAggregationQuery without setting up transaction id when run returns a response', () => {
-            // These tests were created so that when transaction.runAggregateQuery is restructured we
-            // can be confident that it works the same way as before.
-
-            const runAggregationQueryUserResp = [{'average rating': 100}];
-            const runAggregationQueryResp = {
-              batch: {
-                aggregationResults: [
-                  {
-                    aggregateProperties: {
-                      'average rating': {
-                        meaning: 0,
-                        excludeFromIndexes: false,
-                        doubleValue: 100,
-                        valueType: 'doubleValue',
-                      },
-                    },
-                  },
-                ],
-                moreResults:
-                  google.datastore.v1.QueryResultBatch.MoreResultsType
-                    .NO_MORE_RESULTS,
-                readTime: {seconds: '1699390681', nanos: 961667000},
-              },
-              query: null,
-              transaction: testRunResp.transaction,
-            };
-            const namespace = 'run-without-mock';
-            const projectId = 'project-id';
-            const testErrorMessage = 'test-run-Aggregate-Query-error';
-            const options = {
-              projectId,
-              namespace,
-            };
-            const datastore = new Datastore(options);
-            const q = datastore.createQuery('Character');
-            const aggregate = datastore
-              .createAggregationQuery(q)
-              .addAggregation(AggregateField.average('appearances'));
-            let transactionWithoutMock: Transaction;
-            const dataClientName = 'DatastoreClient';
-            let dataClient: ClientStub | undefined;
-            let originalRunAggregateQueryMethod: Function;
-
-            beforeEach(async () => {
-              // Create a fresh transaction for each test because transaction state changes after a commit.
-              transactionWithoutMock = datastore.transaction();
-              // In this before hook, save the original beginTransaction method in a variable.
-              // After tests are finished, reassign beginTransaction to the variable.
-              // This way, mocking beginTransaction in this block doesn't affect other tests.
-              const gapic = Object.freeze({
-                v1: require('../src/v1'),
-              });
-              // Datastore Gapic clients haven't been initialized yet so we initialize them here.
-              datastore.clients_.set(
-                dataClientName,
-                new gapic.v1[dataClientName](options)
-              );
-              dataClient = datastore.clients_.get(dataClientName);
-              if (dataClient && dataClient.runAggregationQuery) {
-                originalRunAggregateQueryMethod =
-                  dataClient.runAggregationQuery;
-              }
-              if (dataClient && dataClient.beginTransaction) {
-                dataClient.beginTransaction = (
-                  request: protos.google.datastore.v1.IBeginTransactionRequest,
-                  options: CallOptions,
-                  callback: Callback<
-                    protos.google.datastore.v1.IBeginTransactionResponse,
-                    | protos.google.datastore.v1.IBeginTransactionRequest
-                    | null
-                    | undefined,
-                    {} | null | undefined
-                  >
-                ) => {
-                  callback(null, testRunResp);
-                };
-              }
-            });
-
-            afterEach(() => {
-              // beginTransaction has likely been mocked out in these tests.
-              // We should reassign beginTransaction back to its original value for tests outside this block.
-              if (dataClient && originalRunAggregateQueryMethod) {
-                dataClient.runAggregationQuery =
-                  originalRunAggregateQueryMethod;
-              }
-            });
-
-            describe('should pass error back to the user', async () => {
-              beforeEach(() => {
-                // Mock out begin transaction and send error back to the user
-                // from the Gapic layer.
-                if (dataClient) {
-                  dataClient.runAggregationQuery = (
-                    request: protos.google.datastore.v1.IRunAggregationQueryRequest,
-                    options: CallOptions,
-                    callback: Callback<
-                      protos.google.datastore.v1.IRunAggregationQueryResponse,
-                      | protos.google.datastore.v1.IRunAggregationQueryRequest
-                      | null
-                      | undefined,
-                      {} | null | undefined
-                    >
-                  ) => {
-                    callback(
-                      new Error(testErrorMessage),
-                      runAggregationQueryResp
-                    );
-                  };
-                }
-              });
-
-              it('should send back the error when awaiting a promise', async () => {
-                try {
-                  await transactionWithoutMock.run();
-                  const results =
-                    await transactionWithoutMock.runAggregationQuery(aggregate);
-                  assert.fail('The run call should have failed.');
-                } catch (error: any) {
-                  // TODO: Substitute type any
-                  assert.strictEqual(error['message'], testErrorMessage);
-                }
-              });
-              it('should send back the error when using a callback', done => {
-                const runAggregateQueryCallback: RequestCallback = (
-                  error: Error | null | undefined,
-                  response?: any
-                ) => {
-                  assert(error);
-                  assert.strictEqual(error.message, testErrorMessage);
-                  assert.deepStrictEqual(response, runAggregationQueryUserResp);
-                  done();
-                };
-                transactionWithoutMock.run(
-                  (
-                    error: Error | null,
-                    transaction: Transaction | null,
-                    response?: google.datastore.v1.IBeginTransactionResponse
-                  ) => {
-                    transactionWithoutMock.runAggregationQuery(
-                      aggregate,
-                      runAggregateQueryCallback
-                    );
-                  }
-                );
-              });
-            });
-            describe('should pass response back to the user', async () => {
-              beforeEach(() => {
-                // Mock out begin transaction and send a response
-                // back to the user from the Gapic layer.
-                if (dataClient) {
-                  dataClient.runAggregationQuery = (
-                    request: protos.google.datastore.v1.IRunAggregationQueryRequest,
-                    options: CallOptions,
-                    callback: Callback<
-                      protos.google.datastore.v1.IRunAggregationQueryResponse,
-                      | protos.google.datastore.v1.IRunAggregationQueryRequest
-                      | null
-                      | undefined,
-                      {} | null | undefined
-                    >
-                  ) => {
-                    callback(null, runAggregationQueryResp);
-                  };
-                }
-              });
-              it('should send back the response when awaiting a promise', async () => {
-                await transactionWithoutMock.run();
-                const allResults =
-                  await transactionWithoutMock.runAggregationQuery(aggregate);
-                const [runAggregateQueryResults] = allResults;
-                assert.deepStrictEqual(
-                  runAggregateQueryResults,
-                  runAggregationQueryUserResp
-                );
-              });
-              it('should send back the response when using a callback', done => {
-                const runAggregateQueryCallback: RequestCallback = (
-                  error: Error | null | undefined,
-                  response?: any
-                ) => {
-                  assert.strictEqual(error, null);
-                  assert.deepStrictEqual(response, runAggregationQueryUserResp);
-                  done();
-                };
-                transactionWithoutMock.run(
-                  (
-                    error: Error | null,
-                    transaction: Transaction | null,
-                    response?: google.datastore.v1.IBeginTransactionResponse
-                  ) => {
-                    transactionWithoutMock.runAggregationQuery(
-                      aggregate,
-                      runAggregateQueryCallback
-                    );
-                  }
-                );
-              });
-            });
-          });
         });
       });
 

From 351840c2ba8fcb439f0e64e46fbfc2334d58db88 Mon Sep 17 00:00:00 2001
From: Daniel Bruce <djbruce@google.com>
Date: Tue, 14 Nov 2023 15:48:40 -0500
Subject: [PATCH 096/129] Correct the comment to be more accurate

The comment should talk about what the tests actually intend to do
---
 test/transaction.ts | 5 ++---
 1 file changed, 2 insertions(+), 3 deletions(-)

diff --git a/test/transaction.ts b/test/transaction.ts
index 9777fa7a0..25659d556 100644
--- a/test/transaction.ts
+++ b/test/transaction.ts
@@ -294,8 +294,8 @@ async.each(
       });
 
       describe('various functions without setting up transaction id when run returns a response', () => {
-        // These tests were created so that when transaction.run is restructured we
-        // can be confident that it works the same way as before.
+        // These tests were created to ensure that various transaction functions maintain the same behavior
+        // after being restructured to start with a run call.
         const testRunResp = {
           transaction: Buffer.from(Array.from(Array(100).keys())),
         };
@@ -687,7 +687,6 @@ async.each(
                 await transaction.runQuery(q);
                 assert.fail('The run call should have failed.');
               } catch (error: any) {
-                // TODO: Substitute type any
                 assert.strictEqual(error['message'], testErrorMessage);
               }
             });

From e797c34f77e73db8e3251e01692858c82a2df564 Mon Sep 17 00:00:00 2001
From: Daniel Bruce <djbruce@google.com>
Date: Wed, 15 Nov 2023 10:44:51 -0500
Subject: [PATCH 097/129] General improvements to code quality

Move declared transactionWrapper and other variables out to shorten code. Remove require and replace with import. Use more specific type.
---
 test/transaction.ts | 17 +++++------------
 1 file changed, 5 insertions(+), 12 deletions(-)

diff --git a/test/transaction.ts b/test/transaction.ts
index 25659d556..0ffd1be92 100644
--- a/test/transaction.ts
+++ b/test/transaction.ts
@@ -28,7 +28,7 @@ import {
   Transaction,
   AggregateField,
 } from '../src';
-import {Entity} from '../src/entity';
+import {Entity, entity} from '../src/entity';
 import * as tsTypes from '../src/transaction';
 import * as sinon from 'sinon';
 import {Callback, CallOptions, ClientStub} from 'google-gax';
@@ -50,9 +50,6 @@ const async = require('async');
 type Any = any;
 type Path = string | [string] | [string, number];
 
-// eslint-disable-next-line @typescript-eslint/no-var-requires
-const {entity} = require('../src/entity');
-
 let promisified = false;
 const fakePfy = Object.assign({}, pfy, {
   promisifyAll(klass: Function, options: pfy.PromisifyAllOptions) {
@@ -420,6 +417,9 @@ async.each(
           }
         }
 
+        let transactionWrapper: MockedTransactionWrapper;
+        let transaction: Transaction;
+
         describe('commit', () => {
           const testCommitResp = {
             mutationResults: [
@@ -435,7 +435,6 @@ async.each(
             ],
           };
           const testErrorMessage = 'test-commit-error';
-          let transactionWrapper: MockedTransactionWrapper;
 
           beforeEach(async () => {
             transactionWrapper = new MockedTransactionWrapper();
@@ -544,8 +543,6 @@ async.each(
             transaction: testRunResp.transaction,
           };
           const testErrorMessage = 'test-run-Aggregate-Query-error';
-          let transactionWrapper: MockedTransactionWrapper;
-          let transaction: Transaction;
           let aggregate: AggregateQuery;
 
           beforeEach(async () => {
@@ -657,8 +654,6 @@ async.each(
           };
           const runQueryUserResp: Entity[] = [];
           const testErrorMessage = 'test-run-Query-error';
-          let transactionWrapper: MockedTransactionWrapper;
-          let transaction: Transaction;
           let q: Query;
 
           beforeEach(async () => {
@@ -781,10 +776,8 @@ async.each(
           };
           const getUserResp = 'post1';
           const testErrorMessage = 'test-run-Query-error';
-          let transactionWrapper: MockedTransactionWrapper;
-          let transaction: Transaction;
           let q: Query;
-          let key: any; // TODO: Replace with key type
+          let key: entity.Key; // TODO: Replace with key type
 
           beforeEach(async () => {
             transactionWrapper = new MockedTransactionWrapper();

From 4eb7a5e72e80d1b79f1a93cf269f94681734a278 Mon Sep 17 00:00:00 2001
From: Daniel Bruce <djbruce@google.com>
Date: Wed, 15 Nov 2023 10:54:45 -0500
Subject: [PATCH 098/129] Add data client check

Add data client check and add type for data client.
---
 test/transaction.ts | 10 ++++++----
 1 file changed, 6 insertions(+), 4 deletions(-)

diff --git a/test/transaction.ts b/test/transaction.ts
index 0ffd1be92..44bb7d597 100644
--- a/test/transaction.ts
+++ b/test/transaction.ts
@@ -43,7 +43,7 @@ import {google} from '../protos/protos';
 import {RunCallback} from '../src/transaction';
 import * as protos from '../protos/protos';
 import {AggregateQuery} from '../src/aggregate';
-import {RunQueryCallback, RunQueryResponse} from '../src/query';
+import {RunQueryCallback} from '../src/query';
 const async = require('async');
 
 // eslint-disable-next-line @typescript-eslint/no-explicit-any
@@ -303,7 +303,7 @@ async.each(
         class MockedTransactionWrapper {
           datastore: Datastore;
           transaction: Transaction;
-          dataClient: any; // TODO: replace with data client type
+          dataClient?: ClientStub;
           mockedBeginTransaction: any;
           mockedFunction: any; // TODO: replace with type
           functionsMocked: {name: string; mockedFunction: any}[];
@@ -411,8 +411,10 @@ async.each(
           // Resetting mocked out Gapic functions ensures other tests don't use these mocks.
           resetGapicFunctions() {
             this.functionsMocked.forEach(functionMocked => {
-              this.dataClient[functionMocked.name] =
-                functionMocked.mockedFunction;
+              if (this.dataClient) {
+                this.dataClient[functionMocked.name] =
+                  functionMocked.mockedFunction;
+              }
             });
           }
         }

From 355c0a5220ead7fcf6be8be821927fb06627c2dd Mon Sep 17 00:00:00 2001
From: Daniel Bruce <djbruce@google.com>
Date: Wed, 15 Nov 2023 10:58:03 -0500
Subject: [PATCH 099/129] Eliminate the mocked function variable

The mocked function variable is not used so remove it from code.
---
 test/transaction.ts | 2 --
 1 file changed, 2 deletions(-)

diff --git a/test/transaction.ts b/test/transaction.ts
index 44bb7d597..a7d653dd2 100644
--- a/test/transaction.ts
+++ b/test/transaction.ts
@@ -305,7 +305,6 @@ async.each(
           transaction: Transaction;
           dataClient?: ClientStub;
           mockedBeginTransaction: any;
-          mockedFunction: any; // TODO: replace with type
           functionsMocked: {name: string; mockedFunction: any}[];
           callBackSignaler: (callbackReached: string) => void = () => {};
 
@@ -379,7 +378,6 @@ async.each(
                 name: functionName,
                 mockedFunction: dataClient[functionName],
               });
-              this.mockedFunction = dataClient[functionName];
             }
             if (dataClient && dataClient[functionName]) {
               dataClient[functionName] = (

From 310b3201349b925eb0a145bbf51067fb4594608a Mon Sep 17 00:00:00 2001
From: Daniel Bruce <djbruce@google.com>
Date: Wed, 15 Nov 2023 13:07:37 -0500
Subject: [PATCH 100/129] Move begin transaction setup code

The same block of code for setting up begin transaction is repeated twice. Move it into one function and use it from both before blocks.
---
 test/transaction.ts | 68 +++++++++++++++++++++++++--------------------
 1 file changed, 38 insertions(+), 30 deletions(-)

diff --git a/test/transaction.ts b/test/transaction.ts
index a7d653dd2..5b13cec72 100644
--- a/test/transaction.ts
+++ b/test/transaction.ts
@@ -159,6 +159,24 @@ async.each(
         });
       });
 
+      type beginTransactionSignature = (
+        request: protos.google.datastore.v1.IBeginTransactionRequest,
+        options: CallOptions,
+        callback: Callback<
+          protos.google.datastore.v1.IBeginTransactionResponse,
+          | protos.google.datastore.v1.IBeginTransactionRequest
+          | null
+          | undefined,
+          {} | null | undefined
+        >
+      ) => Promise<
+        [
+          protos.google.datastore.v1.IBeginTransactionResponse,
+          protos.google.datastore.v1.IBeginTransactionRequest | undefined,
+          {} | undefined,
+        ]
+      > | void;
+
       describe('run without setting up transaction id', () => {
         // These tests were created so that when transaction.run is restructured we
         // can be confident that it works the same way as before.
@@ -204,25 +222,29 @@ async.each(
           }
         });
 
+        function setupBeginTransaction(err: Error | null | undefined) {
+          if (dataClient) {
+            dataClient.beginTransaction = (
+              request: protos.google.datastore.v1.IBeginTransactionRequest,
+              options: CallOptions,
+              callback: Callback<
+                protos.google.datastore.v1.IBeginTransactionResponse,
+                | protos.google.datastore.v1.IBeginTransactionRequest
+                | null
+                | undefined,
+                {} | null | undefined
+              >
+            ) => {
+              callback(err, testRunResp);
+            };
+          }
+        }
+
         describe('should pass error back to the user', async () => {
           beforeEach(() => {
             // Mock out begin transaction and send error back to the user
             // from the Gapic layer.
-            if (dataClient) {
-              dataClient.beginTransaction = (
-                request: protos.google.datastore.v1.IBeginTransactionRequest,
-                options: CallOptions,
-                callback: Callback<
-                  protos.google.datastore.v1.IBeginTransactionResponse,
-                  | protos.google.datastore.v1.IBeginTransactionRequest
-                  | null
-                  | undefined,
-                  {} | null | undefined
-                >
-              ) => {
-                callback(new Error(testErrorMessage), testRunResp);
-              };
-            }
+            setupBeginTransaction(new Error(testErrorMessage));
           });
 
           it('should send back the error when awaiting a promise', async () => {
@@ -253,21 +275,7 @@ async.each(
           beforeEach(() => {
             // Mock out begin transaction and send a response
             // back to the user from the Gapic layer.
-            if (dataClient) {
-              dataClient.beginTransaction = (
-                request: protos.google.datastore.v1.IBeginTransactionRequest,
-                options: CallOptions,
-                callback: Callback<
-                  protos.google.datastore.v1.IBeginTransactionResponse,
-                  | protos.google.datastore.v1.IBeginTransactionRequest
-                  | null
-                  | undefined,
-                  {} | null | undefined
-                >
-              ) => {
-                callback(null, testRunResp);
-              };
-            }
+            setupBeginTransaction(null);
           });
           it('should send back the response when awaiting a promise', async () => {
             const [transaction, resp] = await transactionWithoutMock.run();

From b1517407fbbbabac5096420b474e740aa0be31fb Mon Sep 17 00:00:00 2001
From: Daniel Bruce <djbruce@google.com>
Date: Wed, 15 Nov 2023 13:25:10 -0500
Subject: [PATCH 101/129] Replace the TODO for the key

Add some try blocks to make the errors more visible also.
---
 test/transaction.ts | 29 +++++++++++++++++------------
 1 file changed, 17 insertions(+), 12 deletions(-)

diff --git a/test/transaction.ts b/test/transaction.ts
index 5b13cec72..4405405c1 100644
--- a/test/transaction.ts
+++ b/test/transaction.ts
@@ -252,7 +252,6 @@ async.each(
               await transactionWithoutMock.run();
               assert.fail('The run call should have failed.');
             } catch (error: any) {
-              // TODO: Substitute type any
               assert.strictEqual(error['message'], testErrorMessage);
             }
           });
@@ -262,11 +261,14 @@ async.each(
               transaction: Transaction | null,
               response?: google.datastore.v1.IBeginTransactionResponse
             ) => {
-              assert(error);
-              assert.strictEqual(error.message, testErrorMessage);
-              assert.strictEqual(transaction, null);
-              assert.strictEqual(response, testRunResp);
-              done();
+              try {
+                assert(error);
+                assert.strictEqual(error.message, testErrorMessage);
+                assert.strictEqual(transaction, null);
+                assert.strictEqual(response, testRunResp);
+              } catch (e) {
+                done(e);
+              }
             };
             transactionWithoutMock.run({}, runCallback);
           });
@@ -288,10 +290,13 @@ async.each(
               transaction: Transaction | null,
               response?: google.datastore.v1.IBeginTransactionResponse
             ) => {
-              assert.strictEqual(error, null);
-              assert.deepStrictEqual(response, testRunResp);
-              assert.strictEqual(transaction, transactionWithoutMock);
-              done();
+              try {
+                assert.strictEqual(error, null);
+                assert.deepStrictEqual(response, testRunResp);
+                assert.strictEqual(transaction, transactionWithoutMock);
+              } catch (e) {
+                done(e);
+              }
             };
             transactionWithoutMock.run({}, runCallback);
           });
@@ -312,7 +317,7 @@ async.each(
           datastore: Datastore;
           transaction: Transaction;
           dataClient?: ClientStub;
-          mockedBeginTransaction: any;
+          mockedBeginTransaction: any; // TODO: Function
           functionsMocked: {name: string; mockedFunction: any}[];
           callBackSignaler: (callbackReached: string) => void = () => {};
 
@@ -785,7 +790,7 @@ async.each(
           const getUserResp = 'post1';
           const testErrorMessage = 'test-run-Query-error';
           let q: Query;
-          let key: entity.Key; // TODO: Replace with key type
+          let key: entity.Key;
 
           beforeEach(async () => {
             transactionWrapper = new MockedTransactionWrapper();

From a369202cee54a1cad91f413cbc9e5f40b6a43c5b Mon Sep 17 00:00:00 2001
From: Daniel Bruce <djbruce@google.com>
Date: Wed, 15 Nov 2023 13:28:37 -0500
Subject: [PATCH 102/129] Update description

Update the description for the test to give a better explanation of what the describe block does.
---
 test/transaction.ts | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/test/transaction.ts b/test/transaction.ts
index 4405405c1..738145262 100644
--- a/test/transaction.ts
+++ b/test/transaction.ts
@@ -303,7 +303,7 @@ async.each(
         });
       });
 
-      describe('various functions without setting up transaction id when run returns a response', () => {
+      describe('testing various transaction functions when transaction.run returns a response', () => {
         // These tests were created to ensure that various transaction functions maintain the same behavior
         // after being restructured to start with a run call.
         const testRunResp = {

From 99530ea55b59c401265ef7fbd7899b9aa6ca2cd1 Mon Sep 17 00:00:00 2001
From: Daniel Bruce <djbruce@google.com>
Date: Wed, 15 Nov 2023 13:33:37 -0500
Subject: [PATCH 103/129] Add comments to describe the purpose of signaller

The comments describing the callback signaller should explain the problem it solves.
---
 test/transaction.ts | 2 ++
 1 file changed, 2 insertions(+)

diff --git a/test/transaction.ts b/test/transaction.ts
index 738145262..6fbf33b8c 100644
--- a/test/transaction.ts
+++ b/test/transaction.ts
@@ -319,6 +319,8 @@ async.each(
           dataClient?: ClientStub;
           mockedBeginTransaction: any; // TODO: Function
           functionsMocked: {name: string; mockedFunction: any}[];
+          // The callBackSignaler lets the user of this object get a signal when the mocked function is called.
+          // This is useful for tests that need to know when the mocked function is called.
           callBackSignaler: (callbackReached: string) => void = () => {};
 
           constructor() {

From a42c7fe43c0c4885adad611101073edfa7a21c3f Mon Sep 17 00:00:00 2001
From: Daniel Bruce <djbruce@google.com>
Date: Wed, 15 Nov 2023 13:45:40 -0500
Subject: [PATCH 104/129] Add both dones back in

The done functions were lost in the refactors. Let us add them back in.
---
 test/transaction.ts | 2 ++
 1 file changed, 2 insertions(+)

diff --git a/test/transaction.ts b/test/transaction.ts
index 6fbf33b8c..c232ca3ff 100644
--- a/test/transaction.ts
+++ b/test/transaction.ts
@@ -266,6 +266,7 @@ async.each(
                 assert.strictEqual(error.message, testErrorMessage);
                 assert.strictEqual(transaction, null);
                 assert.strictEqual(response, testRunResp);
+                done();
               } catch (e) {
                 done(e);
               }
@@ -294,6 +295,7 @@ async.each(
                 assert.strictEqual(error, null);
                 assert.deepStrictEqual(response, testRunResp);
                 assert.strictEqual(transaction, transactionWithoutMock);
+                done();
               } catch (e) {
                 done(e);
               }

From ca6f63b99ba1184e8a35ca1473ed900d7542e814 Mon Sep 17 00:00:00 2001
From: Daniel Bruce <djbruce@google.com>
Date: Wed, 15 Nov 2023 13:54:06 -0500
Subject: [PATCH 105/129] mockedBeginTransaction should be Function

Make a more specific type for mockedBeginTransaction and the functions mocked.
---
 test/transaction.ts | 5 +++--
 1 file changed, 3 insertions(+), 2 deletions(-)

diff --git a/test/transaction.ts b/test/transaction.ts
index c232ca3ff..72bb06855 100644
--- a/test/transaction.ts
+++ b/test/transaction.ts
@@ -319,8 +319,8 @@ async.each(
           datastore: Datastore;
           transaction: Transaction;
           dataClient?: ClientStub;
-          mockedBeginTransaction: any; // TODO: Function
-          functionsMocked: {name: string; mockedFunction: any}[];
+          mockedBeginTransaction: Function;
+          functionsMocked: {name: string; mockedFunction: Function}[];
           // The callBackSignaler lets the user of this object get a signal when the mocked function is called.
           // This is useful for tests that need to know when the mocked function is called.
           callBackSignaler: (callbackReached: string) => void = () => {};
@@ -349,6 +349,7 @@ async.each(
             );
             const dataClient = datastore.clients_.get(dataClientName);
             // Mock begin transaction
+            this.mockedBeginTransaction = () => {};
             if (dataClient && dataClient.beginTransaction) {
               this.mockedBeginTransaction = dataClient.beginTransaction;
             }

From 3a76ee03f4e9fde9cfbea297e3b66b01114ef837 Mon Sep 17 00:00:00 2001
From: Daniel Bruce <djbruce@google.com>
Date: Wed, 15 Nov 2023 15:22:16 -0500
Subject: [PATCH 106/129] Use ECMA script modifier

Make sure this function is completely hidden.
---
 src/transaction.ts | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/src/transaction.ts b/src/transaction.ts
index e43340120..87a18b4b9 100644
--- a/src/transaction.ts
+++ b/src/transaction.ts
@@ -161,7 +161,7 @@ class Transaction extends DatastoreRequest {
         : () => {};
     const gaxOptions =
       typeof gaxOptionsOrCallback === 'object' ? gaxOptionsOrCallback : {};
-    this.runCommit(gaxOptions, callback);
+    this.#runCommit(gaxOptions, callback);
   }
 
   /**
@@ -451,7 +451,7 @@ class Transaction extends DatastoreRequest {
     });
   }
 
-  private runCommit(
+  #runCommit(
     gaxOptions: CallOptions,
     callback: CommitCallback
   ): void | Promise<CommitResponse> {

From e250bad1ec76bbd8816b6c22a386fff1070824f8 Mon Sep 17 00:00:00 2001
From: Daniel Bruce <djbruce@google.com>
Date: Wed, 15 Nov 2023 15:56:03 -0500
Subject: [PATCH 107/129] Remove TODOs that no longer apply

The TODO statements no longer need to be followed up on so remove them.
---
 test/transaction.ts | 3 ---
 1 file changed, 3 deletions(-)

diff --git a/test/transaction.ts b/test/transaction.ts
index 72bb06855..e1a66d11e 100644
--- a/test/transaction.ts
+++ b/test/transaction.ts
@@ -478,7 +478,6 @@ async.each(
                 await transactionWrapper.transaction.commit();
                 assert.fail('The run call should have failed.');
               } catch (error: any) {
-                // TODO: Substitute type any
                 assert.strictEqual(error['message'], testErrorMessage);
               }
             });
@@ -592,7 +591,6 @@ async.each(
                 await transaction.runAggregationQuery(aggregate);
                 assert.fail('The run call should have failed.');
               } catch (error: any) {
-                // TODO: Substitute type any
                 assert.strictEqual(error['message'], testErrorMessage);
               }
             });
@@ -824,7 +822,6 @@ async.each(
                 await transaction.get(key);
                 assert.fail('The run call should have failed.');
               } catch (error: any) {
-                // TODO: Substitute type any
                 assert.strictEqual(error['message'], testErrorMessage);
               }
             });

From a146626fecbdb5aa5e4abd28f9dca4d986df3735 Mon Sep 17 00:00:00 2001
From: Daniel Bruce <djbruce@google.com>
Date: Wed, 15 Nov 2023 15:58:45 -0500
Subject: [PATCH 108/129] beginTransaction type definition

Remove the type definition. It is not used.
---
 test/transaction.ts | 20 +-------------------
 1 file changed, 1 insertion(+), 19 deletions(-)

diff --git a/test/transaction.ts b/test/transaction.ts
index e1a66d11e..4d6980631 100644
--- a/test/transaction.ts
+++ b/test/transaction.ts
@@ -158,25 +158,7 @@ async.each(
           assert.deepStrictEqual(transaction.requests_, []);
         });
       });
-
-      type beginTransactionSignature = (
-        request: protos.google.datastore.v1.IBeginTransactionRequest,
-        options: CallOptions,
-        callback: Callback<
-          protos.google.datastore.v1.IBeginTransactionResponse,
-          | protos.google.datastore.v1.IBeginTransactionRequest
-          | null
-          | undefined,
-          {} | null | undefined
-        >
-      ) => Promise<
-        [
-          protos.google.datastore.v1.IBeginTransactionResponse,
-          protos.google.datastore.v1.IBeginTransactionRequest | undefined,
-          {} | undefined,
-        ]
-      > | void;
-
+      
       describe('run without setting up transaction id', () => {
         // These tests were created so that when transaction.run is restructured we
         // can be confident that it works the same way as before.

From 7284d99c22de1acb53e70273e8e98bcecf69de3e Mon Sep 17 00:00:00 2001
From: danieljbruce <danieljbruce@users.noreply.github.com>
Date: Fri, 17 Nov 2023 11:30:18 -0500
Subject: [PATCH 109/129] refactor: Break transaction.run into smaller pieces
 for use with async functions and other read/write calls (#1196)
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

* Add a unit test for commit as a non-tx

The fact that using a transaction object to do a commit results in a non-transaction should be documented so that if we decide to introduce a change later where this behaves differently then it is well documented.

# Conflicts:
#	test/transaction.ts

* use linter

* Write tests to capture current behavior of error

When begin transaction sends back an error, we want some tests to capture what the behavior is so that when we make changes to the run function then behavior is preserved.

* Add tests for passing a response

A response should reach the user the right way. Add tests to make sure behavior is preserved.

* run async close to working

In the run function delegate calls to runAsync and use run async to make promise calls

* Add runAsync to promise excludes

This allows this function to return a promise instead of a promise wrapped in a promise. This makes the tests pass and behave the way they should.

* Remove space

* Change to use this instead of self

Do not call request from self

* Eliminate unused comments

* Add two comments

Comments should actually explain what is being done

* Remove the commit test for this PR

The commit test for this PR should be removed because it is not really relevant for the async run functionality.

* Clarify types throughout the function

The types used should be very specific so that reading the code isn’t confusing.

* Add a bit more typing for clarity

Add a type to the resolve function just to introduce more clarity

* Change types used in the data client callback

Make the types more specific in the data client callback so that it is easier to track down the signature and match against the begin transaction function.

* run the linter

* Add comments to clarify PR

* Refactor the parsing logic out of run

The parsing logic is going to be needed elsewhere so taking it apart now.

* Change interface of request promise callback

The interface name should be changed so that it matches what it is. It is the callback used to form a promise.

* Hide data completely

Change accessors to hide data completely instead of using the private modifier

* PR use if/else block

Eliminate the early return as suggested in the PR

* Add comments to document the new functions

The comments capture the parameters and return type.

* Update return type in docs

* Update the tests to include runAsync

runAsync should be in promisfy excludes

* refactor: Break transaction.run into smaller pieces for use with async functions and other read/write calls

* Rename a function to be more descriptive

Make sure it is explicit that we are parsing begin results.

* Modify comment

Modify comment so that it doesn’t reference the way the code was before.
---
 src/transaction.ts  |  89 ++++++++++++++++++++++------
 test/transaction.ts | 138 ++++++++++++++++++++++++++++++++++++++++++++
 2 files changed, 209 insertions(+), 18 deletions(-)

diff --git a/src/transaction.ts b/src/transaction.ts
index 9bc760dd3..914442e4e 100644
--- a/src/transaction.ts
+++ b/src/transaction.ts
@@ -32,6 +32,18 @@ import {
 } from './request';
 import {AggregateQuery} from './aggregate';
 
+// RequestPromiseReturnType should line up with the types in RequestCallback
+interface RequestPromiseReturnType {
+  err?: Error | null;
+  resp: any; // TODO: Replace with google.datastore.v1.IBeginTransactionResponse and address downstream issues
+}
+interface RequestResolveFunction {
+  (callbackData: RequestPromiseReturnType): void;
+}
+interface RequestAsPromiseCallback {
+  (resolve: RequestResolveFunction): void;
+}
+
 /**
  * A transaction is a set of Datastore operations on one or more entities. Each
  * transaction is guaranteed to be atomic, which means that transactions are
@@ -544,10 +556,47 @@ class Transaction extends DatastoreRequest {
       typeof optionsOrCallback === 'object' ? optionsOrCallback : {};
     const callback =
       typeof optionsOrCallback === 'function' ? optionsOrCallback : cb!;
+    this.#runAsync(options).then((response: RequestPromiseReturnType) => {
+      this.#processBeginResults(response, callback);
+    });
+  }
 
-    const reqOpts = {
+  /**
+   * This function parses results from a beginTransaction call
+   *
+   * @param {RequestPromiseReturnType} response The response from a call to
+   * begin a transaction.
+   * @param {RunCallback} callback A callback that accepts an error and a
+   * response as arguments.
+   *
+   **/
+  #processBeginResults(
+    response: RequestPromiseReturnType,
+    callback: RunCallback
+  ): void {
+    const err = response.err;
+    const resp = response.resp;
+    if (err) {
+      callback(err, null, resp);
+    } else {
+      this.id = resp!.transaction;
+      callback(null, this, resp);
+    }
+  }
+
+  /**
+   * This async function makes a beginTransaction call and returns a promise with
+   * the information returned from the call that was made.
+   *
+   * @param {RunOptions} options The options used for a beginTransaction call.
+   * @returns {Promise<RequestPromiseReturnType>}
+   *
+   *
+   **/
+  async #runAsync(options: RunOptions): Promise<RequestPromiseReturnType> {
+    const reqOpts: RequestOptions = {
       transactionOptions: {},
-    } as RequestOptions;
+    };
 
     if (options.readOnly || this.readOnly) {
       reqOpts.transactionOptions!.readOnly = {};
@@ -562,23 +611,26 @@ class Transaction extends DatastoreRequest {
     if (options.transactionOptions) {
       reqOpts.transactionOptions = options.transactionOptions;
     }
-
-    this.request_(
-      {
-        client: 'DatastoreClient',
-        method: 'beginTransaction',
-        reqOpts,
-        gaxOpts: options.gaxOptions,
-      },
-      (err, resp) => {
-        if (err) {
-          callback(err, null, resp);
-          return;
+    const promiseFunction: RequestAsPromiseCallback = (
+      resolve: RequestResolveFunction
+    ) => {
+      this.request_(
+        {
+          client: 'DatastoreClient',
+          method: 'beginTransaction',
+          reqOpts,
+          gaxOpts: options.gaxOptions,
+        },
+        // Always use resolve because then this function can return both the error and the response
+        (err, resp) => {
+          resolve({
+            err,
+            resp,
+          });
         }
-        this.id = resp!.transaction;
-        callback(null, this, resp);
-      }
-    );
+      );
+    };
+    return new Promise(promiseFunction);
   }
 
   /**
@@ -810,6 +862,7 @@ promisifyAll(Transaction, {
     'createQuery',
     'delete',
     'insert',
+    '#runAsync',
     'save',
     'update',
     'upsert',
diff --git a/test/transaction.ts b/test/transaction.ts
index 06faf3421..0535ae022 100644
--- a/test/transaction.ts
+++ b/test/transaction.ts
@@ -21,15 +21,21 @@ import * as proxyquire from 'proxyquire';
 import {
   Datastore,
   DatastoreOptions,
+  DatastoreClient,
   DatastoreRequest,
   Query,
   TransactionOptions,
+  Transaction,
 } from '../src';
 import {Entity} from '../src/entity';
 import * as tsTypes from '../src/transaction';
 import * as sinon from 'sinon';
+import {Callback, CallOptions, ClientStub} from 'google-gax';
 import {RequestConfig} from '../src/request';
 import {SECOND_DATABASE_ID} from './index';
+import {google} from '../protos/protos';
+import {RunCallback} from '../src/transaction';
+import * as protos from '../protos/protos';
 const async = require('async');
 
 // eslint-disable-next-line @typescript-eslint/no-explicit-any
@@ -51,6 +57,7 @@ const fakePfy = Object.assign({}, pfy, {
       'createQuery',
       'delete',
       'insert',
+      '#runAsync',
       'save',
       'update',
       'upsert',
@@ -147,6 +154,137 @@ async.each(
         });
       });
 
+      describe('run without setting up transaction id', () => {
+        // These tests were created so that when transaction.run is restructured we
+        // can be confident that it works the same way as before.
+        const testResp = {
+          transaction: Buffer.from(Array.from(Array(100).keys())),
+        };
+        const namespace = 'run-without-mock';
+        const projectId = 'project-id';
+        const testErrorMessage = 'test-error';
+        const options = {
+          projectId,
+          namespace,
+        };
+        const datastore = new Datastore(options);
+        const transactionWithoutMock = datastore.transaction();
+        const dataClientName = 'DatastoreClient';
+        let dataClient: ClientStub | undefined;
+        let originalBeginTransactionMethod: Function;
+
+        beforeEach(async () => {
+          // In this before hook, save the original beginTransaction method in a variable.
+          // After tests are finished, reassign beginTransaction to the variable.
+          // This way, mocking beginTransaction in this block doesn't affect other tests.
+          const gapic = Object.freeze({
+            v1: require('../src/v1'),
+          });
+          // Datastore Gapic clients haven't been initialized yet so we initialize them here.
+          datastore.clients_.set(
+            dataClientName,
+            new gapic.v1[dataClientName](options)
+          );
+          dataClient = datastore.clients_.get(dataClientName);
+          if (dataClient && dataClient.beginTransaction) {
+            originalBeginTransactionMethod = dataClient.beginTransaction;
+          }
+        });
+
+        afterEach(() => {
+          // beginTransaction has likely been mocked out in these tests.
+          // We should reassign beginTransaction back to its original value for tests outside this block.
+          if (dataClient && originalBeginTransactionMethod) {
+            dataClient.beginTransaction = originalBeginTransactionMethod;
+          }
+        });
+
+        describe('should pass error back to the user', async () => {
+          beforeEach(() => {
+            // Mock out begin transaction and send error back to the user
+            // from the Gapic layer.
+            if (dataClient) {
+              dataClient.beginTransaction = (
+                request: protos.google.datastore.v1.IBeginTransactionRequest,
+                options: CallOptions,
+                callback: Callback<
+                  protos.google.datastore.v1.IBeginTransactionResponse,
+                  | protos.google.datastore.v1.IBeginTransactionRequest
+                  | null
+                  | undefined,
+                  {} | null | undefined
+                >
+              ) => {
+                callback(new Error(testErrorMessage), testResp);
+              };
+            }
+          });
+
+          it('should send back the error when awaiting a promise', async () => {
+            try {
+              await transactionWithoutMock.run();
+              assert.fail('The run call should have failed.');
+            } catch (error: any) {
+              // TODO: Substitute type any
+              assert.strictEqual(error['message'], testErrorMessage);
+            }
+          });
+          it('should send back the error when using a callback', done => {
+            const runCallback: RunCallback = (
+              error: Error | null,
+              transaction: Transaction | null,
+              response?: google.datastore.v1.IBeginTransactionResponse
+            ) => {
+              assert(error);
+              assert.strictEqual(error.message, testErrorMessage);
+              assert.strictEqual(transaction, null);
+              assert.strictEqual(response, testResp);
+              done();
+            };
+            transactionWithoutMock.run({}, runCallback);
+          });
+        });
+        describe('should pass response back to the user', async () => {
+          beforeEach(() => {
+            // Mock out begin transaction and send a response
+            // back to the user from the Gapic layer.
+            if (dataClient) {
+              dataClient.beginTransaction = (
+                request: protos.google.datastore.v1.IBeginTransactionRequest,
+                options: CallOptions,
+                callback: Callback<
+                  protos.google.datastore.v1.IBeginTransactionResponse,
+                  | protos.google.datastore.v1.IBeginTransactionRequest
+                  | null
+                  | undefined,
+                  {} | null | undefined
+                >
+              ) => {
+                callback(null, testResp);
+              };
+            }
+          });
+          it('should send back the response when awaiting a promise', async () => {
+            const [transaction, resp] = await transactionWithoutMock.run();
+            assert.strictEqual(transaction, transactionWithoutMock);
+            assert.strictEqual(resp, testResp);
+          });
+          it('should send back the response when using a callback', done => {
+            const runCallback: RunCallback = (
+              error: Error | null,
+              transaction: Transaction | null,
+              response?: google.datastore.v1.IBeginTransactionResponse
+            ) => {
+              assert.strictEqual(error, null);
+              assert.strictEqual(response, testResp);
+              assert.strictEqual(transaction, transactionWithoutMock);
+              done();
+            };
+            transactionWithoutMock.run({}, runCallback);
+          });
+        });
+      });
+
       describe('commit', () => {
         beforeEach(() => {
           transaction.id = TRANSACTION_ID;

From cc4bbb824faa0eb18da50801bcd6895e406ad260 Mon Sep 17 00:00:00 2001
From: Daniel Bruce <djbruce@google.com>
Date: Fri, 17 Nov 2023 14:59:36 -0500
Subject: [PATCH 110/129] Add the setupBeginTransaction method

Refactor test code to provide better error catching and also not repeat setup code for the run function.
---
 test/transaction.ts | 81 ++++++++++++++++++++++-----------------------
 1 file changed, 39 insertions(+), 42 deletions(-)

diff --git a/test/transaction.ts b/test/transaction.ts
index c0cffdbad..a3556805d 100644
--- a/test/transaction.ts
+++ b/test/transaction.ts
@@ -860,7 +860,7 @@ async.each(
       describe('run without setting up transaction id', () => {
         // These tests were created so that when transaction.run is restructured we
         // can be confident that it works the same way as before.
-        const testResp = {
+        const testRunResp = {
           transaction: Buffer.from(Array.from(Array(100).keys())),
         };
         const namespace = 'run-without-mock';
@@ -902,25 +902,29 @@ async.each(
           }
         });
 
+        function setupBeginTransaction(err: Error | null | undefined) {
+          if (dataClient) {
+            dataClient.beginTransaction = (
+              request: protos.google.datastore.v1.IBeginTransactionRequest,
+              options: CallOptions,
+              callback: Callback<
+                protos.google.datastore.v1.IBeginTransactionResponse,
+                | protos.google.datastore.v1.IBeginTransactionRequest
+                | null
+                | undefined,
+                {} | null | undefined
+              >
+            ) => {
+              callback(err, testRunResp);
+            };
+          }
+        }
+
         describe('should pass error back to the user', async () => {
           beforeEach(() => {
             // Mock out begin transaction and send error back to the user
             // from the Gapic layer.
-            if (dataClient) {
-              dataClient.beginTransaction = (
-                request: protos.google.datastore.v1.IBeginTransactionRequest,
-                options: CallOptions,
-                callback: Callback<
-                  protos.google.datastore.v1.IBeginTransactionResponse,
-                  | protos.google.datastore.v1.IBeginTransactionRequest
-                  | null
-                  | undefined,
-                  {} | null | undefined
-                >
-              ) => {
-                callback(new Error(testErrorMessage), testResp);
-              };
-            }
+            setupBeginTransaction(new Error(testErrorMessage));
           });
 
           it('should send back the error when awaiting a promise', async () => {
@@ -928,7 +932,6 @@ async.each(
               await transactionWithoutMock.run();
               assert.fail('The run call should have failed.');
             } catch (error: any) {
-              // TODO: Substitute type any
               assert.strictEqual(error['message'], testErrorMessage);
             }
           });
@@ -938,11 +941,15 @@ async.each(
               transaction: Transaction | null,
               response?: google.datastore.v1.IBeginTransactionResponse
             ) => {
-              assert(error);
-              assert.strictEqual(error.message, testErrorMessage);
-              assert.strictEqual(transaction, null);
-              assert.strictEqual(response, testResp);
-              done();
+              try {
+                assert(error);
+                assert.strictEqual(error.message, testErrorMessage);
+                assert.strictEqual(transaction, null);
+                assert.strictEqual(response, testRunResp);
+                done();
+              } catch (e) {
+                done(e);
+              }
             };
             transactionWithoutMock.run({}, runCallback);
           });
@@ -951,26 +958,12 @@ async.each(
           beforeEach(() => {
             // Mock out begin transaction and send a response
             // back to the user from the Gapic layer.
-            if (dataClient) {
-              dataClient.beginTransaction = (
-                request: protos.google.datastore.v1.IBeginTransactionRequest,
-                options: CallOptions,
-                callback: Callback<
-                  protos.google.datastore.v1.IBeginTransactionResponse,
-                  | protos.google.datastore.v1.IBeginTransactionRequest
-                  | null
-                  | undefined,
-                  {} | null | undefined
-                >
-              ) => {
-                callback(null, testResp);
-              };
-            }
+            setupBeginTransaction(null);
           });
           it('should send back the response when awaiting a promise', async () => {
             const [transaction, resp] = await transactionWithoutMock.run();
             assert.strictEqual(transaction, transactionWithoutMock);
-            assert.strictEqual(resp, testResp);
+            assert.strictEqual(resp, testRunResp);
           });
           it('should send back the response when using a callback', done => {
             const runCallback: RunCallback = (
@@ -978,10 +971,14 @@ async.each(
               transaction: Transaction | null,
               response?: google.datastore.v1.IBeginTransactionResponse
             ) => {
-              assert.strictEqual(error, null);
-              assert.strictEqual(response, testResp);
-              assert.strictEqual(transaction, transactionWithoutMock);
-              done();
+              try {
+                assert.strictEqual(error, null);
+                assert.deepStrictEqual(response, testRunResp);
+                assert.strictEqual(transaction, transactionWithoutMock);
+                done();
+              } catch (e) {
+                done(e);
+              }
             };
             transactionWithoutMock.run({}, runCallback);
           });

From cb3ec1b1dbd7090ddbbefa9637f93868a8123daa Mon Sep 17 00:00:00 2001
From: Daniel Bruce <djbruce@google.com>
Date: Fri, 17 Nov 2023 15:11:12 -0500
Subject: [PATCH 111/129] Add a comment for private runCommit method

The comment for the runCommit method just captures the fact that the function is used as a pass-through.
---
 src/transaction.ts | 9 +++++++++
 1 file changed, 9 insertions(+)

diff --git a/src/transaction.ts b/src/transaction.ts
index 87a18b4b9..2ce153e07 100644
--- a/src/transaction.ts
+++ b/src/transaction.ts
@@ -451,6 +451,15 @@ class Transaction extends DatastoreRequest {
     });
   }
 
+  /**
+   * This function is a pass-through for the transaction.commit method
+   * It contains the business logic used for committing a transaction
+   *
+   * @param {object} [gaxOptions] Request configuration options, outlined here:
+   *     https://googleapis.github.io/gax-nodejs/global.html#CallOptions.
+   * @param {function} callback The callback function.
+   * @private
+   */
   #runCommit(
     gaxOptions: CallOptions,
     callback: CommitCallback

From 7b0a98a539bac0b810d47a88476ad8011d72da76 Mon Sep 17 00:00:00 2001
From: Daniel Bruce <djbruce@google.com>
Date: Fri, 17 Nov 2023 15:22:29 -0500
Subject: [PATCH 112/129] Modify comment to be more clear

---
 test/transaction.ts | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/test/transaction.ts b/test/transaction.ts
index a3556805d..f136b9026 100644
--- a/test/transaction.ts
+++ b/test/transaction.ts
@@ -287,8 +287,8 @@ async.each(
       });
 
       describe('testing various transaction functions when transaction.run returns a response', () => {
-        // These tests were created to ensure that various transaction functions maintain the same behavior
-        // after being restructured to start with a run call.
+        // These tests were created to ensure that various transaction functions work correctly after run is called.
+        // This allows us to catch any breaking changes to processes that are meant to remain the same.
         const testRunResp = {
           transaction: Buffer.from(Array.from(Array(100).keys())),
         };

From 8b0d3c712bda9050c913616e283d153a4b4d63ae Mon Sep 17 00:00:00 2001
From: Daniel Bruce <djbruce@google.com>
Date: Fri, 17 Nov 2023 15:31:52 -0500
Subject: [PATCH 113/129] Update comments

The comments should more directly address the problem the tests are meant to solve
---
 test/transaction.ts | 14 ++++++--------
 1 file changed, 6 insertions(+), 8 deletions(-)

diff --git a/test/transaction.ts b/test/transaction.ts
index f136b9026..ddab257d4 100644
--- a/test/transaction.ts
+++ b/test/transaction.ts
@@ -288,7 +288,7 @@ async.each(
 
       describe('testing various transaction functions when transaction.run returns a response', () => {
         // These tests were created to ensure that various transaction functions work correctly after run is called.
-        // This allows us to catch any breaking changes to processes that are meant to remain the same.
+        // This allows us to catch any breaking changes to code usages that should remain the same.
         const testRunResp = {
           transaction: Buffer.from(Array.from(Array(100).keys())),
         };
@@ -420,6 +420,7 @@ async.each(
         let transaction: Transaction;
 
         describe('commit', () => {
+          // These tests were created to catch regressions for transaction.commit changes.
           const testCommitResp = {
             mutationResults: [
               {
@@ -515,8 +516,7 @@ async.each(
           });
         });
         describe('runAggregationQuery', () => {
-          // These tests were created so that when transaction.runAggregationQuery is restructured we
-          // can be confident that it works the same way as before.
+          // These tests were created to catch regressions for transaction.runAggregationQuery changes.
           const runAggregationQueryUserResp = [{'average rating': 100}];
           const runAggregationQueryResp = {
             batch: {
@@ -638,8 +638,7 @@ async.each(
           });
         });
         describe('runQuery', () => {
-          // These tests were created so that when transaction.runQuery is restructured we
-          // can be confident that it works the same way as before.
+          // These tests were created to catch regressions for transaction.runQuery changes.
           const runQueryResp = {
             batch: {
               entityResults: [],
@@ -738,8 +737,7 @@ async.each(
           });
         });
         describe('get', () => {
-          // These tests were created so that when transaction.get is restructured we
-          // can be confident that it works the same way as before.
+          // These tests were created to catch regressions for transaction.get changes.
           const getResp = {
             found: [
               {
@@ -858,7 +856,7 @@ async.each(
       });
 
       describe('run without setting up transaction id', () => {
-        // These tests were created so that when transaction.run is restructured we
+        // These tests were created to catch regressions for transaction.run is restructured we
         // can be confident that it works the same way as before.
         const testRunResp = {
           transaction: Buffer.from(Array.from(Array(100).keys())),

From b7ebc51e84f03df00ddb6ec3229404be8f4d5f9a Mon Sep 17 00:00:00 2001
From: Daniel Bruce <djbruce@google.com>
Date: Fri, 17 Nov 2023 15:47:01 -0500
Subject: [PATCH 114/129] Eliminate redundant test code

This describe block is now duplicated. Remove it.
---
 test/transaction.ts | 127 --------------------------------------------
 1 file changed, 127 deletions(-)

diff --git a/test/transaction.ts b/test/transaction.ts
index ddab257d4..10587524f 100644
--- a/test/transaction.ts
+++ b/test/transaction.ts
@@ -158,133 +158,6 @@ async.each(
           assert.deepStrictEqual(transaction.requests_, []);
         });
       });
-      describe('run without setting up transaction id', () => {
-        // These tests were created so that when transaction.run is restructured we
-        // can be confident that it works the same way as before.
-        const testRunResp = {
-          transaction: Buffer.from(Array.from(Array(100).keys())),
-        };
-        const namespace = 'run-without-mock';
-        const projectId = 'project-id';
-        const testErrorMessage = 'test-error';
-        const options = {
-          projectId,
-          namespace,
-        };
-        const datastore = new Datastore(options);
-        const transactionWithoutMock = datastore.transaction();
-        const dataClientName = 'DatastoreClient';
-        let dataClient: ClientStub | undefined;
-        let originalBeginTransactionMethod: Function;
-
-        beforeEach(async () => {
-          // In this before hook, save the original beginTransaction method in a variable.
-          // After tests are finished, reassign beginTransaction to the variable.
-          // This way, mocking beginTransaction in this block doesn't affect other tests.
-          const gapic = Object.freeze({
-            v1: require('../src/v1'),
-          });
-          // Datastore Gapic clients haven't been initialized yet so we initialize them here.
-          datastore.clients_.set(
-            dataClientName,
-            new gapic.v1[dataClientName](options)
-          );
-          dataClient = datastore.clients_.get(dataClientName);
-          if (dataClient && dataClient.beginTransaction) {
-            originalBeginTransactionMethod = dataClient.beginTransaction;
-          }
-        });
-
-        afterEach(() => {
-          // beginTransaction has likely been mocked out in these tests.
-          // We should reassign beginTransaction back to its original value for tests outside this block.
-          if (dataClient && originalBeginTransactionMethod) {
-            dataClient.beginTransaction = originalBeginTransactionMethod;
-          }
-        });
-
-        function setupBeginTransaction(err: Error | null | undefined) {
-          if (dataClient) {
-            dataClient.beginTransaction = (
-              request: protos.google.datastore.v1.IBeginTransactionRequest,
-              options: CallOptions,
-              callback: Callback<
-                protos.google.datastore.v1.IBeginTransactionResponse,
-                | protos.google.datastore.v1.IBeginTransactionRequest
-                | null
-                | undefined,
-                {} | null | undefined
-              >
-            ) => {
-              callback(err, testRunResp);
-            };
-          }
-        }
-
-        describe('should pass error back to the user', async () => {
-          beforeEach(() => {
-            // Mock out begin transaction and send error back to the user
-            // from the Gapic layer.
-            setupBeginTransaction(new Error(testErrorMessage));
-          });
-
-          it('should send back the error when awaiting a promise', async () => {
-            try {
-              await transactionWithoutMock.run();
-              assert.fail('The run call should have failed.');
-            } catch (error: any) {
-              assert.strictEqual(error['message'], testErrorMessage);
-            }
-          });
-          it('should send back the error when using a callback', done => {
-            const runCallback: RunCallback = (
-              error: Error | null,
-              transaction: Transaction | null,
-              response?: google.datastore.v1.IBeginTransactionResponse
-            ) => {
-              try {
-                assert(error);
-                assert.strictEqual(error.message, testErrorMessage);
-                assert.strictEqual(transaction, null);
-                assert.strictEqual(response, testRunResp);
-                done();
-              } catch (e) {
-                done(e);
-              }
-            };
-            transactionWithoutMock.run({}, runCallback);
-          });
-        });
-        describe('should pass response back to the user', async () => {
-          beforeEach(() => {
-            // Mock out begin transaction and send a response
-            // back to the user from the Gapic layer.
-            setupBeginTransaction(null);
-          });
-          it('should send back the response when awaiting a promise', async () => {
-            const [transaction, resp] = await transactionWithoutMock.run();
-            assert.strictEqual(transaction, transactionWithoutMock);
-            assert.strictEqual(resp, testRunResp);
-          });
-          it('should send back the response when using a callback', done => {
-            const runCallback: RunCallback = (
-              error: Error | null,
-              transaction: Transaction | null,
-              response?: google.datastore.v1.IBeginTransactionResponse
-            ) => {
-              try {
-                assert.strictEqual(error, null);
-                assert.deepStrictEqual(response, testRunResp);
-                assert.strictEqual(transaction, transactionWithoutMock);
-                done();
-              } catch (e) {
-                done(e);
-              }
-            };
-            transactionWithoutMock.run({}, runCallback);
-          });
-        });
-      });
 
       describe('testing various transaction functions when transaction.run returns a response', () => {
         // These tests were created to ensure that various transaction functions work correctly after run is called.

From c050d5072dc135243670c59753259432af202420 Mon Sep 17 00:00:00 2001
From: Daniel Bruce <djbruce@google.com>
Date: Fri, 17 Nov 2023 16:05:15 -0500
Subject: [PATCH 115/129] revert comment

This was a typo
---
 test/transaction.ts | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/test/transaction.ts b/test/transaction.ts
index 10587524f..78bc0254c 100644
--- a/test/transaction.ts
+++ b/test/transaction.ts
@@ -729,7 +729,7 @@ async.each(
       });
 
       describe('run without setting up transaction id', () => {
-        // These tests were created to catch regressions for transaction.run is restructured we
+        // These tests were created so that when transaction.run is restructured we
         // can be confident that it works the same way as before.
         const testRunResp = {
           transaction: Buffer.from(Array.from(Array(100).keys())),

From a5efe6eef9f0833d19bf366b3ab8784ff0b784f8 Mon Sep 17 00:00:00 2001
From: Daniel Bruce <djbruce@google.com>
Date: Fri, 17 Nov 2023 16:05:15 -0500
Subject: [PATCH 116/129] refactor: Move commit logic and add tests that
 prepare for transaction function changes

---
 test/transaction.ts | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/test/transaction.ts b/test/transaction.ts
index 10587524f..78bc0254c 100644
--- a/test/transaction.ts
+++ b/test/transaction.ts
@@ -729,7 +729,7 @@ async.each(
       });
 
       describe('run without setting up transaction id', () => {
-        // These tests were created to catch regressions for transaction.run is restructured we
+        // These tests were created so that when transaction.run is restructured we
         // can be confident that it works the same way as before.
         const testRunResp = {
           transaction: Buffer.from(Array.from(Array(100).keys())),

From 3df2bf4a1a3d1642b2399caf1cd0dfb616bbdceb Mon Sep 17 00:00:00 2001
From: Daniel Bruce <djbruce@google.com>
Date: Mon, 20 Nov 2023 10:32:13 -0500
Subject: [PATCH 117/129] runQuery, put, commit

Another some more integration tests for runQuery, put, commit
---
 system-test/datastore.ts | 56 ++++++++++++++++++++++++++++++++++++++++
 1 file changed, 56 insertions(+)

diff --git a/system-test/datastore.ts b/system-test/datastore.ts
index 19e0db0ab..476c76bb8 100644
--- a/system-test/datastore.ts
+++ b/system-test/datastore.ts
@@ -1804,6 +1804,62 @@ async.each(
             await doPutLookupCommit(transaction);
           });
         });
+        describe.only('runQuery, put, commit', () => {
+          const key = datastore.key(['Company', 'Google']);
+          const obj = {
+            url: 'www.google.com',
+          };
+          afterEach(async () => {
+            await datastore.delete(key);
+          });
+          async function doRunQueryPutCommit(transaction: Transaction) {
+            const query = transaction.createQuery('Company');
+            const [results] = await transaction.runQuery(query);
+            assert.deepStrictEqual(results, []);
+            transaction.save({key, data: obj});
+            await transaction.commit();
+            const [entity] = await datastore.get(key);
+            delete entity[datastore.KEY];
+            assert.deepStrictEqual(entity, obj);
+          }
+          it('should run in a transaction', async () => {
+            const transaction = datastore.transaction();
+            await transaction.run();
+            await doRunQueryPutCommit(transaction);
+          });
+          it('should run in a transaction without run', async () => {
+            const transaction = datastore.transaction();
+            await doRunQueryPutCommit(transaction);
+          });
+        });
+        describe.only('runQuery, put, commit', () => {
+          const key = datastore.key(['Company', 'Google']);
+          const obj = {
+            url: 'www.google.com',
+          };
+          afterEach(async () => {
+            await datastore.delete(key);
+          });
+          async function doPutRunQueryCommit(transaction: Transaction) {
+            transaction.save({key, data: obj});
+            const query = transaction.createQuery('Company');
+            const [results] = await transaction.runQuery(query);
+            assert.deepStrictEqual(results, []);
+            await transaction.commit();
+            const [entity] = await datastore.get(key);
+            delete entity[datastore.KEY];
+            assert.deepStrictEqual(entity, obj);
+          }
+          it('should run in a transaction', async () => {
+            const transaction = datastore.transaction();
+            await transaction.run();
+            await doPutRunQueryCommit(transaction);
+          });
+          it('should run in a transaction without run', async () => {
+            const transaction = datastore.transaction();
+            await doPutRunQueryCommit(transaction);
+          });
+        });
 
         it('should commit all saves and deletes at the end', async () => {
           const deleteKey = datastore.key(['Company', 'Subway']);

From df4f69b542f453b415305ee299cb37e4da839312 Mon Sep 17 00:00:00 2001
From: Daniel Bruce <djbruce@google.com>
Date: Mon, 20 Nov 2023 13:57:42 -0500
Subject: [PATCH 118/129] =?UTF-8?q?run=20linter=20and=20group=20some=20tes?=
 =?UTF-8?q?ts=20into=20a=20describe=20block=20so=20that=20they=20don?=
 =?UTF-8?q?=E2=80=99t=20run=20before=20some=20new=20tests=20we=20are=20goi?=
 =?UTF-8?q?ng=20to=20add.?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 system-test/datastore.ts | 148 ++++++++++++++++++++-------------------
 test/transaction.ts      |  74 ++++++++++----------
 2 files changed, 112 insertions(+), 110 deletions(-)

diff --git a/system-test/datastore.ts b/system-test/datastore.ts
index 476c76bb8..049d0a86c 100644
--- a/system-test/datastore.ts
+++ b/system-test/datastore.ts
@@ -1804,7 +1804,7 @@ async.each(
             await doPutLookupCommit(transaction);
           });
         });
-        describe.only('runQuery, put, commit', () => {
+        describe('runQuery, put, commit', () => {
           const key = datastore.key(['Company', 'Google']);
           const obj = {
             url: 'www.google.com',
@@ -1832,7 +1832,7 @@ async.each(
             await doRunQueryPutCommit(transaction);
           });
         });
-        describe.only('runQuery, put, commit', () => {
+        describe('put, runQuery, commit', () => {
           const key = datastore.key(['Company', 'Google']);
           const obj = {
             url: 'www.google.com',
@@ -1861,89 +1861,91 @@ async.each(
           });
         });
 
-        it('should commit all saves and deletes at the end', async () => {
-          const deleteKey = datastore.key(['Company', 'Subway']);
-          const key = datastore.key(['Company', 'Google']);
-          const incompleteKey = datastore.key('Company');
+        describe('transaction operations on two data points', async () => {
+          it('should commit all saves and deletes at the end', async () => {
+            const deleteKey = datastore.key(['Company', 'Subway']);
+            const key = datastore.key(['Company', 'Google']);
+            const incompleteKey = datastore.key('Company');
 
-          await datastore.save({
-            key: deleteKey,
-            data: {},
-          });
-          const transaction = datastore.transaction();
+            await datastore.save({
+              key: deleteKey,
+              data: {},
+            });
+            const transaction = datastore.transaction();
 
-          await transaction.run();
-          transaction.delete(deleteKey);
+            await transaction.run();
+            transaction.delete(deleteKey);
 
-          transaction.save([
-            {
-              key,
-              data: {rating: 10},
-            },
-            {
-              key: incompleteKey,
-              data: {rating: 100},
-            },
-          ]);
+            transaction.save([
+              {
+                key,
+                data: {rating: 10},
+              },
+              {
+                key: incompleteKey,
+                data: {rating: 100},
+              },
+            ]);
 
-          await transaction.commit();
+            await transaction.commit();
 
-          // Incomplete key should have been given an ID.
-          assert.strictEqual(incompleteKey.path.length, 2);
+            // Incomplete key should have been given an ID.
+            assert.strictEqual(incompleteKey.path.length, 2);
 
-          const [[deletedEntity], [fetchedEntity]] = await Promise.all([
-            // Deletes the key that is in the deletion queue.
-            datastore.get(deleteKey),
-            // Updates data on the key.
-            datastore.get(key),
-          ]);
-          assert.strictEqual(typeof deletedEntity, 'undefined');
-          assert.strictEqual(fetchedEntity.rating, 10);
-        });
+            const [[deletedEntity], [fetchedEntity]] = await Promise.all([
+              // Deletes the key that is in the deletion queue.
+              datastore.get(deleteKey),
+              // Updates data on the key.
+              datastore.get(key),
+            ]);
+            assert.strictEqual(typeof deletedEntity, 'undefined');
+            assert.strictEqual(fetchedEntity.rating, 10);
+          });
 
-        it('should use the last modification to a key', async () => {
-          const incompleteKey = datastore.key('Company');
-          const key = datastore.key(['Company', 'Google']);
-          const transaction = datastore.transaction();
-          await transaction.run();
-          transaction.save([
-            {
-              key,
-              data: {
-                rating: 10,
+          it('should use the last modification to a key', async () => {
+            const incompleteKey = datastore.key('Company');
+            const key = datastore.key(['Company', 'Google']);
+            const transaction = datastore.transaction();
+            await transaction.run();
+            transaction.save([
+              {
+                key,
+                data: {
+                  rating: 10,
+                },
               },
-            },
-            {
-              key: incompleteKey,
-              data: {
-                rating: 100,
+              {
+                key: incompleteKey,
+                data: {
+                  rating: 100,
+                },
               },
-            },
-          ]);
-          transaction.delete(key);
-          await transaction.commit();
+            ]);
+            transaction.delete(key);
+            await transaction.commit();
 
-          // Should not return a result.
-          const [entity] = await datastore.get(key);
-          assert.strictEqual(entity, undefined);
+            // Should not return a result.
+            const [entity] = await datastore.get(key);
+            assert.strictEqual(entity, undefined);
 
-          // Incomplete key should have been given an id.
-          assert.strictEqual(incompleteKey.path.length, 2);
-        });
+            // Incomplete key should have been given an id.
+            assert.strictEqual(incompleteKey.path.length, 2);
+          });
 
-        it('should query within a transaction', async () => {
-          const transaction = datastore.transaction();
-          await transaction.run();
-          const query = transaction.createQuery('Company');
-          let entities;
-          try {
-            [entities] = await query.run();
-          } catch (e) {
-            await transaction.rollback();
-            return;
-          }
-          assert(entities!.length > 0);
-          await transaction.commit();
+          it('should query within a transaction', async () => {
+            const transaction = datastore.transaction();
+            await transaction.run();
+            const query = transaction.createQuery('Company');
+            let entities;
+            try {
+              [entities] = await query.run();
+            } catch (e) {
+              await transaction.rollback();
+              return;
+            }
+            assert(entities!.length > 0);
+            await transaction.commit();
+          });
         });
 
         describe('aggregate query within a transaction', async () => {
diff --git a/test/transaction.ts b/test/transaction.ts
index 59fa59a6c..9098c3caa 100644
--- a/test/transaction.ts
+++ b/test/transaction.ts
@@ -764,8 +764,8 @@ async.each(
                 try {
                   // TODO: assertion check here
                   assert.deepStrictEqual(
-                      this.callbackOrder,
-                      this.expectedOrder
+                    this.callbackOrder,
+                    this.expectedOrder
                   );
                   this.done();
                 } catch (e) {
@@ -775,8 +775,8 @@ async.each(
             }
 
             runCallback: RunCallback = (
-                error: Error | null | undefined,
-                response?: any
+              error: Error | null | undefined,
+              response?: any
             ) => {
               try {
                 this.callbackOrder.push('run callback');
@@ -786,8 +786,8 @@ async.each(
               }
             };
             commitCallback: CommitCallback = (
-                error: Error | null | undefined,
-                response?: google.datastore.v1.ICommitResponse
+              error: Error | null | undefined,
+              response?: google.datastore.v1.ICommitResponse
             ) => {
               try {
                 this.callbackOrder.push('commit callback');
@@ -798,9 +798,9 @@ async.each(
             };
 
             constructor(
-                transactionWrapper: MockedTransactionWrapper,
-                done: (err?: any) => void,
-                expectedOrder: string[]
+              transactionWrapper: MockedTransactionWrapper,
+              done: (err?: any) => void,
+              expectedOrder: string[]
             ) {
               this.expectedOrder = expectedOrder;
               const gapicCallHandler = (call: string) => {
@@ -833,23 +833,23 @@ async.each(
           describe('should pass response back to the user', async () => {
             beforeEach(() => {
               transactionWrapper.mockGapicFunction(
-                  'commit',
-                  testCommitResp,
-                  null
+                'commit',
+                testCommitResp,
+                null
               );
             });
 
             it('should call the callbacks in the proper order with run and commit', done => {
               const transactionOrderTester = new TransactionOrderTester(
-                  transactionWrapper,
-                  done,
-                  [
-                    'functions called',
-                    'beginTransaction called',
-                    'run callback',
-                    'commit called',
-                    'commit callback',
-                  ]
+                transactionWrapper,
+                done,
+                [
+                  'functions called',
+                  'beginTransaction called',
+                  'run callback',
+                  'commit called',
+                  'commit callback',
+                ]
               );
               transactionOrderTester.callRun();
               transactionOrderTester.callCommit();
@@ -857,28 +857,28 @@ async.each(
             });
             it('should call the callbacks in the proper order with commit', done => {
               const transactionOrderTester = new TransactionOrderTester(
-                  transactionWrapper,
-                  done,
-                  [
-                    'functions called',
-                    'beginTransaction called',
-                    'commit called',
-                    'commit callback',
-                  ]
+                transactionWrapper,
+                done,
+                [
+                  'functions called',
+                  'beginTransaction called',
+                  'commit called',
+                  'commit callback',
+                ]
               );
               transactionOrderTester.callCommit();
               transactionOrderTester.pushString('functions called');
             });
             it('should call the callbacks in the proper order with two run calls', done => {
               const transactionOrderTester = new TransactionOrderTester(
-                  transactionWrapper,
-                  done,
-                  [
-                    'functions called',
-                    'beginTransaction called',
-                    'run callback',
-                    'run callback',
-                  ]
+                transactionWrapper,
+                done,
+                [
+                  'functions called',
+                  'beginTransaction called',
+                  'run callback',
+                  'run callback',
+                ]
               );
               transactionOrderTester.callRun();
               transactionOrderTester.callRun();

From 8971c2c84f1e5254146c801260bd5022daa155ed Mon Sep 17 00:00:00 2001
From: Daniel Bruce <djbruce@google.com>
Date: Mon, 20 Nov 2023 14:28:31 -0500
Subject: [PATCH 119/129] Add some runAggregationQuery integration tests

Add one of the test cases from the document (runAggregationQuery tests)
---
 system-test/datastore.ts | 37 ++++++++++++++++++++++++++++++++++++-
 1 file changed, 36 insertions(+), 1 deletion(-)

diff --git a/system-test/datastore.ts b/system-test/datastore.ts
index 049d0a86c..8c50076b1 100644
--- a/system-test/datastore.ts
+++ b/system-test/datastore.ts
@@ -1749,7 +1749,7 @@ async.each(
           assert.deepStrictEqual(results, [{property_1: 4}]);
         });
       });
-      describe('transactions', () => {
+      describe.only('transactions', () => {
         describe('lookup, put, commit', () => {
           const key = datastore.key(['Company', 'Google']);
           const obj = {
@@ -1861,6 +1861,41 @@ async.each(
           });
         });
 
+        describe('runAggregationQuery, put, commit', () => {
+          const key = datastore.key(['Company', 'Google']);
+          const obj = {
+            url: 'www.google.com',
+          };
+          afterEach(async () => {
+            await datastore.delete(key);
+          });
+          async function doRunAggregationQueryPutCommit(
+            transaction: Transaction
+          ) {
+            const query = transaction.createQuery('Company');
+            const aggregateQuery = transaction
+              .createAggregationQuery(query)
+              .count('total');
+            const [results] =
+              await transaction.runAggregationQuery(aggregateQuery);
+            assert.deepStrictEqual(results, [{total: 0}]);
+            transaction.save({key, data: obj});
+            await transaction.commit();
+            const [entity] = await datastore.get(key);
+            delete entity[datastore.KEY];
+            assert.deepStrictEqual(entity, obj);
+          }
+          it('should run in a transaction', async () => {
+            const transaction = datastore.transaction();
+            await transaction.run();
+            await doRunAggregationQueryPutCommit(transaction);
+          });
+          it('should run in a transaction without run', async () => {
+            const transaction = datastore.transaction();
+            await doRunAggregationQueryPutCommit(transaction);
+          });
+        });
+
         describe('transaction operations on two data points', async () => {
           it('should commit all saves and deletes at the end', async () => {
             const deleteKey = datastore.key(['Company', 'Subway']);

From 9cd614c304cce3a126ff83bb6ec43dcc641a160f Mon Sep 17 00:00:00 2001
From: Daniel Bruce <djbruce@google.com>
Date: Mon, 20 Nov 2023 14:39:31 -0500
Subject: [PATCH 120/129] Add tests for put, runAggregationQuery, commit

put, runAggregationQuery, commit tests have been added and now run properly.
---
 system-test/datastore.ts | 34 ++++++++++++++++++++++++++++++++++
 1 file changed, 34 insertions(+)

diff --git a/system-test/datastore.ts b/system-test/datastore.ts
index 8c50076b1..4232db775 100644
--- a/system-test/datastore.ts
+++ b/system-test/datastore.ts
@@ -1895,6 +1895,40 @@ async.each(
             await doRunAggregationQueryPutCommit(transaction);
           });
         });
+        describe('put, runAggregationQuery, commit', () => {
+          const key = datastore.key(['Company', 'Google']);
+          const obj = {
+            url: 'www.google.com',
+          };
+          afterEach(async () => {
+            await datastore.delete(key);
+          });
+          async function doPutRunAggregationQueryCommit(
+            transaction: Transaction
+          ) {
+            transaction.save({key, data: obj});
+            const query = transaction.createQuery('Company');
+            const aggregateQuery = transaction
+              .createAggregationQuery(query)
+              .count('total');
+            const [results] =
+              await transaction.runAggregationQuery(aggregateQuery);
+            assert.deepStrictEqual(results, [{total: 0}]);
+            await transaction.commit();
+            const [entity] = await datastore.get(key);
+            delete entity[datastore.KEY];
+            assert.deepStrictEqual(entity, obj);
+          }
+          it('should run in a transaction', async () => {
+            const transaction = datastore.transaction();
+            await transaction.run();
+            await doPutRunAggregationQueryCommit(transaction);
+          });
+          it('should run in a transaction without run', async () => {
+            const transaction = datastore.transaction();
+            await doPutRunAggregationQueryCommit(transaction);
+          });
+        });
 
         describe('transaction operations on two data points', async () => {
           it('should commit all saves and deletes at the end', async () => {

From bc9eca620e265528d95f56fab8f364bb36683247 Mon Sep 17 00:00:00 2001
From: Daniel Bruce <djbruce@google.com>
Date: Mon, 20 Nov 2023 14:57:34 -0500
Subject: [PATCH 121/129] Write some latency tests

The latency tests measure time taken with and without using the run call.
---
 system-test/datastore.ts | 30 ++++++++++++++++++++++++++++++
 1 file changed, 30 insertions(+)

diff --git a/system-test/datastore.ts b/system-test/datastore.ts
index 4232db775..fcfd1d984 100644
--- a/system-test/datastore.ts
+++ b/system-test/datastore.ts
@@ -1929,6 +1929,36 @@ async.each(
             await doPutRunAggregationQueryCommit(transaction);
           });
         });
+        describe('latency tests', () => {
+          const key = datastore.key(['Company', 'Google']);
+          const obj = {
+            url: 'www.google.com',
+          };
+          afterEach(async () => {
+            await datastore.delete(key);
+          });
+          async function runLatencyTests(transaction: Transaction) {
+            await Promise.all([
+              transaction.get(key),
+              transaction.get(key),
+              transaction.get(key),
+            ]);
+            console.timeEnd('before run');
+            console.timeEnd('after run');
+          }
+          it('should run in a transaction', async () => {
+            const transaction = datastore.transaction();
+            console.time('before run');
+            await transaction.run();
+            console.time('after run');
+            await runLatencyTests(transaction);
+          });
+          it('should run in a transaction without run', async () => {
+            console.time('before run');
+            const transaction = datastore.transaction();
+            await runLatencyTests(transaction);
+          });
+        });
 
         describe('transaction operations on two data points', async () => {
           it('should commit all saves and deletes at the end', async () => {

From 97e9f3c7a4be71072bff06dc94f1b8654703e145 Mon Sep 17 00:00:00 2001
From: Daniel Bruce <djbruce@google.com>
Date: Mon, 20 Nov 2023 16:18:05 -0500
Subject: [PATCH 122/129] Add logs to make latency tests run

The logs will output the time required for the latency tests.
---
 system-test/datastore.ts | 5 ++---
 1 file changed, 2 insertions(+), 3 deletions(-)

diff --git a/system-test/datastore.ts b/system-test/datastore.ts
index fcfd1d984..25a2cc668 100644
--- a/system-test/datastore.ts
+++ b/system-test/datastore.ts
@@ -1931,10 +1931,8 @@ async.each(
         });
         describe('latency tests', () => {
           const key = datastore.key(['Company', 'Google']);
-          const obj = {
-            url: 'www.google.com',
-          };
           afterEach(async () => {
+            console.log('after running latency tests');
             await datastore.delete(key);
           });
           async function runLatencyTests(transaction: Transaction) {
@@ -1943,6 +1941,7 @@ async.each(
               transaction.get(key),
               transaction.get(key),
             ]);
+            await transaction.commit();
             console.timeEnd('before run');
             console.timeEnd('after run');
           }

From a8764ef4b35c30078998f266039e46b2e34bd7be Mon Sep 17 00:00:00 2001
From: Daniel Bruce <djbruce@google.com>
Date: Tue, 21 Nov 2023 10:27:50 -0500
Subject: [PATCH 123/129] Add two tests for put, commit

The two tests for put, commit should make sure that begin transaction is called.
---
 test/transaction.ts | 40 ++++++++++++++++++++++++++++++++++++++++
 1 file changed, 40 insertions(+)

diff --git a/test/transaction.ts b/test/transaction.ts
index 9098c3caa..2b6fc9e72 100644
--- a/test/transaction.ts
+++ b/test/transaction.ts
@@ -884,6 +884,46 @@ async.each(
               transactionOrderTester.callRun();
               transactionOrderTester.pushString('functions called');
             });
+            describe('put, commit', () => {
+              const key = transactionWrapper.datastore.key([
+                'Company',
+                'Google',
+              ]);
+              it('should verify that there is a BeginTransaction call while beginning later', done => {
+                const transactionOrderTester = new TransactionOrderTester(
+                  transactionWrapper,
+                  done,
+                  [
+                    'beginTransaction called',
+                    'commit called',
+                    'commit callback',
+                  ]
+                );
+                transactionOrderTester.transactionWrapper.transaction.save({
+                  key,
+                  data: '',
+                });
+                transactionOrderTester.callCommit();
+              });
+              it('should verify that there is a BeginTransaction call while beginning early', done => {
+                const transactionOrderTester = new TransactionOrderTester(
+                  transactionWrapper,
+                  done,
+                  [
+                    'beginTransaction called',
+                    'run callback',
+                    'commit called',
+                    'commit callback',
+                  ]
+                );
+                transactionOrderTester.transactionWrapper.transaction.save({
+                  key,
+                  data: '',
+                });
+                transactionOrderTester.callRun();
+                transactionOrderTester.callCommit();
+              });
+            });
           });
         });
       });

From fc5546af6ad5dc8199bc6f059eb6b06167b85715 Mon Sep 17 00:00:00 2001
From: Daniel Bruce <djbruce@google.com>
Date: Tue, 21 Nov 2023 11:47:38 -0500
Subject: [PATCH 124/129] Build requests into the transaction order tester

To meet the needs we want for unit testing we must add some checker that lets us record the requests and verify that they are the right values.
---
 test/transaction.ts | 36 +++++++++++++++++++++++++++++-------
 1 file changed, 29 insertions(+), 7 deletions(-)

diff --git a/test/transaction.ts b/test/transaction.ts
index 2b6fc9e72..d3dcf37c2 100644
--- a/test/transaction.ts
+++ b/test/transaction.ts
@@ -178,7 +178,8 @@ async.each(
           functionsMocked: {name: string; mockedFunction: Function}[];
           // The callBackSignaler lets the user of this object get a signal when the mocked function is called.
           // This is useful for tests that need to know when the mocked function is called.
-          callBackSignaler: (callbackReached: string) => void = () => {};
+          callBackSignaler: (callbackReached: string, request?: any) => void =
+            () => {};
 
           constructor() {
             const namespace = 'run-without-mock';
@@ -222,7 +223,7 @@ async.each(
               ) => {
                 // Calls a user provided function that will receive this string
                 // Usually used to track when this code was reached relative to other code
-                this.callBackSignaler('beginTransaction called');
+                this.callBackSignaler('beginTransaction called', request);
                 callback(null, testRunResp);
               };
             }
@@ -264,7 +265,7 @@ async.each(
                   {} | null | undefined
                 >
               ) => {
-                this.callBackSignaler(`${functionName} called`);
+                this.callBackSignaler(`${functionName} called`, request);
                 callback(error, response);
               };
             }
@@ -752,9 +753,11 @@ async.each(
             transactionWrapper.resetGapicFunctions();
           });
 
-          // This object is used for testing the order that different events occur
-          // The events can include
+          // This object is used for testing the order that different events occur.
+          // The events can include user code reached, gapic code reached and callbacks called.
           class TransactionOrderTester {
+            expectedRequests?: {call: string; request?: any}[];
+            requests: {call: string; request?: any}[] = [];
             expectedOrder: string[] = [];
             callbackOrder: string[] = [];
             transactionWrapper: MockedTransactionWrapper;
@@ -767,6 +770,12 @@ async.each(
                     this.callbackOrder,
                     this.expectedOrder
                   );
+                  if (this.expectedRequests) {
+                    assert.deepStrictEqual(
+                      this.requests,
+                      this.expectedRequests
+                    );
+                  }
                   this.done();
                 } catch (e) {
                   this.done(e);
@@ -800,11 +809,14 @@ async.each(
             constructor(
               transactionWrapper: MockedTransactionWrapper,
               done: (err?: any) => void,
-              expectedOrder: string[]
+              expectedOrder: string[],
+              expectedRequests?: {call: string; request?: any}[]
             ) {
               this.expectedOrder = expectedOrder;
-              const gapicCallHandler = (call: string) => {
+              this.expectedRequests = expectedRequests;
+              const gapicCallHandler = (call: string, request?: any) => {
                 try {
+                  this.requests.push({call, request});
                   this.callbackOrder.push(call);
                   this.checkForCompletion();
                 } catch (e) {
@@ -840,6 +852,7 @@ async.each(
             });
 
             it('should call the callbacks in the proper order with run and commit', done => {
+              // zz
               const transactionOrderTester = new TransactionOrderTester(
                 transactionWrapper,
                 done,
@@ -884,6 +897,15 @@ async.each(
               transactionOrderTester.callRun();
               transactionOrderTester.pushString('functions called');
             });
+          });
+          describe('should pass response back to the user and check the request', async () => {
+            beforeEach(() => {
+              transactionWrapper.mockGapicFunction(
+                'commit',
+                testCommitResp,
+                null
+              );
+            });
             describe('put, commit', () => {
               const key = transactionWrapper.datastore.key([
                 'Company',

From c051035b598faa1a5d33fdb6f08b0cd4b9cb7588 Mon Sep 17 00:00:00 2001
From: Daniel Bruce <djbruce@google.com>
Date: Tue, 21 Nov 2023 14:36:12 -0500
Subject: [PATCH 125/129] Modify order testing

Modify the order tester object to include more calls. Also add expected requests to the existing tests.
---
 test/transaction.ts | 120 ++++++++++++++++++++++++++++++++++++++++----
 1 file changed, 111 insertions(+), 9 deletions(-)

diff --git a/test/transaction.ts b/test/transaction.ts
index d3dcf37c2..8f4532459 100644
--- a/test/transaction.ts
+++ b/test/transaction.ts
@@ -28,12 +28,13 @@ import {
   Transaction,
   AggregateField,
 } from '../src';
-import {Entity, entity} from '../src/entity';
+import {Entities, Entity, entity} from '../src/entity';
 import * as tsTypes from '../src/transaction';
 import * as sinon from 'sinon';
 import {Callback, CallOptions, ClientStub} from 'google-gax';
 import {
   CommitCallback,
+  CreateReadStreamOptions,
   GetCallback,
   RequestCallback,
   RequestConfig,
@@ -43,7 +44,7 @@ import {google} from '../protos/protos';
 import {RunCallback} from '../src/transaction';
 import * as protos from '../protos/protos';
 import {AggregateQuery} from '../src/aggregate';
-import {RunQueryCallback} from '../src/query';
+import {RunQueryCallback, RunQueryInfo, RunQueryOptions} from '../src/query';
 const async = require('async');
 
 // eslint-disable-next-line @typescript-eslint/no-explicit-any
@@ -806,6 +807,43 @@ async.each(
               }
             };
 
+            getCallback: GetCallback = (
+              error: Error | null | undefined,
+              response?: Entities
+            ) => {
+              try {
+                this.callbackOrder.push('get callback');
+                this.checkForCompletion();
+              } catch (e) {
+                this.done(e);
+              }
+            };
+
+            runQueryCallback: RunQueryCallback = (
+              err: Error | null | undefined,
+              entities?: Entity[],
+              info?: RunQueryInfo
+            ) => {
+              try {
+                this.callbackOrder.push('runQuery callback');
+                this.checkForCompletion();
+              } catch (e) {
+                this.done(e);
+              }
+            };
+
+            runAggregationQueryCallback: RequestCallback = (
+              a?: Error | null,
+              b?: any
+            ) => {
+              try {
+                this.callbackOrder.push('runAggregationQuery callback');
+                this.checkForCompletion();
+              } catch (e) {
+                this.done(e);
+              }
+            };
+
             constructor(
               transactionWrapper: MockedTransactionWrapper,
               done: (err?: any) => void,
@@ -836,6 +874,33 @@ async.each(
               this.transactionWrapper.transaction.commit(this.commitCallback);
             }
 
+            callGet(keys: entity.Key, options: CreateReadStreamOptions) {
+              this.transactionWrapper.transaction.get(
+                keys,
+                options,
+                this.getCallback
+              );
+            }
+
+            callRunQuery(query: Query, options: RunQueryOptions) {
+              this.transactionWrapper.transaction.runQuery(
+                query,
+                options,
+                this.runQueryCallback
+              );
+            }
+
+            callRunAggregationQuery(
+              query: AggregateQuery,
+              options: RunQueryOptions
+            ) {
+              this.transactionWrapper.transaction.runAggregationQuery(
+                query,
+                options,
+                this.runAggregationQueryCallback
+              );
+            }
+
             pushString(callbackPushed: string) {
               this.callbackOrder.push(callbackPushed);
               this.checkForCompletion();
@@ -898,19 +963,54 @@ async.each(
               transactionOrderTester.pushString('functions called');
             });
           });
-          describe('should pass response back to the user and check the request', async () => {
+          describe.only('should pass response back to the user and check the request', async () => {
+            let key: entity.Key;
             beforeEach(() => {
+              key = transactionWrapper.datastore.key(['Company', 'Google']);
               transactionWrapper.mockGapicFunction(
                 'commit',
                 testCommitResp,
                 null
               );
             });
+            const beginTransactionRequest = {
+              transactionOptions: {},
+              projectId: 'project-id',
+            };
+            const commitRequest = {
+              mode: 'TRANSACTIONAL',
+              transaction: testRunResp.transaction,
+              projectId: 'project-id',
+              mutations: [
+                {
+                  upsert: {
+                    properties: {},
+                    key: {
+                      partitionId: {
+                        namespaceId: 'run-without-mock',
+                      },
+                      path: [
+                        {
+                          kind: 'Company',
+                          name: 'Google',
+                        },
+                      ],
+                    },
+                  },
+                },
+              ],
+            };
             describe('put, commit', () => {
-              const key = transactionWrapper.datastore.key([
-                'Company',
-                'Google',
-              ]);
+              const expectedRequests = [
+                {
+                  call: 'beginTransaction called',
+                  request: beginTransactionRequest,
+                },
+                {
+                  call: 'commit called',
+                  request: commitRequest,
+                },
+              ];
               it('should verify that there is a BeginTransaction call while beginning later', done => {
                 const transactionOrderTester = new TransactionOrderTester(
                   transactionWrapper,
@@ -919,7 +1019,8 @@ async.each(
                     'beginTransaction called',
                     'commit called',
                     'commit callback',
-                  ]
+                  ],
+                  expectedRequests
                 );
                 transactionOrderTester.transactionWrapper.transaction.save({
                   key,
@@ -936,7 +1037,8 @@ async.each(
                     'run callback',
                     'commit called',
                     'commit callback',
-                  ]
+                  ],
+                  expectedRequests
                 );
                 transactionOrderTester.transactionWrapper.transaction.save({
                   key,

From 6d05dc1141f4cc08fa2c60c3fc8310f1dd935e25 Mon Sep 17 00:00:00 2001
From: Daniel Bruce <djbruce@google.com>
Date: Tue, 21 Nov 2023 16:26:20 -0500
Subject: [PATCH 126/129] Create lookup, lookup, put, commit

Create test for a read, read and then commit.
---
 test/transaction.ts | 131 +++++++++++++++++++++++++++++++++++++++++++-
 1 file changed, 130 insertions(+), 1 deletion(-)

diff --git a/test/transaction.ts b/test/transaction.ts
index 8f4532459..d06a36896 100644
--- a/test/transaction.ts
+++ b/test/transaction.ts
@@ -743,6 +743,68 @@ async.each(
               },
             ],
           };
+          const testGetResp = {
+            found: [
+              {
+                entity: {
+                  key: {
+                    path: [
+                      {
+                        kind: 'Post',
+                        name: 'post1',
+                        idType: 'name',
+                      },
+                    ],
+                    partitionId: {
+                      projectId: 'projectId',
+                      databaseId: 'databaseId',
+                      namespaceId: 'namespaceId',
+                    },
+                  },
+                  excludeFromIndexes: false,
+                  properties: {},
+                },
+              },
+            ],
+            missing: [],
+            deferred: [],
+            transaction: testRunResp.transaction,
+            readTime: {
+              seconds: '1699470605',
+              nanos: 201398000,
+            },
+          };
+          const testRunQueryResp = {
+            batch: {
+              entityResults: [],
+              endCursor: {
+                type: 'Buffer',
+                data: Buffer.from(Array.from(Array(100).keys())),
+              },
+            },
+          };
+          const testRunAggregationQueryResp = {
+            batch: {
+              aggregationResults: [
+                {
+                  aggregateProperties: {
+                    'average rating': {
+                      meaning: 0,
+                      excludeFromIndexes: false,
+                      doubleValue: 100,
+                      valueType: 'doubleValue',
+                    },
+                  },
+                },
+              ],
+              moreResults:
+                google.datastore.v1.QueryResultBatch.MoreResultsType
+                  .NO_MORE_RESULTS,
+              readTime: {seconds: '1699390681', nanos: 961667000},
+            },
+            query: null,
+            transaction: testRunResp.transaction,
+          };
           let transactionWrapper: MockedTransactionWrapper;
 
           beforeEach(async () => {
@@ -854,6 +916,7 @@ async.each(
               this.expectedRequests = expectedRequests;
               const gapicCallHandler = (call: string, request?: any) => {
                 try {
+                  console.log(`Getting call ${call}`);
                   this.requests.push({call, request});
                   this.callbackOrder.push(call);
                   this.checkForCompletion();
@@ -963,7 +1026,7 @@ async.each(
               transactionOrderTester.pushString('functions called');
             });
           });
-          describe.only('should pass response back to the user and check the request', async () => {
+          describe('should pass response back to the user and check the request', async () => {
             let key: entity.Key;
             beforeEach(() => {
               key = transactionWrapper.datastore.key(['Company', 'Google']);
@@ -972,6 +1035,17 @@ async.each(
                 testCommitResp,
                 null
               );
+              transactionWrapper.mockGapicFunction('lookup', testGetResp, null);
+              transactionWrapper.mockGapicFunction(
+                'runQuery',
+                testRunQueryResp,
+                null
+              );
+              transactionWrapper.mockGapicFunction(
+                'runAggregationQuery',
+                testRunAggregationQueryResp,
+                null
+              );
             });
             const beginTransactionRequest = {
               transactionOptions: {},
@@ -1048,6 +1122,61 @@ async.each(
                 transactionOrderTester.callCommit();
               });
             });
+            describe.only('lookup, lookup, put, commit', () => {
+              const expectedRequests = [
+                {
+                  call: 'beginTransaction called',
+                  request: beginTransactionRequest,
+                },
+                {
+                  call: 'commit called',
+                  request: commitRequest,
+                },
+              ];
+              it('should verify that there is a BeginTransaction call while beginning later', done => {
+                const transactionOrderTester = new TransactionOrderTester(
+                  transactionWrapper,
+                  done,
+                  [
+                    'beginTransaction called',
+                    'commit called',
+                    'commit callback',
+                    'get callback',
+                    'get callback',
+                  ],
+                  expectedRequests
+                );
+                transactionOrderTester.callGet(key, {consistency: 'eventual'});
+                transactionOrderTester.callGet(key, {consistency: 'eventual'});
+                transactionOrderTester.transactionWrapper.transaction.save({
+                  key,
+                  data: '',
+                });
+                transactionOrderTester.callCommit();
+              });
+              it('should verify that there is a BeginTransaction call while beginning early', done => {
+                const transactionOrderTester = new TransactionOrderTester(
+                  transactionWrapper,
+                  done,
+                  [
+                    'beginTransaction called',
+                    'commit called',
+                    'commit callback',
+                    'get callback',
+                    'get callback',
+                  ],
+                  expectedRequests
+                );
+                transactionOrderTester.callRun();
+                transactionOrderTester.callGet(key, {consistency: 'eventual'});
+                transactionOrderTester.callGet(key, {consistency: 'eventual'});
+                transactionOrderTester.transactionWrapper.transaction.save({
+                  key,
+                  data: '',
+                });
+                transactionOrderTester.callCommit();
+              });
+            });
           });
         });
       });

From fb6ee90481b1b8465f818c16bd404b50a77fbf27 Mon Sep 17 00:00:00 2001
From: Daniel Bruce <djbruce@google.com>
Date: Tue, 21 Nov 2023 16:32:13 -0500
Subject: [PATCH 127/129] Fix the unit test

Unit test should capture the fact that the run callback is used.
---
 test/transaction.ts | 1 +
 1 file changed, 1 insertion(+)

diff --git a/test/transaction.ts b/test/transaction.ts
index d06a36896..144f5627d 100644
--- a/test/transaction.ts
+++ b/test/transaction.ts
@@ -1160,6 +1160,7 @@ async.each(
                   done,
                   [
                     'beginTransaction called',
+                    'run callback',
                     'commit called',
                     'commit callback',
                     'get callback',

From beb4c8c3ae50e19f399bb020b4746be710662603 Mon Sep 17 00:00:00 2001
From: Daniel Bruce <djbruce@google.com>
Date: Wed, 22 Nov 2023 10:06:56 -0500
Subject: [PATCH 128/129] Add lookup requests

When using get requests without passing consistency, we should see two lookup requests reach the gapic layer. Consistency should be removed from the test because it is not meant to reach the Gapic layer.
---
 test/transaction.ts | 42 ++++++++++++++++++++++++++++++++++++++----
 1 file changed, 38 insertions(+), 4 deletions(-)

diff --git a/test/transaction.ts b/test/transaction.ts
index 144f5627d..8934f333e 100644
--- a/test/transaction.ts
+++ b/test/transaction.ts
@@ -1074,6 +1074,25 @@ async.each(
                 },
               ],
             };
+            const lookupTransactionRequest = {
+              keys: [
+                {
+                  partitionId: {
+                    namespaceId: 'run-without-mock',
+                  },
+                  path: [
+                    {
+                      kind: 'Company',
+                      name: 'Google',
+                    },
+                  ],
+                },
+              ],
+              projectId: 'project-id',
+              readOptions: {
+                transaction: testRunResp.transaction,
+              },
+            };
             describe('put, commit', () => {
               const expectedRequests = [
                 {
@@ -1132,6 +1151,14 @@ async.each(
                   call: 'commit called',
                   request: commitRequest,
                 },
+                {
+                  call: 'lookup called',
+                  request: lookupTransactionRequest,
+                },
+                {
+                  call: 'lookup called',
+                  request: lookupTransactionRequest,
+                },
               ];
               it('should verify that there is a BeginTransaction call while beginning later', done => {
                 const transactionOrderTester = new TransactionOrderTester(
@@ -1141,18 +1168,23 @@ async.each(
                     'beginTransaction called',
                     'commit called',
                     'commit callback',
+                    'lookup called',
+                    'lookup called',
                     'get callback',
                     'get callback',
                   ],
                   expectedRequests
                 );
-                transactionOrderTester.callGet(key, {consistency: 'eventual'});
-                transactionOrderTester.callGet(key, {consistency: 'eventual'});
+                transactionOrderTester.callGet(key, {});
+                console.log('after get 1');
+                transactionOrderTester.callGet(key, {});
+                console.log('after get 2');
                 transactionOrderTester.transactionWrapper.transaction.save({
                   key,
                   data: '',
                 });
                 transactionOrderTester.callCommit();
+                console.log('after commit');
               });
               it('should verify that there is a BeginTransaction call while beginning early', done => {
                 const transactionOrderTester = new TransactionOrderTester(
@@ -1163,14 +1195,16 @@ async.each(
                     'run callback',
                     'commit called',
                     'commit callback',
+                    'lookup called',
+                    'lookup called',
                     'get callback',
                     'get callback',
                   ],
                   expectedRequests
                 );
                 transactionOrderTester.callRun();
-                transactionOrderTester.callGet(key, {consistency: 'eventual'});
-                transactionOrderTester.callGet(key, {consistency: 'eventual'});
+                transactionOrderTester.callGet(key, {});
+                transactionOrderTester.callGet(key, {});
                 transactionOrderTester.transactionWrapper.transaction.save({
                   key,
                   data: '',

From 4f42a6705e867c5eb938155f5b7e63cef03398fc Mon Sep 17 00:00:00 2001
From: Daniel Bruce <djbruce@google.com>
Date: Wed, 22 Nov 2023 10:09:23 -0500
Subject: [PATCH 129/129] Remove the console logs

console logs are not needed. Remove them so that the tests are cleaner.
---
 test/transaction.ts | 4 ----
 1 file changed, 4 deletions(-)

diff --git a/test/transaction.ts b/test/transaction.ts
index 8934f333e..848e2b5a1 100644
--- a/test/transaction.ts
+++ b/test/transaction.ts
@@ -916,7 +916,6 @@ async.each(
               this.expectedRequests = expectedRequests;
               const gapicCallHandler = (call: string, request?: any) => {
                 try {
-                  console.log(`Getting call ${call}`);
                   this.requests.push({call, request});
                   this.callbackOrder.push(call);
                   this.checkForCompletion();
@@ -1176,15 +1175,12 @@ async.each(
                   expectedRequests
                 );
                 transactionOrderTester.callGet(key, {});
-                console.log('after get 1');
                 transactionOrderTester.callGet(key, {});
-                console.log('after get 2');
                 transactionOrderTester.transactionWrapper.transaction.save({
                   key,
                   data: '',
                 });
                 transactionOrderTester.callCommit();
-                console.log('after commit');
               });
               it('should verify that there is a BeginTransaction call while beginning early', done => {
                 const transactionOrderTester = new TransactionOrderTester(