Permalink
886 lines (796 sloc) 40.3 KB
import {
_getAdministration,
_interceptReads,
action,
computed,
intercept,
getAtom,
IObjectWillChange,
isComputedProp,
observable,
observe,
set,
IObjectDidChange
} from "mobx"
import {
addHiddenFinalProp,
addHiddenWritableProp,
ArrayType,
ComplexType,
createActionInvoker,
createObjectNode,
EMPTY_ARRAY,
EMPTY_OBJECT,
escapeJsonPath,
fail,
flattenTypeErrors,
freeze,
getContextForPath,
getPrimitiveFactoryFromValue,
getStateTreeNode,
IAnyType,
IChildNodesMap,
IValidationContext,
IJsonPatch,
isPlainObject,
isPrimitive,
isStateTreeNode,
IStateTreeNode,
isType,
IType,
IValidationResult,
mobxShallow,
optional,
MapType,
typecheckInternal,
typeCheckFailure,
TypeFlags,
ExtractC,
ExtractS,
ExtractT,
Hook,
AnyObjectNode,
AnyNode,
_CustomOrOther,
_NotCustomized,
OptionalValue,
asArray,
cannotDetermineSubtype
} from "../../internal"
const PRE_PROCESS_SNAPSHOT = "preProcessSnapshot"
const POST_PROCESS_SNAPSHOT = "postProcessSnapshot"
/** @hidden */
export interface ModelProperties {
[key: string]: IAnyType
}
/** @hidden */
export type ModelPrimitive = string | number | boolean | Date
/** @hidden */
export interface ModelPropertiesDeclaration {
[key: string]: ModelPrimitive | IAnyType
}
/**
* Unmaps syntax property declarations to a map of { propName: IType }
*
* @hidden
*/
export type ModelPropertiesDeclarationToProperties<T extends ModelPropertiesDeclaration> = {
[K in keyof T]: T[K] extends string
? IType<string | undefined, string, string> & OptionalProperty
: T[K] extends number
? IType<number | undefined, number, number> & OptionalProperty
: T[K] extends boolean
? IType<boolean | undefined, boolean, boolean> & OptionalProperty
: T[K] extends Date
? IType<number | Date | undefined, number, Date> & OptionalProperty
: T[K] extends IAnyType
? T[K]
: never
}
/** @hidden */
declare const $optionalProperty: unique symbol
/** @hidden */
export interface OptionalProperty {
// fake, only used for typing
readonly [$optionalProperty]: undefined
}
/**
* Maps property types to the snapshot, including omitted optional attributes
* @hidden
*/
export type RequiredPropNames<T> = {
[K in keyof T]: T[K] extends OptionalProperty ? never : K
}[keyof T]
/** @hidden */
export type RequiredProps<T> = Pick<T, RequiredPropNames<T>>
/** @hidden */
export type RequiredPropsObject<P extends ModelProperties> = { [K in keyof RequiredProps<P>]: P[K] }
/** @hidden */
export type OptionalPropNames<T> = {
[K in keyof T]: T[K] extends OptionalProperty ? K : never
}[keyof T]
/** @hidden */
export type OptionalProps<T> = Pick<T, OptionalPropNames<T>>
/** @hidden */
export type OptionalPropsObject<P extends ModelProperties> = {
[K in keyof OptionalProps<P>]?: P[K]
}
/** @hidden */
export type ExtractCFromProps<P extends ModelProperties> = { [k in keyof P]: ExtractC<P[k]> }
/** @hidden */
export type ModelCreationType<P extends ModelProperties> = ExtractCFromProps<
RequiredPropsObject<P> & OptionalPropsObject<P>
>
/** @hidden */
export type ModelCreationType2<P extends ModelProperties, CustomC> = _CustomOrOther<
CustomC,
ModelCreationType<P>
>
/** @hidden */
export type ModelSnapshotType<P extends ModelProperties> = { [K in keyof P]: ExtractS<P[K]> }
/** @hidden */
export type ModelSnapshotType2<P extends ModelProperties, CustomS> = _CustomOrOther<
CustomS,
ModelSnapshotType<P>
>
/**
* @hidden
* we keep this separate from ModelInstanceType to shorten model instance types generated declarations
*/
export type ModelInstanceTypeProps<P extends ModelProperties> = { [K in keyof P]: ExtractT<P[K]> }
/**
* @hidden
* do not transform this to an interface or model instance type generated declarations will be longer
*/
export type ModelInstanceType<
P extends ModelProperties,
O,
CustomC,
CustomS
> = ModelInstanceTypeProps<P> &
O &
IStateTreeNode<ModelCreationType2<P, CustomC>, ModelSnapshotType2<P, CustomS>>
/** @hidden */
export interface ModelActions {
[key: string]: Function
}
export interface IModelType<
PROPS extends ModelProperties,
OTHERS,
CustomC = _NotCustomized,
CustomS = _NotCustomized
>
extends IType<
ModelCreationType2<PROPS, CustomC>,
ModelSnapshotType2<PROPS, CustomS>,
ModelInstanceType<PROPS, OTHERS, CustomC, CustomS>
> {
readonly properties: PROPS
named(newName: string): IModelType<PROPS, OTHERS, CustomC, CustomS>
// warning: redefining props after a process snapshot is used ends up on the fixed (custom) C, S typings being overridden
// so it is recommended to use pre/post process snapshot after all props have been defined
props<PROPS2 extends ModelPropertiesDeclaration>(
props: PROPS2
): IModelType<PROPS & ModelPropertiesDeclarationToProperties<PROPS2>, OTHERS, CustomC, CustomS>
views<V extends Object>(
fn: (self: ModelInstanceType<PROPS, OTHERS, CustomC, CustomS>) => V
): IModelType<PROPS, OTHERS & V, CustomC, CustomS>
actions<A extends ModelActions>(
fn: (self: ModelInstanceType<PROPS, OTHERS, CustomC, CustomS>) => A
): IModelType<PROPS, OTHERS & A, CustomC, CustomS>
volatile<TP extends object>(
fn: (self: ModelInstanceType<PROPS, OTHERS, CustomC, CustomS>) => TP
): IModelType<PROPS, OTHERS & TP, CustomC, CustomS>
extend<A extends ModelActions = {}, V extends Object = {}, VS extends Object = {}>(
fn: (
self: ModelInstanceType<PROPS, OTHERS, CustomC, CustomS>
) => { actions?: A; views?: V; state?: VS }
): IModelType<PROPS, OTHERS & A & V & VS, CustomC, CustomS>
/** @deprecated See `types.snapshotProcessor` */
preProcessSnapshot<NewC = ModelCreationType2<PROPS, CustomC>>(
fn: (snapshot: NewC) => ModelCreationType2<PROPS, CustomC>
): IModelType<PROPS, OTHERS, NewC, CustomS>
/** @deprecated See `types.snapshotProcessor` */
postProcessSnapshot<NewS = ModelSnapshotType2<PROPS, CustomS>>(
fn: (snapshot: ModelSnapshotType2<PROPS, CustomS>) => NewS
): IModelType<PROPS, OTHERS, CustomC, NewS>
}
/**
* Any model type.
*/
export interface IAnyModelType extends IModelType<any, any, any, any> {}
/** @hidden */
export type ExtractProps<T extends IAnyModelType> = T extends IModelType<infer P, any, any, any>
? P
: never
/** @hidden */
export type ExtractOthers<T extends IAnyModelType> = T extends IModelType<any, infer O, any, any>
? O
: never
function objectTypeToString(this: any) {
return getStateTreeNode(this).toString()
}
/**
* @internal
* @hidden
*/
export interface ModelTypeConfig {
name?: string
properties?: ModelPropertiesDeclaration
initializers?: ReadonlyArray<(instance: any) => any>
preProcessor?: (snapshot: any) => any
postProcessor?: (snapshot: any) => any
}
const defaultObjectOptions = {
name: "AnonymousModel",
properties: {},
initializers: EMPTY_ARRAY
}
function toPropertiesObject(declaredProps: ModelPropertiesDeclaration): ModelProperties {
// loop through properties and ensures that all items are types
return Object.keys(declaredProps).reduce(
(props, key) => {
// warn if user intended a HOOK
if (key in Hook)
throw fail(
`Hook '${key}' was defined as property. Hooks should be defined as part of the actions`
)
// the user intended to use a view
const descriptor = Object.getOwnPropertyDescriptor(props, key)!
if ("get" in descriptor) {
throw fail("Getters are not supported as properties. Please use views instead")
}
// undefined and null are not valid
const value = descriptor.value
if (value === null || value === undefined) {
throw fail(
"The default value of an attribute cannot be null or undefined as the type cannot be inferred. Did you mean `types.maybe(someType)`?"
)
// its a primitive, convert to its type
} else if (isPrimitive(value)) {
return Object.assign({}, props, {
[key]: optional(getPrimitiveFactoryFromValue(value), value)
})
// map defaults to empty object automatically for models
} else if (value instanceof MapType) {
return Object.assign({}, props, {
[key]: optional(value, {})
})
} else if (value instanceof ArrayType) {
return Object.assign({}, props, { [key]: optional(value, []) })
// its already a type
} else if (isType(value)) {
return props
// its a function, maybe the user wanted a view?
} else if (process.env.NODE_ENV !== "production" && typeof value === "function") {
throw fail(
`Invalid type definition for property '${key}', it looks like you passed a function. Did you forget to invoke it, or did you intend to declare a view / action?`
)
// no other complex values
} else if (process.env.NODE_ENV !== "production" && typeof value === "object") {
throw fail(
`Invalid type definition for property '${key}', it looks like you passed an object. Try passing another model type or a types.frozen.`
)
// WTF did you pass in mate?
} else {
throw fail(
`Invalid type definition for property '${key}', cannot infer a type from a value like '${value}' (${typeof value})`
)
}
},
declaredProps as any
)
}
/**
* @internal
* @hidden
*/
export type AnyModelType = ModelType<any, any, any, any, any>
/**
* @internal
* @hidden
*/
export class ModelType<
PROPS extends ModelProperties,
OTHERS,
CustomC,
CustomS,
MT extends IModelType<PROPS, OTHERS, CustomC, CustomS>
>
extends ComplexType<
ModelCreationType2<PROPS, CustomC>,
ModelSnapshotType2<PROPS, CustomS>,
ModelInstanceType<PROPS, OTHERS, CustomC, CustomS>
>
implements IModelType<PROPS, OTHERS, CustomC, CustomS> {
readonly flags = TypeFlags.Object
/*
* The original object definition
*/
public readonly initializers!: ((instance: any) => any)[]
public readonly properties!: PROPS
private preProcessor!: (snapshot: any) => any | undefined
private postProcessor!: (snapshot: any) => any | undefined
private readonly propertyNames: string[]
constructor(opts: ModelTypeConfig) {
super(opts.name || defaultObjectOptions.name)
const name = opts.name || defaultObjectOptions.name
// TODO: this test still needed?
if (!/^\w[\w\d_]*$/.test(name)) throw fail(`Typename should be a valid identifier: ${name}`)
Object.assign(this, defaultObjectOptions, opts)
// ensures that any default value gets converted to its related type
this.properties = toPropertiesObject(this.properties) as PROPS
freeze(this.properties) // make sure nobody messes with it
this.propertyNames = Object.keys(this.properties)
this.identifierAttribute = this._getIdentifierAttribute()
}
private _getIdentifierAttribute(): string | undefined {
let identifierAttribute: string | undefined = undefined
this.forAllProps((propName, propType) => {
if (propType.flags & TypeFlags.Identifier) {
if (identifierAttribute)
throw fail(
`Cannot define property '${propName}' as object identifier, property '${identifierAttribute}' is already defined as identifier property`
)
identifierAttribute = propName
}
})
return identifierAttribute
}
cloneAndEnhance(opts: ModelTypeConfig): IAnyModelType {
return new ModelType({
name: opts.name || this.name,
properties: Object.assign({}, this.properties, opts.properties),
initializers: this.initializers.concat(opts.initializers || []),
preProcessor: opts.preProcessor || this.preProcessor,
postProcessor: opts.postProcessor || this.postProcessor
})
}
actions: MT["actions"] = fn => {
const actionInitializer = (self: this["T"]) => {
this.instantiateActions(self, fn(self))
return self
}
return this.cloneAndEnhance({ initializers: [actionInitializer] })
}
private instantiateActions(self: this["T"], actions: ModelActions): void {
// check if return is correct
if (!isPlainObject(actions))
throw fail(`actions initializer should return a plain object containing actions`)
// bind actions to the object created
Object.keys(actions).forEach(name => {
// warn if preprocessor was given
if (name === PRE_PROCESS_SNAPSHOT)
throw fail(
`Cannot define action '${PRE_PROCESS_SNAPSHOT}', it should be defined using 'type.preProcessSnapshot(fn)' instead`
)
// warn if postprocessor was given
if (name === POST_PROCESS_SNAPSHOT)
throw fail(
`Cannot define action '${POST_PROCESS_SNAPSHOT}', it should be defined using 'type.postProcessSnapshot(fn)' instead`
)
let action2 = actions[name]
// apply hook composition
let baseAction = (self as any)[name]
if (name in Hook && baseAction) {
let specializedAction = action2
action2 = function() {
baseAction.apply(null, arguments)
specializedAction.apply(null, arguments)
}
}
// the goal of this is to make sure actions using "this" can call themselves,
// while still allowing the middlewares to register them
const middlewares = (action2 as any).$mst_middleware // make sure middlewares are not lost
let boundAction = action2.bind(actions)
boundAction.$mst_middleware = middlewares
const actionInvoker = createActionInvoker(self, name, boundAction)
actions[name] = actionInvoker
// See #646, allow models to be mocked
;(process.env.NODE_ENV === "production" ? addHiddenFinalProp : addHiddenWritableProp)(
self,
name,
actionInvoker
)
})
}
named: MT["named"] = name => {
return this.cloneAndEnhance({ name })
}
props: MT["props"] = properties => {
return this.cloneAndEnhance({ properties })
}
volatile: MT["volatile"] = fn => {
const stateInitializer = (self: this["T"]) => {
this.instantiateVolatileState(self, fn(self))
return self
}
return this.cloneAndEnhance({ initializers: [stateInitializer] })
}
private instantiateVolatileState(
self: this["T"],
state: {
[key: string]: any
}
): void {
// check views return
if (!isPlainObject(state))
throw fail(`volatile state initializer should return a plain object containing state`)
set(self, state)
}
extend: MT["extend"] = fn => {
const initializer = (self: this["T"]) => {
const { actions, views, state, ...rest } = fn(self)
for (let key in rest)
throw fail(
`The \`extend\` function should return an object with a subset of the fields 'actions', 'views' and 'state'. Found invalid key '${key}'`
)
if (state) this.instantiateVolatileState(self, state)
if (views) this.instantiateViews(self, views)
if (actions) this.instantiateActions(self, actions)
return self
}
return this.cloneAndEnhance({ initializers: [initializer] })
}
views: MT["views"] = fn => {
const viewInitializer = (self: this["T"]) => {
this.instantiateViews(self, fn(self))
return self
}
return this.cloneAndEnhance({ initializers: [viewInitializer] })
}
private instantiateViews(self: this["T"], views: Object): void {
// check views return
if (!isPlainObject(views))
throw fail(`views initializer should return a plain object containing views`)
Object.keys(views).forEach(key => {
// is this a computed property?
const descriptor = Object.getOwnPropertyDescriptor(views, key)!
const { value } = descriptor
if ("get" in descriptor) {
if (isComputedProp(self, key)) {
const computedValue = _getAdministration(self, key)
// TODO: mobx currently does not allow redefining computes yet, pending #1121
// FIXME: this binds to the internals of mobx!
computedValue.derivation = descriptor.get
computedValue.scope = self
if (descriptor.set)
computedValue.setter = action(
computedValue.name + "-setter",
descriptor.set
)
} else {
// use internal api as shortcut
;(computed as any)(self, key, descriptor, true)
}
} else if (typeof value === "function") {
// this is a view function, merge as is!
// See #646, allow models to be mocked
;(process.env.NODE_ENV === "production"
? addHiddenFinalProp
: addHiddenWritableProp)(self, key, value)
} else {
throw fail(`A view member should either be a function or getter based property`)
}
})
}
preProcessSnapshot: MT["preProcessSnapshot"] = preProcessor => {
const currentPreprocessor = this.preProcessor
if (!currentPreprocessor) return this.cloneAndEnhance({ preProcessor })
else
return this.cloneAndEnhance({
preProcessor: snapshot => currentPreprocessor(preProcessor(snapshot))
})
}
postProcessSnapshot: MT["postProcessSnapshot"] = postProcessor => {
const currentPostprocessor = this.postProcessor
if (!currentPostprocessor) return this.cloneAndEnhance({ postProcessor })
else
return this.cloneAndEnhance({
postProcessor: snapshot => postProcessor(currentPostprocessor(snapshot))
})
}
instantiate(
parent: AnyObjectNode | null,
subpath: string,
environment: any,
initialValue: this["C"] | this["T"]
): this["N"] {
const value = isStateTreeNode(initialValue)
? initialValue
: this.applySnapshotPreProcessor(initialValue)
return createObjectNode(this, parent, subpath, environment, value)
// Optimization: record all prop- view- and action names after first construction, and generate an optimal base class
// that pre-reserves all these fields for fast object-member lookups
}
initializeChildNodes(objNode: this["N"], initialSnapshot: any = {}): IChildNodesMap {
const type = objNode.type as this
const result: IChildNodesMap = {}
type.forAllProps((name, childType) => {
result[name] = childType.instantiate(
objNode,
name,
objNode.environment,
(initialSnapshot as any)[name]
)
})
return result
}
createNewInstance(node: this["N"], childNodes: IChildNodesMap): this["T"] {
return observable.object(childNodes, EMPTY_OBJECT, mobxShallow) as any
}
finalizeNewInstance(node: this["N"], instance: this["T"]): void {
addHiddenFinalProp(instance, "toString", objectTypeToString)
this.forAllProps(name => {
_interceptReads(instance, name, node.unbox)
})
this.initializers.reduce((self, fn) => fn(self), instance)
intercept(instance, this.willChange)
observe(instance, this.didChange)
}
private willChange(chg: IObjectWillChange): IObjectWillChange | null {
// TODO: mobx typings don't seem to take into account that newValue can be set even when removing a prop
const change = chg as IObjectWillChange & { newValue?: any }
const childNode = getStateTreeNode(change.object)
childNode.assertWritable({ subpath: change.name })
const childType = (childNode.type as this).properties[change.name]
// only properties are typed, state are stored as-is references
if (childType) {
typecheckInternal(childType, change.newValue)
change.newValue = childType.reconcile(
childNode.getChildNode(change.name),
change.newValue
)
}
return change
}
private didChange(chg: IObjectDidChange) {
// TODO: mobx typings don't seem to take into account that newValue can be set even when removing a prop
const change = chg as IObjectWillChange & { newValue?: any; oldValue?: any }
const childNode = getStateTreeNode(change.object)
const childType = (childNode.type as this).properties[change.name]
if (!childType) {
// don't emit patches for volatile state
return
}
const oldChildValue = change.oldValue ? change.oldValue.snapshot : undefined
childNode.emitPatch(
{
op: "replace",
path: escapeJsonPath(change.name),
value: change.newValue.snapshot,
oldValue: oldChildValue
},
childNode
)
}
getChildren(node: this["N"]): ReadonlyArray<AnyNode> {
const res: AnyNode[] = []
this.forAllProps(name => {
res.push(this.getChildNode(node, name))
})
return res
}
getChildNode(node: this["N"], key: string): AnyNode {
if (!(key in this.properties)) throw fail("Not a value property: " + key)
const childNode = _getAdministration(node.storedValue, key).value // TODO: blegh!
if (!childNode) throw fail("Node not available for property " + key)
return childNode
}
getSnapshot(node: this["N"], applyPostProcess = true): this["S"] {
const res = {} as any
this.forAllProps((name, type) => {
// TODO: FIXME, make sure the observable ref is used!
;(getAtom(node.storedValue, name) as any).reportObserved()
res[name] = this.getChildNode(node, name).snapshot
})
if (applyPostProcess) {
return this.applySnapshotPostProcessor(res)
}
return res
}
processInitialSnapshot(childNodes: IChildNodesMap): this["S"] {
const processed = {} as any
Object.keys(childNodes).forEach(key => {
processed[key] = childNodes[key].getSnapshot()
})
return this.applySnapshotPostProcessor(this.applyOptionalValuesToSnapshot(processed))
}
applyPatchLocally(node: this["N"], subpath: string, patch: IJsonPatch): void {
if (!(patch.op === "replace" || patch.op === "add")) {
throw fail(`object does not support operation ${patch.op}`)
}
node.storedValue[subpath] = patch.value
}
@action
applySnapshot(node: this["N"], snapshot: this["C"]): void {
const preProcessedSnapshot = this.applySnapshotPreProcessor(snapshot)
typecheckInternal(this, preProcessedSnapshot)
this.forAllProps(name => {
node.storedValue[name] = preProcessedSnapshot[name]
})
}
applySnapshotPreProcessor(snapshot: any) {
const processor = this.preProcessor
return processor ? processor.call(null, snapshot) : snapshot
}
applyOptionalValuesToSnapshot(snapshot: any) {
if (snapshot) {
snapshot = Object.assign({}, snapshot)
this.forAllProps((name, type) => {
if (!(name in snapshot)) {
const optional2 = tryGetOptional(type)
if (optional2) {
snapshot[name] = optional2.getDefaultValueSnapshot()
}
}
})
}
return snapshot
}
applySnapshotPostProcessor(snapshot: any) {
const postProcessor = this.postProcessor
if (postProcessor) return postProcessor.call(null, snapshot)
return snapshot
}
getChildType(propertyName: string): IAnyType {
if (process.env.NODE_ENV !== "production") {
if (propertyName === undefined || propertyName === null) {
throw fail("getChildType requires a property name when used over a model type")
}
}
return this.properties[propertyName]
}
isValidSnapshot(value: this["C"], context: IValidationContext): IValidationResult {
let snapshot = this.applySnapshotPreProcessor(value)
if (!isPlainObject(snapshot)) {
return typeCheckFailure(context, snapshot, "Value is not a plain object")
}
return flattenTypeErrors(
this.propertyNames.map(key =>
this.properties[key].validate(
snapshot[key],
getContextForPath(context, key, this.properties[key])
)
)
)
}
private forAllProps(fn: (name: string, type: IAnyType) => void) {
this.propertyNames.forEach(key => fn(key, this.properties[key]))
}
describe() {
// optimization: cache
return (
"{ " +
this.propertyNames.map(key => key + ": " + this.properties[key].describe()).join("; ") +
" }"
)
}
getDefaultSnapshot(): this["C"] {
return EMPTY_OBJECT as this["C"]
}
removeChild(node: this["N"], subpath: string) {
;(node.storedValue as any)[subpath] = undefined
}
}
export function model<P extends ModelPropertiesDeclaration = {}>(
name: string,
properties?: P
): IModelType<ModelPropertiesDeclarationToProperties<P>, {}>
export function model<P extends ModelPropertiesDeclaration = {}>(
properties?: P
): IModelType<ModelPropertiesDeclarationToProperties<P>, {}>
/**
* `types.model` - Creates a new model type by providing a name, properties, volatile state and actions.
*
* See the [model type](https://github.com/mobxjs/mobx-state-tree#creating-models) description or the [getting started](https://github.com/mobxjs/mobx-state-tree/blob/master/docs/getting-started.md#getting-started-1) tutorial.
*/
export function model(...args: any[]): any {
const name = typeof args[0] === "string" ? args.shift() : "AnonymousModel"
const properties = args.shift() || {}
return new ModelType({ name, properties })
}
// TODO: this can be simplified in TS3, since we can transform _NotCustomized to unknown, since unkonwn & X = X
// and then back unkown to _NotCustomized if needed
/** @hidden */
export type _CustomJoin<A, B> = A extends _NotCustomized ? B : A & B
// generated with C:\VSProjects\github\mobx-state-tree-upstream\packages\mobx-state-tree\scripts\generate-compose-type.js
// prettier-ignore
export function compose<PA extends ModelProperties, OA, FCA, FSA, PB extends ModelProperties, OB, FCB, FSB>(name: string, A: IModelType<PA, OA, FCA, FSA>, B: IModelType<PB, OB, FCB, FSB>): IModelType<PA & PB, OA & OB, _CustomJoin<FCA, FCB>, _CustomJoin<FSA, FSB>>
// prettier-ignore
export function compose<PA extends ModelProperties, OA, FCA, FSA, PB extends ModelProperties, OB, FCB, FSB>(A: IModelType<PA, OA, FCA, FSA>, B: IModelType<PB, OB, FCB, FSB>): IModelType<PA & PB, OA & OB, _CustomJoin<FCA, FCB>, _CustomJoin<FSA, FSB>>
// prettier-ignore
export function compose<PA extends ModelProperties, OA, FCA, FSA, PB extends ModelProperties, OB, FCB, FSB, PC extends ModelProperties, OC, FCC, FSC>(name: string, A: IModelType<PA, OA, FCA, FSA>, B: IModelType<PB, OB, FCB, FSB>, C: IModelType<PC, OC, FCC, FSC>): IModelType<PA & PB & PC, OA & OB & OC, _CustomJoin<FCA, _CustomJoin<FCB, FCC>>, _CustomJoin<FSA, _CustomJoin<FSB, FSC>>>
// prettier-ignore
export function compose<PA extends ModelProperties, OA, FCA, FSA, PB extends ModelProperties, OB, FCB, FSB, PC extends ModelProperties, OC, FCC, FSC>(A: IModelType<PA, OA, FCA, FSA>, B: IModelType<PB, OB, FCB, FSB>, C: IModelType<PC, OC, FCC, FSC>): IModelType<PA & PB & PC, OA & OB & OC, _CustomJoin<FCA, _CustomJoin<FCB, FCC>>, _CustomJoin<FSA, _CustomJoin<FSB, FSC>>>
// prettier-ignore
export function compose<PA extends ModelProperties, OA, FCA, FSA, PB extends ModelProperties, OB, FCB, FSB, PC extends ModelProperties, OC, FCC, FSC, PD extends ModelProperties, OD, FCD, FSD>(name: string, A: IModelType<PA, OA, FCA, FSA>, B: IModelType<PB, OB, FCB, FSB>, C: IModelType<PC, OC, FCC, FSC>, D: IModelType<PD, OD, FCD, FSD>): IModelType<PA & PB & PC & PD, OA & OB & OC & OD, _CustomJoin<FCA, _CustomJoin<FCB, _CustomJoin<FCC, FCD>>>, _CustomJoin<FSA, _CustomJoin<FSB, _CustomJoin<FSC, FSD>>>>
// prettier-ignore
export function compose<PA extends ModelProperties, OA, FCA, FSA, PB extends ModelProperties, OB, FCB, FSB, PC extends ModelProperties, OC, FCC, FSC, PD extends ModelProperties, OD, FCD, FSD>(A: IModelType<PA, OA, FCA, FSA>, B: IModelType<PB, OB, FCB, FSB>, C: IModelType<PC, OC, FCC, FSC>, D: IModelType<PD, OD, FCD, FSD>): IModelType<PA & PB & PC & PD, OA & OB & OC & OD, _CustomJoin<FCA, _CustomJoin<FCB, _CustomJoin<FCC, FCD>>>, _CustomJoin<FSA, _CustomJoin<FSB, _CustomJoin<FSC, FSD>>>>
// prettier-ignore
export function compose<PA extends ModelProperties, OA, FCA, FSA, PB extends ModelProperties, OB, FCB, FSB, PC extends ModelProperties, OC, FCC, FSC, PD extends ModelProperties, OD, FCD, FSD, PE extends ModelProperties, OE, FCE, FSE>(name: string, A: IModelType<PA, OA, FCA, FSA>, B: IModelType<PB, OB, FCB, FSB>, C: IModelType<PC, OC, FCC, FSC>, D: IModelType<PD, OD, FCD, FSD>, E: IModelType<PE, OE, FCE, FSE>): IModelType<PA & PB & PC & PD & PE, OA & OB & OC & OD & OE, _CustomJoin<FCA, _CustomJoin<FCB, _CustomJoin<FCC, _CustomJoin<FCD, FCE>>>>, _CustomJoin<FSA, _CustomJoin<FSB, _CustomJoin<FSC, _CustomJoin<FSD, FSE>>>>>
// prettier-ignore
export function compose<PA extends ModelProperties, OA, FCA, FSA, PB extends ModelProperties, OB, FCB, FSB, PC extends ModelProperties, OC, FCC, FSC, PD extends ModelProperties, OD, FCD, FSD, PE extends ModelProperties, OE, FCE, FSE>(A:
IModelType<PA, OA, FCA, FSA>, B: IModelType<PB, OB, FCB, FSB>, C: IModelType<PC, OC, FCC, FSC>, D: IModelType<PD, OD, FCD, FSD>, E: IModelType<PE, OE, FCE, FSE>): IModelType<PA & PB & PC & PD & PE, OA & OB & OC & OD & OE, _CustomJoin<FCA,
_CustomJoin<FCB, _CustomJoin<FCC, _CustomJoin<FCD, FCE>>>>, _CustomJoin<FSA, _CustomJoin<FSB, _CustomJoin<FSC, _CustomJoin<FSD, FSE>>>>>
// prettier-ignore
export function compose<PA extends ModelProperties, OA, FCA, FSA, PB extends ModelProperties, OB, FCB, FSB, PC extends ModelProperties, OC, FCC, FSC, PD extends ModelProperties, OD, FCD, FSD, PE extends ModelProperties, OE, FCE, FSE, PF
extends ModelProperties, OF, FCF, FSF>(name: string, A: IModelType<PA, OA, FCA, FSA>, B: IModelType<PB, OB, FCB, FSB>, C: IModelType<PC, OC, FCC, FSC>, D: IModelType<PD, OD, FCD, FSD>, E: IModelType<PE, OE, FCE, FSE>, F: IModelType<PF, OF, FCF, FSF>): IModelType<PA & PB & PC & PD & PE & PF, OA & OB & OC & OD & OE & OF, _CustomJoin<FCA, _CustomJoin<FCB, _CustomJoin<FCC, _CustomJoin<FCD, _CustomJoin<FCE, FCF>>>>>, _CustomJoin<FSA, _CustomJoin<FSB, _CustomJoin<FSC, _CustomJoin<FSD, _CustomJoin<FSE, FSF>>>>>>
// prettier-ignore
export function compose<PA extends ModelProperties, OA, FCA, FSA, PB extends ModelProperties, OB, FCB, FSB, PC extends ModelProperties, OC, FCC, FSC, PD extends ModelProperties, OD, FCD, FSD, PE extends ModelProperties, OE, FCE, FSE, PF
extends ModelProperties, OF, FCF, FSF>(A: IModelType<PA, OA, FCA, FSA>, B: IModelType<PB, OB, FCB, FSB>, C: IModelType<PC, OC, FCC, FSC>, D: IModelType<PD, OD, FCD, FSD>, E: IModelType<PE, OE, FCE, FSE>, F: IModelType<PF, OF, FCF, FSF>): IModelType<PA & PB & PC & PD & PE & PF, OA & OB & OC & OD & OE & OF, _CustomJoin<FCA, _CustomJoin<FCB, _CustomJoin<FCC, _CustomJoin<FCD, _CustomJoin<FCE, FCF>>>>>, _CustomJoin<FSA, _CustomJoin<FSB, _CustomJoin<FSC, _CustomJoin<FSD, _CustomJoin<FSE, FSF>>>>>>
// prettier-ignore
export function compose<PA extends ModelProperties, OA, FCA, FSA, PB extends ModelProperties, OB, FCB, FSB, PC extends ModelProperties, OC, FCC, FSC, PD extends ModelProperties, OD, FCD, FSD, PE extends ModelProperties, OE, FCE, FSE, PF
extends ModelProperties, OF, FCF, FSF, PG extends ModelProperties, OG, FCG, FSG>(name: string, A: IModelType<PA, OA, FCA, FSA>, B: IModelType<PB, OB, FCB, FSB>, C: IModelType<PC, OC, FCC, FSC>, D: IModelType<PD, OD, FCD, FSD>, E: IModelType<PE, OE, FCE, FSE>, F: IModelType<PF, OF, FCF, FSF>, G: IModelType<PG, OG, FCG, FSG>): IModelType<PA & PB & PC & PD & PE & PF & PG, OA & OB & OC & OD & OE & OF & OG, _CustomJoin<FCA, _CustomJoin<FCB, _CustomJoin<FCC, _CustomJoin<FCD, _CustomJoin<FCE, _CustomJoin<FCF, FCG>>>>>>, _CustomJoin<FSA, _CustomJoin<FSB, _CustomJoin<FSC, _CustomJoin<FSD, _CustomJoin<FSE, _CustomJoin<FSF, FSG>>>>>>>
// prettier-ignore
export function compose<PA extends ModelProperties, OA, FCA, FSA, PB extends ModelProperties, OB, FCB, FSB, PC extends ModelProperties, OC, FCC, FSC, PD extends ModelProperties, OD, FCD, FSD, PE extends ModelProperties, OE, FCE, FSE, PF
extends ModelProperties, OF, FCF, FSF, PG extends ModelProperties, OG, FCG, FSG>(A: IModelType<PA, OA, FCA, FSA>, B: IModelType<PB, OB, FCB, FSB>, C: IModelType<PC, OC, FCC, FSC>, D: IModelType<PD, OD, FCD, FSD>, E: IModelType<PE, OE, FCE, FSE>, F: IModelType<PF, OF, FCF, FSF>, G: IModelType<PG, OG, FCG, FSG>): IModelType<PA & PB & PC & PD & PE & PF & PG, OA & OB & OC & OD & OE & OF & OG, _CustomJoin<FCA, _CustomJoin<FCB, _CustomJoin<FCC, _CustomJoin<FCD, _CustomJoin<FCE, _CustomJoin<FCF, FCG>>>>>>, _CustomJoin<FSA, _CustomJoin<FSB, _CustomJoin<FSC, _CustomJoin<FSD, _CustomJoin<FSE, _CustomJoin<FSF, FSG>>>>>>>
// prettier-ignore
export function compose<PA extends ModelProperties, OA, FCA, FSA, PB extends ModelProperties, OB, FCB, FSB, PC extends ModelProperties, OC, FCC, FSC, PD extends ModelProperties, OD, FCD, FSD, PE extends ModelProperties, OE, FCE, FSE, PF
extends ModelProperties, OF, FCF, FSF, PG extends ModelProperties, OG, FCG, FSG, PH extends ModelProperties, OH, FCH, FSH>(name: string, A: IModelType<PA, OA, FCA, FSA>, B: IModelType<PB, OB, FCB, FSB>, C: IModelType<PC, OC, FCC, FSC>, D: IModelType<PD, OD, FCD, FSD>, E: IModelType<PE, OE, FCE, FSE>, F: IModelType<PF, OF, FCF, FSF>, G: IModelType<PG, OG, FCG, FSG>, H: IModelType<PH, OH, FCH, FSH>): IModelType<PA & PB & PC & PD & PE & PF & PG & PH, OA & OB & OC & OD & OE & OF & OG & OH, _CustomJoin<FCA, _CustomJoin<FCB, _CustomJoin<FCC, _CustomJoin<FCD, _CustomJoin<FCE, _CustomJoin<FCF, _CustomJoin<FCG, FCH>>>>>>>, _CustomJoin<FSA, _CustomJoin<FSB, _CustomJoin<FSC, _CustomJoin<FSD, _CustomJoin<FSE, _CustomJoin<FSF, _CustomJoin<FSG, FSH>>>>>>>>
// prettier-ignore
export function compose<PA extends ModelProperties, OA, FCA, FSA, PB extends ModelProperties, OB, FCB, FSB, PC extends ModelProperties, OC, FCC, FSC, PD extends ModelProperties, OD, FCD, FSD, PE extends ModelProperties, OE, FCE, FSE, PF
extends ModelProperties, OF, FCF, FSF, PG extends ModelProperties, OG, FCG, FSG, PH extends ModelProperties, OH, FCH, FSH>(A: IModelType<PA, OA, FCA, FSA>, B: IModelType<PB, OB, FCB, FSB>, C: IModelType<PC, OC, FCC, FSC>, D: IModelType<PD, OD, FCD, FSD>, E: IModelType<PE, OE, FCE, FSE>, F: IModelType<PF, OF, FCF, FSF>, G: IModelType<PG, OG, FCG, FSG>, H: IModelType<PH, OH, FCH, FSH>): IModelType<PA & PB & PC & PD & PE & PF & PG & PH, OA & OB & OC & OD & OE & OF & OG & OH, _CustomJoin<FCA, _CustomJoin<FCB, _CustomJoin<FCC, _CustomJoin<FCD, _CustomJoin<FCE, _CustomJoin<FCF, _CustomJoin<FCG, FCH>>>>>>>, _CustomJoin<FSA, _CustomJoin<FSB, _CustomJoin<FSC, _CustomJoin<FSD, _CustomJoin<FSE, _CustomJoin<FSF, _CustomJoin<FSG, FSH>>>>>>>>
// prettier-ignore
export function compose<PA extends ModelProperties, OA, FCA, FSA, PB extends ModelProperties, OB, FCB, FSB, PC extends ModelProperties, OC, FCC, FSC, PD extends ModelProperties, OD, FCD, FSD, PE extends ModelProperties, OE, FCE, FSE, PF
extends ModelProperties, OF, FCF, FSF, PG extends ModelProperties, OG, FCG, FSG, PH extends ModelProperties, OH, FCH, FSH, PI extends ModelProperties, OI, FCI, FSI>(name: string, A: IModelType<PA, OA, FCA, FSA>, B: IModelType<PB, OB, FCB, FSB>, C: IModelType<PC, OC, FCC, FSC>, D: IModelType<PD, OD, FCD, FSD>, E: IModelType<PE, OE, FCE, FSE>, F: IModelType<PF, OF, FCF, FSF>, G: IModelType<PG, OG, FCG, FSG>, H: IModelType<PH, OH, FCH, FSH>, I: IModelType<PI, OI, FCI, FSI>): IModelType<PA & PB & PC & PD & PE & PF & PG & PH & PI, OA & OB & OC & OD & OE & OF & OG & OH & OI, _CustomJoin<FCA, _CustomJoin<FCB, _CustomJoin<FCC, _CustomJoin<FCD, _CustomJoin<FCE, _CustomJoin<FCF, _CustomJoin<FCG, _CustomJoin<FCH, FCI>>>>>>>>, _CustomJoin<FSA, _CustomJoin<FSB, _CustomJoin<FSC, _CustomJoin<FSD, _CustomJoin<FSE, _CustomJoin<FSF, _CustomJoin<FSG, _CustomJoin<FSH, FSI>>>>>>>>>
// prettier-ignore
export function compose<PA extends ModelProperties, OA, FCA, FSA, PB extends ModelProperties, OB, FCB, FSB, PC extends ModelProperties, OC, FCC, FSC, PD extends ModelProperties, OD, FCD, FSD, PE extends ModelProperties, OE, FCE, FSE, PF
extends ModelProperties, OF, FCF, FSF, PG extends ModelProperties, OG, FCG, FSG, PH extends ModelProperties, OH, FCH, FSH, PI extends ModelProperties, OI, FCI, FSI>(A: IModelType<PA, OA, FCA, FSA>, B: IModelType<PB, OB, FCB, FSB>, C: IModelType<PC, OC, FCC, FSC>, D: IModelType<PD, OD, FCD, FSD>, E: IModelType<PE, OE, FCE, FSE>, F: IModelType<PF, OF, FCF, FSF>, G: IModelType<PG, OG, FCG, FSG>, H: IModelType<PH, OH, FCH, FSH>, I: IModelType<PI, OI, FCI, FSI>): IModelType<PA & PB & PC & PD & PE & PF & PG & PH & PI, OA & OB & OC & OD & OE & OF & OG & OH & OI, _CustomJoin<FCA, _CustomJoin<FCB, _CustomJoin<FCC, _CustomJoin<FCD, _CustomJoin<FCE, _CustomJoin<FCF, _CustomJoin<FCG, _CustomJoin<FCH, FCI>>>>>>>>, _CustomJoin<FSA, _CustomJoin<FSB, _CustomJoin<FSC, _CustomJoin<FSD, _CustomJoin<FSE, _CustomJoin<FSF, _CustomJoin<FSG, _CustomJoin<FSH, FSI>>>>>>>>>
/**
* `types.compose` - Composes a new model from one or more existing model types.
* This method can be invoked in two forms:
* Given 2 or more model types, the types are composed into a new Type.
* Given first parameter as a string and 2 or more model types,
* the types are composed into a new Type with the given name
*/
export function compose(...args: any[]): any {
// TODO: just join the base type names if no name is provided
const typeName: string = typeof args[0] === "string" ? args.shift() : "AnonymousModel"
// check all parameters
if (process.env.NODE_ENV !== "production") {
args.forEach(type => {
if (!isModelType(type))
throw fail("expected a mobx-state-tree model type, got " + type + " instead")
})
}
return args
.reduce((prev, cur) =>
prev.cloneAndEnhance({
name: prev.name + "_" + cur.name,
properties: cur.properties,
initializers: cur.initializers,
preProcessor: (snapshot: any) =>
cur.applySnapshotPreProcessor(prev.applySnapshotPreProcessor(snapshot)),
postProcessor: (snapshot: any) =>
cur.applySnapshotPostProcessor(prev.applySnapshotPostProcessor(snapshot))
})
)
.named(typeName)
}
/**
* Returns if a given value represents a model type.
*
* @param type
* @returns
*/
export function isModelType<IT extends IAnyModelType = IAnyModelType>(type: IAnyType): type is IT {
return isType(type) && (type.flags & TypeFlags.Object) > 0
}
function tryGetOptional(type: IAnyType): OptionalValue<any> | undefined {
if (type instanceof OptionalValue) {
return type
}
const subtypes = type.getSubTypes()
if (!subtypes || subtypes === cannotDetermineSubtype) {
return undefined
}
const subtypesArray = asArray(subtypes)
for (const subtype of subtypesArray) {
const opt = tryGetOptional(subtype)
if (opt) {
return opt
}
}
return undefined
}