mobx
Version:
Simple, scalable state management.
157 lines (144 loc) • 4.73 kB
text/typescript
import {
ObservableObjectAdministration,
createAction,
isAction,
defineProperty,
die,
isFunction,
Annotation,
globalState,
MakeResult,
assert20223DecoratorType
} from "../internal"
export function createActionAnnotation(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 {
// bound
if (this.options_?.bound) {
return this.extend_(adm, key, descriptor, false) === null
? MakeResult.Cancel
: MakeResult.Break
}
// own
if (source === adm.target_) {
return this.extend_(adm, key, descriptor, false) === null
? MakeResult.Cancel
: MakeResult.Continue
}
// prototype
if (isAction(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 actionDescriptor = createActionDescriptor(adm, this, key, descriptor, false)
defineProperty(source, key, actionDescriptor)
return MakeResult.Continue
}
function extend_(
this: Annotation,
adm: ObservableObjectAdministration,
key: PropertyKey,
descriptor: PropertyDescriptor,
proxyTrap: boolean
): boolean | null {
const actionDescriptor = createActionDescriptor(adm, this, key, descriptor)
return adm.defineProperty_(key, actionDescriptor, proxyTrap)
}
function decorate_20223_(this: Annotation, mthd, context: DecoratorContext) {
if (__DEV__) {
assert20223DecoratorType(context, ["method", "field"])
}
const { kind, name, addInitializer } = context
const ann = this
const _createAction = m =>
createAction(ann.options_?.name ?? name!.toString(), m, ann.options_?.autoAction ?? false)
if (kind == "field") {
return function (initMthd) {
let mthd = initMthd
if (!isAction(mthd)) {
mthd = _createAction(mthd)
}
if (ann.options_?.bound) {
mthd = mthd.bind(this)
mthd.isMobxAction = true
}
return mthd
}
}
if (kind == "method") {
if (!isAction(mthd)) {
mthd = _createAction(mthd)
}
if (this.options_?.bound) {
addInitializer(function () {
const self = this as any
const bound = self[name].bind(self)
bound.isMobxAction = true
self[name] = bound
})
}
return mthd
}
die(
`Cannot apply '${ann.annotationType_}' to '${String(name)}' (kind: ${kind}):` +
`\n'${ann.annotationType_}' can only be used on properties with a function value.`
)
}
function assertActionDescriptor(
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 function value.`
)
}
}
export function createActionDescriptor(
adm: ObservableObjectAdministration,
annotation: Annotation,
key: PropertyKey,
descriptor: PropertyDescriptor,
// provides ability to disable safeDescriptors for prototypes
safeDescriptors: boolean = globalState.safeDescriptors
) {
assertActionDescriptor(adm, annotation, key, descriptor)
let { value } = descriptor
if (annotation.options_?.bound) {
value = value.bind(adm.proxy_ ?? adm.target_)
}
return {
value: createAction(
annotation.options_?.name ?? key.toString(),
value,
annotation.options_?.autoAction ?? false,
// https://github.com/mobxjs/mobx/discussions/3140
annotation.options_?.bound ? adm.proxy_ ?? adm.target_ : undefined
),
// 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
}
}