Skip to content

Commit

Permalink
Merge pull request #71 from mobxjs/first-class-types
Browse files Browse the repository at this point in the history
Introduce `type.Type`, to make it possible to refer to the type produced by a factory
  • Loading branch information
mattiamanzati authored Apr 5, 2017
2 parents 26c305b + f7780b5 commit 3f2909f
Show file tree
Hide file tree
Showing 5 changed files with 74 additions and 4 deletions.
16 changes: 16 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -673,3 +673,19 @@ So far this might look a lot like an immutable state tree as found for example i
- mobx-state-tree allows for fine grained and efficient observability on any point in the state tree
- mobx-state-tree generates json patches for any modification that is made
- (?) mobx-state-tree is a valid redux store, providing the same api (TODO)

## TypeScript & MST

When using models, you write interface along with it's property types that will be used to perform type checks at runtime.
What about compile time? You can use TypeScript interfaces indeed to perform those checks, but that would require writing again all the properties and their actions!
Good news? You don't need to write it twice! Using the `typeof` operator of TypeScript over the `.Type` property of a MST Type, will result in a valid TypeScript Type!

```typescript
const Todo = types.model({
title: types.string,
setTitle(v: string) {
this.title = v
}
})
type ITodo = typeof Todo.Type // => ITodo is now a valid TypeScript type with { title: string; setTitle: (v: string) => void }
```
4 changes: 4 additions & 0 deletions changelog.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
# 0.2.1

* Introduced .Type and .SnapshotType to be used with TypeScript to get the type for a model

# 0.2.0

* Renamed `createFactory` to `types.model` (breaking!)
Expand Down
4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
"coverage": "tsc && tsc -p test/ && nyc ava && nyc report -r html && nyc report -r lcov",
"build-docs": "npm run quick-build && documentation readme lib/index.js --github --section API",
"lint": "tslint -c tslint.json 'src/**/*.ts'",
"clean": "rm lib && rm test-lib"
"clean": "rm -rf lib && rm -rf test-lib"
},
"repository": {
"type": "git",
Expand Down Expand Up @@ -67,4 +67,4 @@
"lib/**/*.js"
]
}
}
}
11 changes: 10 additions & 1 deletion src/core/type.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,9 @@ export interface IType<S, T> {
is(thing: any): thing is S | T
create(snapshot?: S): T
isType: boolean
describe(): string
describe(): string,
Type: T
SnapshotType: S
}

export abstract class Type<S, T> implements IType<S, T> { // TODO: generic for config and state of target
Expand All @@ -23,6 +25,13 @@ export abstract class Type<S, T> implements IType<S, T> { // TODO: generic for c
abstract create(snapshot: any): any
abstract is(thing: any): thing is S | T
abstract describe(): string

get Type(): T {
return fail("Factory.Type should not be actually called. It is just a Type signature that can be used at compile time with Typescript, by using `typeof type.Type`")
}
get SnapshotType(): S {
return fail("Factory.SnapshotType should not be actually called. It is just a Type signature that can be used at compile time with Typescript, by using `typeof type.SnapshotType`")
}
}

export function typecheck(type: IType<any, any>, snapshot: any) {
Expand Down
43 changes: 42 additions & 1 deletion test/type-system.ts
Original file line number Diff line number Diff line change
Expand Up @@ -185,4 +185,45 @@ test("can create factories with maybe primitives", t => {
t.is(F.create({ x: undefined}).x, null)
t.is(F.create({ x: ""}).x, "")
t.is(F.create({ x: "3"}).x, "3")
})
})

test("it is possible to refer to a type", t => {
const Todo = types.model({
title: types.string,
setTitle(v: string) {

}
})

function x(): typeof Todo.Type {
return Todo.create({ title: "test" }) as any // as any to make sure the type is not inferred accidentally
}

const z = x()
z.setTitle("bla")
z.title = "bla"
// z.title = 3 // Test manual: should give compile error
})

test(".Type should not be callable", t => {
const Todo = types.model({
title: types.string,
setTitle(v: string) {

}
})

t.throws(() => Todo.Type)
})


test(".SnapshotType should not be callable", t => {
const Todo = types.model({
title: types.string,
setTitle(v: string) {

}
})

t.throws(() => Todo.SnapshotType)
})

0 comments on commit 3f2909f

Please sign in to comment.