Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

remove store limitations #108

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 0 additions & 2 deletions store/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -202,8 +202,6 @@ Store factory created with `notifyAfterCreation` === `false`.

## Limitations

`Map`, `Set`, `WeakMap`, `WeakSet` cannot be used as values in current implementation.

Mixing proxied values and values from an underlying object can fail for cases where code needs checking for equality.

For example searching for an array element from the underlying object in a proxied array will fail.
Expand Down
48 changes: 39 additions & 9 deletions store/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,44 +11,44 @@
"exports": {
".": {
"types": "./dist/index.d.ts",
"import": "./dist/index.mjs",
"require": "./dist/index.cjs",
"import": "./dist/index.mjs",
"default": "./dist/index.mjs"
},
"./core": {
"types": "./dist/core.d.ts",
"import": "./dist/core.mjs",
"require": "./dist/core.cjs",
"import": "./dist/core.mjs",
"default": "./dist/core.mjs"
},
"./react": {
"types": "./dist/react.d.ts",
"import": "./dist/react.mjs",
"require": "./dist/react.cjs",
"import": "./dist/react.mjs",
"default": "./dist/react.mjs"
},
"./preact": {
"types": "./dist/preact.d.ts",
"import": "./dist/preact.mjs",
"require": "./dist/preact.cjs",
"import": "./dist/preact.mjs",
"default": "./dist/preact.mjs"
},
"./svelte": {
"types": "./dist/svelte.d.ts",
"import": "./dist/svelte.mjs",
"require": "./dist/svelte.cjs",
"import": "./dist/svelte.mjs",
"default": "./dist/svelte.mjs"
},
"./angular": {
"types": "./dist/angular.d.ts",
"import": "./dist/angular.mjs",
"require": "./dist/angular.cjs",
"import": "./dist/angular.mjs",
"default": "./dist/angular.mjs"
},
"./rxjs": {
"types": "./dist/rxjs.d.ts",
"import": "./dist/rxjs.mjs",
"require": "./dist/rxjs.cjs",
"import": "./dist/rxjs.mjs",
"default": "./dist/rxjs.mjs"
},
"./package.json": "./package.json"
Expand Down Expand Up @@ -76,9 +76,10 @@
"proxy"
],
"scripts": {
"build": "pkgbld-internal --umd=index,preact,react,svelte,angular,rxjs",
"build": "pkgbld-internal",
"test": "jest --collectCoverage",
"lint": "eslint ./src"
"lint": "eslint ./src",
"prepack": "pkgbld-internal prune"
},
"devDependencies": {
"@types/react": "^18.0.0",
Expand Down Expand Up @@ -113,5 +114,34 @@
"rxjs": {
"optional": true
}
},
"typesVersions": {
"*": {
".": [
"dist/index.d.ts"
],
"./core": [
"dist/core.d.ts"
],
"./react": [
"dist/react.d.ts"
],
"./preact": [
"dist/preact.d.ts"
],
"./svelte": [
"dist/svelte.d.ts"
],
"./angular": [
"dist/angular.d.ts"
],
"./rxjs": [
"dist/rxjs.d.ts"
],
"*": [
"dist/index.d.ts",
"dist/*"
]
}
}
}
14 changes: 9 additions & 5 deletions store/src/core.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,20 +31,24 @@ export const createStoreFactory = (notifyAfterCreation: boolean) => {
}
};
const handler: ProxyHandler<T> = {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
set(target: T, p: string | symbol, value: any, receiver: any) {
set(target: T, p: string | symbol, value: unknown, receiver: unknown) {
const realValue = unwrapValue(value);
if (Reflect.get(target, p, receiver) !== realValue) {
Reflect.set(target, p, realValue, receiver);
enqueueNotification();
}
return true;
},
// eslint-disable-next-line @typescript-eslint/no-explicit-any
get(target: T, p: string | symbol, receiver: any) {
get(target: T, p: string | symbol, receiver: unknown) {
if (p === unwrap) return target;
const value = Reflect.get(target, p, receiver);
return value !== null && typeof value === 'object' && !(value instanceof RegExp) ? createProxy(value as T) : value;
const valueType = typeof value;
// TODO maybe cache the wrapper function
// TODO check if arrow function is fine here
return valueType === 'function' ? (...args: unknown[]) => {
enqueueNotification();
return (value as Function).apply(target, args.map(unwrapValue));
} : (value !== null && valueType === 'object' ? createProxy(value as T) : value);
},
defineProperty(...args: [T, string | symbol, PropertyDescriptor]) {
enqueueNotification();
Expand Down
69 changes: 42 additions & 27 deletions store/tests/index.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { createStoreFactory, unwrapValue } from '../src';
import util from 'util';

const createStore = createStoreFactory(false);
const createStoreWithNofificationAboutInitialState = createStoreFactory(true);
const createStoreWithNotificationAboutInitialState = createStoreFactory(true);

function flushPromises() {
return new Promise(resolve => setTimeout(resolve));
Expand All @@ -21,7 +21,7 @@ describe('store', () => {
store(subscriber);
state.prop = 'test2';
await flushPromises();
expect(subscriber).toBeCalledWith(expect.objectContaining({
expect(subscriber).toHaveBeenCalledWith(expect.objectContaining({
prop: 'test2'
}));
expect(store().prop).toBe('test2');
Expand All @@ -34,7 +34,7 @@ describe('store', () => {
store(subscriber);
state.prop = 42;
await flushPromises();
expect(subscriber).toBeCalledWith(expect.objectContaining({
expect(subscriber).toHaveBeenCalledWith(expect.objectContaining({
prop: 42
}));
expect(store().prop).toBe(42);
Expand All @@ -47,7 +47,7 @@ describe('store', () => {
store(subscriber);
state.prop = true;
await flushPromises();
expect(subscriber).toBeCalledWith(expect.objectContaining({
expect(subscriber).toHaveBeenCalledWith(expect.objectContaining({
prop: true
}));
expect(store().prop).toBe(true);
Expand All @@ -60,7 +60,7 @@ describe('store', () => {
store(subscriber);
state.prop = null;
await flushPromises();
expect(subscriber).toBeCalledWith(expect.objectContaining({
expect(subscriber).toHaveBeenCalledWith(expect.objectContaining({
prop: null
}));
expect(store().prop).toBe(null);
Expand All @@ -73,7 +73,7 @@ describe('store', () => {
store(subscriber);
state.prop = undefined;
await flushPromises();
expect(subscriber).toBeCalledWith(expect.objectContaining({
expect(subscriber).toHaveBeenCalledWith(expect.objectContaining({
prop: undefined
}));
expect(store().prop).toBe(undefined);
Expand All @@ -87,7 +87,7 @@ describe('store', () => {
store(subscriber);
state.prop = { b: 2 };
await flushPromises();
expect(subscriber).toBeCalledWith(expect.objectContaining({
expect(subscriber).toHaveBeenCalledWith(expect.objectContaining({
prop: { b: 2 }
}));
expect(store().prop).toEqual({ b: 2 });
Expand All @@ -100,7 +100,7 @@ describe('store', () => {
store(subscriber);
state.push(42);
await flushPromises();
expect(subscriber).toBeCalledWith([42]);
expect(subscriber).toHaveBeenCalledWith([42]);
expect(store()).toEqual([42]);
expect(state).toEqual([42]);
});
Expand All @@ -122,7 +122,7 @@ describe('store', () => {
await flushPromises();
state.prop = 24;
await flushPromises();
expect(subscriber).toBeCalledWith(expect.objectContaining({
expect(subscriber).toHaveBeenCalledWith(expect.objectContaining({
prop: 24
}));
expect(store().prop).toBe(24);
Expand All @@ -137,7 +137,7 @@ describe('store', () => {
value: 42
});
await flushPromises();
expect(subscriber).toBeCalledWith(expect.objectContaining({
expect(subscriber).toHaveBeenCalledWith(expect.objectContaining({
prop: 42
}));
expect(store().prop).toBe(42);
Expand Down Expand Up @@ -195,7 +195,7 @@ describe('store', () => {
store(subscriber);
state.prop.a = 2;
await flushPromises();
expect(subscriber).toBeCalledWith(expect.objectContaining({
expect(subscriber).toHaveBeenCalledWith(expect.objectContaining({
prop: { a: 2 }
}));
expect(store().prop).toEqual({ a: 2 });
Expand All @@ -208,7 +208,7 @@ describe('store', () => {
store(subscriber);
delete state.prop;
await flushPromises();
expect(subscriber).toBeCalledWith(expect.objectContaining({}));
expect(subscriber).toHaveBeenCalledWith(expect.objectContaining({}));
expect(store().prop).toBe(undefined);
expect(state.prop).toBe(undefined);
});
Expand All @@ -219,7 +219,7 @@ describe('store', () => {
store(subscriber);
state.push(state[0] as {prop: number});
await flushPromises();
expect(subscriber).toBeCalledWith([{prop: 42},{prop: 42}]);
expect(subscriber).toHaveBeenCalledWith([{prop: 42},{prop: 42}]);
expect(store()).toEqual([{prop: 42},{prop: 42}]);
expect(state).toEqual([{prop: 42},{prop: 42}]);
expect(store().every(item => !util.types.isProxy(item))).toBeTruthy();
Expand All @@ -231,7 +231,7 @@ describe('store', () => {
store(subscriber);
(state.data[0] as {prop: string}).prop += 'test';
await flushPromises();
expect(subscriber).toBeCalledWith({data:[{prop: 'test'}]});
expect(subscriber).toHaveBeenCalledWith({data:[{prop: 'test'}]});
expect(store()).toEqual({data:[{prop: 'test'}]});
expect(state).toEqual({data:[{prop: 'test'}]});
});
Expand All @@ -242,11 +242,26 @@ describe('store', () => {
expect(index).toBe(0);
});

// it('find index in array (mixed objects)', () => {
// const [state, store] = createStore({data:[{prop: ''}]});
// const index = state.data.indexOf(store().data[0]);
// expect(index).toBe(0);
// });
it('Map in store', () => {
const [state] = createStore(new Map([['prop', 'test']]));
expect(state.get('prop')).toBe('test');
});

it('Map in store (set value)', async () => {
const subscriber = jest.fn();
const value = new Map([['prop', 'test']]);
const [state, store] = createStore(value);
store(subscriber);
state.set('prop', 'test2');
await flushPromises();
expect(subscriber).toHaveBeenCalledWith(value);
});

it('find index in array (mixed objects)', () => {
const [state, store] = createStore({data:[{prop: ''}]});
const index = state.data.indexOf(store().data[0]!);
expect(index).toBe(0);
});

it('swap in array', async () => {
const subscriber = jest.fn();
Expand All @@ -256,7 +271,7 @@ describe('store', () => {
(state.data[0] as {prop: string}) = state.data[1] as {prop: string};
(state.data[1] as {prop: string}) = temp;
await flushPromises();
expect(subscriber).toBeCalledWith({data:[{prop: '2'},{prop: '1'}]});
expect(subscriber).toHaveBeenCalledWith({data:[{prop: '2'},{prop: '1'}]});
expect(store()).toEqual({data:[{prop: '2'},{prop: '1'}]});
expect(state).toEqual({data:[{prop: '2'},{prop: '1'}]});
});
Expand All @@ -267,7 +282,7 @@ describe('store', () => {
store(subscriber);
Object.assign(state, {test: true});
await flushPromises();
expect(subscriber).toBeCalledWith({test: true});
expect(subscriber).toHaveBeenCalledWith({test: true});
expect(store()).toEqual({test: true});
expect(state).toEqual({test: true});
});
Expand All @@ -278,7 +293,7 @@ describe('store', () => {
store(subscriber);
Object.assign(state, {test: true});
await flushPromises();
expect(subscriber).toBeCalledWith({data: false, test: true});
expect(subscriber).toHaveBeenCalledWith({data: false, test: true});
expect(store()).toEqual({data: false, test: true});
expect(state).toEqual({data: false, test: true});
});
Expand Down Expand Up @@ -352,20 +367,20 @@ describe('store', () => {
describe('store with initial notification', () => {
it('notifies after creation', async () => {
const subscriber = jest.fn();
const [, store] = createStoreWithNofificationAboutInitialState({prop: 'test'});
const [, store] = createStoreWithNotificationAboutInitialState({prop: 'test'});
store(subscriber);
await flushPromises();
expect(subscriber).toBeCalledTimes(1);
expect(subscriber).toBeCalledWith({prop: 'test'});
expect(subscriber).toHaveBeenCalledWith({prop: 'test'});
});

it('deafult state', async () => {
it('default state', async () => {
const subscriber = jest.fn();
const [, store] = createStoreWithNofificationAboutInitialState();
const [, store] = createStoreWithNotificationAboutInitialState();
store(subscriber);
await flushPromises();
expect(subscriber).toBeCalledTimes(1);
expect(subscriber).toBeCalledWith({});
expect(subscriber).toHaveBeenCalledWith({});
});
});

Expand Down
Loading