npm package diff

Package: @forge/storage

Versions: 1.5.15-experimental-10722bc - 1.6.0-next.0

Removed:package/out/entity-storage/custom-entity-transaction-api.js

Removed:package/out/kvs-transaction-api.js

Removed:package/out/transaction-api.js

Removed:package/out/__test__/transaction-api.test.js

Removed:package/out/entity-storage/__test__/transaction-api.test.js

Removed:package/out/entity-storage/custom-entity-transaction-api.d.ts.map

Removed:package/out/kvs-transaction-api.d.ts.map

Removed:package/out/transaction-api.d.ts.map

Removed:package/out/__test__/transaction-api.test.d.ts.map

Removed:package/out/entity-storage/__test__/transaction-api.test.d.ts.map

Removed:package/out/entity-storage/custom-entity-transaction-api.d.ts

Removed:package/out/kvs-transaction-api.d.ts

Removed:package/out/transaction-api.d.ts

Removed:package/out/__test__/transaction-api.test.d.ts

Removed:package/out/entity-storage/__test__/transaction-api.test.d.ts

Modified:package/out/global-storage.js

Index: package/out/global-storage.js
===================================================================
--- package/out/global-storage.js
+++ package/out/global-storage.js
@@ -26,12 +26,14 @@
 }
 class GlobalStorage {
     getAppContextAri;
     apiClient;
+    getMetrics;
     endpoint = '/forge/entities/graphql';
-    constructor(getAppContextAri, apiClient) {
+    constructor(getAppContextAri, apiClient, getMetrics) {
         this.getAppContextAri = getAppContextAri;
         this.apiClient = apiClient;
+        this.getMetrics = getMetrics;
     }
     doGetAppContextAri() {
         return typeof this.getAppContextAri === 'function' ? this.getAppContextAri() : this.getAppContextAri;
     }
@@ -44,9 +46,9 @@
     async list(options) {
         const requestBody = process.env.IS_CLEANUP_FUNCTION === 'true'
             ? gql_queries_1.UntypedQueries.listQueryForCleanup(this.doGetAppContextAri(), options)
             : gql_queries_1.UntypedQueries.listQuery(this.doGetAppContextAri(), options);
-        const response = await this.query(requestBody);
+        const response = await this.wrapInMetric('untyped', 'query', false, async () => await this.query(requestBody));
         const edges = process.env.IS_CLEANUP_FUNCTION === 'true'
             ? response.appStoredEntitiesForCleanup.edges
             : response.appStoredEntities.edges;
         const nextCursor = edges.length > 0 ? edges[edges.length - 1].cursor : undefined;
@@ -55,21 +57,11 @@
             results,
             nextCursor
         };
     }
-    async transaction(options, isCustomEntity) {
-        if (isCustomEntity) {
-            const requestBody = gql_queries_1.CustomEntityQueries.transaction(this.doGetAppContextAri(), options);
-            await this.mutation(requestBody, 'appStorageCustomEntity', 'transactAppStoredCustomEntity');
-        }
-        else {
-            const requestBody = gql_queries_1.UntypedQueries.transaction(this.doGetAppContextAri(), options);
-            await this.mutation(requestBody, 'appStorage', 'transactAppStoredEntity');
-        }
-    }
     async listCustomEntities(options) {
         const requestBody = gql_queries_1.CustomEntityQueries.listQuery(this.doGetAppContextAri(), options);
-        const response = await this.query(requestBody);
+        const response = await this.wrapInMetric('typed', 'query', false, async () => await this.query(requestBody));
         const edges = response.appStoredCustomEntities.edges;
         const results = edges.map(({ node }) => node);
         return {
             results,
@@ -77,51 +69,41 @@
         };
     }
     async set(key, value) {
         const requestBody = gql_queries_1.UntypedQueries.set(this.doGetAppContextAri(), key, value, false);
-        await this.mutation(requestBody, 'appStorage', 'setAppStoredEntity');
+        await this.wrapInMetric('untyped', 'set', false, async () => await this.mutation(requestBody, 'appStorage', 'setAppStoredEntity'));
     }
     async setSecret(key, value) {
         const requestBody = gql_queries_1.UntypedQueries.set(this.doGetAppContextAri(), key, value, true);
-        await this.mutation(requestBody, 'appStorage', 'setAppStoredEntity');
+        await this.wrapInMetric('untyped', 'set', true, async () => await this.mutation(requestBody, 'appStorage', 'setAppStoredEntity'));
     }
-    async bulkSet(items) {
-        const requestBody = gql_queries_1.UntypedQueries.bulkSet(this.doGetAppContextAri(), items, false);
-        const response = await this.mutation(requestBody, 'appStorage', 'setAppStoredEntities', true);
-        const failedKeys = response.failedKeys;
-        const savedKeys = response.savedKeys;
-        return {
-            savedKeys,
-            failedKeys
-        };
-    }
     async delete(key) {
         const requestBody = gql_queries_1.UntypedQueries.delete(this.doGetAppContextAri(), key, false);
-        await this.mutation(requestBody, 'appStorage', 'deleteAppStoredEntity');
+        await this.wrapInMetric('untyped', 'delete', false, async () => this.mutation(requestBody, 'appStorage', 'deleteAppStoredEntity'));
     }
     async deleteSecret(key) {
         const requestBody = gql_queries_1.UntypedQueries.delete(this.doGetAppContextAri(), key, true);
-        await this.mutation(requestBody, 'appStorage', 'deleteAppStoredEntity');
+        await this.wrapInMetric('untyped', 'delete', true, async () => this.mutation(requestBody, 'appStorage', 'deleteAppStoredEntity'));
     }
     async getEntity(entityName, entityKey) {
         return this.getEntityInternal(entityName, entityKey);
     }
     async setEntity(entityName, entityKey, value) {
         const requestBody = gql_queries_1.CustomEntityQueries.set(this.doGetAppContextAri(), entityName, entityKey, value);
-        await this.mutation(requestBody, 'appStorageCustomEntity', 'setAppStoredCustomEntity');
+        await this.wrapInMetric('typed', 'set', false, async () => this.mutation(requestBody, 'appStorageCustomEntity', 'setAppStoredCustomEntity'));
     }
     async deleteEntity(entityName, entityKey) {
         const requestBody = gql_queries_1.CustomEntityQueries.delete(this.doGetAppContextAri(), entityName, entityKey);
-        await this.mutation(requestBody, 'appStorageCustomEntity', 'deleteAppStoredCustomEntity');
+        await this.wrapInMetric('typed', 'delete', false, async () => await this.mutation(requestBody, 'appStorageCustomEntity', 'deleteAppStoredCustomEntity'));
     }
     async getInternal(key, encrypted) {
         const requestBody = gql_queries_1.UntypedQueries.get(this.doGetAppContextAri(), key, encrypted);
-        const { appStoredEntity: { value } } = await this.query(requestBody);
+        const { appStoredEntity: { value } } = await this.wrapInMetric('untyped', 'get', encrypted, async () => await this.query(requestBody));
         return value ?? undefined;
     }
     async getEntityInternal(entityName, entityKey) {
         const requestBody = gql_queries_1.CustomEntityQueries.get(this.doGetAppContextAri(), entityName, entityKey);
-        const { appStoredCustomEntity: { value } } = await this.query(requestBody);
+        const { appStoredCustomEntity: { value } } = await this.wrapInMetric('typed', 'get', false, async () => await this.query(requestBody));
         return value ?? undefined;
     }
     buildRequest(requestBody) {
         return {
@@ -135,18 +117,46 @@
     async query(body) {
         const response = await this.apiClient(this.endpoint, this.buildRequest(body));
         return await getResponseBody(response);
     }
-    async mutation(body, namespace, mutationMethod, returnResponseBody) {
+    async mutation(body, namespace, mutationMethod) {
         const response = await this.apiClient(this.endpoint, this.buildRequest(body));
-        const { [namespace]: { [mutationMethod]: mutationResponse } } = await getResponseBody(response);
-        assertNoErrors(mutationResponse.errors);
-        if (!mutationResponse.success) {
+        const { [namespace]: { [mutationMethod]: { success, errors } } } = await getResponseBody(response);
+        assertNoErrors(errors);
+        if (!success) {
             throw errors_1.APIError.forStatus(500);
         }
-        if (returnResponseBody) {
-            return mutationResponse;
-        }
         return response;
     }
+    async wrapInMetric(store, operation, encrypted, fn) {
+        const metrics = this.getMetrics();
+        const timer = metrics
+            .timing('forge.runtime.storage.operation.latency', { store, operation, encrypted: String(encrypted) })
+            .measure();
+        try {
+            const result = await fn();
+            timer.stop({ success: 'true' });
+            metrics
+                .counter('forge.runtime.storage.operation', {
+                store,
+                operation,
+                encrypted: String(encrypted),
+                success: 'true'
+            })
+                .incr();
+            return result;
+        }
+        catch (error) {
+            timer.stop({ success: 'false' });
+            metrics
+                .counter('forge.runtime.storage.operation', {
+                store,
+                operation,
+                encrypted: String(encrypted),
+                success: 'false'
+            })
+                .incr();
+            throw error;
+        }
+    }
 }
 exports.GlobalStorage = GlobalStorage;

Modified:package/out/__test__/global-storage.test.js

Index: package/out/__test__/global-storage.test.js
===================================================================
--- package/out/__test__/global-storage.test.js
+++ package/out/__test__/global-storage.test.js
@@ -2,17 +2,48 @@
 Object.defineProperty(exports, "__esModule", { value: true });
 const errors_1 = require("../errors");
 const global_storage_1 = require("../global-storage");
 const gql_queries_1 = require("../gql-queries");
+const mocks_1 = require("@atlassian/metrics-interface/dist/mocks");
 const contextAri = 'app-ari';
-const getStorage = (apiClientMock) => new global_storage_1.GlobalStorage(() => contextAri, apiClientMock);
+const getStorage = (apiClientMock, metrics) => new global_storage_1.GlobalStorage(() => contextAri, apiClientMock, () => metrics);
 const getApiClientMock = (response, statusCode = 200) => {
     return jest.fn().mockReturnValue({
         ok: statusCode === 200,
         status: statusCode,
         text: jest.fn().mockResolvedValue(JSON.stringify(response))
     });
 };
+const getMetricMock = () => {
+    const mockMetrics = new mocks_1.MockMetrics();
+    const mockCounter = new mocks_1.MockCounter('');
+    const mockTiming = new mocks_1.MockTiming('');
+    const mockStopTiming = jest.fn();
+    const mockMeasure = {
+        stop: mockStopTiming
+    };
+    mockMetrics.counter.mockReturnValue(mockCounter);
+    mockMetrics.timing.mockReturnValue(mockTiming);
+    mockTiming.measure.mockReturnValue(mockMeasure);
+    return {
+        mockMetrics,
+        mockCounter,
+        mockStopTiming
+    };
+};
+const checkMetricsFired = ({ mockMetrics, mockCounter, mockStopTiming }, { encrypted, ...restTags }, success) => {
+    expect(mockMetrics.counter).toHaveBeenCalledWith('forge.runtime.storage.operation', {
+        ...restTags,
+        encrypted: String(encrypted),
+        success: String(success)
+    });
+    expect(mockCounter.incr).toHaveBeenCalled();
+    expect(mockMetrics.timing).toHaveBeenCalledWith('forge.runtime.storage.operation.latency', {
+        ...restTags,
+        encrypted: String(encrypted)
+    });
+    expect(mockStopTiming).toHaveBeenCalledWith({ success: String(success) });
+};
 const getApiClientMockInvalidJson = (response, statusCode = 200) => {
     return jest.fn().mockReturnValue({
         ok: statusCode === 200,
         status: statusCode,
@@ -42,691 +73,686 @@
         global.api = {
             __getAppAri: jest.fn().mockReturnValue(contextAri)
         };
     });
-    describe('Untyped entities', () => {
-        describe('get', () => {
-            it('should call the storage API, passing the provided key and returning the stored value', async () => {
-                const apiClientMock = getApiClientMock({
-                    data: {
-                        appStoredEntity: {
-                            value: 'testValue'
-                        }
+    describe('get', () => {
+        it('should call the storage API, passing the provided key and returning the stored value', async () => {
+            const apiClientMock = getApiClientMock({
+                data: {
+                    appStoredEntity: {
+                        value: 'testValue'
                     }
-                });
-                const globalStorage = getStorage(apiClientMock);
-                const returnedValue = await globalStorage.get('testKey');
-                verifyApiClientCalledWith(apiClientMock, {
-                    contextAri,
-                    key: 'testKey',
-                    encrypted: false
-                });
-                expect(returnedValue).toEqual('testValue');
+                }
             });
-            it('should call the storage API, passing the provided key and returning undefined if the key doesnt exist', async () => {
-                const apiClientMock = getApiClientMock({
-                    data: {
-                        appStoredEntity: {
-                            value: null
-                        }
-                    }
-                });
-                const globalStorage = getStorage(apiClientMock);
-                const returnedValue = await globalStorage.get('testKey');
-                verifyApiClientCalledWith(apiClientMock, {
-                    contextAri,
-                    key: 'testKey',
-                    encrypted: false
-                });
-                expect(returnedValue).toEqual(undefined);
+            const metricMocks = getMetricMock();
+            const globalStorage = getStorage(apiClientMock, metricMocks.mockMetrics);
+            const returnedValue = await globalStorage.get('testKey');
+            verifyApiClientCalledWith(apiClientMock, {
+                contextAri,
+                key: 'testKey',
+                encrypted: false
             });
-            it('should call the storage API, passing the provided key and returning the stored falsey value 0', async () => {
-                const apiClientMock = getApiClientMock({
-                    data: {
-                        appStoredEntity: {
-                            value: 0
-                        }
+            expect(returnedValue).toEqual('testValue');
+            checkMetricsFired(metricMocks, { store: 'untyped', operation: 'get', encrypted: false }, true);
+        });
+        it('should call the storage API, passing the provided key and returning undefined if the key doesnt exist', async () => {
+            const apiClientMock = getApiClientMock({
+                data: {
+                    appStoredEntity: {
+                        value: null
                     }
-                });
-                const globalStorage = getStorage(apiClientMock);
-                const returnedValue = await globalStorage.get('testKey');
-                verifyApiClientCalledWith(apiClientMock, {
-                    contextAri,
-                    key: 'testKey',
-                    encrypted: false
-                });
-                expect(returnedValue).toEqual(0);
+                }
             });
-            it('should call the storage API, passing the provided key and returning the stored empty string', async () => {
-                const apiClientMock = getApiClientMock({
-                    data: {
-                        appStoredEntity: {
-                            value: ''
-                        }
+            const metricMocks = getMetricMock();
+            const globalStorage = getStorage(apiClientMock, metricMocks.mockMetrics);
+            const returnedValue = await globalStorage.get('testKey');
+            verifyApiClientCalledWith(apiClientMock, {
+                contextAri,
+                key: 'testKey',
+                encrypted: false
+            });
+            expect(returnedValue).toEqual(undefined);
+            checkMetricsFired(metricMocks, { store: 'untyped', operation: 'get', encrypted: false }, true);
+        });
+        it('should call the storage API, passing the provided key and returning the stored falsey value 0', async () => {
+            const apiClientMock = getApiClientMock({
+                data: {
+                    appStoredEntity: {
+                        value: 0
                     }
-                });
-                const globalStorage = getStorage(apiClientMock);
-                const returnedValue = await globalStorage.get('testKey');
-                verifyApiClientCalledWith(apiClientMock, {
-                    contextAri,
-                    key: 'testKey',
-                    encrypted: false
-                });
-                expect(returnedValue).toEqual('');
+                }
             });
-            it('should throw an error with the returned status for non-200 status codes', async () => {
-                const apiClientMock = getApiClientMock(undefined, 400);
-                const globalStorage = getStorage(apiClientMock);
-                const response = globalStorage.get('testKey');
-                expect(apiClientMock).toHaveBeenCalled();
-                await expect(response).rejects.toThrow(errors_1.APIError.forStatus(400));
+            const metricMocks = getMetricMock();
+            const globalStorage = getStorage(apiClientMock, metricMocks.mockMetrics);
+            const returnedValue = await globalStorage.get('testKey');
+            verifyApiClientCalledWith(apiClientMock, {
+                contextAri,
+                key: 'testKey',
+                encrypted: false
             });
-            it('should throw an error with the returned error message for failed responses', async () => {
-                const apiClientMock = getApiClientMock({
-                    errors: [INVALID_CURSOR_ERROR]
-                }, 200);
-                const globalStorage = getStorage(apiClientMock);
-                const response = globalStorage.get('testKey');
-                expect(apiClientMock).toHaveBeenCalled();
-                await expect(response).rejects.toThrow(errors_1.APIError.forErrorCode('CURSOR_INVALID', 'error message'));
+            expect(returnedValue).toEqual(0);
+            checkMetricsFired(metricMocks, { store: 'untyped', operation: 'get', encrypted: false }, true);
+        });
+        it('should call the storage API, passing the provided key and returning the stored empty string', async () => {
+            const apiClientMock = getApiClientMock({
+                data: {
+                    appStoredEntity: {
+                        value: ''
+                    }
+                }
             });
-            it('should throw an error if the response is not a valid JSON', async () => {
-                const apiClientMock = getApiClientMockInvalidJson('test', 200);
-                const globalStorage = getStorage(apiClientMock);
-                const response = globalStorage.get('testKey');
-                expect(apiClientMock).toHaveBeenCalled();
-                await expect(response).rejects.toThrow(errors_1.APIError.forUnexpected('Response text was not a valid JSON: test'));
+            const metricMocks = getMetricMock();
+            const globalStorage = getStorage(apiClientMock, metricMocks.mockMetrics);
+            const returnedValue = await globalStorage.get('testKey');
+            verifyApiClientCalledWith(apiClientMock, {
+                contextAri,
+                key: 'testKey',
+                encrypted: false
             });
+            expect(returnedValue).toEqual('');
+            checkMetricsFired(metricMocks, { store: 'untyped', operation: 'get', encrypted: false }, true);
         });
-        describe('set', () => {
-            it('should call the storage API, passing the provided key and value', async () => {
-                const apiClientMock = getApiClientMock({
-                    data: {
-                        appStorage: {
-                            setAppStoredEntity: {
-                                success: true
-                            }
-                        }
+        it('should throw an error with the returned status for non-200 status codes', async () => {
+            const apiClientMock = getApiClientMock(undefined, 400);
+            const metricMocks = getMetricMock();
+            const globalStorage = getStorage(apiClientMock, metricMocks.mockMetrics);
+            const response = globalStorage.get('testKey');
+            expect(apiClientMock).toHaveBeenCalled();
+            await expect(response).rejects.toThrow(errors_1.APIError.forStatus(400));
+            checkMetricsFired(metricMocks, { store: 'untyped', operation: 'get', encrypted: false }, false);
+        });
+        it('should throw an error with the returned error message for failed responses', async () => {
+            const apiClientMock = getApiClientMock({
+                errors: [INVALID_CURSOR_ERROR]
+            }, 200);
+            const metricMocks = getMetricMock();
+            const globalStorage = getStorage(apiClientMock, metricMocks.mockMetrics);
+            const response = globalStorage.get('testKey');
+            expect(apiClientMock).toHaveBeenCalled();
+            await expect(response).rejects.toThrow(errors_1.APIError.forErrorCode('CURSOR_INVALID', 'error message'));
+            checkMetricsFired(metricMocks, { store: 'untyped', operation: 'get', encrypted: false }, false);
+        });
+        it('should throw an error if the response is not a valid JSON', async () => {
+            const apiClientMock = getApiClientMockInvalidJson('test', 200);
+            const metricMocks = getMetricMock();
+            const globalStorage = getStorage(apiClientMock, metricMocks.mockMetrics);
+            const response = globalStorage.get('testKey');
+            expect(apiClientMock).toHaveBeenCalled();
+            await expect(response).rejects.toThrow(errors_1.APIError.forUnexpected('Response text was not a valid JSON: test'));
+            checkMetricsFired(metricMocks, { store: 'untyped', operation: 'get', encrypted: false }, false);
+        });
+    });
+    describe('get secret', () => {
+        it('should call the storage API, passing the provided key and returning the stored value', async () => {
+            const apiClientMock = getApiClientMock({
+                data: {
+                    appStoredEntity: {
+                        value: 'testValue'
                     }
-                });
-                const globalStorage = getStorage(apiClientMock);
-                await globalStorage.set('testKey', 'testValue');
-                verifyApiClientCalledWith(apiClientMock, {
-                    input: {
-                        contextAri,
-                        key: 'testKey',
-                        value: 'testValue',
-                        encrypted: false
-                    }
-                });
+                }
             });
-            it('should throw an error if the storage API returns successful = false', async () => {
-                const apiClientMock = getApiClientMock({
-                    data: {
-                        appStorage: {
-                            setAppStoredEntity: {
-                                success: false,
-                                errors: [INVALID_CURSOR_ERROR]
-                            }
+            const metricMocks = getMetricMock();
+            const globalStorage = getStorage(apiClientMock, metricMocks.mockMetrics);
+            const returnedValue = await globalStorage.getSecret('testKey');
+            verifyApiClientCalledWith(apiClientMock, {
+                contextAri,
+                key: 'testKey',
+                encrypted: true
+            });
+            expect(returnedValue).toEqual('testValue');
+            checkMetricsFired(metricMocks, { store: 'untyped', operation: 'get', encrypted: true }, true);
+        });
+    });
+    describe('set', () => {
+        it('should call the storage API, passing the provided key and value', async () => {
+            const apiClientMock = getApiClientMock({
+                data: {
+                    appStorage: {
+                        setAppStoredEntity: {
+                            success: true
                         }
                     }
-                });
-                const globalStorage = getStorage(apiClientMock);
-                const response = globalStorage.set('testKey', 'testValue');
-                expect(apiClientMock).toHaveBeenCalled();
-                await expect(response).rejects.toThrow(errors_1.APIError.forErrorCode('INVALID_CURSOR', 'error message'));
+                }
             });
-            it('should throw an error if the storage API returns a non 200 status code', async () => {
-                const apiClientMock = getApiClientMockInvalidJson('', 400);
-                const globalStorage = getStorage(apiClientMock);
-                const response = globalStorage.set('testKey', 'testValue');
-                expect(apiClientMock).toHaveBeenCalled();
-                await expect(response).rejects.toThrow(errors_1.APIError.forStatus(400));
+            const metricMocks = getMetricMock();
+            const globalStorage = getStorage(apiClientMock, metricMocks.mockMetrics);
+            await globalStorage.set('testKey', 'testValue');
+            verifyApiClientCalledWith(apiClientMock, {
+                input: {
+                    contextAri,
+                    key: 'testKey',
+                    value: 'testValue',
+                    encrypted: false
+                }
             });
-            it('should throw a 500 error if success=false but no errors were returned', async () => {
-                const apiClientMock = getApiClientMock({
-                    data: {
-                        appStorage: {
-                            setAppStoredEntity: {
-                                success: false
-                            }
+            checkMetricsFired(metricMocks, { store: 'untyped', operation: 'set', encrypted: false }, true);
+        });
+        it('should throw an error if the storage API returns successful = false', async () => {
+            const apiClientMock = getApiClientMock({
+                data: {
+                    appStorage: {
+                        setAppStoredEntity: {
+                            success: false,
+                            errors: [INVALID_CURSOR_ERROR]
                         }
                     }
-                });
-                const globalStorage = getStorage(apiClientMock);
-                await expect(globalStorage.set('testKey', 'testValue')).rejects.toThrow(errors_1.APIError.forStatus(500));
-                verifyApiClientCalledWith(apiClientMock, {
-                    input: {
-                        contextAri,
-                        key: 'testKey',
-                        value: 'testValue',
-                        encrypted: false
-                    }
-                });
+                }
             });
+            const metricMocks = getMetricMock();
+            const globalStorage = getStorage(apiClientMock, metricMocks.mockMetrics);
+            const response = globalStorage.set('testKey', 'testValue');
+            expect(apiClientMock).toHaveBeenCalled();
+            await expect(response).rejects.toThrow(errors_1.APIError.forErrorCode('INVALID_CURSOR', 'error message'));
+            checkMetricsFired(metricMocks, { store: 'untyped', operation: 'set', encrypted: false }, false);
         });
-        describe('delete', () => {
-            it('should call the storage API, passing the provided key', async () => {
-                const apiClientMock = getApiClientMock({
-                    data: {
-                        appStorage: {
-                            deleteAppStoredEntity: {
-                                success: true
-                            }
+        it('should throw an error if the storage API returns a non 200 status code', async () => {
+            const apiClientMock = getApiClientMockInvalidJson('', 400);
+            const metricMocks = getMetricMock();
+            const globalStorage = getStorage(apiClientMock, metricMocks.mockMetrics);
+            const response = globalStorage.set('testKey', 'testValue');
+            expect(apiClientMock).toHaveBeenCalled();
+            await expect(response).rejects.toThrow(errors_1.APIError.forStatus(400));
+            checkMetricsFired(metricMocks, { store: 'untyped', operation: 'set', encrypted: false }, false);
+        });
+        it('should throw a 500 error if success=false but no errors were returned', async () => {
+            const apiClientMock = getApiClientMock({
+                data: {
+                    appStorage: {
+                        setAppStoredEntity: {
+                            success: false
                         }
                     }
-                });
-                const globalStorage = getStorage(apiClientMock);
-                await globalStorage.delete('testKey');
-                verifyApiClientCalledWith(apiClientMock, {
-                    input: {
-                        contextAri,
-                        key: 'testKey',
-                        encrypted: false
-                    }
-                });
+                }
             });
-            it('should throw an error if the storage API returns successful = false', async () => {
-                const apiClientMock = getApiClientMock({
-                    data: {
-                        appStorage: {
-                            deleteAppStoredEntity: {
-                                success: false,
-                                errors: [INVALID_CURSOR_ERROR]
-                            }
+            const metricMocks = getMetricMock();
+            const globalStorage = getStorage(apiClientMock, metricMocks.mockMetrics);
+            await expect(globalStorage.set('testKey', 'testValue')).rejects.toThrow(errors_1.APIError.forStatus(500));
+            verifyApiClientCalledWith(apiClientMock, {
+                input: {
+                    contextAri,
+                    key: 'testKey',
+                    value: 'testValue',
+                    encrypted: false
+                }
+            });
+            checkMetricsFired(metricMocks, { store: 'untyped', operation: 'set', encrypted: false }, false);
+        });
+    });
+    describe('set secret', () => {
+        it('should call the storage API, passing the provided key and value', async () => {
+            const apiClientMock = getApiClientMock({
+                data: {
+                    appStorage: {
+                        setAppStoredEntity: {
+                            success: true
                         }
                     }
-                });
-                const globalStorage = getStorage(apiClientMock);
-                const response = globalStorage.delete('testKey');
-                expect(apiClientMock).toHaveBeenCalled();
-                await expect(response).rejects.toThrow(errors_1.APIError.forErrorCode('CURSOR_INVALID', 'error message'));
+                }
             });
-            it('should throw an error if the storage API returns a non 200 status code and has no body', async () => {
-                const apiClientMock = getApiClientMockInvalidJson('', 400);
-                const globalStorage = getStorage(apiClientMock);
-                const response = globalStorage.delete('testKey');
-                expect(apiClientMock).toHaveBeenCalled();
-                await expect(response).rejects.toThrow(errors_1.APIError.forStatus(400));
+            const metricMocks = getMetricMock();
+            const globalStorage = getStorage(apiClientMock, metricMocks.mockMetrics);
+            await globalStorage.setSecret('testKey', 'testValue');
+            verifyApiClientCalledWith(apiClientMock, {
+                input: {
+                    contextAri,
+                    key: 'testKey',
+                    value: 'testValue',
+                    encrypted: true
+                }
             });
+            checkMetricsFired(metricMocks, { store: 'untyped', operation: 'set', encrypted: true }, true);
         });
-        describe('bulkSet', () => {
-            it('should call the storage API for bulkSet and return savedKeys', async () => {
-                const apiClientMock = getApiClientMock({
-                    data: {
-                        appStorage: {
-                            setAppStoredEntities: {
-                                success: true,
-                                savedKeys: ['testKey'],
-                                failedKeys: [
-                                    {
-                                        key: 'testKey2',
-                                        code: 'KEY_TOO_LARGE',
-                                        message: 'The provided key exceeds maximum allowed length'
-                                    }
-                                ]
-                            }
+    });
+    describe('delete', () => {
+        it('should call the storage API, passing the provided key', async () => {
+            const apiClientMock = getApiClientMock({
+                data: {
+                    appStorage: {
+                        deleteAppStoredEntity: {
+                            success: true
                         }
                     }
-                });
-                const globalStorage = getStorage(apiClientMock);
-                const response = await globalStorage.bulkSet([
-                    {
-                        key: 'testKey',
-                        value: 'testValue'
-                    },
-                    {
-                        key: 'testKey2',
-                        value: 'testValue2'
-                    }
-                ]);
-                verifyApiClientCalledWith(apiClientMock, {
-                    input: {
-                        contextAri,
-                        entities: [
-                            {
-                                key: 'testKey',
-                                value: 'testValue'
-                            },
-                            {
-                                key: 'testKey2',
-                                value: 'testValue2'
-                            }
-                        ],
-                        encrypted: false
-                    }
-                });
-                expect(response).toHaveProperty('savedKeys', ['testKey']);
-                expect(response).toHaveProperty('failedKeys', [
-                    { key: 'testKey2', code: 'KEY_TOO_LARGE', message: 'The provided key exceeds maximum allowed length' }
-                ]);
+                }
             });
-            it('should throw an error if the storage API returns successful = false', async () => {
-                const apiClientMock = getApiClientMock({
-                    data: {
-                        appStorage: {
-                            setAppStoredEntities: {
-                                success: false,
-                                errors: [INVALID_CURSOR_ERROR]
-                            }
+            const metricMocks = getMetricMock();
+            const globalStorage = getStorage(apiClientMock, metricMocks.mockMetrics);
+            await globalStorage.delete('testKey');
+            verifyApiClientCalledWith(apiClientMock, {
+                input: {
+                    contextAri,
+                    key: 'testKey',
+                    encrypted: false
+                }
+            });
+            checkMetricsFired(metricMocks, { store: 'untyped', operation: 'delete', encrypted: false }, true);
+        });
+        it('should throw an error if the storage API returns successful = false', async () => {
+            const apiClientMock = getApiClientMock({
+                data: {
+                    appStorage: {
+                        deleteAppStoredEntity: {
+                            success: false,
+                            errors: [INVALID_CURSOR_ERROR]
                         }
                     }
-                });
-                const globalStorage = getStorage(apiClientMock);
-                const response = globalStorage.bulkSet([
-                    {
-                        key: 'testKey',
-                        value: 'testValue'
-                    }
-                ]);
-                expect(apiClientMock).toHaveBeenCalled();
-                await expect(response).rejects.toThrow(errors_1.APIError.forErrorCode('INVALID_CURSOR', 'error message'));
+                }
             });
+            const metricMocks = getMetricMock();
+            const globalStorage = getStorage(apiClientMock, metricMocks.mockMetrics);
+            const response = globalStorage.delete('testKey');
+            expect(apiClientMock).toHaveBeenCalled();
+            await expect(response).rejects.toThrow(errors_1.APIError.forErrorCode('CURSOR_INVALID', 'error message'));
+            checkMetricsFired(metricMocks, { store: 'untyped', operation: 'delete', encrypted: false }, false);
         });
+        it('should throw an error if the storage API returns a non 200 status code and has no body', async () => {
+            const apiClientMock = getApiClientMockInvalidJson('', 400);
+            const metricMocks = getMetricMock();
+            const globalStorage = getStorage(apiClientMock, metricMocks.mockMetrics);
+            const response = globalStorage.delete('testKey');
+            expect(apiClientMock).toHaveBeenCalled();
+            await expect(response).rejects.toThrow(errors_1.APIError.forStatus(400));
+            checkMetricsFired(metricMocks, { store: 'untyped', operation: 'delete', encrypted: false }, false);
+        });
     });
-    describe('Secret storage', () => {
-        describe('get secret', () => {
-            it('should call the storage API, passing the provided key and returning the stored value', async () => {
-                const apiClientMock = getApiClientMock({
-                    data: {
-                        appStoredEntity: {
-                            value: 'testValue'
+    describe('delete secret', () => {
+        it('should call the storage API, passing the provided key', async () => {
+            const apiClientMock = getApiClientMock({
+                data: {
+                    appStorage: {
+                        deleteAppStoredEntity: {
+                            success: true
                         }
                     }
-                });
-                const globalStorage = getStorage(apiClientMock);
-                const returnedValue = await globalStorage.getSecret('testKey');
-                verifyApiClientCalledWith(apiClientMock, {
+                }
+            });
+            const metricMocks = getMetricMock();
+            const globalStorage = getStorage(apiClientMock, metricMocks.mockMetrics);
+            await globalStorage.deleteSecret('testKey');
+            verifyApiClientCalledWith(apiClientMock, {
+                input: {
                     contextAri,
                     key: 'testKey',
                     encrypted: true
-                });
-                expect(returnedValue).toEqual('testValue');
+                }
             });
+            checkMetricsFired(metricMocks, { store: 'untyped', operation: 'delete', encrypted: true }, true);
         });
-        describe('set secret', () => {
-            it('should call the storage API, passing the provided key and value', async () => {
-                const apiClientMock = getApiClientMock({
-                    data: {
-                        appStorage: {
-                            setAppStoredEntity: {
-                                success: true
-                            }
-                        }
+    });
+    describe('getEntity', () => {
+        it('should call the storage API, passing the provided entity name and entity key and returning the stored value', async () => {
+            const apiClientMock = getApiClientMock({
+                data: {
+                    appStoredCustomEntity: {
+                        value: 'testValue'
                     }
-                });
-                const globalStorage = getStorage(apiClientMock);
-                await globalStorage.setSecret('testKey', 'testValue');
-                verifyApiClientCalledWith(apiClientMock, {
-                    input: {
-                        contextAri,
-                        key: 'testKey',
-                        value: 'testValue',
-                        encrypted: true
+                }
+            });
+            const metricMocks = getMetricMock();
+            const globalStorage = getStorage(apiClientMock, metricMocks.mockMetrics);
+            const returnedValue = await globalStorage.getEntity('testEntityName', 'testEntityKey');
+            verifyApiClientCalledWith(apiClientMock, {
+                contextAri,
+                entityName: 'testEntityName',
+                key: 'testEntityKey'
+            });
+            expect(returnedValue).toEqual('testValue');
+            checkMetricsFired(metricMocks, { store: 'typed', operation: 'get', encrypted: false }, true);
+        });
+        it('should call the storage API, passing the provided entity key and returning undefined if the key doesnt exist', async () => {
+            const apiClientMock = getApiClientMock({
+                data: {
+                    appStoredCustomEntity: {
+                        value: null
                     }
-                });
+                }
             });
+            const metricMocks = getMetricMock();
+            const globalStorage = getStorage(apiClientMock, metricMocks.mockMetrics);
+            const returnedValue = await globalStorage.getEntity('testEntityName', 'testEntityKey');
+            verifyApiClientCalledWith(apiClientMock, {
+                contextAri,
+                entityName: 'testEntityName',
+                key: 'testEntityKey'
+            });
+            expect(returnedValue).toEqual(undefined);
+            checkMetricsFired(metricMocks, { store: 'typed', operation: 'get', encrypted: false }, true);
         });
-        describe('delete secret', () => {
-            it('should call the storage API, passing the provided key', async () => {
-                const apiClientMock = getApiClientMock({
-                    data: {
-                        appStorage: {
-                            deleteAppStoredEntity: {
-                                success: true
-                            }
-                        }
+        it('should call the storage API, passing the provided key and returning the stored falsey value 0', async () => {
+            const apiClientMock = getApiClientMock({
+                data: {
+                    appStoredCustomEntity: {
+                        value: 0
                     }
-                });
-                const globalStorage = getStorage(apiClientMock);
-                await globalStorage.deleteSecret('testKey');
-                verifyApiClientCalledWith(apiClientMock, {
-                    input: {
-                        contextAri,
-                        key: 'testKey',
-                        encrypted: true
+                }
+            });
+            const metricMocks = getMetricMock();
+            const globalStorage = getStorage(apiClientMock, metricMocks.mockMetrics);
+            const returnedValue = await globalStorage.getEntity('testEntityName', 'testEntityKey');
+            verifyApiClientCalledWith(apiClientMock, {
+                contextAri,
+                entityName: 'testEntityName',
+                key: 'testEntityKey'
+            });
+            expect(returnedValue).toEqual(0);
+            checkMetricsFired(metricMocks, { store: 'typed', operation: 'get', encrypted: false }, true);
+        });
+        it('should call the storage API, passing the provided key and returning the stored empty string', async () => {
+            const apiClientMock = getApiClientMock({
+                data: {
+                    appStoredCustomEntity: {
+                        value: ''
                     }
-                });
+                }
             });
+            const metricMocks = getMetricMock();
+            const globalStorage = getStorage(apiClientMock, metricMocks.mockMetrics);
+            const returnedValue = await globalStorage.getEntity('testEntityName', 'testEntityKey');
+            verifyApiClientCalledWith(apiClientMock, {
+                contextAri,
+                entityName: 'testEntityName',
+                key: 'testEntityKey'
+            });
+            expect(returnedValue).toEqual('');
+            checkMetricsFired(metricMocks, { store: 'typed', operation: 'get', encrypted: false }, true);
         });
+        it('should throw an error with the returned status for non-200 status codes', async () => {
+            const apiClientMock = getApiClientMock(undefined, 400);
+            const metricMocks = getMetricMock();
+            const globalStorage = getStorage(apiClientMock, metricMocks.mockMetrics);
+            const response = globalStorage.getEntity('testEntityName', 'testEntityKey');
+            expect(apiClientMock).toHaveBeenCalled();
+            await expect(response).rejects.toThrow(errors_1.APIError.forStatus(400));
+            checkMetricsFired(metricMocks, { store: 'typed', operation: 'get', encrypted: false }, false);
+        });
+        it('should throw an error with the returned error message for failed responses', async () => {
+            const apiClientMock = getApiClientMock({
+                errors: [INVALID_CURSOR_ERROR]
+            }, 200);
+            const metricMocks = getMetricMock();
+            const globalStorage = getStorage(apiClientMock, metricMocks.mockMetrics);
+            const response = globalStorage.getEntity('testEntityName', 'testEntityKey');
+            expect(apiClientMock).toHaveBeenCalled();
+            await expect(response).rejects.toThrow(errors_1.APIError.forErrorCode('CURSOR_INVALID', 'error message'));
+            checkMetricsFired(metricMocks, { store: 'typed', operation: 'get', encrypted: false }, false);
+        });
+        it('should throw an error if the response is not a valid JSON', async () => {
+            const apiClientMock = getApiClientMockInvalidJson('test', 200);
+            const metricMocks = getMetricMock();
+            const globalStorage = getStorage(apiClientMock, metricMocks.mockMetrics);
+            const response = globalStorage.getEntity('testEntityName', 'testEntityKey');
+            expect(apiClientMock).toHaveBeenCalled();
+            await expect(response).rejects.toThrow(errors_1.APIError.forUnexpected('Response text was not a valid JSON: test'));
+            checkMetricsFired(metricMocks, { store: 'typed', operation: 'get', encrypted: false }, false);
+        });
     });
-    describe('Custom entities', () => {
-        describe('getEntity', () => {
-            it('should call the storage API, passing the provided entity name and entity key and returning the stored value', async () => {
-                const apiClientMock = getApiClientMock({
-                    data: {
-                        appStoredCustomEntity: {
-                            value: 'testValue'
+    describe('setEntity', () => {
+        it('should call the storage API, passing the provided entity name, entity key and value', async () => {
+            const apiClientMock = getApiClientMock({
+                data: {
+                    appStorageCustomEntity: {
+                        setAppStoredCustomEntity: {
+                            success: true
                         }
                     }
-                });
-                const globalStorage = getStorage(apiClientMock);
-                const returnedValue = await globalStorage.getEntity('testEntityName', 'testEntityKey');
-                verifyApiClientCalledWith(apiClientMock, {
+                }
+            });
+            const metricMocks = getMetricMock();
+            const globalStorage = getStorage(apiClientMock, metricMocks.mockMetrics);
+            await globalStorage.setEntity('testEntityName', 'testEntityKey', 'testValue');
+            verifyApiClientCalledWith(apiClientMock, {
+                input: {
                     contextAri,
                     entityName: 'testEntityName',
-                    key: 'testEntityKey'
-                });
-                expect(returnedValue).toEqual('testValue');
+                    key: 'testEntityKey',
+                    value: 'testValue'
+                }
             });
-            it('should call the storage API, passing the provided entity key and returning undefined if the key doesnt exist', async () => {
-                const apiClientMock = getApiClientMock({
-                    data: {
-                        appStoredCustomEntity: {
-                            value: null
+            checkMetricsFired(metricMocks, { store: 'typed', operation: 'set', encrypted: false }, true);
+        });
+        it('should throw an error if the storage API returns successful = false', async () => {
+            const apiClientMock = getApiClientMock({
+                data: {
+                    appStorageCustomEntity: {
+                        setAppStoredCustomEntity: {
+                            success: false,
+                            errors: [INVALID_CURSOR_ERROR]
                         }
                     }
-                });
-                const globalStorage = getStorage(apiClientMock);
-                const returnedValue = await globalStorage.getEntity('testEntityName', 'testEntityKey');
-                verifyApiClientCalledWith(apiClientMock, {
-                    contextAri,
-                    entityName: 'testEntityName',
-                    key: 'testEntityKey'
-                });
-                expect(returnedValue).toEqual(undefined);
+                }
             });
-            it('should call the storage API, passing the provided key and returning the stored falsey value 0', async () => {
-                const apiClientMock = getApiClientMock({
-                    data: {
-                        appStoredCustomEntity: {
-                            value: 0
+            const metricMocks = getMetricMock();
+            const globalStorage = getStorage(apiClientMock, metricMocks.mockMetrics);
+            const response = globalStorage.setEntity('testEntityName', 'testEntityKey', 'testValue');
+            expect(apiClientMock).toHaveBeenCalled();
+            await expect(response).rejects.toThrow(errors_1.APIError.forErrorCode('INVALID_CURSOR', 'error message'));
+            checkMetricsFired(metricMocks, { store: 'typed', operation: 'set', encrypted: false }, false);
+        });
+        it('should throw an error if the storage API returns a non 200 status code', async () => {
+            const apiClientMock = getApiClientMockInvalidJson('', 400);
+            const metricMocks = getMetricMock();
+            const globalStorage = getStorage(apiClientMock, metricMocks.mockMetrics);
+            const response = globalStorage.setEntity('testEntityName', 'testEntityKey', 'testValue');
+            expect(apiClientMock).toHaveBeenCalled();
+            await expect(response).rejects.toThrow(errors_1.APIError.forStatus(400));
+            checkMetricsFired(metricMocks, { store: 'typed', operation: 'set', encrypted: false }, false);
+        });
+        it('should throw a 500 error if success=false but no errors were returned', async () => {
+            const apiClientMock = getApiClientMock({
+                data: {
+                    appStorageCustomEntity: {
+                        setAppStoredCustomEntity: {
+                            success: false
                         }
                     }
-                });
-                const globalStorage = getStorage(apiClientMock);
-                const returnedValue = await globalStorage.getEntity('testEntityName', 'testEntityKey');
-                verifyApiClientCalledWith(apiClientMock, {
+                }
+            });
+            const metricMocks = getMetricMock();
+            const globalStorage = getStorage(apiClientMock, metricMocks.mockMetrics);
+            await expect(globalStorage.setEntity('testEntityName', 'testEntityKey', 'testValue')).rejects.toThrow(errors_1.APIError.forStatus(500));
+            verifyApiClientCalledWith(apiClientMock, {
+                input: {
                     contextAri,
                     entityName: 'testEntityName',
-                    key: 'testEntityKey'
-                });
-                expect(returnedValue).toEqual(0);
+                    key: 'testEntityKey',
+                    value: 'testValue'
+                }
             });
-            it('should call the storage API, passing the provided key and returning the stored empty string', async () => {
-                const apiClientMock = getApiClientMock({
-                    data: {
-                        appStoredCustomEntity: {
-                            value: ''
+            checkMetricsFired(metricMocks, { store: 'typed', operation: 'set', encrypted: false }, false);
+        });
+    });
+    describe('deleteEntity', () => {
+        it('should call the storage API, passing the provided entity name and key', async () => {
+            const apiClientMock = getApiClientMock({
+                data: {
+                    appStorageCustomEntity: {
+                        deleteAppStoredCustomEntity: {
+                            success: true
                         }
                     }
-                });
-                const globalStorage = getStorage(apiClientMock);
-                const returnedValue = await globalStorage.getEntity('testEntityName', 'testEntityKey');
-                verifyApiClientCalledWith(apiClientMock, {
+                }
+            });
+            const metricMocks = getMetricMock();
+            const globalStorage = getStorage(apiClientMock, metricMocks.mockMetrics);
+            await globalStorage.deleteEntity('testEntityName', 'testEntityKey');
+            verifyApiClientCalledWith(apiClientMock, {
+                input: {
                     contextAri,
                     entityName: 'testEntityName',
                     key: 'testEntityKey'
-                });
-                expect(returnedValue).toEqual('');
+                }
             });
-            it('should throw an error with the returned status for non-200 status codes', async () => {
-                const apiClientMock = getApiClientMock(undefined, 400);
-                const globalStorage = getStorage(apiClientMock);
-                const response = globalStorage.getEntity('testEntityName', 'testEntityKey');
-                expect(apiClientMock).toHaveBeenCalled();
-                await expect(response).rejects.toThrow(errors_1.APIError.forStatus(400));
-            });
-            it('should throw an error with the returned error message for failed responses', async () => {
-                const apiClientMock = getApiClientMock({
-                    errors: [INVALID_CURSOR_ERROR]
-                }, 200);
-                const globalStorage = getStorage(apiClientMock);
-                const response = globalStorage.getEntity('testEntityName', 'testEntityKey');
-                expect(apiClientMock).toHaveBeenCalled();
-                await expect(response).rejects.toThrow(errors_1.APIError.forErrorCode('CURSOR_INVALID', 'error message'));
-            });
-            it('should throw an error if the response is not a valid JSON', async () => {
-                const apiClientMock = getApiClientMockInvalidJson('test', 200);
-                const globalStorage = getStorage(apiClientMock);
-                const response = globalStorage.getEntity('testEntityName', 'testEntityKey');
-                expect(apiClientMock).toHaveBeenCalled();
-                await expect(response).rejects.toThrow(errors_1.APIError.forUnexpected('Response text was not a valid JSON: test'));
-            });
+            checkMetricsFired(metricMocks, { store: 'typed', operation: 'delete', encrypted: false }, true);
         });
-        describe('setEntity', () => {
-            it('should call the storage API, passing the provided entity name, entity key and value', async () => {
-                const apiClientMock = getApiClientMock({
-                    data: {
-                        appStorageCustomEntity: {
-                            setAppStoredCustomEntity: {
-                                success: true
-                            }
+        it('should throw an error if the storage API returns successful = false', async () => {
+            const apiClientMock = getApiClientMock({
+                data: {
+                    appStorageCustomEntity: {
+                        deleteAppStoredCustomEntity: {
+                            success: false,
+                            errors: [INVALID_CURSOR_ERROR]
                         }
                     }
-                });
-                const globalStorage = getStorage(apiClientMock);
-                await globalStorage.setEntity('testEntityName', 'testEntityKey', 'testValue');
-                verifyApiClientCalledWith(apiClientMock, {
-                    input: {
-                        contextAri,
-                        entityName: 'testEntityName',
-                        key: 'testEntityKey',
-                        value: 'testValue'
-                    }
-                });
+                }
             });
-            it('should throw an error if the storage API returns successful = false', async () => {
-                const apiClientMock = getApiClientMock({
-                    data: {
-                        appStorageCustomEntity: {
-                            setAppStoredCustomEntity: {
-                                success: false,
-                                errors: [INVALID_CURSOR_ERROR]
-                            }
-                        }
+            const metricMocks = getMetricMock();
+            const globalStorage = getStorage(apiClientMock, metricMocks.mockMetrics);
+            const response = globalStorage.deleteEntity('testEntityKey', 'testEntityKey');
+            expect(apiClientMock).toHaveBeenCalled();
+            await expect(response).rejects.toThrow(errors_1.APIError.forErrorCode('CURSOR_INVALID', 'error message'));
+            checkMetricsFired(metricMocks, { store: 'typed', operation: 'delete', encrypted: false }, false);
+        });
+        it('should throw an error if the storage API returns a non 200 status code and has no body', async () => {
+            const apiClientMock = getApiClientMockInvalidJson('', 400);
+            const metricMocks = getMetricMock();
+            const globalStorage = getStorage(apiClientMock, metricMocks.mockMetrics);
+            const response = globalStorage.deleteEntity('testEntityKey', 'testEntityKey');
+            expect(apiClientMock).toHaveBeenCalled();
+            await expect(response).rejects.toThrow(errors_1.APIError.forStatus(400));
+            checkMetricsFired(metricMocks, { store: 'typed', operation: 'delete', encrypted: false }, false);
+        });
+    });
+    describe('list', () => {
+        it('should call the storage API with the provided parameters', async () => {
+            const apiClientMock = getApiClientMock({
+                data: {
+                    appStoredEntities: {
+                        edges: [
+                            { node: { key: 'key1', value: 'testValue' }, cursor: 'cursor1' },
+                            { node: { key: 'key2', value: 'testValue' }, cursor: 'cursor2' }
+                        ]
                     }
-                });
-                const globalStorage = getStorage(apiClientMock);
-                const response = globalStorage.setEntity('testEntityName', 'testEntityKey', 'testValue');
-                expect(apiClientMock).toHaveBeenCalled();
-                await expect(response).rejects.toThrow(errors_1.APIError.forErrorCode('INVALID_CURSOR', 'error message'));
+                }
             });
-            it('should throw an error if the storage API returns a non 200 status code', async () => {
-                const apiClientMock = getApiClientMockInvalidJson('', 400);
-                const globalStorage = getStorage(apiClientMock);
-                const response = globalStorage.setEntity('testEntityName', 'testEntityKey', 'testValue');
-                expect(apiClientMock).toHaveBeenCalled();
-                await expect(response).rejects.toThrow(errors_1.APIError.forStatus(400));
-            });
-            it('should throw a 500 error if success=false but no errors were returned', async () => {
-                const apiClientMock = getApiClientMock({
-                    data: {
-                        appStorageCustomEntity: {
-                            setAppStoredCustomEntity: {
-                                success: false
-                            }
-                        }
+            const metricMocks = getMetricMock();
+            const globalStorage = getStorage(apiClientMock, metricMocks.mockMetrics);
+            const where = [
+                {
+                    field: 'key',
+                    condition: 'STARTS_WITH',
+                    value: 'test'
+                }
+            ];
+            const cursor = 'cursor';
+            const limit = 10;
+            const response = await globalStorage.list({ where, cursor, limit });
+            verifyApiClientCalledWith(apiClientMock, {
+                contextAri,
+                where,
+                cursor,
+                limit
+            }, gql_queries_1.UntypedQueries.listQuery(contextAri, {}).query);
+            expect(response).toEqual(expect.objectContaining({
+                results: [
+                    { key: 'key1', value: 'testValue' },
+                    { key: 'key2', value: 'testValue' }
+                ],
+                nextCursor: 'cursor2'
+            }));
+            checkMetricsFired(metricMocks, { store: 'untyped', operation: 'query', encrypted: false }, true);
+        });
+        it('should query the appStoredEntitiesForCleanup endpoint given process.env.IS_CLEANUP_FUNCTION is set to true', async () => {
+            process.env.IS_CLEANUP_FUNCTION = 'true';
+            const apiClientMock = getApiClientMock({
+                data: {
+                    appStoredEntitiesForCleanup: {
+                        edges: [
+                            { node: { key: 'key1', value: 'testValue' }, cursor: 'cursor1' },
+                            { node: { key: 'key2', value: 'testValue' }, cursor: 'cursor2' }
+                        ]
                     }
-                });
-                const globalStorage = getStorage(apiClientMock);
-                await expect(globalStorage.setEntity('testEntityName', 'testEntityKey', 'testValue')).rejects.toThrow(errors_1.APIError.forStatus(500));
-                verifyApiClientCalledWith(apiClientMock, {
-                    input: {
-                        contextAri,
-                        entityName: 'testEntityName',
-                        key: 'testEntityKey',
-                        value: 'testValue'
-                    }
-                });
+                }
             });
+            const metricMocks = getMetricMock();
+            const globalStorage = getStorage(apiClientMock, metricMocks.mockMetrics);
+            const where = [
+                {
+                    field: 'key',
+                    condition: 'STARTS_WITH',
+                    value: 'test'
+                }
+            ];
+            const cursor = 'cursor';
+            const limit = 10;
+            const response = await globalStorage.list({ where, cursor, limit });
+            verifyApiClientCalledWith(apiClientMock, {
+                contextAri,
+                where,
+                cursor,
+                limit
+            }, gql_queries_1.UntypedQueries.listQueryForCleanup(contextAri, {}).query);
+            expect(response).toEqual(expect.objectContaining({
+                results: [
+                    { key: 'key1', value: 'testValue' },
+                    { key: 'key2', value: 'testValue' }
+                ],
+                nextCursor: 'cursor2'
+            }));
+            checkMetricsFired(metricMocks, { store: 'untyped', operation: 'query', encrypted: false }, true);
+            process.env.IS_CLEANUP_FUNCTION = '';
         });
-        describe('deleteEntity', () => {
-            it('should call the storage API, passing the provided entity name and key', async () => {
-                const apiClientMock = getApiClientMock({
-                    data: {
-                        appStorageCustomEntity: {
-                            deleteAppStoredCustomEntity: {
-                                success: true
-                            }
-                        }
+        it('should use default values', async () => {
+            const apiClientMock = getApiClientMock({
+                data: {
+                    appStoredEntities: {
+                        edges: []
                     }
-                });
-                const globalStorage = getStorage(apiClientMock);
-                await globalStorage.deleteEntity('testEntityName', 'testEntityKey');
-                verifyApiClientCalledWith(apiClientMock, {
-                    input: {
-                        contextAri,
-                        entityName: 'testEntityName',
-                        key: 'testEntityKey'
-                    }
-                });
+                }
             });
-            it('should throw an error if the storage API returns successful = false', async () => {
-                const apiClientMock = getApiClientMock({
-                    data: {
-                        appStorageCustomEntity: {
-                            deleteAppStoredCustomEntity: {
-                                success: false,
-                                errors: [INVALID_CURSOR_ERROR]
-                            }
-                        }
+            const metricMocks = getMetricMock();
+            const globalStorage = getStorage(apiClientMock, metricMocks.mockMetrics);
+            await globalStorage.list({});
+            verifyApiClientCalledWith(apiClientMock, {
+                contextAri,
+                where: null,
+                cursor: null,
+                limit: null
+            }, gql_queries_1.UntypedQueries.listQuery(contextAri, {}).query);
+            checkMetricsFired(metricMocks, { store: 'untyped', operation: 'query', encrypted: false }, true);
+        });
+        it('should handle an empty result set', async () => {
+            const apiClientMock = getApiClientMock({
+                data: {
+                    appStoredEntities: {
+                        edges: []
                     }
-                });
-                const globalStorage = getStorage(apiClientMock);
-                const response = globalStorage.deleteEntity('testEntityKey', 'testEntityKey');
-                expect(apiClientMock).toHaveBeenCalled();
-                await expect(response).rejects.toThrow(errors_1.APIError.forErrorCode('CURSOR_INVALID', 'error message'));
+                }
             });
-            it('should throw an error if the storage API returns a non 200 status code and has no body', async () => {
-                const apiClientMock = getApiClientMockInvalidJson('', 400);
-                const globalStorage = getStorage(apiClientMock);
-                const response = globalStorage.deleteEntity('testEntityKey', 'testEntityKey');
-                expect(apiClientMock).toHaveBeenCalled();
-                await expect(response).rejects.toThrow(errors_1.APIError.forStatus(400));
-            });
+            const metricMocks = getMetricMock();
+            const globalStorage = getStorage(apiClientMock, metricMocks.mockMetrics);
+            const where = [
+                {
+                    field: 'key',
+                    condition: 'STARTS_WITH',
+                    value: 'test'
+                }
+            ];
+            const response = await globalStorage.list({ where });
+            expect(response).toEqual(expect.objectContaining({
+                results: [],
+                nextCursor: undefined
+            }));
+            checkMetricsFired(metricMocks, { store: 'untyped', operation: 'query', encrypted: false }, true);
         });
-        describe('list', () => {
-            it('should call the storage API with the provided parameters', async () => {
-                const apiClientMock = getApiClientMock({
-                    data: {
-                        appStoredEntities: {
-                            edges: [
-                                { node: { key: 'key1', value: 'testValue' }, cursor: 'cursor1' },
-                                { node: { key: 'key2', value: 'testValue' }, cursor: 'cursor2' }
-                            ]
-                        }
-                    }
-                });
-                const globalStorage = getStorage(apiClientMock);
-                const where = [
-                    {
-                        field: 'key',
-                        condition: 'STARTS_WITH',
-                        value: 'test'
-                    }
-                ];
-                const cursor = 'cursor';
-                const limit = 10;
-                const response = await globalStorage.list({ where, cursor, limit });
-                verifyApiClientCalledWith(apiClientMock, {
-                    contextAri,
-                    where,
-                    cursor,
-                    limit
-                }, gql_queries_1.UntypedQueries.listQuery(contextAri, {}).query);
-                expect(response).toEqual(expect.objectContaining({
-                    results: [
-                        { key: 'key1', value: 'testValue' },
-                        { key: 'key2', value: 'testValue' }
-                    ],
-                    nextCursor: 'cursor2'
-                }));
+        it('should throw an error if the storage API returns an error', async () => {
+            const apiClientMock = getApiClientMock({
+                errors: [INVALID_CURSOR_ERROR]
             });
-            it('should query the appStoredEntitiesForCleanup endpoint given process.env.IS_CLEANUP_FUNCTION is set to true', async () => {
-                process.env.IS_CLEANUP_FUNCTION = 'true';
-                const apiClientMock = getApiClientMock({
-                    data: {
-                        appStoredEntitiesForCleanup: {
-                            edges: [
-                                { node: { key: 'key1', value: 'testValue' }, cursor: 'cursor1' },
-                                { node: { key: 'key2', value: 'testValue' }, cursor: 'cursor2' }
-                            ]
-                        }
-                    }
-                });
-                const globalStorage = getStorage(apiClientMock);
-                const where = [
-                    {
-                        field: 'key',
-                        condition: 'STARTS_WITH',
-                        value: 'test'
-                    }
-                ];
-                const cursor = 'cursor';
-                const limit = 10;
-                const response = await globalStorage.list({ where, cursor, limit });
-                verifyApiClientCalledWith(apiClientMock, {
-                    contextAri,
-                    where,
-                    cursor,
-                    limit
-                }, gql_queries_1.UntypedQueries.listQueryForCleanup(contextAri, {}).query);
-                expect(response).toEqual(expect.objectContaining({
-                    results: [
-                        { key: 'key1', value: 'testValue' },
-                        { key: 'key2', value: 'testValue' }
-                    ],
-                    nextCursor: 'cursor2'
-                }));
-                process.env.IS_CLEANUP_FUNCTION = '';
-            });
-            it('should use default values', async () => {
-                const apiClientMock = getApiClientMock({
-                    data: {
-                        appStoredEntities: {
-                            edges: []
-                        }
-                    }
-                });
-                const globalStorage = getStorage(apiClientMock);
-                await globalStorage.list({});
-                verifyApiClientCalledWith(apiClientMock, {
-                    contextAri,
-                    where: null,
-                    cursor: null,
-                    limit: null
-                }, gql_queries_1.UntypedQueries.listQuery(contextAri, {}).query);
-            });
-            it('should handle an empty result set', async () => {
-                const apiClientMock = getApiClientMock({
-                    data: {
-                        appStoredEntities: {
-                            edges: []
-                        }
-                    }
-                });
-                const globalStorage = getStorage(apiClientMock);
-                const where = [
-                    {
-                        field: 'key',
-                        condition: 'STARTS_WITH',
-                        value: 'test'
-                    }
-                ];
-                const response = await globalStorage.list({ where });
-                expect(response).toEqual(expect.objectContaining({
-                    results: [],
-                    nextCursor: undefined
-                }));
-            });
-            it('should throw an error if the storage API returns an error', async () => {
-                const apiClientMock = getApiClientMock({
-                    errors: [INVALID_CURSOR_ERROR]
-                });
-                const globalStorage = getStorage(apiClientMock);
-                const response = globalStorage.list({});
-                expect(apiClientMock).toHaveBeenCalled();
-                await expect(response).rejects.toThrow(errors_1.APIError.forErrorCode('CURSOR_INVALID', 'error message'));
-            });
-            it('should throw an error if the storage API returns a non 200 status code and has no body', async () => {
-                const apiClientMock = getApiClientMockInvalidJson('', 400);
-                const globalStorage = getStorage(apiClientMock);
-                const response = globalStorage.list({});
-                expect(apiClientMock).toHaveBeenCalled();
-                await expect(response).rejects.toThrow(errors_1.APIError.forStatus(400));
-            });
+            const metricMocks = getMetricMock();
+            const globalStorage = getStorage(apiClientMock, metricMocks.mockMetrics);
+            const response = globalStorage.list({});
+            expect(apiClientMock).toHaveBeenCalled();
+            await expect(response).rejects.toThrow(errors_1.APIError.forErrorCode('CURSOR_INVALID', 'error message'));
+            checkMetricsFired(metricMocks, { store: 'untyped', operation: 'query', encrypted: false }, false);
         });
+        it('should throw an error if the storage API returns a non 200 status code and has no body', async () => {
+            const apiClientMock = getApiClientMockInvalidJson('', 400);
+            const metricMocks = getMetricMock();
+            const globalStorage = getStorage(apiClientMock, metricMocks.mockMetrics);
+            const response = globalStorage.list({});
+            expect(apiClientMock).toHaveBeenCalled();
+            await expect(response).rejects.toThrow(errors_1.APIError.forStatus(400));
+            checkMetricsFired(metricMocks, { store: 'untyped', operation: 'query', encrypted: false }, false);
+        });
     });
     describe('listCustomEntities', () => {
         it('should use default values', async () => {
             const apiClientMock = getApiClientMock({
@@ -735,17 +761,19 @@
                         edges: []
                     }
                 }
             });
-            const globalStorage = getStorage(apiClientMock);
+            const metricMocks = getMetricMock();
+            const globalStorage = getStorage(apiClientMock, metricMocks.mockMetrics);
             const response = await globalStorage.listCustomEntities({});
             expect(response).toMatchObject({
                 results: [],
                 nextCursor: null
             });
             verifyApiClientCalledWith(apiClientMock, {
                 contextAri
             }, gql_queries_1.CustomEntityQueries.listQuery(contextAri, {}).query);
+            checkMetricsFired(metricMocks, { store: 'typed', operation: 'query', encrypted: false }, true);
         });
         it('should return cursor when results are not present', async () => {
             const apiClientMock = getApiClientMock({
                 data: {
@@ -754,16 +782,38 @@
                         cursor: 'DUMMY_CURSOR'
                     }
                 }
             });
-            const globalStorage = getStorage(apiClientMock);
+            const metricMocks = getMetricMock();
+            const globalStorage = getStorage(apiClientMock, metricMocks.mockMetrics);
             const response = await globalStorage.listCustomEntities({});
             expect(response).toMatchObject({
                 results: [],
                 nextCursor: 'DUMMY_CURSOR'
             });
             verifyApiClientCalledWith(apiClientMock, {
                 contextAri
             }, gql_queries_1.CustomEntityQueries.listQuery(contextAri, {}).query);
+            checkMetricsFired(metricMocks, { store: 'typed', operation: 'query', encrypted: false }, true);
         });
+        it('should throw an error if the storage API returns an error', async () => {
+            const apiClientMock = getApiClientMock({
+                errors: [INVALID_CURSOR_ERROR]
+            });
+            const metricMocks = getMetricMock();
+            const globalStorage = getStorage(apiClientMock, metricMocks.mockMetrics);
+            const response = globalStorage.listCustomEntities({});
+            expect(apiClientMock).toHaveBeenCalled();
+            await expect(response).rejects.toThrow(errors_1.APIError.forErrorCode('CURSOR_INVALID', 'error message'));
+            checkMetricsFired(metricMocks, { store: 'typed', operation: 'query', encrypted: false }, false);
+        });
+        it('should throw an error if the storage API returns a non 200 status code and has no body', async () => {
+            const apiClientMock = getApiClientMockInvalidJson('', 400);
+            const metricMocks = getMetricMock();
+            const globalStorage = getStorage(apiClientMock, metricMocks.mockMetrics);
+            const response = globalStorage.listCustomEntities({});
+            expect(apiClientMock).toHaveBeenCalled();
+            await expect(response).rejects.toThrow(errors_1.APIError.forStatus(400));
+            checkMetricsFired(metricMocks, { store: 'typed', operation: 'query', encrypted: false }, false);
+        });
     });
 });

Modified:package/out/gql-queries.js

Index: package/out/gql-queries.js
===================================================================
--- package/out/gql-queries.js
+++ package/out/gql-queries.js
@@ -49,9 +49,9 @@
       mutation forge_app_deleteApplicationStorageEntity($input: DeleteAppStoredEntityMutationInput!) {
         appStorage {
           deleteAppStoredEntity(input: $input) {
             success
-
+  
             errors {
               message
               extensions {
                 errorType
@@ -78,9 +78,9 @@
             node {
               value
               key
             }
-
+  
             cursor
           }
         }
       }
@@ -100,9 +100,9 @@
             node {
               value
               key
             }
-
+  
             cursor
           }
         }
       }
@@ -113,63 +113,8 @@
             cursor: options.cursor ?? null,
             limit: options.limit ?? null
         }
     });
-    static bulkSet = (contextAri, values, encrypted) => ({
-        query: `
-      mutation forge_app_setApplicationStorageEntities($input: SetAppStoredEntitiesMutationInput!) {
-        appStorage{
-          setAppStoredEntities(input: $input) {
-            success
-            savedKeys
-            failedKeys {
-              key
-              code
-              message
-            }
-            errors {
-              message
-              extensions {
-                  errorType
-                  statusCode
-              }
-            }
-          }
-        }
-      }
-    `,
-        variables: {
-            input: {
-                contextAri,
-                entities: values,
-                encrypted
-            }
-        }
-    });
-    static transaction = (contextAri, items) => ({
-        query: `
-      mutation forge_app_setApplicationStorageTransact($input: TransactMutationInput!) {
-        appStorage {
-          transactAppStoredEntity(input: $input) {
-            success
-            errors {
-              message
-              extensions {
-                  errorType
-                  statusCode
-              }
-            }
-          }
-        }
-      }
-    `,
-        variables: {
-            input: {
-                contextAri,
-                items
-            }
-        }
-    });
 }
 exports.UntypedQueries = UntypedQueries;
 class CustomEntityQueries {
     static get = (contextAri, entityName, key) => ({
@@ -193,9 +138,9 @@
       mutation forge_app_setApplicationStorageCustomEntity($input: SetAppStoredCustomEntityMutationInput!) {
         appStorageCustomEntity{
           setAppStoredCustomEntity(input: $input) {
             success
-
+  
             errors {
               message
               extensions {
                 errorType
@@ -220,9 +165,9 @@
       mutation forge_app_deleteApplicationStorageCustomEntity($input: DeleteAppStoredCustomEntityMutationInput!) {
         appStorageCustomEntity {
           deleteAppStoredCustomEntity(input: $input) {
             success
-
+  
             errors {
               message
               extensions {
                 errorType
@@ -259,9 +204,9 @@
             }
             totalCount
             cursor
         }
-  }
+  } 
       `,
             variables: {
                 contextAri,
                 entityName: options.entityName,
@@ -280,30 +225,6 @@
                 ...(options.limit ? { limit: options.limit } : {})
             }
         };
     };
-    static transaction = (contextAri, items) => ({
-        query: `
-      mutation forge_app_setApplicationStorageTransact($input: TransactMutationInput!) {
-        appStorageCustomEntity {
-          transactAppStoredCustomEntity(input: $input) {
-            success
-            errors {
-              message
-              extensions {
-                  errorType
-                  statusCode
-              }
-            }
-          }
-        }
-      }
-    `,
-        variables: {
-            input: {
-                contextAri,
-                items
-            }
-        }
-    });
 }
 exports.CustomEntityQueries = CustomEntityQueries;

Modified:package/out/index.js

Index: package/out/index.js
===================================================================
--- package/out/index.js
+++ package/out/index.js
@@ -2,20 +2,17 @@
 Object.defineProperty(exports, "__esModule", { value: true });
 exports.CustomEntityIndexBuilder = exports.APIError = exports.SortOrder = exports.EntityStorageBuilder = exports.FilterConditions = exports.WhereConditions = exports.startsWith = exports.GlobalStorage = exports.getStorageInstanceWithQuery = void 0;
 const entity_storage_1 = require("./entity-storage");
 const query_api_1 = require("./query-api");
-const transaction_api_1 = require("./transaction-api");
 const getStorageInstanceWithQuery = (adapter) => {
     return {
         get: adapter.get.bind(adapter),
         set: adapter.set.bind(adapter),
         delete: adapter.delete.bind(adapter),
         getSecret: adapter.getSecret.bind(adapter),
         setSecret: adapter.setSecret.bind(adapter),
         deleteSecret: adapter.deleteSecret.bind(adapter),
-        bulkSet: adapter.bulkSet.bind(adapter),
         query: () => new query_api_1.DefaultQueryBuilder(adapter),
-        transaction: () => new transaction_api_1.DefaultTransactionBuilder(adapter),
         entity: (entityName) => new entity_storage_1.EntityStorageBuilder(entityName, adapter)
     };
 };
 exports.getStorageInstanceWithQuery = getStorageInstanceWithQuery;

Modified:package/package.json

Index: package/package.json
===================================================================
--- package/package.json
+++ package/package.json
@@ -1,7 +1,7 @@
 {
   "name": "@forge/storage",
-  "version": "1.5.15-experimental-10722bc",
+  "version": "1.6.0-next.0",
   "description": "Forge Storage methods",
   "author": "Atlassian",
   "license": "UNLICENSED",
   "main": "out/index.js",
@@ -16,7 +16,9 @@
   },
   "devDependencies": {
     "@types/node": "14.18.63",
     "@types/node-fetch": "^2.6.11",
-    "node-fetch": "2.7.0"
+    "node-fetch": "2.7.0",
+    "@forge/util": "1.4.4",
+    "@atlassian/metrics-interface": "4.0.0"
   }
-}
\ No newline at end of file
+}

Modified:package/out/global-storage.d.ts.map

Index: package/out/global-storage.d.ts.map
===================================================================
--- package/out/global-storage.d.ts.map
+++ package/out/global-storage.d.ts.map
@@ -1,1 +1,1 @@
-{"version":3,"file":"global-storage.d.ts","sourceRoot":"","sources":["../src/global-storage.ts"],"names":[],"mappings":"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"}
\ No newline at end of file
+{"version":3,"file":"global-storage.d.ts","sourceRoot":"","sources":["../src/global-storage.ts"],"names":[],"mappings":"AAAA,OAAO,EAAe,WAAW,EAAE,MAAM,SAAS,CAAC;AAInD,OAAO,EAAE,uBAAuB,EAAE,WAAW,EAAE,MAAM,oBAAoB,CAAC;AAC1E,OAAO,EAAE,oBAAoB,EAAE,MAAM,mBAAmB,CAAC;AACzD,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,wCAAwC,CAAC;AAEtE,UAAU,WAAW;IACnB,OAAO,EAAE;QAAE,GAAG,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,GAAG,CAAA;KAAE,EAAE,CAAC;IACvC,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAcD,oBAAY,SAAS,GAAG,OAAO,GAAG,SAAS,CAAC;AAC5C,oBAAY,aAAa,GAAG,KAAK,GAAG,KAAK,GAAG,OAAO,GAAG,QAAQ,CAAC;AA+B/D,qBAAa,aAAc,YAAW,oBAAoB;IAGtD,OAAO,CAAC,gBAAgB;IACxB,OAAO,CAAC,SAAS;IACjB,OAAO,CAAC,QAAQ,CAAC,UAAU;IAJ7B,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAA6B;gBAE5C,gBAAgB,EAAE,CAAC,MAAM,MAAM,CAAC,GAAG,MAAM,EACzC,SAAS,EAAE,WAAW,EACb,UAAU,EAAE,MAAM,OAAO;IAG5C,OAAO,CAAC,kBAAkB;IAIpB,GAAG,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,GAAG,CAAC;IAI9B,SAAS,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,GAAG,CAAC;IAIpC,IAAI,CAAC,OAAO,EAAE,WAAW,GAAG,OAAO,CAAC,WAAW,CAAC;IAqBhD,kBAAkB,CAAC,OAAO,EAAE,uBAAuB,GAAG,OAAO,CAAC,WAAW,CAAC;IAc1E,GAAG,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,GAAG,GAAG,OAAO,CAAC,IAAI,CAAC;IAU3C,SAAS,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,GAAG,GAAG,OAAO,CAAC,IAAI,CAAC;IAUjD,MAAM,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAOlC,YAAY,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAOxC,SAAS,CAAC,CAAC,EAAE,UAAU,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,CAAC,CAAC;IAI/D,SAAS,CAAC,CAAC,EAAE,UAAU,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,EAAE,KAAK,EAAE,CAAC,GAAG,OAAO,CAAC,IAAI,CAAC;IAO5E,YAAY,CAAC,UAAU,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;YAU1D,WAAW;YAUX,iBAAiB;IAU/B,OAAO,CAAC,YAAY;YAUN,KAAK;YAML,QAAQ;YAsBR,YAAY;CAoC3B"}
\ No newline at end of file

Modified:package/out/gql-queries.d.ts.map

Index: package/out/gql-queries.d.ts.map
===================================================================
--- package/out/gql-queries.d.ts.map
+++ package/out/gql-queries.d.ts.map
@@ -1,1 +1,1 @@
-{"version":3,"file":"gql-queries.d.ts","sourceRoot":"","sources":["../src/gql-queries.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,uBAAuB,EAAE,MAAM,kBAAkB,CAAC;AAC3D,OAAO,EAAE,uBAAuB,EAAE,WAAW,EAAE,MAAM,oBAAoB,CAAC;AAC1E,OAAO,EAAE,QAAQ,EAAE,MAAM,mBAAmB,CAAC;AAE7C,qBAAa,cAAc;IACzB,OAAc,GAAG,eAAgB,MAAM,OAAO,MAAM,aAAa,OAAO;;;;;;;MAcrE;IAEH,OAAc,GAAG,eAAgB,MAAM,OAAO,MAAM,SAAS,GAAG,aAAa,OAAO;;;;;;;;;;MA0BjF;IAEH,OAAc,MAAM,eAAgB,MAAM,OAAO,MAAM,aAAa,OAAO;;;;;;;;;MAyBxE;IAEH,OAAc,SAAS,eAAgB,MAAM,WAAW,WAAW;;;;;;;;MAuBhE;IAEH,OAAc,mBAAmB,eAAgB,MAAM,WAAW,WAAW;;;;;;;;MAuB1E;IAEH,OAAc,OAAO,eAAgB,MAAM,UAAU,QAAQ,EAAE,aAAa,OAAO;;;;;;;;;MA8BhF;IAEH,OAAc,WAAW,eAAgB,MAAM,SAAS,uBAAuB;;;;;;;;MAuB5E;CACJ;AAED,qBAAa,mBAAmB;IAC9B,OAAc,GAAG,eAAgB,MAAM,cAAc,MAAM,OAAO,MAAM;;;;;;;MAerE;IAEH,OAAc,GAAG,eAAgB,MAAM,cAAc,MAAM,OAAO,MAAM,SAAS,GAAG;;;;;;;;;;MA0BjF;IAEH,OAAc,MAAM,eAAgB,MAAM,cAAc,MAAM,OAAO,MAAM;;;;;;;;;MAyBxE;IAEH,OAAc,SAAS,eAAgB,MAAM,WAAW,uBAAuB;;;;;;;;;;;;;;;MAuC7E;IAEF,OAAc,WAAW,eAAgB,MAAM,SAAS,uBAAuB;;;;;;;;MAuB5E;CACJ"}
\ No newline at end of file
+{"version":3,"file":"gql-queries.d.ts","sourceRoot":"","sources":["../src/gql-queries.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,uBAAuB,EAAE,WAAW,EAAE,MAAM,oBAAoB,CAAC;AAE1E,qBAAa,cAAc;IACzB,OAAc,GAAG,eAAgB,MAAM,OAAO,MAAM,aAAa,OAAO;;;;;;;MAcrE;IAEH,OAAc,GAAG,eAAgB,MAAM,OAAO,MAAM,SAAS,GAAG,aAAa,OAAO;;;;;;;;;;MA0BjF;IAEH,OAAc,MAAM,eAAgB,MAAM,OAAO,MAAM,aAAa,OAAO;;;;;;;;;MAyBxE;IAEH,OAAc,SAAS,eAAgB,MAAM,WAAW,WAAW;;;;;;;;MAuBhE;IAEH,OAAc,mBAAmB,eAAgB,MAAM,WAAW,WAAW;;;;;;;;MAuB1E;CACJ;AAED,qBAAa,mBAAmB;IAC9B,OAAc,GAAG,eAAgB,MAAM,cAAc,MAAM,OAAO,MAAM;;;;;;;MAerE;IAEH,OAAc,GAAG,eAAgB,MAAM,cAAc,MAAM,OAAO,MAAM,SAAS,GAAG;;;;;;;;;;MA0BjF;IAEH,OAAc,MAAM,eAAgB,MAAM,cAAc,MAAM,OAAO,MAAM;;;;;;;;;MAyBxE;IAEH,OAAc,SAAS,eAAgB,MAAM,WAAW,uBAAuB;;;;;;;;;;;;;;;MAuC7E;CACH"}
\ No newline at end of file

Modified:package/out/index.d.ts.map

Index: package/out/index.d.ts.map
===================================================================
--- package/out/index.d.ts.map
+++ package/out/index.d.ts.map
@@ -1,1 +1,1 @@
-{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;AACnD,OAAO,EAAE,oBAAoB,EAAE,MAAM,kBAAkB,CAAC;AAExD,OAAO,EAAE,aAAa,EAAE,MAAM,kBAAkB,CAAC;AACjD,OAAO,EAAE,mBAAmB,EAAE,MAAM,aAAa,CAAC;AAClD,OAAO,EAAE,yBAAyB,EAAE,MAAM,mBAAmB,CAAC;AAE9D,oBAAY,WAAW,GAAG,IAAI,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,GAAG,aAAa,GAAG,IAAI,GAAG,QAAQ,GAAG,YAAY,CAAC,CAAC;AAC3G,oBAAY,WAAW,GAAG,CAAC,GAAG,EAAE,MAAM,EAAE,IAAI,EAAE,WAAW,KAAK,OAAO,CAAC,WAAW,CAAC,CAAC;AAEnF,eAAO,MAAM,2BAA2B,YAAa,aAAa;;;;;;;;iBASnD,mBAAmB;uBACb,yBAAyB;4BAClB,MAAM;CAEjC,CAAC;AAEF,OAAO,EAAE,aAAa,EAAE,MAAM,kBAAkB,CAAC;AAEjD,OAAO,EAAE,UAAU,EAAE,MAAM,cAAc,CAAC;AAC1C,OAAO,EAAE,eAAe,EAAE,gBAAgB,EAAE,MAAM,kBAAkB,CAAC;AAErE,OAAO,EACL,YAAY,EACZ,QAAQ,EACR,SAAS,EACT,UAAU,EACV,SAAS,EACT,MAAM,EACN,gBAAgB,EAChB,cAAc,EACd,eAAe,EACf,cAAc,EACd,QAAQ,EACR,YAAY,EACb,MAAM,mBAAmB,CAAC;AAC3B,OAAO,EAAE,oBAAoB,EAAE,wBAAwB,EAAE,MAAM,kBAAkB,CAAC;AAClF,OAAO,EAAE,KAAK,EAAE,SAAS,EAAE,MAAM,oBAAoB,CAAC;AAEtD,OAAO,EAAE,QAAQ,EAAE,MAAM,UAAU,CAAC;AACpC,OAAO,EAAE,wBAAwB,EAAE,MAAM,4BAA4B,CAAC"}
\ No newline at end of file
+{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;AACnD,OAAO,EAAE,oBAAoB,EAAE,MAAM,kBAAkB,CAAC;AAExD,OAAO,EAAE,aAAa,EAAE,MAAM,kBAAkB,CAAC;AACjD,OAAO,EAAE,mBAAmB,EAAE,MAAM,aAAa,CAAC;AAElD,oBAAY,WAAW,GAAG,IAAI,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,GAAG,aAAa,GAAG,IAAI,GAAG,QAAQ,GAAG,YAAY,CAAC,CAAC;AAC3G,oBAAY,WAAW,GAAG,CAAC,GAAG,EAAE,MAAM,EAAE,IAAI,EAAE,WAAW,KAAK,OAAO,CAAC,WAAW,CAAC,CAAC;AAEnF,eAAO,MAAM,2BAA2B,YAAa,aAAa;;;;;;;iBAQnD,mBAAmB;4BACN,MAAM;CAEjC,CAAC;AAEF,OAAO,EAAE,aAAa,EAAE,MAAM,kBAAkB,CAAC;AAEjD,OAAO,EAAE,UAAU,EAAE,MAAM,cAAc,CAAC;AAC1C,OAAO,EAAE,eAAe,EAAE,gBAAgB,EAAE,MAAM,kBAAkB,CAAC;AAErE,OAAO,EACL,YAAY,EACZ,QAAQ,EACR,SAAS,EACT,UAAU,EACV,SAAS,EACT,MAAM,EACN,gBAAgB,EAChB,cAAc,EACd,eAAe,EAChB,MAAM,mBAAmB,CAAC;AAC3B,OAAO,EAAE,oBAAoB,EAAE,wBAAwB,EAAE,MAAM,kBAAkB,CAAC;AAClF,OAAO,EAAE,KAAK,EAAE,SAAS,EAAE,MAAM,oBAAoB,CAAC;AAEtD,OAAO,EAAE,QAAQ,EAAE,MAAM,UAAU,CAAC;AACpC,OAAO,EAAE,wBAAwB,EAAE,MAAM,4BAA4B,CAAC"}
\ No newline at end of file

Modified:package/out/storage-adapter.d.ts.map

Index: package/out/storage-adapter.d.ts.map
===================================================================
--- package/out/storage-adapter.d.ts.map
+++ package/out/storage-adapter.d.ts.map
@@ -1,1 +1,1 @@
-{"version":3,"file":"storage-adapter.d.ts","sourceRoot":"","sources":["../src/storage-adapter.ts"],"names":[],"mappings":"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"}
\ No newline at end of file
+{"version":3,"file":"storage-adapter.d.ts","sourceRoot":"","sources":["../src/storage-adapter.ts"],"names":[],"mappings":"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"}
\ No newline at end of file

Modified:package/README.md

Index: package/README.md
===================================================================
--- package/README.md
+++ package/README.md
@@ -6,8 +6,9 @@
 import fetch, { RequestInit } from 'node-fetch';
 
 import { GlobalStorage } from './global-storage';
 import { APIResponse, getStorageInstanceWithQuery } from './index';
+import { getMetrics } from './runtime/fetch-and-storage';
 
 const API_BASE = 'https://api.atlassian.com';
 
 // For user agent header
@@ -36,9 +37,9 @@
   init.headers = Object.assign(init.headers!, extraHeaders);
   return fetch(url, init);
 }
 
-const adapter = new GlobalStorage(() => appContextAri, apiClient);
+const adapter = new GlobalStorage(() => appContextAri, apiClient, getMetrics);
 const storage = getStorageInstanceWithQuery(adapter);
 
 async function demo() {
   await storage.set('key', 'value');

Modified:package/out/global-storage.d.ts

Index: package/out/global-storage.d.ts
===================================================================
--- package/out/global-storage.d.ts
+++ package/out/global-storage.d.ts
@@ -1,48 +1,30 @@
-import { BulkResponse, FetchMethod } from './index';
-import { CustomEntityListOptions, FilterClause, ListOptions } from './query-interfaces';
-import { BulkItem, SharedStorageAdapter } from './storage-adapter';
+import { FetchMethod } from './index';
+import { CustomEntityListOptions, ListOptions } from './query-interfaces';
+import { SharedStorageAdapter } from './storage-adapter';
+import type { Metrics } from '@forge/util/packages/metrics-interface';
 interface ListResults {
     results: {
         key: string;
         value: any;
     }[];
     nextCursor?: string;
 }
-export declare type colorsEnum = 'or' | 'and';
-declare type TransactionRequestCondition = {
-    [key in colorsEnum]?: FilterClause[];
-};
-export interface TransactionRequestSet {
-    key: string;
-    value: string | number | boolean | Record<string, any> | any[];
-    entityName?: string;
-    conditions?: TransactionRequestCondition;
-}
-export interface TransactionRequestDeleteCheck {
-    key: string;
-    entityName?: string;
-    conditions?: TransactionRequestCondition;
-}
-export interface TransactionRequestInput {
-    set?: TransactionRequestSet[];
-    delete?: TransactionRequestDeleteCheck[];
-    check?: TransactionRequestDeleteCheck[];
-}
+export declare type StoreType = 'typed' | 'untyped';
+export declare type OperationType = 'get' | 'set' | 'query' | 'delete';
 export declare class GlobalStorage implements SharedStorageAdapter {
     private getAppContextAri;
     private apiClient;
+    private readonly getMetrics;
     private readonly endpoint;
-    constructor(getAppContextAri: (() => string) | string, apiClient: FetchMethod);
+    constructor(getAppContextAri: (() => string) | string, apiClient: FetchMethod, getMetrics: () => Metrics);
     private doGetAppContextAri;
     get(key: string): Promise<any>;
     getSecret(key: string): Promise<any>;
     list(options: ListOptions): Promise<ListResults>;
-    transaction(options: TransactionRequestInput, isCustomEntity?: boolean): Promise<void>;
     listCustomEntities(options: CustomEntityListOptions): Promise<ListResults>;
     set(key: string, value: any): Promise<void>;
     setSecret(key: string, value: any): Promise<void>;
-    bulkSet(items: BulkItem[]): Promise<BulkResponse>;
     delete(key: string): Promise<void>;
     deleteSecret(key: string): Promise<void>;
     getEntity<T>(entityName: string, entityKey: string): Promise<T>;
     setEntity<T>(entityName: string, entityKey: string, value: T): Promise<void>;
@@ -51,7 +33,8 @@
     private getEntityInternal;
     private buildRequest;
     private query;
     private mutation;
+    private wrapInMetric;
 }
 export {};
 //# sourceMappingURL=global-storage.d.ts.map
\ No newline at end of file

Modified:package/out/gql-queries.d.ts

Index: package/out/gql-queries.d.ts
===================================================================
--- package/out/gql-queries.d.ts
+++ package/out/gql-queries.d.ts
@@ -1,7 +1,5 @@
-import { TransactionRequestInput } from './global-storage';
 import { CustomEntityListOptions, ListOptions } from './query-interfaces';
-import { BulkItem } from './storage-adapter';
 export declare class UntypedQueries {
     static get: (contextAri: string, key: string, encrypted: boolean) => {
         query: string;
         variables: {
@@ -48,27 +46,8 @@
             cursor: string | null;
             limit: number | null;
         };
     };
-    static bulkSet: (contextAri: string, values: BulkItem[], encrypted: boolean) => {
-        query: string;
-        variables: {
-            input: {
-                contextAri: string;
-                entities: BulkItem[];
-                encrypted: boolean;
-            };
-        };
-    };
-    static transaction: (contextAri: string, items: TransactionRequestInput) => {
-        query: string;
-        variables: {
-            input: {
-                contextAri: string;
-                items: TransactionRequestInput;
-            };
-        };
-    };
 }
 export declare class CustomEntityQueries {
     static get: (contextAri: string, entityName: string, key: string) => {
         query: string;
@@ -114,15 +93,6 @@
             indexName: string | undefined;
             range: import("./query-interfaces").RangeClause | undefined;
         };
     };
-    static transaction: (contextAri: string, items: TransactionRequestInput) => {
-        query: string;
-        variables: {
-            input: {
-                contextAri: string;
-                items: TransactionRequestInput;
-            };
-        };
-    };
 }
 //# sourceMappingURL=gql-queries.d.ts.map
\ No newline at end of file

Modified:package/out/index.d.ts

Index: package/out/index.d.ts
===================================================================
--- package/out/index.d.ts
+++ package/out/index.d.ts
@@ -1,9 +1,8 @@
 import { RequestInit, Response } from 'node-fetch';
 import { EntityStorageBuilder } from './entity-storage';
 import { GlobalStorage } from './global-storage';
 import { DefaultQueryBuilder } from './query-api';
-import { DefaultTransactionBuilder } from './transaction-api';
 export declare type APIResponse = Pick<Response, 'json' | 'text' | 'arrayBuffer' | 'ok' | 'status' | 'statusText'>;
 export declare type FetchMethod = (url: string, init: RequestInit) => Promise<APIResponse>;
 export declare const getStorageInstanceWithQuery: (adapter: GlobalStorage) => {
     get: (key: string) => Promise<any>;
@@ -11,17 +10,15 @@
     delete: (key: string) => Promise<void>;
     getSecret: (key: string) => Promise<any>;
     setSecret: (key: string, value: any) => Promise<void>;
     deleteSecret: (key: string) => Promise<void>;
-    bulkSet: (items: import("./storage-adapter").BulkItem[]) => Promise<import("./storage-adapter").BulkResponse>;
     query: () => DefaultQueryBuilder;
-    transaction: () => DefaultTransactionBuilder;
     entity: <T>(entityName: string) => EntityStorageBuilder<T>;
 };
 export { GlobalStorage } from './global-storage';
 export { startsWith } from './conditions';
 export { WhereConditions, FilterConditions } from './eap/conditions';
-export { QueryBuilder, QueryApi, Condition, ListResult, Predicate, Result, EntityStorageApi, WherePredicate, FilterPredicate, TransactionApi, BulkItem, BulkResponse } from './storage-adapter';
+export { QueryBuilder, QueryApi, Condition, ListResult, Predicate, Result, EntityStorageApi, WherePredicate, FilterPredicate } from './storage-adapter';
 export { EntityStorageBuilder, EntityStorageBuilderType } from './entity-storage';
 export { Value, SortOrder } from './query-interfaces';
 export { APIError } from './errors';
 export { CustomEntityIndexBuilder } from './entity-storage/query-api';

Modified:package/out/storage-adapter.d.ts

Index: package/out/storage-adapter.d.ts
===================================================================
--- package/out/storage-adapter.d.ts
+++ package/out/storage-adapter.d.ts
@@ -1,28 +1,13 @@
 import { EntityStorageBuilderType } from './entity-storage';
-import { CustomEntityTransactionBuilder } from './entity-storage/custom-entity-transaction-api';
 import { BeginsWithClause, BetweenClause, ExistsClause, DoesNotExistClause, GreaterThanClause, GreaterThanEqualToClause, StartsWith, NotEqualTo, In, LessThanClause, LessThanEqualToClause, ContainsClause, DoesNotContainClause, IsNotEqualToClause, EqualToClause } from './query-interfaces';
-export interface BulkItem {
-    key: string;
-    value: string | number | boolean | Record<string, any> | any[];
-}
-export interface FailedKey {
-    key: string;
-    code: string;
-    message: string;
-}
-export interface BulkResponse {
-    savedKeys: string[];
-    failedKeys: FailedKey[];
-}
 export interface StorageAdapter {
     get(key: string): Promise<any>;
     set(key: string, value: string | number | boolean | Record<string, any> | any[]): Promise<void>;
     delete(key: string): Promise<void>;
     getSecret(key: string): Promise<any>;
     setSecret(key: string, value: any): Promise<void>;
     deleteSecret(key: string): Promise<void>;
-    bulkSet(items: BulkItem[]): Promise<BulkResponse>;
 }
 export interface EntityStorageAdapter {
     getEntity<T>(entityName: string, entityKey: string): Promise<T>;
     setEntity<T>(entityName: string, entityKey: string, value: T): Promise<void>;
@@ -34,11 +19,8 @@
 export declare type SharedStorageAdapter = StorageAdapter & EntityStorageAdapter;
 export interface QueryApi {
     query(): QueryBuilder;
 }
-export interface TransactionApi {
-    transaction(): TransactionBuilder;
-}
 export declare type Predicate = StartsWith | NotEqualTo | In;
 export declare type Condition = Predicate;
 export interface QueryBuilder {
     where(field: 'key', condition: Condition): QueryBuilder;
@@ -46,19 +28,8 @@
     limit(limit: number): QueryBuilder;
     getMany(): Promise<ListResult>;
     getOne(): Promise<Result | undefined>;
 }
-export interface KVSTransactionBuilderInterface {
-    set(key: string, value: string | number | boolean | Record<string, any> | any[]): KVSTransactionBuilderInterface;
-    delete(key: string): KVSTransactionBuilderInterface;
-    execute(): Promise<void>;
-}
-export interface TransactionBuilder {
-    entity(name: string): CustomEntityTransactionBuilder;
-    set(key: string, value: string | number | boolean | Record<string, any> | any[]): KVSTransactionBuilderInterface;
-    delete(key: string): KVSTransactionBuilderInterface;
-    execute(): Promise<void>;
-}
 export declare type FilterPredicate = BetweenClause | BeginsWithClause | ExistsClause | DoesNotExistClause | GreaterThanClause | GreaterThanEqualToClause | LessThanClause | LessThanEqualToClause | ContainsClause | DoesNotContainClause | EqualToClause | IsNotEqualToClause;
 export declare type WherePredicate = BetweenClause | BeginsWithClause | EqualToClause | GreaterThanClause | GreaterThanEqualToClause | LessThanClause | LessThanEqualToClause;
 export interface Result<T = object> {
     key: string;