mobx
Version:
Simple, scalable state management.
135 lines (125 loc) • 4.08 kB
text/typescript
import {
ObservableObjectAdministration,
Annotation,
defineProperty,
die,
flow,
isFlow,
isFunction,
globalState,
MakeResult,
hasProp,
assert20223DecoratorType
} from "../internal"
export function createFlowAnnotation(name: string, options?: object): Annotation {
return {
annotationType_: name,
options_: options,
make_,
extend_,
decorate_20223_
}
}
function make_(
this: Annotation,
adm: ObservableObjectAdministration,
key: PropertyKey,
descriptor: PropertyDescriptor,
source: object
): MakeResult {
// own
if (source === adm.target_) {
return this.extend_(adm, key, descriptor, false) === null
? MakeResult.Cancel
: MakeResult.Continue
}
// prototype
// bound - must annotate protos to support super.flow()
if (this.options_?.bound && (!hasProp(adm.target_, key) || !isFlow(adm.target_[key]))) {
if (this.extend_(adm, key, descriptor, false) === null) {
return MakeResult.Cancel
}
}
if (isFlow(descriptor.value)) {
// A prototype could have been annotated already by other constructor,
// rest of the proto chain must be annotated already
return MakeResult.Break
}
const flowDescriptor = createFlowDescriptor(adm, this, key, descriptor, false, false)
defineProperty(source, key, flowDescriptor)
return MakeResult.Continue
}
function extend_(
this: Annotation,
adm: ObservableObjectAdministration,
key: PropertyKey,
descriptor: PropertyDescriptor,
proxyTrap: boolean
): boolean | null {
const flowDescriptor = createFlowDescriptor(adm, this, key, descriptor, this.options_?.bound)
return adm.defineProperty_(key, flowDescriptor, proxyTrap)
}
function decorate_20223_(this: Annotation, mthd, context: ClassMethodDecoratorContext) {
if (__DEV__) {
assert20223DecoratorType(context, ["method"])
}
const { name, addInitializer } = context
if (!isFlow(mthd)) {
mthd = flow(mthd)
}
if (this.options_?.bound) {
addInitializer(function () {
const self = this as any
const bound = self[name].bind(self)
bound.isMobXFlow = true
self[name] = bound
})
}
return mthd
}
function assertFlowDescriptor(
adm: ObservableObjectAdministration,
{ annotationType_ }: Annotation,
key: PropertyKey,
{ value }: PropertyDescriptor
) {
if (__DEV__ && !isFunction(value)) {
die(
`Cannot apply '${annotationType_}' to '${adm.name_}.${key.toString()}':` +
`\n'${annotationType_}' can only be used on properties with a generator function value.`
)
}
}
function createFlowDescriptor(
adm: ObservableObjectAdministration,
annotation: Annotation,
key: PropertyKey,
descriptor: PropertyDescriptor,
bound: boolean,
// provides ability to disable safeDescriptors for prototypes
safeDescriptors: boolean = globalState.safeDescriptors
): PropertyDescriptor {
assertFlowDescriptor(adm, annotation, key, descriptor)
let { value } = descriptor
// In case of flow.bound, the descriptor can be from already annotated prototype
if (!isFlow(value)) {
value = flow(value)
}
if (bound) {
// We do not keep original function around, so we bind the existing flow
value = value.bind(adm.proxy_ ?? adm.target_)
// This is normally set by `flow`, but `bind` returns new function...
value.isMobXFlow = true
}
return {
value,
// Non-configurable for classes
// prevents accidental field redefinition in subclass
configurable: safeDescriptors ? adm.isPlainObject_ : true,
// https://github.com/mobxjs/mobx/pull/2641#issuecomment-737292058
enumerable: false,
// Non-obsevable, therefore non-writable
// Also prevents rewriting in subclass constructor
writable: safeDescriptors ? false : true
}
}