diff --git a/src/common/SharedState.js b/src/common/SharedState.js index a569baa9..d9a605c4 100644 --- a/src/common/SharedState.js +++ b/src/common/SharedState.js @@ -1,4 +1,4 @@ -import { isPlainObject } from '@ircam/sc-utils'; +import { isPlainObject, isString } from '@ircam/sc-utils'; import ParameterBag from './ParameterBag.js'; import PromiseStore from './PromiseStore.js'; @@ -419,11 +419,15 @@ ${JSON.stringify(initValues, null, 2)}`); } if (isPlainObject(arguments[0]) && isPlainObject(arguments[1])) { - logger.deprecated('SharedState.set(updates, context)', 'a regular parameter set with `event=true` behavior', '4.0.0-alpha.29'); + logger.removed('`context` argument in SharedState.set(updates, context)', 'a regular parameter set with `event=true` behavior', '4.0.0-alpha.29'); } - if (!isPlainObject(updates)) { - throw new TypeError(`[SharedState] State "${this.#className}": state.set(updates) should receive an object as first parameter`); + if (arguments.length === 2 && isString(updates)) { + updates = { [updates]: arguments[1] }; + } else if (isPlainObject(updates)) { + updates = updates; + } else { + throw new TypeError(`Cannot execute 'set' on SharedState: 'updates' argument should be an object`); } const newValues = {}; diff --git a/src/common/SharedStateCollection.js b/src/common/SharedStateCollection.js index 2068bb33..b88089ff 100644 --- a/src/common/SharedStateCollection.js +++ b/src/common/SharedStateCollection.js @@ -224,10 +224,10 @@ class SharedStateCollection { * Update all states of the collection with given values. * @param {object} updates - key / value pairs of updates to apply to the state. */ - async set(updates) { + async set(...args) { // we can delegate to the state.set(update) method for throwing in case of // filtered keys, as the Promise.all will reject on first reject Promise - const promises = this.#states.map(state => state.set(updates)); + const promises = this.#states.map(state => state.set(...args)); return Promise.all(promises); } diff --git a/src/common/logger.js b/src/common/logger.js index 7f1a0403..c95f666f 100644 --- a/src/common/logger.js +++ b/src/common/logger.js @@ -163,14 +163,23 @@ dependencies on both your server and clients. !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!`); }, - deprecated(oldAPI, newAPI, deprecationVersion) { - if (!deprecationVersion) { + deprecated(oldAPI, newAPI, lastSupportedVersion) { + if (!lastSupportedVersion) { throw new Error(`Invalid 'logger.deprecated call: a deprecation version is required`); } - const msg = `[Deprecation Warning] ${oldAPI} is deprecated (last supported version: ${deprecationVersion}) and will be removed in next major revision, please use ${newAPI} instead`; + const msg = `[Deprecation Warning] ${oldAPI} is deprecated (last supported version: ${lastSupportedVersion}) and will be removed in next major revision, please use ${newAPI} instead.`; console.warn(chalk.yellow(msg)); - } + }, + + removed(oldAPI, hint, lastSupportedVersion) { + if (!lastSupportedVersion) { + throw new Error(`Invalid 'logger.deprecated call: a deprecation version is required`); + } + + const msg = `[Removed API] ${oldAPI} has been removed (last supported version: ${lastSupportedVersion}), please use ${hint} instead.`; + throw new Error(msg); + }, }; export default logger; diff --git a/tests/misc/deprecated.spec.js b/tests/misc/deprecated.spec.js index 92478ece..27e0bebf 100644 --- a/tests/misc/deprecated.spec.js +++ b/tests/misc/deprecated.spec.js @@ -6,8 +6,8 @@ import { Client } from '../../src/client/index.js'; import config from '../utils/config.js'; import { a, aExpectedDescription } from '../utils/class-description.js'; -describe('# deprecated API', () => { - describe('from v4.0.0-alpha.29', () => { +describe('from v4.0.0-alpha.29', () => { + describe('# deprecated API', () => { it('SharedState#schemaName', async () => { const server = new Server(config); server.stateManager.defineClass('a', a); @@ -70,15 +70,24 @@ describe('# deprecated API', () => { server.stateManager.deleteClass('a'); await server.stop(); }); + }); + describe('# removed API', () => { it('SharedState#set(updates, context)', async () => { const server = new Server(config); server.stateManager.defineClass('a', a); await server.start(); + let errored = false; const state = await server.stateManager.create('a'); - state.set({}, {}); - + try { + await state.set({}, {}); + } catch (err) { + errored = true; + console.log(err.message); + } + + assert.isTrue(errored); await server.stop(); }); }); diff --git a/tests/states/SharedState.spec.js b/tests/states/SharedState.spec.js index a9e1a926..3b86810d 100644 --- a/tests/states/SharedState.spec.js +++ b/tests/states/SharedState.spec.js @@ -101,6 +101,13 @@ describe('# SharedState', () => { await a.delete(); }); + it('should support `set(name, value)`', async () => { + const a = await server.stateManager.create('a'); + await a.set('bool', true); + assert.deepEqual(a.get('bool'), true); + await a.delete(); + }); + it('should resolve after `onUpdate` even if onUpdate callback is async', async () => { const a = await server.stateManager.create('a'); let asyncCallbackCalled = false; @@ -608,7 +615,7 @@ describe('# SharedState - filtered attached state', () => { }); }); - describe(`## set(updates)`, () => { + describe(`## async set(updates)`, () => { it(`should throw early if trying to set modify a param which is not filtered`, async () => { const filter = ['bool', 'string']; const owned = await server.stateManager.create('filtered'); diff --git a/tests/states/SharedStateCollection.spec.js b/tests/states/SharedStateCollection.spec.js index 98067c36..8faf5b10 100644 --- a/tests/states/SharedStateCollection.spec.js +++ b/tests/states/SharedStateCollection.spec.js @@ -192,7 +192,7 @@ describe(`# SharedStateCollection`, () => { }); }); - describe(`## set(updates, context = null)`, () => { + describe(`## set(updates)`, () => { it(`should properly progate updates`, async () => { const state0 = await clients[0].stateManager.create('a'); const state1 = await clients[1].stateManager.create('a'); @@ -208,7 +208,7 @@ describe(`# SharedStateCollection`, () => { const expected = [{ bool: true }, { bool: true }]; assert.deepEqual(result, expected); - await delay(50); + await delay(10); // should be propagated to everyone assert.equal(state0.get('bool'), true); assert.equal(state1.get('bool'), true); @@ -220,6 +220,27 @@ describe(`# SharedStateCollection`, () => { await state1.delete(); }); + it('should support `set(name, value)`', async () => { + const state0 = await clients[0].stateManager.create('a'); + const state1 = await clients[1].stateManager.create('a'); + const collection = await clients[2].stateManager.getCollection('a'); + + const updates = await collection.set('bool', true); + const expected = [{ bool: true }, { bool: true }]; + assert.deepEqual(updates, expected); + assert.deepEqual(collection.get('bool'), [true, true]); + + // "real" state are updated async compared to the collection + await delay(10); + // should be propagated to everyone + assert.equal(state0.get('bool'), true); + assert.equal(state1.get('bool'), true); + + await collection.detach(); + await state0.delete(); + await state1.delete(); + }); + it(`test several collections from same schema`, async () => { const state0 = await clients[0].stateManager.create('a'); const state1 = await clients[1].stateManager.create('a'); @@ -328,11 +349,10 @@ describe(`# SharedStateCollection`, () => { const collection = await server.stateManager.getCollection('with-event'); let onUpdateCalled = false; - collection.onUpdate((state, newValues, oldValues, context) => { + collection.onUpdate((state, newValues, oldValues) => { onUpdateCalled = true; assert.deepEqual(newValues, { int: 20 }); assert.deepEqual(oldValues, {}); - assert.deepEqual(context, null); }, true); await delay(10);