Skip to content

Commit

Permalink
fix optional array types
Browse files Browse the repository at this point in the history
  • Loading branch information
xaviergonz committed Mar 18, 2019
1 parent a81647e commit ccc466c
Show file tree
Hide file tree
Showing 14 changed files with 107 additions and 65 deletions.
48 changes: 34 additions & 14 deletions packages/mobx-state-tree/__tests__/core/type-system.test.ts
Expand Up @@ -953,20 +953,40 @@ test("#1117", () => {
})

test("MST array type should be assignable to plain array type", () => {
const Todo = types
.model({
done: false,
name: types.string
{
const Todo = types
.model({
done: false,
name: types.string
})
.actions(self => ({
toggleDone() {
self.done = !self.done
}
}))
const TodoArray = types.array(Todo)

const todoArray = TodoArray.create([{ done: true, name: "todo1" }, { name: "todo2" }])
unprotect(todoArray)
const otherTodoArray: Array<Instance<typeof Todo>> = todoArray
otherTodoArray.push(cast({ done: false, name: "todo2" }))
}

{
const T = types.model({
a: types.optional(types.array(types.number), [])
})
.actions(self => ({
toggleDone() {
self.done = !self.done
}
}))
const TodoArray = types.array(Todo)

const todoArray = TodoArray.create([{ done: true, name: "todo1" }, { name: "todo2" }])
unprotect(todoArray)
const otherTodoArray: Array<Instance<typeof Todo>> = todoArray
otherTodoArray.push(cast({ done: false, name: "todo2" }))
const arr: Array<number> = T.create().a
}

{
const T = types.model({
a: types.optional(types.array(types.number), [], [5])
})

const arr: Array<number> = T.create({
a: 5
}).a
}
})
8 changes: 4 additions & 4 deletions packages/mobx-state-tree/src/core/mst-operations.ts
Expand Up @@ -101,7 +101,7 @@ export function onPatch(
* @returns
*/
export function onSnapshot<S>(
target: IStateTreeNode<any, S>,
target: IStateTreeNode<any, S, any>,
callback: (snapshot: S) => void
): IDisposer {
// check all arguments
Expand Down Expand Up @@ -270,7 +270,7 @@ export function isProtected(target: IAnyStateTreeNode): boolean {
* @param snapshot
* @returns
*/
export function applySnapshot<C>(target: IStateTreeNode<C, any>, snapshot: C) {
export function applySnapshot<C>(target: IStateTreeNode<C, any, any>, snapshot: C) {
// check all arguments
assertIsStateTreeNode(target, 1)

Expand All @@ -285,7 +285,7 @@ export function applySnapshot<C>(target: IStateTreeNode<C, any>, snapshot: C) {
* @param applyPostProcess If true (the default) then postProcessSnapshot gets applied.
* @returns
*/
export function getSnapshot<S>(target: IStateTreeNode<any, S>, applyPostProcess = true): S {
export function getSnapshot<S>(target: IStateTreeNode<any, S, any>, applyPostProcess = true): S {
// check all arguments
assertIsStateTreeNode(target, 1)

Expand Down Expand Up @@ -783,7 +783,7 @@ export interface IModelReflectionPropertiesData {
* @returns
*/
export function getPropertyMembers(
typeOrNode: IAnyModelType | IStateTreeNode
typeOrNode: IAnyModelType | IAnyStateTreeNode
): IModelReflectionPropertiesData {
let type

Expand Down
28 changes: 17 additions & 11 deletions packages/mobx-state-tree/src/core/node/node-utils.ts
Expand Up @@ -29,35 +29,39 @@ declare const $stateTreeNodeTypes: unique symbol
* Common interface that represents a node instance.
* @hidden
*/
export interface IStateTreeNode<C = any, S = any> {
export interface IStateTreeNode<C = any, S = any, T = any> {
/**
* @internal
*/
readonly $treenode?: any

// fake, will never be present, just for typing
// we use this weird trick to allow reference types to work
readonly [$stateTreeNodeTypes]?: [C, S] | [any, any]
readonly [$stateTreeNodeTypes]?: [C, S, T] | [any, any, any]
}

type Omit<T, K> = Pick<T, Exclude<keyof T, K>>
/** @hidden */
export type RedefineIStateTreeNode<
T,
NewC,
NewS,
NewT = ExtractNodeT<T>
> = T extends IAnyStateTreeNode ? ExtractNodeT<T> & IStateTreeNode<NewC, NewS, NewT> : T

/** @hidden */
export type RedefineIStateTreeNode<T, STN extends IAnyStateTreeNode> = T extends IAnyStateTreeNode
? Omit<T, typeof $stateTreeNodeTypes> & STN
: T
export type ExtractNodeC<T> = T extends IStateTreeNode<infer C, any, any> ? C : never

/** @hidden */
export type ExtractNodeC<T> = T extends IStateTreeNode<infer C, any> ? C : never
export type ExtractNodeS<T> = T extends IStateTreeNode<any, infer S, any> ? S : never

/** @hidden */
export type ExtractNodeS<T> = T extends IStateTreeNode<any, infer S> ? S : never
export type ExtractNodeT<T> = T extends IStateTreeNode<any, any, infer X> ? X : never

/**
* Represents any state tree node instance.
* @hidden
*/
export interface IAnyStateTreeNode extends IStateTreeNode<any, any> {}
export interface IAnyStateTreeNode extends IStateTreeNode<any, any, any> {}

/**
* Returns true if the given value is a node in a state tree.
Expand All @@ -67,7 +71,9 @@ export interface IAnyStateTreeNode extends IStateTreeNode<any, any> {}
* @param value
* @returns true if the value is a state tree node.
*/
export function isStateTreeNode<C = any, S = any>(value: any): value is IStateTreeNode<C, S> {
export function isStateTreeNode<C = any, S = any, T = any>(
value: any
): value is IStateTreeNode<C, S, T> {
return !!(value && value.$treenode)
}

Expand Down Expand Up @@ -110,7 +116,7 @@ export function getStateTreeNodeSafe(value: IAnyStateTreeNode): AnyObjectNode |
* @internal
* @hidden
*/
export function toJSON<S>(this: IStateTreeNode<any, S>): S {
export function toJSON<S>(this: IStateTreeNode<any, S, any>): S {
return getStateTreeNode(this).snapshot
}

Expand Down
2 changes: 1 addition & 1 deletion packages/mobx-state-tree/src/core/node/object-node.ts
Expand Up @@ -81,7 +81,7 @@ type InternalEventHandlers<S> = {
*/
export class ObjectNode<C, S, T> extends BaseNode<C, S, T> {
readonly type!: ComplexType<C, S, T>
storedValue!: T & IStateTreeNode<C, S>
storedValue!: T & IStateTreeNode<C, S, T>

readonly nodeId = ++nextNodeId
readonly identifierAttribute?: string
Expand Down
6 changes: 3 additions & 3 deletions packages/mobx-state-tree/src/core/type/type.ts
Expand Up @@ -228,7 +228,7 @@ export type Primitives = ModelPrimitive | null | undefined
* @deprecated just for compatibility with old versions, could be deprecated on the next major version
* @hidden
*/
export interface IComplexType<C, S, T> extends IType<C, S, T & IStateTreeNode<C, S>> {}
export interface IComplexType<C, S, T> extends IType<C, S, T & IStateTreeNode<C, S, T>> {}

// do not convert to an interface
/**
Expand All @@ -255,7 +255,7 @@ export type Instance<T> = T extends IType<any, any, infer TT> ? TT : T
/**
* The input (creation) snapshot representation of a given type.
*/
export type SnapshotIn<T> = T extends IStateTreeNode<infer STNC, any>
export type SnapshotIn<T> = T extends IStateTreeNode<infer STNC, any, any>
? STNC
: T extends IType<infer TC, any, any>
? TC
Expand All @@ -264,7 +264,7 @@ export type SnapshotIn<T> = T extends IStateTreeNode<infer STNC, any>
/**
* The output snapshot representation of a given type.
*/
export type SnapshotOut<T> = T extends IStateTreeNode<any, infer STNS>
export type SnapshotOut<T> = T extends IStateTreeNode<any, infer STNS, any>
? STNS
: T extends IType<any, infer TS, any>
? TS
Expand Down
3 changes: 3 additions & 0 deletions packages/mobx-state-tree/src/index.ts
Expand Up @@ -7,8 +7,10 @@ export {
IAnyModelType,
IDisposer,
IMSTMap,
IMSTMapWithoutSTN,
IMapType,
IMSTArray,
IMSTArrayWithoutSTN,
IArrayType,
IType,
IAnyType,
Expand Down Expand Up @@ -113,6 +115,7 @@ export {
getPropertyMembers,
ExtractNodeC,
ExtractNodeS,
ExtractNodeT,
cast,
castToSnapshot,
castToReferenceSnapshot,
Expand Down
7 changes: 5 additions & 2 deletions packages/mobx-state-tree/src/types/complex-types/array.ts
Expand Up @@ -50,8 +50,11 @@ import {

/** @hidden */
export interface IMSTArray<IT extends IAnyType>
extends IObservableArray<ExtractT<IT>>,
IStateTreeNode<ExtractC<IT>[] | undefined, ExtractS<IT>[]> {
extends IMSTArrayWithoutSTN<IT>,
IStateTreeNode<ExtractC<IT>[] | undefined, ExtractS<IT>[], IMSTArrayWithoutSTN<IT>> {}

/** @hidden */
export interface IMSTArrayWithoutSTN<IT extends IAnyType> extends IObservableArray<ExtractT<IT>> {
// needs to be split or else it will complain about not being compatible with the array interface
push(...items: ExtractT<IT>[]): number
push(...items: ExtractCST<IT>[]): number
Expand Down
26 changes: 19 additions & 7 deletions packages/mobx-state-tree/src/types/complex-types/map.ts
Expand Up @@ -60,15 +60,20 @@ export interface IMapType<IT extends IAnyType>

/** @hidden */
export interface IMSTMap<IT extends IAnyType>
extends IStateTreeNode<IKeyValueMap<ExtractC<IT>> | undefined, IKeyValueMap<ExtractS<IT>>> {
extends IMSTMapWithoutSTN<IT>,
IStateTreeNode<
IKeyValueMap<ExtractC<IT>> | undefined,
IKeyValueMap<ExtractS<IT>>,
IMSTMapWithoutSTN<IT>
> {}

/** @hidden */
export interface IMSTMapWithoutSTN<IT extends IAnyType> {
// bases on ObservableMap, but fine tuned to the auto snapshot conversion of MST

clear(): void
delete(key: string): boolean
forEach(
callbackfn: (value: ExtractT<IT>, key: string, map: IMSTMap<IT>) => void,
thisArg?: any
): void
forEach(callbackfn: (value: ExtractT<IT>, key: string, map: this) => void, thisArg?: any): void
get(key: string): ExtractT<IT> | undefined
has(key: string): boolean
set(key: string, value: ExtractCST<IT>): this
Expand All @@ -79,8 +84,15 @@ export interface IMSTMap<IT extends IAnyType>
entries(): IterableIterator<[string, ExtractT<IT>]>
[Symbol.iterator](): IterableIterator<[string, ExtractT<IT>]>
/** Merge another object into this map, returns self. */
merge(other: IMSTMap<IType<any, any, ExtractT<IT>>> | IKeyValueMap<ExtractCST<IT>> | any): this
replace(values: IMSTMap<IType<any, any, ExtractT<IT>>> | IKeyValueMap<ExtractT<IT>>): this
merge(
other: IMSTMapWithoutSTN<IType<any, any, ExtractT<IT>>> | IKeyValueMap<ExtractCST<IT>> | any
): this
replace(
values:
| IMSTMapWithoutSTN<IType<any, any, ExtractT<IT>>>
| IKeyValueMap<ExtractCST<IT>>
| any
): this

/**
* Returns a plain object that represents this map.
Expand Down
6 changes: 5 additions & 1 deletion packages/mobx-state-tree/src/types/complex-types/model.ts
Expand Up @@ -157,7 +157,11 @@ export type ModelInstanceType<
CustomS
> = ModelInstanceTypeProps<P> &
O &
IStateTreeNode<ModelCreationType2<P, CustomC>, ModelSnapshotType2<P, CustomS>>
IStateTreeNode<
ModelCreationType2<P, CustomC>,
ModelSnapshotType2<P, CustomS>,
ModelInstanceTypeProps<P> & O
>

/** @hidden */
export interface ModelActions {
Expand Down
14 changes: 9 additions & 5 deletions packages/mobx-state-tree/src/types/utility-types/maybe.ts
Expand Up @@ -2,17 +2,15 @@ import {
union,
optional,
IType,
isType,
fail,
undefinedType,
nullType,
IAnyType,
ExtractC,
ExtractS,
ExtractT,
IStateTreeNode,
RedefineIStateTreeNode,
assertIsType
assertIsType,
ExtractNodeT
} from "../../internal"

const optionalUndefinedType = optional(undefinedType, undefined)
Expand All @@ -23,7 +21,13 @@ export interface IMaybeIType<IT extends IAnyType, C, O>
extends IType<
ExtractC<IT> | C,
ExtractS<IT> | O,
RedefineIStateTreeNode<ExtractT<IT>, IStateTreeNode<ExtractC<IT> | C, ExtractS<IT> | O>> | O
| RedefineIStateTreeNode<
ExtractT<IT>,
ExtractC<IT> | C,
ExtractS<IT> | O,
ExtractNodeT<ExtractT<IT>> | O
>
| O
> {}

/** @hidden */
Expand Down
12 changes: 2 additions & 10 deletions packages/mobx-state-tree/src/types/utility-types/optional.ts
@@ -1,6 +1,5 @@
import {
isStateTreeNode,
getStateTreeNode,
IType,
TypeFlags,
isType,
Expand All @@ -15,7 +14,6 @@ import {
ExtractC,
ExtractCST,
RedefineIStateTreeNode,
IStateTreeNode,
AnyObjectNode,
BaseType,
assertIsType
Expand All @@ -41,10 +39,7 @@ export class OptionalValue<
> extends BaseType<
ExtractC<IT> | OptionalVals[number],
ExtractS<IT>,
RedefineIStateTreeNode<
ExtractT<IT>,
IStateTreeNode<ExtractC<IT> | OptionalVals[number], ExtractS<IT>>
>
RedefineIStateTreeNode<ExtractT<IT>, ExtractC<IT> | OptionalVals[number], ExtractS<IT>>
> {
get flags() {
return this._subtype.flags | TypeFlags.Optional
Expand Down Expand Up @@ -133,10 +128,7 @@ export interface IOptionalIType<IT extends IAnyType, OptionalVals extends ValidO
extends IType<
ExtractC<IT> | OptionalVals[number],
ExtractS<IT>,
RedefineIStateTreeNode<
ExtractT<IT>,
IStateTreeNode<ExtractC<IT> | OptionalVals[number], ExtractS<IT>>
>
RedefineIStateTreeNode<ExtractT<IT>, ExtractC<IT> | OptionalVals[number], ExtractS<IT>>
> {}

function checkOptionalPreconditions<IT extends IAnyType>(
Expand Down
5 changes: 2 additions & 3 deletions packages/mobx-state-tree/src/types/utility-types/reference.ts
Expand Up @@ -4,7 +4,6 @@ import {
createScalarNode,
IType,
TypeFlags,
isType,
IValidationContext,
IValidationResult,
typeCheckSuccess,
Expand All @@ -14,7 +13,6 @@ import {
ExtractT,
IAnyStateTreeNode,
IAnyComplexType,
IStateTreeNode,
RedefineIStateTreeNode,
Hook,
IDisposer,
Expand Down Expand Up @@ -59,7 +57,8 @@ function getInvalidationCause(hook: Hook): "detach" | "destroy" | undefined {
/** @hidden */
export type ReferenceT<IT extends IAnyType> = RedefineIStateTreeNode<
ExtractT<IT>,
IStateTreeNode<ReferenceIdentifier, ReferenceIdentifier>
ReferenceIdentifier,
ReferenceIdentifier
>

class StoredReference<IT extends IAnyType> {
Expand Down

0 comments on commit ccc466c

Please sign in to comment.