UNPKG

7.19 kBPlain TextView Raw
1import {
2 IEnhancer,
3 IEqualsComparer,
4 IObservableArray,
5 IObservableMapInitialValues,
6 IObservableSetInitialValues,
7 IObservableValue,
8 ObservableMap,
9 ObservableSet,
10 ObservableValue,
11 asDynamicObservableObject,
12 createObservableArray,
13 deepEnhancer,
14 extendObservable,
15 isES6Map,
16 isES6Set,
17 isObservable,
18 isPlainObject,
19 referenceEnhancer,
20 Annotation,
21 shallowEnhancer,
22 refStructEnhancer,
23 AnnotationsMap,
24 asObservableObject,
25 storeAnnotation,
26 createDecoratorAnnotation,
27 createLegacyArray,
28 globalState,
29 assign,
30 isStringish,
31 createObservableAnnotation,
32 createAutoAnnotation
33} from "../internal"
34
35export const OBSERVABLE = "observable"
36export const OBSERVABLE_REF = "observable.ref"
37export const OBSERVABLE_SHALLOW = "observable.shallow"
38export const OBSERVABLE_STRUCT = "observable.struct"
39
40export type CreateObservableOptions = {
41 name?: string
42 equals?: IEqualsComparer<any>
43 deep?: boolean
44 defaultDecorator?: Annotation
45 proxy?: boolean
46 autoBind?: boolean
47}
48
49// Predefined bags of create observable options, to avoid allocating temporarily option objects
50// in the majority of cases
51export const defaultCreateObservableOptions: CreateObservableOptions = {
52 deep: true,
53 name: undefined,
54 defaultDecorator: undefined,
55 proxy: true
56}
57Object.freeze(defaultCreateObservableOptions)
58
59export function asCreateObservableOptions(thing: any): CreateObservableOptions {
60 return thing || defaultCreateObservableOptions
61}
62
63const observableAnnotation = createObservableAnnotation(OBSERVABLE)
64const observableRefAnnotation = createObservableAnnotation(OBSERVABLE_REF, {
65 enhancer: referenceEnhancer
66})
67const observableShallowAnnotation = createObservableAnnotation(OBSERVABLE_SHALLOW, {
68 enhancer: shallowEnhancer
69})
70const observableStructAnnotation = createObservableAnnotation(OBSERVABLE_STRUCT, {
71 enhancer: refStructEnhancer
72})
73const observableDecoratorAnnotation = createDecoratorAnnotation(observableAnnotation)
74
75export function getEnhancerFromOptions(options: CreateObservableOptions): IEnhancer<any> {
76 return options.deep === true
77 ? deepEnhancer
78 : options.deep === false
79 ? referenceEnhancer
80 : getEnhancerFromAnnotation(options.defaultDecorator)
81}
82
83export function getAnnotationFromOptions(
84 options?: CreateObservableOptions
85): Annotation | undefined {
86 return options ? options.defaultDecorator ?? createAutoAnnotation(options) : undefined
87}
88
89export function getEnhancerFromAnnotation(annotation?: Annotation): IEnhancer<any> {
90 return !annotation ? deepEnhancer : annotation.options_?.enhancer ?? deepEnhancer
91}
92
93/**
94 * Turns an object, array or function into a reactive structure.
95 * @param v the value which should become observable.
96 */
97function createObservable(v: any, arg2?: any, arg3?: any) {
98 // @observable someProp;
99 if (isStringish(arg2)) {
100 storeAnnotation(v, arg2, observableAnnotation)
101 return
102 }
103
104 // already observable - ignore
105 if (isObservable(v)) return v
106
107 // plain object
108 if (isPlainObject(v)) return observable.object(v, arg2, arg3)
109
110 // Array
111 if (Array.isArray(v)) return observable.array(v, arg2)
112
113 // Map
114 if (isES6Map(v)) return observable.map(v, arg2)
115
116 // Set
117 if (isES6Set(v)) return observable.set(v, arg2)
118
119 // other object - ignore
120 if (typeof v === "object" && v !== null) return v
121
122 // anything else
123 return observable.box(v, arg2)
124}
125Object.assign(createObservable, observableDecoratorAnnotation)
126
127export interface IObservableFactory extends Annotation, PropertyDecorator {
128 <T = any>(value: T[], options?: CreateObservableOptions): IObservableArray<T>
129 <T = any>(value: Set<T>, options?: CreateObservableOptions): ObservableSet<T>
130 <K = any, V = any>(value: Map<K, V>, options?: CreateObservableOptions): ObservableMap<K, V>
131 <T extends object>(
132 value: T,
133 decorators?: AnnotationsMap<T, never>,
134 options?: CreateObservableOptions
135 ): T
136
137 box: <T = any>(value?: T, options?: CreateObservableOptions) => IObservableValue<T>
138 array: <T = any>(initialValues?: T[], options?: CreateObservableOptions) => IObservableArray<T>
139 set: <T = any>(
140 initialValues?: IObservableSetInitialValues<T>,
141 options?: CreateObservableOptions
142 ) => ObservableSet<T>
143 map: <K = any, V = any>(
144 initialValues?: IObservableMapInitialValues<K, V>,
145 options?: CreateObservableOptions
146 ) => ObservableMap<K, V>
147 object: <T = any>(
148 props: T,
149 decorators?: AnnotationsMap<T, never>,
150 options?: CreateObservableOptions
151 ) => T
152
153 /**
154 * Decorator that creates an observable that only observes the references, but doesn't try to turn the assigned value into an observable.ts.
155 */
156 ref: Annotation & PropertyDecorator
157 /**
158 * Decorator that creates an observable converts its value (objects, maps or arrays) into a shallow observable structure
159 */
160 shallow: Annotation & PropertyDecorator
161 deep: Annotation & PropertyDecorator
162 struct: Annotation & PropertyDecorator
163}
164
165const observableFactories: IObservableFactory = {
166 box<T = any>(value?: T, options?: CreateObservableOptions): IObservableValue<T> {
167 const o = asCreateObservableOptions(options)
168 return new ObservableValue(value, getEnhancerFromOptions(o), o.name, true, o.equals)
169 },
170 array<T = any>(initialValues?: T[], options?: CreateObservableOptions): IObservableArray<T> {
171 const o = asCreateObservableOptions(options)
172 return (globalState.useProxies === false || o.proxy === false
173 ? createLegacyArray
174 : createObservableArray)(initialValues, getEnhancerFromOptions(o), o.name)
175 },
176 map<K = any, V = any>(
177 initialValues?: IObservableMapInitialValues<K, V>,
178 options?: CreateObservableOptions
179 ): ObservableMap<K, V> {
180 const o = asCreateObservableOptions(options)
181 return new ObservableMap<K, V>(initialValues, getEnhancerFromOptions(o), o.name)
182 },
183 set<T = any>(
184 initialValues?: IObservableSetInitialValues<T>,
185 options?: CreateObservableOptions
186 ): ObservableSet<T> {
187 const o = asCreateObservableOptions(options)
188 return new ObservableSet<T>(initialValues, getEnhancerFromOptions(o), o.name)
189 },
190 object<T = any>(
191 props: T,
192 decorators?: AnnotationsMap<T, never>,
193 options?: CreateObservableOptions
194 ): T {
195 return extendObservable(
196 globalState.useProxies === false || options?.proxy === false
197 ? asObservableObject({}, options)
198 : asDynamicObservableObject({}, options),
199 props,
200 decorators
201 )
202 },
203 ref: createDecoratorAnnotation(observableRefAnnotation),
204 shallow: createDecoratorAnnotation(observableShallowAnnotation),
205 deep: observableDecoratorAnnotation,
206 struct: createDecoratorAnnotation(observableStructAnnotation)
207} as any
208
209// eslint-disable-next-line
210export var observable: IObservableFactory = assign(createObservable, observableFactories)