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

simple api, 1st pass #52

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
104 changes: 99 additions & 5 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,10 +23,10 @@ export interface Store<State extends object> {
getState(): Readonly<State>
}

export class StoreSnapshot<State extends object> implements Store<State> {
export class LegacyStoreSnapshot<State extends object> implements Store<State> {
constructor(
private state: State,
private storeDefinition: StoreDefinition<State>
private storeDefinition: LegacyStoreDefinition<State>
) { }
get<K extends keyof State>(key: K) {
return this.state[key]
Expand All @@ -45,6 +45,44 @@ export class StoreSnapshot<State extends object> implements Store<State> {
}
}

function StoreSnapshot<State extends object>(
state: State,
storeDefinition: StoreDefinition<State>
): State {
return Object.freeze(Object.preventExtensions(
Object.defineProperties({}, mapValues(state, (value, key) => {
let d = Object.getOwnPropertyDescriptor(state, key)
let memoizeCache: Partial<State> = {}
if (d && d.get) {
return {
configurable: false,
enumerable: true,
get() {
if (key in memoizeCache) {
return memoizeCache[key]
}
memoizeCache[key] = state[key]
return memoizeCache[key]
},
set(v: typeof value) {
return storeDefinition.set(key)(v)
}
}
}
return {
configurable: false,
enumerable: true,
get() {
return value
},
set(v: typeof value) {
return storeDefinition.set(key)(v)
}
}
}))
))
}

export type Options = {
isDevMode: boolean
}
Expand All @@ -54,7 +92,63 @@ let DEFAULT_OPTIONS: Readonly<Options> = {
}

export class StoreDefinition<State extends object> implements Store<State> {
private storeSnapshot: StoreSnapshot<State>
private storeSnapshot: State
private alls: Emitter<Undux<State>>
private emitter: Emitter<State>
private setters: {
readonly [K in keyof State]: (value: State[K]) => void
}
constructor(state: State, options: Options) {

let emitterOptions = {
isDevMode: options.isDevMode,
onCycle(chain: (string | number | symbol)[]) {
console.error(CYCLE_ERROR_MESSAGE + chain.join(' -> '))
}
}

// Initialize emitters
this.alls = new Emitter(emitterOptions)
this.emitter = new Emitter(emitterOptions)

// Set initial state
this.storeSnapshot = StoreSnapshot(state, this)

// Cache setters
this.setters = mapValues(state, (v, key) =>
(value: typeof v) => {
let previousValue = this.storeSnapshot[key]
this.storeSnapshot = StoreSnapshot(Object.assign({}, this.storeSnapshot, { [key]: value }), this)
this.emitter.emit(key, value)
this.alls.emit(key, { key, previousValue, value })
}
)
}
on<K extends keyof State>(key: K): Observable<State[K]> {
return this.emitter.on(key)
}
onAll(): Observable<Undux<State>[keyof State]> {
return this.alls.all()
}
get<K extends keyof State>(key: K) {
return this.storeSnapshot[key]
}
set<K extends keyof State>(key: K): (value: State[K]) => void {
return this.setters[key]
}
getCurrentSnapshot() {
return this.storeSnapshot
}
toStore(): State {
return this.storeSnapshot
}
getState() {
return this.storeSnapshot
}
}

export class LegacyStoreDefinition<State extends object> implements Store<State> {
private storeSnapshot: LegacyStoreSnapshot<State>
private alls: Emitter<Undux<State>>
private emitter: Emitter<State>
private setters: {
Expand All @@ -74,13 +168,13 @@ export class StoreDefinition<State extends object> implements Store<State> {
this.emitter = new Emitter(emitterOptions)

// Set initial state
this.storeSnapshot = new StoreSnapshot(state, this)
this.storeSnapshot = new LegacyStoreSnapshot(state, this)

// Cache setters
this.setters = mapValues(state, (v, key) =>
(value: typeof v) => {
let previousValue = this.storeSnapshot.get(key)
this.storeSnapshot = new StoreSnapshot(
this.storeSnapshot = new LegacyStoreSnapshot(
Object.assign({}, this.storeSnapshot.getState(), { [key]: value }),
this
)
Expand Down
6 changes: 3 additions & 3 deletions src/react/connect.tsx
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
import * as React from 'react'
import { Subscription } from 'rxjs'
import { Store, StoreDefinition, StoreSnapshot } from '../'
import { LegacyStoreDefinition, LegacyStoreSnapshot, Store } from '../'
import { Diff, equals, getDisplayName } from '../utils'

/**
* @deprecated Use `createConnectedStore` instead.
*/
export function connect<StoreState extends object>(store: StoreDefinition<StoreState>) {
export function connect<StoreState extends object>(store: LegacyStoreDefinition<StoreState>) {
return function <
Props,
PropsWithStore extends { store: Store<StoreState> } & Props = { store: Store<StoreState> } & Props
Expand All @@ -15,7 +15,7 @@ export function connect<StoreState extends object>(store: StoreDefinition<StoreS
): React.ComponentClass<Diff<PropsWithStore, { store: Store<StoreState> }>> {

type State = {
store: StoreSnapshot<StoreState>
store: LegacyStoreSnapshot<StoreState>
subscription: Subscription
}

Expand Down
4 changes: 2 additions & 2 deletions src/react/connectAs.tsx
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
import * as React from 'react'
import { Subscription } from 'rxjs'
import { StoreDefinition } from '../'
import { LegacyStoreDefinition } from '../'
import { Diff, equals, getDisplayName, keys, mapValues, some } from '../utils'

/**
* @deprecated Use `createConnectedStoreAs` instead.
*/
export function connectAs<
Stores extends {[alias: string]: StoreDefinition<any>}
Stores extends {[alias: string]: LegacyStoreDefinition<any>}
>(
stores: Stores
) {
Expand Down
16 changes: 8 additions & 8 deletions src/react/createConnectedStore.tsx
Original file line number Diff line number Diff line change
@@ -1,15 +1,15 @@
import * as React from 'react'
import { Subscription } from 'rxjs'
import { createStore, Effects, Store, StoreDefinition, StoreSnapshot } from '..'
import { createStore, Effects, StoreDefinition } from '..'
import { Diff, getDisplayName } from '../utils'

export type Connect<State extends object> = {
Container: React.ComponentType<ContainerProps<State>>
withStore: <
Props extends {store: Store<State>}
Props extends {store: State}
>(
Component: React.ComponentType<Props>
) => React.ComponentType<Diff<Props, {store: Store<State>}>>
) => React.ComponentType<Diff<Props, {store: State}>>
}

export type ContainerProps<State extends object> = {
Expand All @@ -25,7 +25,7 @@ export function createConnectedStore<State extends object>(

type ContainerState = {
storeDefinition: StoreDefinition<State> | null
storeSnapshot: StoreSnapshot<State> | null
storeSnapshot: State | null
subscription: Subscription
}

Expand Down Expand Up @@ -67,7 +67,7 @@ export function createConnectedStore<State extends object>(
}

let Consumer = (props: {
children: (store: StoreSnapshot<State>) => JSX.Element
children: (store: State) => JSX.Element
displayName: string
}) =>
<Context.Consumer>
Expand All @@ -80,8 +80,8 @@ export function createConnectedStore<State extends object>(
</Context.Consumer>

function withStore<
Props extends {store: Store<State>},
PropsWithoutStore = Diff<Props, {store: Store<State>}>
Props extends {store: State},
PropsWithoutStore = Diff<Props, {store: State}>
>(
Component: React.ComponentType<Props>
): React.ComponentType<PropsWithoutStore> {
Expand All @@ -101,7 +101,7 @@ export function createConnectedStore<State extends object>(
}

function isInitialized<State extends object>(
store: StoreSnapshot<State> | {__MISSING_PROVIDER__: true}
store: State | {__MISSING_PROVIDER__: true}
) {
return !('__MISSING_PROVIDER__' in store)
}
8 changes: 4 additions & 4 deletions src/react/createConnectedStoreAs.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import * as React from 'react'
import { Subscription } from 'rxjs'
import { createStore, EffectsAs, Store, StoreDefinition, StoreSnapshot } from '..'
import { createStore, EffectsAs, LegacyStoreSnapshot, Store, StoreDefinition } from '..'
import { Diff, getDisplayName, mapValues } from '../utils'

export type ConnectAs<States extends {
Expand Down Expand Up @@ -35,7 +35,7 @@ export function createConnectedStoreAs<States extends {
[K in keyof States]: StoreDefinition<States[K]> | null
}
storeSnapshots: {
[K in keyof States]: StoreSnapshot<States[K]> | null
[K in keyof States]: LegacyStoreSnapshot<States[K]> | null
}
subscriptions: {
[K in keyof States]: Subscription
Expand Down Expand Up @@ -87,7 +87,7 @@ export function createConnectedStoreAs<States extends {
}

let Consumer = (props: {
children: (stores: { [K in keyof States]: StoreSnapshot<States[K]> }) => JSX.Element
children: (stores: { [K in keyof States]: LegacyStoreSnapshot<States[K]> }) => JSX.Element
displayName: string
}) =>
<Context.Consumer>
Expand Down Expand Up @@ -122,7 +122,7 @@ export function createConnectedStoreAs<States extends {
}

function isInitialized<State extends object>(
store: StoreSnapshot<State> | {__MISSING_PROVIDER__: true}
store: LegacyStoreSnapshot<State> | {__MISSING_PROVIDER__: true}
) {
return !('__MISSING_PROVIDER__' in store)
}