Skip to content

Commit 000f854

Browse files
authored
Merge pull request #65 from mobxjs/improved-types
WIP: Improved types, Fixes #64, #56, #9
2 parents 02a6dfb + 2d88f1a commit 000f854

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

50 files changed

+1679
-1100
lines changed

README.md

Lines changed: 134 additions & 116 deletions
Large diffs are not rendered by default.

changelog.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
# 0.2.0
2+
3+
* Renamed `createFactory` to `types.model` (breaking!)
4+
* Renamed `composeFactory` to `types.extend` (breaking!)
5+
* Models are no longer constructed by invoking the factory as function, but by calling `factory.create` (breaking!)
6+
* Introduced `identifier`
7+
* Introduced / improved `reference`
8+
* Greatly improved typescript support, type inference etc. However there are still limitations as the full typesystem of MST cannot be expressed in TypeScript. Especially concerning the type of snapshots and the possibility to use snapshots as first class value.

src/core/action.ts

Lines changed: 43 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,4 @@
1-
import {isObservable} from "mobx"
2-
import {isModel} from "./factories"
3-
import {resolve} from "../top-level-api"
4-
import {addHiddenFinalProp, invariant, isPlainObject, isPrimitive} from "../utils"
5-
import {Node, getNode, getRelativePath} from "./node"
1+
import { action as mobxAction, isObservable } from "mobx"
62

73
export type IActionCall = {
84
name: string;
@@ -12,59 +8,56 @@ export type IActionCall = {
128

139
export type IActionHandler = (actionCall: IActionCall, next: () => void) => void
1410

15-
export function createNonActionWrapper(instance: any, key: any, func: any) {
16-
addHiddenFinalProp(instance, key, func.bind(instance))
17-
}
11+
export function createActionInvoker(name: string, fn: Function) {
12+
const action = mobxAction(name, fn)
1813

19-
export function createActionWrapper(instance: any, key: any, action: Function) {
20-
addHiddenFinalProp(
21-
instance,
22-
key,
23-
function(...args: any[]) {
24-
const adm = getNode(instance)
25-
const runAction = () => {
26-
return action.apply(instance, args)
27-
}
28-
if (adm.isRunningAction()) {
29-
// an action is already running in this tree, invoking this action does not emit a new action
30-
return runAction()
31-
} else {
32-
// start the action!
33-
const root = adm.root
34-
root._isRunningAction = true
35-
try {
36-
return adm.emitAction(
37-
adm,
38-
{
39-
name: key,
40-
path: "",
41-
args: args.map((arg, index) => serializeArgument(adm, key, index, arg))
42-
},
43-
runAction
44-
)
45-
} finally {
46-
root._isRunningAction = false
47-
}
14+
const actionInvoker = function () {
15+
const adm = getMST(this)
16+
if (adm.isRunningAction()) {
17+
// an action is already running in this tree, invoking this action does not emit a new action
18+
return action.apply(this, arguments)
19+
} else {
20+
// start the action!
21+
const root = adm.root
22+
const args = arguments
23+
root._isRunningAction = true
24+
try {
25+
return adm.emitAction(
26+
adm,
27+
{
28+
name: name,
29+
path: "",
30+
args: argsToArray(args).map((arg, index) => serializeArgument(adm, name, index, arg))
31+
},
32+
() => action.apply(this, args)
33+
)
34+
} finally {
35+
root._isRunningAction = false
4836
}
4937
}
50-
)
38+
}
39+
40+
// This construction helps producing a better function name in the stack trace, but could be optimized
41+
// away in prod builds, and `invoker` be returned directly
42+
return createNamedFunction(name, actionInvoker)
5143
}
5244

53-
function serializeArgument(adm: Node, actionName: string, index: number, arg: any): any {
45+
46+
function serializeArgument(adm: MSTAdminisration, actionName: string, index: number, arg: any): any {
5447
if (isPrimitive(arg))
5548
return arg
56-
if (isModel(arg)) {
57-
const targetNode = getNode(arg)
49+
if (isMST(arg)) {
50+
const targetNode = getMST(arg)
5851
if (adm.root !== targetNode.root)
5952
throw new Error(`Argument ${index} that was passed to action '${actionName}' is a model that is not part of the same state tree. Consider passing a snapshot or some representative ID instead`)
6053
return ({
61-
$ref: getRelativePath(adm, getNode(arg))
54+
$ref: getRelativePath(adm, getMST(arg))
6255
})
6356
}
6457
if (typeof arg === "function")
6558
throw new Error(`Argument ${index} that was passed to action '${actionName}' should be a primitive, model object or plain object, received a function`)
6659
if (typeof arg === "object" && !isPlainObject(arg))
67-
throw new Error(`Argument ${index} that was passed to action '${actionName}' should be a primitive, model object or plain object, received a ${(arg && arg.constructor) ? arg.constructor.name : "Complex Object"}`)
60+
throw new Error(`Argument ${index} that was passed to action '${actionName}' should be a primitive, model object or plain object, received a ${(arg as any && (arg as any).constructor) ? (arg as any).constructor.name : "Complex Object"}`)
6861
if (isObservable(arg))
6962
throw new Error(`Argument ${index} that was passed to action '${actionName}' should be a primitive, model object or plain object, received an mobx observable.`)
7063
try {
@@ -77,7 +70,7 @@ function serializeArgument(adm: Node, actionName: string, index: number, arg: an
7770
}
7871
}
7972

80-
function deserializeArgument(adm: Node, value: any): any {
73+
function deserializeArgument(adm: MSTAdminisration, value: any): any {
8174
if (typeof value === "object") {
8275
const keys = Object.keys(value)
8376
if (keys.length === 1 && keys[0] === "$ref")
@@ -86,10 +79,15 @@ function deserializeArgument(adm: Node, value: any): any {
8679
return value
8780
}
8881

89-
export function applyActionLocally(node: Node, instance: any, action: IActionCall) {
82+
export function applyActionLocally(node: MSTAdminisration, instance: any, action: IActionCall) {
9083
invariant(typeof instance[action.name] === "function", `Action '${action.name}' does not exist in '${node.path}'`)
9184
instance[action.name].apply(
9285
instance,
9386
action.args ? action.args.map(v => deserializeArgument(node, v)) : []
9487
)
9588
}
89+
90+
import {isMST, getMST, getRelativePath} from "./mst-node"
91+
import {MSTAdminisration} from "./mst-node-administration"
92+
import {resolve} from "../top-level-api"
93+
import {invariant, isPlainObject, isPrimitive, argsToArray, createNamedFunction} from "../utils"

src/core/complex-type.ts

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
import { IType, Type, typecheck } from "./type"
2+
3+
/**
4+
* A complex type produces a MST node (Node in the state tree)
5+
*/
6+
export abstract class ComplexType<S, T> extends Type<S, T> {
7+
create(snapshot: any = this.getDefaultSnapshot()) {
8+
typecheck(this, snapshot)
9+
const instance = this.createNewInstance()
10+
// tslint:disable-next-line:no_unused-variable
11+
const node = new MSTAdminisration(instance, this)
12+
this.finalizeNewInstance(instance, snapshot)
13+
Object.seal(instance)
14+
return instance
15+
}
16+
17+
abstract createNewInstance(): any
18+
abstract finalizeNewInstance(target: any, snapshot: any): void
19+
abstract applySnapshot(node: MSTAdminisration, target: any, snapshot: any): void
20+
abstract getDefaultSnapshot(): any
21+
abstract getChildMSTs(node: MSTAdminisration, target: any): [string, MSTAdminisration][]
22+
abstract getChildMST(node: MSTAdminisration, target: any, key: string): MSTAdminisration | null
23+
abstract serialize(node: MSTAdminisration, target: any): any
24+
abstract applyPatchLocally(node: MSTAdminisration, target: any, subpath: string, patch: IJsonPatch): void
25+
abstract getChildType(key: string): IType<any, any>
26+
abstract isValidSnapshot(snapshot: any): boolean
27+
28+
is(value: any): value is S | IMSTNode<S, T> {
29+
if (!value || typeof value !== "object")
30+
return false
31+
if (hasMST(value))
32+
return this.isValidSnapshot(getMST(value).snapshot) // could check factory, but that doesn't check structurally...
33+
return this.isValidSnapshot(value)
34+
}
35+
}
36+
37+
import { IMSTNode, hasMST, getMST } from "./mst-node"
38+
import { MSTAdminisration } from "./mst-node-administration"
39+
import { IJsonPatch } from "./json-patch"

src/core/factories.ts

Lines changed: 0 additions & 31 deletions
This file was deleted.

src/core/index.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
export * from "./type"
2+
export * from "./complex-type"
3+
export * from "./mst-node"
4+
export * from "./mst-node-administration"
5+
export * from "./action"
6+
export * from "./json-patch"

src/core/json-patch.ts

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,6 @@ export interface IJsonPatch {
1111
}
1212

1313

14-
// TODO: use https://www.npmjs.com/package/json-pointer
15-
1614
/**
1715
* escape slashes and backslashes
1816
* http://tools.ietf.org/html/rfc6901
@@ -32,13 +30,12 @@ export function joinJsonPath(path: string[]): string {
3230
// `/` refers to property with an empty name, while `` refers to root itself!
3331
if (path.length === 0)
3432
return ""
35-
return "/" + path.map(escapeJsonPath).join("/")
33+
return path.map(escapeJsonPath).join("/")
3634
}
3735

3836
export function splitJsonPath(path: string): string[] {
3937
// `/` refers to property with an empty name, while `` refers to root itself!
4038
if (path === "")
4139
return []
42-
invariant(path[0] === "/", `Expected path to start with '/', got: '${path}'`)
43-
return path.substr(1).split("/").map(unescapeJsonPath)
40+
return path.replace(/\/$/, "").split("/").map(unescapeJsonPath)
4441
}

0 commit comments

Comments
 (0)