-
Notifications
You must be signed in to change notification settings - Fork 78
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
Store Middleware #391
Store Middleware #391
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,54 @@ | ||
import { destroy, invalidator, create } from '../vdom'; | ||
import injector from '../middleware/injector'; | ||
import Store, { StatePaths, Path } from '../../stores/Store'; | ||
import { Process } from '../../stores/process'; | ||
|
||
const factory = create({ destroy, invalidator, injector }); | ||
|
||
export const createStoreMiddleware = <S = any>(initial?: (store: Store<S>) => void) => { | ||
let store = new Store<S>(); | ||
let initialized = false; | ||
initial && initial(store); | ||
const storeMiddleware = factory(({ middleware: { destroy, invalidator, injector } }) => { | ||
const handles: any[] = []; | ||
destroy(() => { | ||
let handle: any; | ||
while ((handle = handles.pop())) { | ||
handle(); | ||
} | ||
}); | ||
if (!initialized) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Does a one time check for a store in the application registry, if it exists the middleware will use that instead |
||
const injectedStore = injector.get<Store<S>>('state'); | ||
if (injectedStore) { | ||
store = injectedStore; | ||
} | ||
initialized = true; | ||
} | ||
const registeredPaths: string[] = []; | ||
const path: StatePaths<S> = (path: any, ...segments: any) => { | ||
return (store as any).path(path, ...segments); | ||
}; | ||
return { | ||
get<U = any>(path: Path<S, U>): U { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Automatically registers paths to invalidate on for a widget |
||
if (registeredPaths.indexOf(path.path) === -1) { | ||
const handle = store.onChange(path, () => { | ||
invalidator(); | ||
}); | ||
handles.push(() => handle.remove()); | ||
registeredPaths.push(path.path); | ||
} | ||
return store.get(path); | ||
}, | ||
path, | ||
at<U = any>(path: Path<S, U[]>, index: number) { | ||
return store.at(path, index); | ||
}, | ||
executor<T extends Process<any, any>>(process: T): ReturnType<T> { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. function that returns an executor from a process, |
||
return process(store) as any; | ||
} | ||
}; | ||
}); | ||
return storeMiddleware; | ||
}; | ||
|
||
export default createStoreMiddleware; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Exports a factory that creates the middleware, this is so that it can be typed with the store state interface, also factories a Store that is shared across all instances of the middleware. |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,158 @@ | ||
const { it, describe, afterEach, beforeEach } = intern.getInterface('bdd'); | ||
const { assert } = intern.getPlugin('chai'); | ||
import { sandbox } from 'sinon'; | ||
|
||
import { createProcess } from '../../../../src/stores/process'; | ||
import { replace } from '../../../../src/stores/state/operations'; | ||
import Store from '../../../../src/stores/Store'; | ||
import createStoreMiddleware from '../../../../src/core/middleware/store'; | ||
|
||
const sb = sandbox.create(); | ||
const destroyStub = sb.stub(); | ||
const invalidatorStub = sb.stub(); | ||
const injectorStub = { | ||
get: sb.stub(), | ||
subscribe: sb.stub() | ||
}; | ||
let storeMiddleware = createStoreMiddleware(); | ||
|
||
describe('store middleware', () => { | ||
beforeEach(() => { | ||
storeMiddleware = createStoreMiddleware(); | ||
}); | ||
|
||
afterEach(() => { | ||
sb.resetHistory(); | ||
}); | ||
|
||
it('Should return data from store and subscribe to data changes for the path', () => { | ||
const { callback } = storeMiddleware(); | ||
const store = callback({ | ||
id: 'test', | ||
middleware: { | ||
destroy: destroyStub, | ||
invalidator: invalidatorStub, | ||
injector: injectorStub | ||
}, | ||
properties: {} | ||
}); | ||
let result = store.get(store.path('my-state')); | ||
assert.isUndefined(result); | ||
const testProcess = createProcess('test', [ | ||
({ path }) => { | ||
return [replace(path('my-state'), 'test-data')]; | ||
} | ||
]); | ||
store.executor(testProcess)({}); | ||
assert.isTrue(invalidatorStub.calledOnce); | ||
result = store.get(store.path('my-state')); | ||
assert.strictEqual(result, 'test-data'); | ||
}); | ||
|
||
it('Should be able to work with arrays in the store', () => { | ||
const { callback } = storeMiddleware(); | ||
const store = callback({ | ||
id: 'test', | ||
middleware: { | ||
destroy: destroyStub, | ||
invalidator: invalidatorStub, | ||
injector: injectorStub | ||
}, | ||
properties: {} | ||
}); | ||
let result = store.get(store.path('my-state')); | ||
assert.isUndefined(result); | ||
let testProcess = createProcess('test', [ | ||
({ path }) => { | ||
return [replace(path('my-state'), [1])]; | ||
} | ||
]); | ||
store.executor(testProcess)({}); | ||
assert.isTrue(invalidatorStub.calledOnce); | ||
result = store.get(store.at(store.path('my-state'), 0)); | ||
assert.deepEqual(result, 1); | ||
testProcess = createProcess('test', [ | ||
({ path, at }) => { | ||
return [replace(at(path('my-state'), 1), 2)]; | ||
} | ||
]); | ||
store.executor(testProcess)({}); | ||
assert.isTrue(invalidatorStub.calledTwice); | ||
result = store.get(store.at(store.path('my-state'), 1)); | ||
assert.deepEqual(result, 2); | ||
}); | ||
|
||
it('Should remove subscription handles when destroyed', () => { | ||
const { callback } = storeMiddleware(); | ||
const store = callback({ | ||
id: 'test', | ||
middleware: { | ||
destroy: destroyStub, | ||
invalidator: invalidatorStub, | ||
injector: injectorStub | ||
}, | ||
properties: {} | ||
}); | ||
let result = store.get(store.path('my-state')); | ||
assert.isUndefined(result); | ||
let testProcess = createProcess('test', [ | ||
({ path }) => { | ||
return [replace(path('my-state'), 'test-data')]; | ||
} | ||
]); | ||
store.executor(testProcess)({}); | ||
assert.isTrue(invalidatorStub.calledOnce); | ||
result = store.get(store.path('my-state')); | ||
assert.strictEqual(result, 'test-data'); | ||
destroyStub.getCall(0).callArg(0); | ||
testProcess = createProcess('test', [ | ||
({ path }) => { | ||
return [replace(path('my-state'), 'test')]; | ||
} | ||
]); | ||
store.executor(testProcess)({}); | ||
assert.isTrue(invalidatorStub.calledOnce); | ||
result = store.get(store.path('my-state')); | ||
assert.strictEqual(result, 'test'); | ||
}); | ||
|
||
it('Should use an injected store if available', () => { | ||
const { callback } = storeMiddleware(); | ||
const injectedStore = new Store(); | ||
let testProcess = createProcess('test', [ | ||
({ path }) => { | ||
return [replace(path('my', 'nested', 'state'), 'existing-data')]; | ||
} | ||
]); | ||
testProcess(injectedStore)({}); | ||
injectorStub.get.returns(injectedStore); | ||
const store = callback({ | ||
id: 'test', | ||
middleware: { | ||
destroy: destroyStub, | ||
invalidator: invalidatorStub, | ||
injector: injectorStub | ||
}, | ||
properties: {} | ||
}); | ||
const result = store.get(store.path('my', 'nested', 'state')); | ||
assert.strictEqual(result, 'existing-data'); | ||
const otherStore = callback({ | ||
id: 'test', | ||
middleware: { | ||
destroy: destroyStub, | ||
invalidator: invalidatorStub, | ||
injector: injectorStub | ||
}, | ||
properties: {} | ||
}); | ||
const resultTwo = otherStore.get(store.path('my', 'nested', 'state')); | ||
assert.strictEqual(resultTwo, 'existing-data'); | ||
}); | ||
|
||
it('Should run initialize function on creation', () => { | ||
const init = sb.stub(); | ||
storeMiddleware = createStoreMiddleware(init); | ||
assert.isTrue(init.calledOnce); | ||
}); | ||
}); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Accepts an optional
initial
(perhaps should be calledinit
orinitialize
) function that receives the factorie'd store and can initialize state etc.