From f373c8195a34374f9e9c3fa01e8a630e803decaf Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Mon, 9 Mar 2026 01:42:16 +0000 Subject: [PATCH 1/2] core: return storage dispatch promises Co-authored-by: David Wells --- packages/analytics-core/src/index.js | 6 ++- .../tests/storage/storage.test.js | 52 +++++++++++++++++++ 2 files changed, 56 insertions(+), 2 deletions(-) create mode 100644 packages/analytics-core/tests/storage/storage.test.js diff --git a/packages/analytics-core/src/index.js b/packages/analytics-core/src/index.js index 1c049b2d..4843cb79 100644 --- a/packages/analytics-core/src/index.js +++ b/packages/analytics-core/src/index.js @@ -769,13 +769,14 @@ function analytics(config = {}) { * @param {String} key - storage key * @param {any} value - storage value * @param {Object} [options] - storage options + * @return {Promise} Dispatch promise for storage pipeline * * @example * * analytics.storage.setItem('storage_key', 'value') */ setItem: (key, value, options) => { - store.dispatch({ + return store.dispatch({ type: EVENTS.setItemStart, key: key, value: value, @@ -787,13 +788,14 @@ function analytics(config = {}) { * @typedef {Function} RemoveItem * @param {String} key - storage key * @param {Object} [options] - storage options + * @return {Promise} Dispatch promise for storage pipeline * * @example * * analytics.storage.removeItem('storage_key') */ removeItem: (key, options) => { - store.dispatch({ + return store.dispatch({ type: EVENTS.removeItemStart, key: key, options: options diff --git a/packages/analytics-core/tests/storage/storage.test.js b/packages/analytics-core/tests/storage/storage.test.js new file mode 100644 index 00000000..f23b63c5 --- /dev/null +++ b/packages/analytics-core/tests/storage/storage.test.js @@ -0,0 +1,52 @@ +import '../_setup.js' +import { test } from 'uvu' +import * as assert from 'uvu/assert' +import isPromise from '../_utils/isPromise.js' +import Analytics from '../../src/index.js' + +test('storage.setItem returns a promise', () => { + const analytics = Analytics({ + app: 'appname', + version: 100 + }) + + const promise = analytics.storage.setItem('storage_key', 'storage_value') + assert.is(isPromise(promise), true) +}) + +test('storage.removeItem returns a promise', () => { + const analytics = Analytics({ + app: 'appname', + version: 100 + }) + + const promise = analytics.storage.removeItem('storage_key') + assert.is(isPromise(promise), true) +}) + +test('awaiting storage.setItem allows immediate reads', async () => { + const analytics = Analytics({ + app: 'appname', + version: 100 + }) + + await analytics.storage.setItem('storage_key', 'storage_value') + + const value = analytics.storage.getItem('storage_key') + assert.is(value, 'storage_value') +}) + +test('awaiting storage.removeItem removes value', async () => { + const analytics = Analytics({ + app: 'appname', + version: 100 + }) + + await analytics.storage.setItem('storage_key', 'storage_value') + await analytics.storage.removeItem('storage_key') + + const value = analytics.storage.getItem('storage_key') + assert.is(value, undefined) +}) + +test.run() From 872f11719ede9f196d3e4beda1a033c0911cd9ef Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Mon, 9 Mar 2026 01:42:22 +0000 Subject: [PATCH 2/2] core: support alwaysEnabled plugins with all:false Co-authored-by: David Wells --- .../src/utils/filterDisabled.js | 5 ++- .../tests/plugins-scoped-calls.test.js | 39 +++++++++++++++++++ 2 files changed, 42 insertions(+), 2 deletions(-) diff --git a/packages/analytics-core/src/utils/filterDisabled.js b/packages/analytics-core/src/utils/filterDisabled.js index c9ee2025..dfb97dee 100644 --- a/packages/analytics-core/src/utils/filterDisabled.js +++ b/packages/analytics-core/src/utils/filterDisabled.js @@ -2,14 +2,15 @@ import { isBoolean } from '@analytics/type-utils' export default function fitlerDisabledPlugins(allPlugins, settings = {}, options = {}) { return Object.keys(allPlugins).filter((name) => { + const plugin = allPlugins[name] const fromCallOptions = options.plugins || {} // If enabled/disabled by options. Override settings if (isBoolean(fromCallOptions[name])) { return fromCallOptions[name] } - // If all: false disable everything unless true explicitly set + // If all: false disable everything unless explicitly enabled or alwaysEnabled if (fromCallOptions.all === false) { - return false + return plugin && plugin.alwaysEnabled === true } // else use state.plugin settings if (settings[name] && settings[name].enabled === false) { diff --git a/packages/analytics-core/tests/plugins-scoped-calls.test.js b/packages/analytics-core/tests/plugins-scoped-calls.test.js index 0f62c4a7..864e5eba 100644 --- a/packages/analytics-core/tests/plugins-scoped-calls.test.js +++ b/packages/analytics-core/tests/plugins-scoped-calls.test.js @@ -63,6 +63,45 @@ test('No plugins get called if config.plugins.all set to false', async () => { assert.is(dummyThree.callCount, 0) }) +test('alwaysEnabled plugin still runs when config.plugins.all false', async () => { + const destinationSpy = sandbox.spy() + const enricherSpy = sandbox.spy() + + const analytics = Analytics({ + app: 'appname', + plugins: [ + { + name: 'destination-plugin', + page: destinationSpy, + track: destinationSpy, + identify: destinationSpy + }, + { + name: 'enricher-plugin', + alwaysEnabled: true, + page: enricherSpy, + track: enricherSpy, + identify: enricherSpy + }, + ] + }) + + const opts = { + plugins: { + all: false, + } + } + + analytics.page({}, opts) + analytics.track('eventName', {}, opts) + analytics.identify('userId-123', {}, opts) + + await delay(100) + + assert.is(destinationSpy.callCount, 0) + assert.is(enricherSpy.callCount, 3) +}) + test('Single destination via config.plugins.all false works', async () => { const dummyOne = sandbox.spy() const dummyTwo = sandbox.spy()