Skip to content

Commit

Permalink
feat(core): added circular reference detection for mutations
Browse files Browse the repository at this point in the history
  • Loading branch information
andrewcourtice committed Jul 31, 2021
1 parent 318bb50 commit 7719b62
Show file tree
Hide file tree
Showing 3 changed files with 26 additions and 1 deletion.
10 changes: 10 additions & 0 deletions core/src/store.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,19 +41,22 @@ function localiseHandler(name: string, handler: EventHandler): EventHandler {
export default class Store<TState extends object = any> implements InternalStore<TState> {

private options: InternalStoreOptions;
private stack: Set<string>;
private readState: ReadState<TState>;
private writeState: WriteState<TState>;

public name: string;
public getters: Map<string, Function>;
public mutations: Map<string, Mutation<any>>;


constructor(name: string, state: TState, options?: Partial<InternalStoreOptions>) {
this.options = {
allowOverwrite: true,
...options
};

this.stack = new Set();
this.writeState = reactive(state) as WriteState<TState>;
this.readState = readonly(this.writeState) as ReadState<TState>;

Expand Down Expand Up @@ -101,20 +104,27 @@ export default class Store<TState extends object = any> implements InternalStore
};

private mutate<TPayload, TResult = void>(name: string, sender: string, mutator: Mutator<TState, TPayload, TResult>, payload: TPayload): TResult {
if (this.stack.has(name)) {
throw new Error('Circular mutation reference detected. Avoid calling mutations inside other mutations to prevent circular references.');
}

const eventData: MutationEventData<TPayload, TResult> = {
payload,
mutation: name
};

let result: TResult;

this.stack.add(name);
this.emit(EVENTS.mutation.before, sender, eventData);

try {
result = mutator(this.writeState, payload);
} catch (error) {
this.emit(EVENTS.mutation.error, sender, eventData);
throw error;
} finally {
this.stack.delete(name);
}

this.emit(EVENTS.mutation.after, sender, {
Expand Down
15 changes: 15 additions & 0 deletions core/test/index.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,14 @@ function getStore() {
state.lastName = payload;
});

const circularParent = mutation('circular-parent', state => {
circularChild();
});

const circularChild = mutation('circular-child', state => {
circularParent();
});

return {
state,
getter,
Expand All @@ -43,6 +51,8 @@ function getStore() {
setId,
setFirstName,
setLastName,
circularParent,
circularChild,
onBeforeMutation,
onAfterMutation
};
Expand All @@ -58,6 +68,7 @@ describe('Harlem Core', () => {
setId,
setFirstName,
setLastName,
circularParent,
onBeforeMutation,
onAfterMutation
} = getStore();
Expand Down Expand Up @@ -119,6 +130,10 @@ describe('Harlem Core', () => {
expect(typeof id).toBe('number');
});

test('Should detect a circular reference', () => {
expect(() => circularParent()).toThrow();
});

});

describe('Triggers', () => {
Expand Down
2 changes: 1 addition & 1 deletion jest.config.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
module.exports = {
preset: 'ts-jest',
testEnvironment: 'node',
testEnvironment: 'jsdom',
verbose: true
};

1 comment on commit 7719b62

@vercel
Copy link

@vercel vercel bot commented on 7719b62 Jul 31, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please sign in to comment.