Skip to content

Commit

Permalink
Merge pull request #28 from poly-state/feat/batch
Browse files Browse the repository at this point in the history
feat/batch
  • Loading branch information
shahriar-shojib committed Mar 18, 2024
2 parents 9963361 + e6ba9da commit afafd9f
Show file tree
Hide file tree
Showing 16 changed files with 466 additions and 244 deletions.
7 changes: 7 additions & 0 deletions .changeset/hungry-forks-fry.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
"@poly-state/core": patch
---

- Added Batching
- Added callbacks for various store methods
- Added devtoolsConnectionInstance on withDevtools
14 changes: 0 additions & 14 deletions .changeset/pre.json

This file was deleted.

2 changes: 1 addition & 1 deletion .github/workflows/pr.yml
Original file line number Diff line number Diff line change
Expand Up @@ -28,4 +28,4 @@ jobs:
- run: pnpm nx affected --target=format:check --parallel=3
- run: pnpm nx affected --target=build --parallel=3
- run: pnpm nx affected --target=lint --parallel=3
- run: pnpm nx affected --target=test --parallel=3 --ci --code-coverage
- run: pnpm nx affected --target=test --parallel=3
2 changes: 1 addition & 1 deletion .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ jobs:
- run: pnpm nx affected --target=format:check --parallel=3
- run: pnpm nx affected --target=build --parallel=3
- run: pnpm nx affected --target=lint --parallel=3
- run: pnpm nx affected --target=test --parallel=3 --ci --code-coverage
- run: pnpm nx affected --target=test --parallel=3

- name: Create Release Pull Request or Publish to npm
uses: changesets/action@v1
Expand Down
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
node_modules
.DS_Store
.nx
8 changes: 2 additions & 6 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -13,14 +13,10 @@
"nx": "nx",
"release": "changeset publish"
},
"workspaces": [
"packages/*",
"examples/*"
],
"dependencies": {
"@changesets/cli": "^2.23.2",
"@nrwl/nx-cloud": "14.3.0",
"nx": "14.5.4"
"@nrwl/nx-cloud": "latest",
"nx": "latest"
},
"devDependencies": {
"@changesets/changelog-github": "^0.4.5"
Expand Down
8 changes: 4 additions & 4 deletions packages/core/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,12 @@
- Global transactions support

```js
import { transact } from "@poly-state/core";
import { transact } from '@poly-state/core';

transact(() => {
store1.setState({ count: 1 });
store2.setState({ name: test });
store2.setState({ address: "London" });
store1.setState({ count: 1 });
store2.setState({ name: test });
store2.setState({ address: 'London' });
});
```

Expand Down
3 changes: 2 additions & 1 deletion packages/core/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,8 @@
"testRegex": ".spec.ts$",
"transform": {
"^.+\\.(t|j)s$": "@swc/jest"
}
},
"testEnvironment": "jsdom"
},
"repository": {
"type": "git",
Expand Down
4 changes: 2 additions & 2 deletions packages/core/src/devtools/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { ReturnStoreType, StateConstraint } from '../types';
export const withDevTools = <T extends StateConstraint>(
store: ReturnStoreType<T>,
identifier: string
): ReturnStoreType<T> => {
) => {
let devToolsInstance: ReduxDevToolsConnection | null = null;

const connectToDevTools = () => {
Expand Down Expand Up @@ -59,5 +59,5 @@ export const withDevTools = <T extends StateConstraint>(
},
});

return store;
return devToolsInstance as ReduxDevToolsConnection | null;
};
2 changes: 1 addition & 1 deletion packages/core/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { ReturnStoreType, StateConstraint, StoreConfig } from './types';

export const createStore = <T extends StateConstraint>(
initialState: T,
config?: StoreConfig
config?: StoreConfig<T>
): ReturnStoreType<T> => {
const Store = getStoreClass<T>();
return new Store(initialState, config) as unknown as ReturnStoreType<T>;
Expand Down
63 changes: 34 additions & 29 deletions packages/core/src/store.class.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
/* eslint-disable @typescript-eslint/ban-types */
/* eslint-disable @typescript-eslint/no-explicit-any */
import { GLOBAL_TRANSACT } from './transact';
import { getGlobalTransaction } from './transact';
import {
AllSettersMiddlewareCallback,
CallBack,
EqualityComparatorFunction,
GeneratedActions,
MiddlewareCallback,
ReturnStoreType,
Expand All @@ -13,36 +13,36 @@ import {
StoreMiddleWareFunction,
StoreType,
} from './types';
import { capitalize, isFunction } from './utils';
import { capitalize, getStoreIdentifier, isFunction, shallowCompare } from './utils';

type StoreFactory<T extends StateConstraint> = new (
initialState: T,
config?: StoreConfig
config?: StoreConfig<T>
) => ReturnStoreType<T>;

const defaultEqualityChecker = (a: any, b: any) => a === b;

export const getStoreClass = <T extends StateConstraint>(): StoreFactory<T> => {
// eslint-disable-next-line @typescript-eslint/no-unused-vars
interface Store<State extends StateConstraint> {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
[key: string]: any;
}
// eslint-disable-next-line @typescript-eslint/no-unsafe-declaration-merging
class Store<State extends StateConstraint> implements StoreType<State> {
private id = `${Math.random()}${Date.now()}`;
private id: string;

private isHydrated = false;
private listeners = new Set<CallBack<Readonly<State>>>();
private isEqual: EqualityComparatorFunction;

private SET_STATE_MIDDLEWARES: MiddlewareCallback<State>[] = [];
private HYDRATE_MIDDLEWARES: MiddlewareCallback<State>[] = [];
private ALL_SETTERS_MIDDLEWARES: AllSettersMiddlewareCallback<State>[] = [];
private keyMiddlewares: GeneratedActions<State>[] = [];

constructor(private state: State, config: StoreConfig) {
this.isEqual = config?.equalityComparator ?? defaultEqualityChecker;
private get isEqual() {
return this.config.equalityComparator || shallowCompare;
}

constructor(
private state: State,
private config: StoreConfig<State> = {
equalityComparator: shallowCompare,
}
) {
this.id = getStoreIdentifier('');
this.createMethods();
}

Expand All @@ -58,6 +58,7 @@ export const getStoreClass = <T extends StateConstraint>(): StoreFactory<T> => {

this.state = afterMiddleware;
this.isHydrated = true;
this.config.onEvent?.SET_STATE?.(this.state);
}

setState(valueORcallback: SetStateArgs<State>) {
Expand All @@ -74,6 +75,7 @@ export const getStoreClass = <T extends StateConstraint>(): StoreFactory<T> => {
if (!shouldNotifyAll) return;
this.state = newVal;
this.notifySubscribers();
this.config.onEvent?.SET_STATE?.(this.state);

return this;
}
Expand Down Expand Up @@ -111,18 +113,17 @@ export const getStoreClass = <T extends StateConstraint>(): StoreFactory<T> => {
}

private notifySubscribers() {
if (GLOBAL_TRANSACT.running) {
if (GLOBAL_TRANSACT.callBacks.has(this.id)) {
return;
}

GLOBAL_TRANSACT.callBacks.set(this.id, () => {
this.flush();
});
const GLOBAL_TRANSACT = getGlobalTransaction();
if (!GLOBAL_TRANSACT.running) {
return this.flush();
}

if (GLOBAL_TRANSACT.callBacks.has(this.id)) {
return;
}

this.flush();
GLOBAL_TRANSACT.callBacks.set(this.id, () => this.flush());
return;
}

private flush() {
Expand All @@ -137,7 +138,7 @@ export const getStoreClass = <T extends StateConstraint>(): StoreFactory<T> => {
string & keyof State
>}`;

Store.prototype[action] = function (this: Store<State>, valueORcallback: any) {
(Store.prototype as any)[action] = function (this: Store<State>, valueORcallback: any) {
const newVal = isFunction(valueORcallback)
? valueORcallback(this.state[key])
: valueORcallback;
Expand All @@ -157,10 +158,14 @@ export const getStoreClass = <T extends StateConstraint>(): StoreFactory<T> => {

const shouldNotify = !this.isEqual(this.state[key], afterMiddleware);

if (shouldNotify) {
this.state = { ...this.state, [key]: afterMiddleware };
this.notifySubscribers();
if (!shouldNotify) {
return;
}

this.state = { ...this.state, [key]: afterMiddleware };
this.notifySubscribers();
this.config.onEvent?.ALL_SETTERS?.(this.state);
(this.config.onEvent?.[action] as Function | undefined)?.(afterMiddleware);
};
}
}
Expand Down
2 changes: 1 addition & 1 deletion packages/core/src/tests/store-transact.spec.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { createStore, transact } from '..';

describe('Poly Stat Transact functionality', () => {
describe('Poly State Transact functionality', () => {
it('should perform batch updates', () => {
const store = createStore({ count: 0, name: 'test', other: {} });

Expand Down
36 changes: 30 additions & 6 deletions packages/core/src/transact.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,35 @@
export const GLOBAL_TRANSACT = {
callBacks: new Map<string, () => void>(),
running: false,
// eslint-disable-next-line @typescript-eslint/no-explicit-any
type TODO = any;

const SymbolForGlobalTransaction = '__POLY_STATE_GLOBAL_TRANSACTION__';

export const getGlobalTransaction = () => {
const defaultObject = {
callBacks: new Map<string, () => void>(),
running: false,
};

if (typeof globalThis === 'undefined') {
throw new Error('Transact is not possible within this environment');
}

const globalTransaction = (globalThis as TODO)[SymbolForGlobalTransaction];

if (!globalTransaction) {
(globalThis as TODO)[SymbolForGlobalTransaction] = defaultObject;
}

return (globalThis as TODO)[SymbolForGlobalTransaction] as {
callBacks: Map<string, () => void>;
running: boolean;
};
};

export const transact = (cb: () => void) => {
GLOBAL_TRANSACT.running = true;
const globalTransaction = getGlobalTransaction();
globalTransaction.running = true;
cb();
GLOBAL_TRANSACT.callBacks.forEach((cb) => cb());
GLOBAL_TRANSACT.running = false;
globalTransaction.callBacks.forEach((cb) => cb());
globalTransaction.callBacks.clear();
globalTransaction.running = false;
};
13 changes: 11 additions & 2 deletions packages/core/src/types/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,12 +32,21 @@ export type ReturnStoreType<T extends StateConstraint> = StoreType<T> & Generate

export type EqualityComparatorFunction = (a: unknown, b: unknown) => boolean;

export type StoreConfig = {
type GeneratedEvents<T extends StateConstraint> = {
[Key in keyof T as `set${Capitalize<string & Key>}`]?: (newData: T[Key]) => void;
};

export type StoreConfig<T extends StateConstraint> = {
/**
* Custom Equality comparator function
* @default isEqual (built in)
* @default shallowCompare (built in)
*/
equalityComparator?: EqualityComparatorFunction;
onEvent?: GeneratedEvents<T> & {
ALL_SETTERS?: (newData: T) => void;
SET_STATE?: (newData: T) => void;
HYDRATE?: (newData: T) => void;
};
};

export type GeneratedActions<T extends StateConstraint> = {
Expand Down
8 changes: 4 additions & 4 deletions packages/react/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,12 @@
- Global transactions support

```js
import { transact } from "@poly-state/core";
import { transact } from '@poly-state/core';

transact(() => {
store1.setState({ count: 1 });
store2.setState({ name: test });
store2.setState({ address: "London" });
store1.setState({ count: 1 });
store2.setState({ name: test });
store2.setState({ address: 'London' });
});
```

Expand Down
Loading

0 comments on commit afafd9f

Please sign in to comment.