Skip to content

Commit

Permalink
Implemented observable sets (#1853)
Browse files Browse the repository at this point in the history
* implement observable set

* WIP

* Added some missing bits and pieces
  • Loading branch information
mweststrate authored Jan 21, 2019
1 parent 3186d2a commit bf02e77
Show file tree
Hide file tree
Showing 16 changed files with 666 additions and 17 deletions.
17 changes: 14 additions & 3 deletions src/api/become-observed.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,17 @@ import {
Lambda,
ObservableMap,
fail,
getAtom
getAtom,
ObservableSet
} from "../internal"

export function onBecomeObserved(
value: IObservable | IComputedValue<any> | IObservableArray<any> | ObservableMap<any, any>,
value:
| IObservable
| IComputedValue<any>
| IObservableArray<any>
| ObservableMap<any, any>
| ObservableSet<any>,
listener: Lambda
): Lambda
export function onBecomeObserved<K, V = any>(
Expand All @@ -22,7 +28,12 @@ export function onBecomeObserved(thing, arg2, arg3?): Lambda {
}

export function onBecomeUnobserved(
value: IObservable | IComputedValue<any> | IObservableArray<any> | ObservableMap<any, any>,
value:
| IObservable
| IComputedValue<any>
| IObservableArray<any>
| ObservableMap<any, any>
| ObservableSet<any>,
listener: Lambda
): Lambda
export function onBecomeUnobserved<K, V = any>(
Expand Down
7 changes: 6 additions & 1 deletion src/api/intercept-read.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,8 @@ import {
isObservableArray,
isObservableMap,
isObservableObject,
isObservableValue
isObservableValue,
ObservableSet
} from "../internal"

export type ReadInterceptor<T> = (value: any) => T
Expand All @@ -23,6 +24,10 @@ export function interceptReads<K, V>(
observableMap: ObservableMap<K, V>,
handler: ReadInterceptor<V>
): Lambda
export function interceptReads<V>(
observableSet: ObservableSet<V>,
handler: ReadInterceptor<V>
): Lambda
export function interceptReads(
object: Object,
property: string,
Expand Down
8 changes: 7 additions & 1 deletion src/api/intercept.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,9 @@ import {
IValueWillChange,
Lambda,
ObservableMap,
getAdministration
getAdministration,
ObservableSet,
ISetWillChange
} from "../internal"

export function intercept<T>(
Expand All @@ -24,6 +26,10 @@ export function intercept<K, V>(
observableMap: ObservableMap<K, V>,
handler: IInterceptor<IMapWillChange<K, V>>
): Lambda
export function intercept<V>(
observableMap: ObservableSet<V>,
handler: IInterceptor<ISetWillChange<V>>
): Lambda
export function intercept<K, V>(
observableMap: ObservableMap<K, V>,
property: K,
Expand Down
24 changes: 22 additions & 2 deletions src/api/object-api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,19 +3,22 @@ import {
IIsObservableObject,
IObservableArray,
ObservableMap,
ObservableSet,
ObservableObjectAdministration,
endBatch,
fail,
getAdministration,
invariant,
isObservableArray,
isObservableMap,
isObservableSet,
isObservableObject,
startBatch
} from "../internal"

export function keys<K>(map: ObservableMap<K, any>): ReadonlyArray<K>
export function keys<T>(ar: IObservableArray<T>): ReadonlyArray<number>
export function keys<T>(set: ObservableSet<T>): ReadonlyArray<T>
export function keys<T extends Object>(obj: T): ReadonlyArray<string>
export function keys(obj: any): any {
if (isObservableObject(obj)) {
Expand All @@ -24,16 +27,20 @@ export function keys(obj: any): any {
if (isObservableMap(obj)) {
return Array.from(obj.keys())
}
if (isObservableSet(obj)) {
return Array.from(obj.keys())
}
if (isObservableArray(obj)) {
return obj.map((_, index) => index)
}
return fail(
process.env.NODE_ENV !== "production" &&
"'keys()' can only be used on observable objects, arrays and maps"
"'keys()' can only be used on observable objects, arrays, sets and maps"
)
}

export function values<K, T>(map: ObservableMap<K, T>): ReadonlyArray<T>
export function values<T>(set: ObservableSet<T>): ReadonlyArray<T>
export function values<T>(ar: IObservableArray<T>): ReadonlyArray<T>
export function values<T = any>(obj: T): ReadonlyArray<any>
export function values(obj: any): string[] {
Expand All @@ -43,16 +50,20 @@ export function values(obj: any): string[] {
if (isObservableMap(obj)) {
return keys(obj).map(key => obj.get(key))
}
if (isObservableSet(obj)) {
return Array.from(obj.values())
}
if (isObservableArray(obj)) {
return obj.slice()
}
return fail(
process.env.NODE_ENV !== "production" &&
"'values()' can only be used on observable objects, arrays and maps"
"'values()' can only be used on observable objects, arrays, sets and maps"
)
}

export function entries<K, T>(map: ObservableMap<K, T>): ReadonlyArray<[K, T]>
export function entries<T>(set: ObservableSet<T>): ReadonlyArray<[T, T]>
export function entries<T>(ar: IObservableArray<T>): ReadonlyArray<[number, T]>
export function entries<T = any>(obj: T): ReadonlyArray<[string, any]>
export function entries(obj: any): any {
Expand All @@ -62,6 +73,9 @@ export function entries(obj: any): any {
if (isObservableMap(obj)) {
return keys(obj).map(key => [key, obj.get(key)])
}
if (isObservableSet(obj)) {
return Array.from(obj.entries())
}
if (isObservableArray(obj)) {
return obj.map((key, index) => [index, key])
}
Expand Down Expand Up @@ -113,13 +127,16 @@ export function set(obj: any, key: any, value?: any): void {
}

export function remove<K, V>(obj: ObservableMap<K, V>, key: K)
export function remove<T>(obj: ObservableSet<T>, key: T)
export function remove<T>(obj: IObservableArray<T>, index: number)
export function remove<T extends Object>(obj: T, key: string)
export function remove(obj: any, key: any): void {
if (isObservableObject(obj)) {
;((obj as any) as IIsObservableObject)[$mobx].remove(key)
} else if (isObservableMap(obj)) {
obj.delete(key)
} else if (isObservableSet(obj)) {
obj.delete(key)
} else if (isObservableArray(obj)) {
if (typeof key !== "number") key = parseInt(key, 10)
invariant(key >= 0, `Not a valid index: '${key}'`)
Expand All @@ -133,6 +150,7 @@ export function remove(obj: any, key: any): void {
}

export function has<K>(obj: ObservableMap<K, any>, key: K): boolean
export function has<T>(obj: ObservableSet<T>, key: T): boolean
export function has<T>(obj: IObservableArray<T>, index: number): boolean
export function has<T extends Object>(obj: T, key: string): boolean
export function has(obj: any, key: any): boolean {
Expand All @@ -142,6 +160,8 @@ export function has(obj: any, key: any): boolean {
return adm.has(key)
} else if (isObservableMap(obj)) {
return obj.has(key)
} else if (isObservableSet(obj)) {
return obj.has(key)
} else if (isObservableArray(obj)) {
return key >= 0 && key < obj.length
} else {
Expand Down
19 changes: 18 additions & 1 deletion src/api/observable.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,11 @@ import {
IObservableArray,
IObservableDecorator,
IObservableMapInitialValues,
IObservableSetInitialValues,
IObservableObject,
IObservableValue,
ObservableMap,
ObservableSet,
ObservableValue,
createDecoratorForEnhancer,
createDynamicObservableObject,
Expand All @@ -14,6 +16,7 @@ import {
extendObservable,
fail,
isES6Map,
isES6Set,
isObservable,
isPlainObject,
refStructEnhancer,
Expand Down Expand Up @@ -88,7 +91,9 @@ function createObservable(v: any, arg2?: any, arg3?: any) {
? observable.array(v, arg2)
: isES6Map(v)
? observable.map(v, arg2)
: v
: isES6Set(v)
? observable.set(v, arg2)
: v

// this value could be converted to a new observable data structure, return it
if (res !== v) return res
Expand Down Expand Up @@ -116,6 +121,10 @@ export interface IObservableFactory {
export interface IObservableFactories {
box<T = any>(value?: T, options?: CreateObservableOptions): IObservableValue<T>
array<T = any>(initialValues?: T[], options?: CreateObservableOptions): IObservableArray<T>
set<T = any>(
initialValues?: IObservableSetInitialValues<T>,
options?: CreateObservableOptions
): ObservableSet<T>
map<K = any, V = any>(
initialValues?: IObservableMapInitialValues<K, V>,
options?: CreateObservableOptions
Expand Down Expand Up @@ -157,6 +166,14 @@ const observableFactories: IObservableFactories = {
const o = asCreateObservableOptions(options)
return new ObservableMap<K, V>(initialValues, getEnhancerFromOptions(o), o.name)
},
set<T = any>(
initialValues?: IObservableSetInitialValues<T>,
options?: CreateObservableOptions
): ObservableSet<T> {
if (arguments.length > 2) incorrectlyUsedAsDecorator("set")
const o = asCreateObservableOptions(options)
return new ObservableSet<T>(initialValues, getEnhancerFromOptions(o), o.name)
},
object<T = any>(
props: T,
decorators?: { [K in keyof T]: Function },
Expand Down
9 changes: 8 additions & 1 deletion src/api/observe.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,9 @@ import {
IValueDidChange,
Lambda,
ObservableMap,
getAdministration
getAdministration,
ObservableSet,
ISetDidChange
} from "../internal"

export function observe<T>(
Expand All @@ -22,6 +24,11 @@ export function observe<T>(
listener: (change: IArrayChange<T> | IArraySplice<T>) => void,
fireImmediately?: boolean
): Lambda
export function observe<V>(
observableMap: ObservableSet<V>,
listener: (change: ISetDidChange<V>) => void,
fireImmediately?: boolean
): Lambda
export function observe<K, V>(
observableMap: ObservableMap<K, V>,
listener: (change: IMapDidChange<K, V>) => void,
Expand Down
21 changes: 19 additions & 2 deletions src/api/tojs.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,8 @@ import {
isObservable,
isObservableArray,
isObservableValue,
isObservableMap
isObservableMap,
isObservableSet
} from "../internal"

export type ToJSOptions = {
Expand All @@ -27,7 +28,7 @@ function toJSHelper(source, options: ToJSOptions, __alreadySeen: Map<any, any>)
if (!options.recurseEverything && !isObservable(source)) return source

if (typeof source !== "object") return source

// Directly return null if source is null
if (source === null) return null

Expand All @@ -53,6 +54,22 @@ function toJSHelper(source, options: ToJSOptions, __alreadySeen: Map<any, any>)
return res
}

if (isObservableSet(source) || Object.getPrototypeOf(source) === Set.prototype) {
if (options.exportMapsAsObjects === false) {
const res = cache(__alreadySeen, source, new Set(), options)
source.forEach(value => {
res.add(toJSHelper(value, options!, __alreadySeen))
})
return res
} else {
const res = cache(__alreadySeen, source, [] as any[], options)
source.forEach(value => {
res.push(toJSHelper(value, options!, __alreadySeen))
})
return res
}
}

if (isObservableMap(source) || Object.getPrototypeOf(source) === Map.prototype) {
if (options.exportMapsAsObjects === false) {
const res = cache(__alreadySeen, source, new Map(), options)
Expand Down
1 change: 1 addition & 0 deletions src/internal.ts
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ export * from "./types/intercept-utils"
export * from "./types/listen-utils"
export * from "./types/observablearray"
export * from "./types/observablemap"
export * from "./types/observableset"
export * from "./types/observableobject"
export * from "./types/type-utils"
export * from "./utils/eq"
Expand Down
5 changes: 5 additions & 0 deletions src/mobx.ts
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,11 @@ export {
IMapDidChange,
isObservableMap,
IObservableMapInitialValues,
ObservableSet,
isObservableSet,
ISetDidChange,
ISetWillChange,
IObservableSetInitialValues,
transaction,
observable,
IObservableFactory,
Expand Down
9 changes: 7 additions & 2 deletions src/types/modifiers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,11 @@ import {
deepEqual,
fail,
isES6Map,
isES6Set,
isObservable,
isObservableArray,
isObservableMap,
isObservableSet,
isObservableObject,
isPlainObject,
observable
Expand All @@ -22,20 +24,23 @@ export function deepEnhancer(v, _, name) {
if (Array.isArray(v)) return observable.array(v, { name })
if (isPlainObject(v)) return observable.object(v, undefined, { name })
if (isES6Map(v)) return observable.map(v, { name })
if (isES6Set(v)) return observable.set(v, { name })

return v
}

export function shallowEnhancer(v, _, name): any {
if (v === undefined || v === null) return v
if (isObservableObject(v) || isObservableArray(v) || isObservableMap(v)) return v
if (isObservableObject(v) || isObservableArray(v) || isObservableMap(v) || isObservableSet(v))
return v
if (Array.isArray(v)) return observable.array(v, { name, deep: false })
if (isPlainObject(v)) return observable.object(v, undefined, { name, deep: false })
if (isES6Map(v)) return observable.map(v, { name, deep: false })
if (isES6Set(v)) return observable.set(v, { name, deep: false })

return fail(
process.env.NODE_ENV !== "production" &&
"The shallow modifier / decorator can only used in combination with arrays, objects and maps"
"The shallow modifier / decorator can only used in combination with arrays, objects, maps and sets"
)
}

Expand Down
Loading

0 comments on commit bf02e77

Please sign in to comment.