Skip to content

Commit

Permalink
feat: support SharedState#set(name, value) signature
Browse files Browse the repository at this point in the history
  • Loading branch information
b-ma committed Oct 4, 2024
1 parent 3e6eb7a commit 565d062
Show file tree
Hide file tree
Showing 6 changed files with 68 additions and 19 deletions.
12 changes: 8 additions & 4 deletions src/common/SharedState.js
Original file line number Diff line number Diff line change
@@ -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';
Expand Down Expand Up @@ -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 = {};
Expand Down
4 changes: 2 additions & 2 deletions src/common/SharedStateCollection.js
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}

Expand Down
17 changes: 13 additions & 4 deletions src/common/logger.js
Original file line number Diff line number Diff line change
Expand Up @@ -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;
17 changes: 13 additions & 4 deletions tests/misc/deprecated.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down Expand Up @@ -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();
});
});
Expand Down
9 changes: 8 additions & 1 deletion tests/states/SharedState.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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');
Expand Down
28 changes: 24 additions & 4 deletions tests/states/SharedStateCollection.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -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');
Expand All @@ -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);
Expand All @@ -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');
Expand Down Expand Up @@ -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);
Expand Down

0 comments on commit 565d062

Please sign in to comment.