Skip to content

Instantly share code, notes, and snippets.

@aiya000
Last active July 11, 2019 09:45
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save aiya000/59c56cf69effa41f754abded677697c9 to your computer and use it in GitHub Desktop.
Save aiya000/59c56cf69effa41f754abded677697c9 to your computer and use it in GitHub Desktop.
Allows to decide take's type
import * as Untyped from 'tns-core-modules/data/observable'
import deprecated from 'deprecated-decorator' // npm install --save-dev deprecated-decorator
import { Field, BooleanT, True, False } from '@/data/types'
/**
* Don't dirty your hands.
* You must use this instead of [[Untyped.Observable]].
*
* This description is [here](http://aiya000.github.io/posts/2019-07-04-recover-nativescript-type-unsafe-observable.html).
*/
export default class Observable<Base extends object, IsDecidable extends BooleanT> extends Untyped.Observable {
private constructor() {
super()
}
// TODO: Don't allow this vvv
// const x = new Observable<{ value: number }>()
// x.assign<'value', number | string>('value', 10 as number | string)
/**
* A typed safety set()
*/
public assign<K extends string, T>(name: Field<Base, K, T>, value: T): void {
super.set(name, value)
}
@deprecated('assign')
public set(name: string, value: any): void {
super.set(name, value)
}
/**
* A type safety get()
*/
public take<K extends string, T>(key: Field<Base, K, T>): IsDecidable extends True ? T : (T | undefined) {
return super.get(key)
}
@deprecated('take')
public get(name: string): any {
super.get(name)
}
static makeUndecidable<Base extends object>(): Observable<Base, False> {
return new Observable<Base, False>()
}
static makeDecidable<Base extends object>(init: Base): Observable<Base, True> {
const x = new Observable<Base, True>()
// TODO: 型レベルに計算してあげて(Don't use set())
for (const prop in init) {
x.set(prop, init[prop])
}
return x
}
}
const x = Observable.makeUndecidable<{value: number}>()
const y: number | undefined = x.take('value')
// 2322: Type 'number | undefined' is not assignable to type 'number'. Type 'undefined' is not assignable to type 'number'.
// const y: number = x.take('value')
const a = Observable.makeDecidable<{value: number}>({value: 10})
const b: number = a.take('value')
// 2322: Type 'string' is not assignable to type 'number'.
// const v = Observable.makeDecidable<{value: number}>({value: 'x'})
/**
* Declare a type Value of a property Key via Key of a type Base.
*
* - Field<{foo: number, bar: string}, 'foo', number> = 'foo'
* - Field<{foo: number, bar: string}, 'bar', string> = 'bar'
* - Field<{foo: number, bar: string}, 'foo', string> = never
*/
export type Field<Base, Key extends string, Value> = Key extends keyof Base
? Value extends Base[Key] ? Key : never
: never
/**
* Maps null to specified fields.
*
* ```typescript
* interface X {
* x: number
* y: string
* z: boolean
* }
*
* type T = Nullable<X, 'x' | 'z'>
* // type T = {
* // x: number | null
* // y: string
* // z: boolean | null
* // }
* ```
*/
export type Nullable<Base, Keys> = {
[Key in keyof Base]: Key extends Keys
? (Base[Key] | null)
: Base[Key]
}
/**
* A type level boolean
*/
export interface BooleanT {
bool: never
}
export interface True extends BooleanT {
true: never
}
export interface False extends BooleanT {
false: never
}
@aiya000
Copy link
Author

aiya000 commented Jul 11, 2019

I should use true instead of True, and use false instead ofFalse.
Or I should define Decidable and Undecidable types onto types.ts.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment