├── .github └── issue_template.md ├── .gitignore ├── .travis.yml ├── LICENSE ├── README.md ├── package-lock.json ├── package.json ├── scripts ├── rollup.config.js └── webpack.config.test.js ├── src ├── bindings.ts ├── index.ts └── tsconfig.json ├── test ├── bindings.ts └── tsconfig.json ├── testem.yml ├── tsconfig.base.json └── tslint.json /.github/issue_template.md: -------------------------------------------------------------------------------- 1 | 16 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | typings/ 3 | 4 | /lib/ 5 | /dist/ 6 | /.tmp/ 7 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - "10" 4 | cache: npm 5 | script: 6 | - npm run lint 7 | - npm run test 8 | - npm run build 9 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2017 katashin 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is 8 | furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in 11 | all copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | THE SOFTWARE. 20 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # vuex-class 2 | 3 | [![vuex-class Dev Token](https://badge.devtoken.rocks/vuex-class)](https://devtoken.rocks/package/vuex-class) 4 | 5 | Binding helpers for Vuex and vue-class-component 6 | 7 | ## Dependencies 8 | 9 | - [Vue](https://github.com/vuejs/vue) 10 | - [Vuex](https://github.com/vuejs/vuex) 11 | - [vue-class-component](https://github.com/vuejs/vue-class-component) 12 | 13 | ## Installation 14 | 15 | ```bash 16 | $ npm install --save vuex-class 17 | # or 18 | $ yarn add vuex-class 19 | ``` 20 | 21 | ## Example 22 | 23 | ```js 24 | import Vue from 'vue' 25 | import Component from 'vue-class-component' 26 | import { 27 | State, 28 | Getter, 29 | Action, 30 | Mutation, 31 | namespace 32 | } from 'vuex-class' 33 | 34 | const someModule = namespace('path/to/module') 35 | 36 | @Component 37 | export class MyComp extends Vue { 38 | @State('foo') stateFoo 39 | @State(state => state.bar) stateBar 40 | @Getter('foo') getterFoo 41 | @Action('foo') actionFoo 42 | @Mutation('foo') mutationFoo 43 | @someModule.Getter('foo') moduleGetterFoo 44 | 45 | // If the argument is omitted, use the property name 46 | // for each state/getter/action/mutation type 47 | @State foo 48 | @Getter bar 49 | @Action baz 50 | @Mutation qux 51 | 52 | created () { 53 | this.stateFoo // -> store.state.foo 54 | this.stateBar // -> store.state.bar 55 | this.getterFoo // -> store.getters.foo 56 | this.actionFoo({ value: true }) // -> store.dispatch('foo', { value: true }) 57 | this.mutationFoo({ value: true }) // -> store.commit('foo', { value: true }) 58 | this.moduleGetterFoo // -> store.getters['path/to/module/foo'] 59 | } 60 | } 61 | ``` 62 | 63 | ## Issue Reporting Guideline 64 | 65 | ### Questions 66 | 67 | For general usage question which is not related to vuex-class should be posted to [StackOverflow](https://stackoverflow.com/) or other Q&A forum. Such questions will be closed without an answer. 68 | 69 | ### Bug Reports 70 | 71 | Please make sure to provide minimal and self-contained reproduction when you report a bug. Otherwise the issue will be closed immediately. 72 | 73 | ## License 74 | 75 | MIT 76 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "vuex-class", 3 | "version": "0.3.2", 4 | "author": "katashin", 5 | "description": "Binding helpers for Vuex and vue-class-component", 6 | "keywords": [ 7 | "vue", 8 | "vuex", 9 | "bindings" 10 | ], 11 | "license": "MIT", 12 | "main": "dist/vuex-class.cjs.js", 13 | "module": "lib/index.js", 14 | "typings": "lib/index.d.ts", 15 | "files": [ 16 | "dist", 17 | "lib" 18 | ], 19 | "homepage": "https://github.com/ktsn/vuex-class", 20 | "bugs": "https://github.com/ktsn/vuex-class/issues", 21 | "repository": { 22 | "type": "git", 23 | "url": "https://github.com/ktsn/vuex-class.git" 24 | }, 25 | "scripts": { 26 | "prepublishOnly": "run-s lint test clean build", 27 | "clean": "rm -rf lib dist .tmp", 28 | "build": "run-s build:ts build:cjs build:dev build:prod", 29 | "build:ts": "tsc -p src", 30 | "build:cjs": "rollup -c scripts/rollup.config.js --environment BUILD:commonjs", 31 | "build:dev": "rollup -c scripts/rollup.config.js --environment BUILD:development", 32 | "build:prod": "rollup -c scripts/rollup.config.js --environment BUILD:production | uglifyjs -mc warnings=false --comments -o dist/vuex-class.min.js", 33 | "watch:test": "webpack --watch --config scripts/webpack.config.test.js", 34 | "lint": "tslint \"src/**/*.ts\" && tslint \"test/**/*.ts\"", 35 | "testem": "testem", 36 | "dev": "run-p watch:test testem", 37 | "test": "webpack --config scripts/webpack.config.test.js && testem ci --launch PhantomJS" 38 | }, 39 | "devDependencies": { 40 | "@types/mocha": "^5.2.0", 41 | "@types/power-assert": "1.5.0", 42 | "@types/sinon": "^4.3.3", 43 | "es6-promise": "^4.2.4", 44 | "glob": "^7.1.2", 45 | "json-loader": "^0.5.7", 46 | "npm-run-all": "^4.1.3", 47 | "power-assert": "^1.5.0", 48 | "rollup": "^0.58.2", 49 | "rollup-plugin-replace": "^2.0.0", 50 | "sinon": "^5.0.7", 51 | "testem": "^2.4.0", 52 | "ts-loader": "^4.3.0", 53 | "tslint": "^5.10.0", 54 | "tslint-config-ktsn": "^2.1.0", 55 | "typescript": "^2.8.3", 56 | "uglify-js": "^3.3.25", 57 | "vue": "^2.5.16", 58 | "vue-class-component": "^6.2.0", 59 | "vuex": "^3.0.1", 60 | "webpack": "^4.8.3", 61 | "webpack-cli": "^2.1.3", 62 | "webpack-espower-loader": "^2.0.0" 63 | }, 64 | "peerDependencies": { 65 | "vue": "^2.5.0", 66 | "vuex": "^3.0.0", 67 | "vue-class-component": "^6.0.0 || ^7.0.0" 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /scripts/rollup.config.js: -------------------------------------------------------------------------------- 1 | const replace = require('rollup-plugin-replace') 2 | const meta = require('../package.json') 3 | 4 | const banner = `/*! 5 | * ${meta.name} v${meta.version} 6 | * ${meta.homepage} 7 | * 8 | * @license 9 | * Copyright (c) 2017 ${meta.author} 10 | * Released under the MIT license 11 | * ${meta.homepage}/blob/master/LICENSE 12 | */` 13 | 14 | const config = { 15 | input: 'lib/index.js', 16 | output: { 17 | name: 'VuexClass', 18 | globals: { 19 | vue: 'Vue', 20 | vuex: 'Vuex', 21 | 'vue-class-component': 'VueClassComponent' 22 | }, 23 | banner 24 | }, 25 | plugins: [], 26 | external: [ 27 | 'vue', 28 | 'vuex', 29 | 'vue-class-component' 30 | ] 31 | } 32 | 33 | switch (process.env.BUILD) { 34 | case 'commonjs': 35 | config.output.file = `dist/${meta.name}.cjs.js` 36 | config.output.format = 'cjs' 37 | break 38 | case 'development': 39 | config.output.file = `dist/${meta.name}.js` 40 | config.output.format = 'umd' 41 | config.plugins.push( 42 | replace({ 43 | 'process.env.NODE_ENV': JSON.stringify('development') 44 | }) 45 | ) 46 | break 47 | case 'production': 48 | config.output.format = 'umd' 49 | config.plugins.push( 50 | replace({ 51 | 'process.env.NODE_ENV': JSON.stringify('production') 52 | }) 53 | ) 54 | break 55 | default: 56 | throw new Error('Unknown build environment') 57 | } 58 | 59 | module.exports = config 60 | -------------------------------------------------------------------------------- /scripts/webpack.config.test.js: -------------------------------------------------------------------------------- 1 | const path = require('path') 2 | const glob = require('glob') 3 | 4 | module.exports = { 5 | mode: 'development', 6 | entry: ['es6-promise/auto'].concat(glob.sync(path.resolve(__dirname, '../test/**/*.ts'))), 7 | output: { 8 | path: path.resolve(__dirname, '../.tmp'), 9 | filename: 'test.js' 10 | }, 11 | resolve: { 12 | extensions: ['.ts', '.js'] 13 | }, 14 | module: { 15 | rules: [ 16 | { test: /\.ts$/, loaders: ['webpack-espower-loader', 'ts-loader'] } 17 | ] 18 | }, 19 | devtool: 'source-map' 20 | } 21 | -------------------------------------------------------------------------------- /src/bindings.ts: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | import { createDecorator } from 'vue-class-component' 3 | import { 4 | mapState, 5 | mapGetters, 6 | mapActions, 7 | mapMutations 8 | } from 'vuex' 9 | 10 | export type VuexDecorator = (proto: V, key: string) => void 11 | 12 | export type StateTransformer = (state: any, getters: any) => any 13 | 14 | export type MapHelper = typeof mapState | typeof mapGetters 15 | | typeof mapActions | typeof mapMutations 16 | 17 | export interface BindingOptions { 18 | namespace?: string 19 | } 20 | 21 | export interface BindingHelper { 22 | (proto: V, key: string): void 23 | (type: string, options?: BindingOptions): VuexDecorator 24 | } 25 | 26 | export interface StateBindingHelper extends BindingHelper { 27 | (type: StateTransformer, options?: BindingOptions): VuexDecorator 28 | } 29 | 30 | export interface BindingHelpers { 31 | State: StateBindingHelper 32 | Getter: BindingHelper 33 | Mutation: BindingHelper 34 | Action: BindingHelper 35 | } 36 | 37 | export const State = createBindingHelper('computed', mapState) as StateBindingHelper 38 | 39 | export const Getter = createBindingHelper('computed', mapGetters) 40 | 41 | export const Action = createBindingHelper('methods', mapActions) 42 | 43 | export const Mutation = createBindingHelper('methods', mapMutations) 44 | 45 | export function namespace (namespace: string): BindingHelpers 46 | export function namespace ( 47 | namespace: string, 48 | helper: T 49 | ): T 50 | export function namespace ( 51 | namespace: string, 52 | helper?: T 53 | ): any { 54 | function createNamespacedHelper (helper: T): T { 55 | // T is BindingHelper or StateBindingHelper 56 | function namespacedHelper (proto: Vue, key: string): void 57 | function namespacedHelper (type: any, options?: BindingOptions): VuexDecorator 58 | function namespacedHelper (a: Vue | any, b?: string | BindingOptions): VuexDecorator | void { 59 | if (typeof b === 'string') { 60 | const key: string = b 61 | const proto: Vue = a 62 | return helper(key, { namespace })(proto, key) 63 | } 64 | 65 | const type = a 66 | const options = merge(b || {}, { namespace }) 67 | return helper(type, options) 68 | } 69 | 70 | return namespacedHelper as T 71 | } 72 | 73 | if (helper) { 74 | console.warn('[vuex-class] passing the 2nd argument to `namespace` function is deprecated. pass only namespace string instead.') 75 | return createNamespacedHelper(helper) 76 | } 77 | 78 | return { 79 | State: createNamespacedHelper(State as any), 80 | Getter: createNamespacedHelper(Getter as any), 81 | Mutation: createNamespacedHelper(Mutation as any), 82 | Action: createNamespacedHelper(Action as any) 83 | } 84 | } 85 | 86 | function createBindingHelper ( 87 | bindTo: 'computed' | 'methods', 88 | mapFn: MapHelper 89 | ): BindingHelper { 90 | function makeDecorator (map: any, namespace: string | undefined) { 91 | return createDecorator((componentOptions, key) => { 92 | if (!componentOptions[bindTo]) { 93 | componentOptions[bindTo] = {} 94 | } 95 | 96 | const mapObject = { [key]: map } 97 | 98 | componentOptions[bindTo]![key] = namespace !== undefined 99 | ? mapFn(namespace, mapObject)[key] 100 | : mapFn(mapObject)[key] 101 | }) 102 | } 103 | 104 | function helper (proto: Vue, key: string): void 105 | function helper (type: any, options?: BindingOptions): VuexDecorator 106 | function helper (a: Vue | any, b?: string | BindingOptions): VuexDecorator | void { 107 | if (typeof b === 'string') { 108 | const key: string = b 109 | const proto: Vue = a 110 | return makeDecorator(key, undefined)(proto, key) 111 | } 112 | 113 | const namespace = extractNamespace(b) 114 | const type = a 115 | return makeDecorator(type, namespace) 116 | } 117 | 118 | return helper 119 | } 120 | 121 | function extractNamespace (options: BindingOptions | undefined): string | undefined { 122 | const n = options && options.namespace 123 | 124 | if (typeof n !== 'string') { 125 | return undefined 126 | } 127 | 128 | if (n[n.length - 1] !== '/') { 129 | return n + '/' 130 | } 131 | 132 | return n 133 | } 134 | 135 | function merge (a: T, b: U): T & U { 136 | const res: any = {} 137 | ;[a, b].forEach((obj: any) => { 138 | Object.keys(obj).forEach(key => { 139 | res[key] = obj[key] 140 | }) 141 | }) 142 | return res 143 | } 144 | -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | export { 2 | State, 3 | Getter, 4 | Action, 5 | Mutation, 6 | namespace 7 | } from './bindings' 8 | -------------------------------------------------------------------------------- /src/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../tsconfig.base.json", 3 | "compilerOptions": { 4 | "outDir": "../lib", 5 | "declaration": true 6 | }, 7 | "include": [ 8 | "**/*.ts" 9 | ] 10 | } -------------------------------------------------------------------------------- /test/bindings.ts: -------------------------------------------------------------------------------- 1 | import assert = require('power-assert') 2 | import sinon = require('sinon') 3 | import Vue from 'vue' 4 | import Vuex from 'vuex' 5 | import Component from 'vue-class-component' 6 | import { 7 | State, 8 | Getter, 9 | Action, 10 | Mutation, 11 | namespace 12 | } from '../src/bindings' 13 | 14 | Vue.config.productionTip = false 15 | Vue.config.devtools = false 16 | 17 | describe('binding helpers', () => { 18 | Vue.use(Vuex) 19 | 20 | it('State: type', () => { 21 | const store = new Vuex.Store({ 22 | state: { value: 1 } 23 | }) 24 | 25 | @Component 26 | class MyComp extends Vue { 27 | @State('value') 28 | foo: number 29 | } 30 | 31 | assert(new MyComp({ store }).foo === 1) 32 | }) 33 | 34 | it('State: function', () => { 35 | const store = new Vuex.Store({ 36 | state: { value: 1 } 37 | }) 38 | 39 | @Component 40 | class MyComp extends Vue { 41 | @State((state: { value: number }) => { 42 | return state.value + 10 43 | }) 44 | foo: number 45 | } 46 | 47 | assert(new MyComp({ store }).foo === 11) 48 | }) 49 | 50 | it('State: implicit state name', () => { 51 | const store = new Vuex.Store({ 52 | state: { value: 1 } 53 | }) 54 | 55 | @Component 56 | class MyComp extends Vue { 57 | @State value: number 58 | } 59 | 60 | const c = new MyComp({ store }) 61 | assert(c.value === 1) 62 | }) 63 | 64 | it('State: namespace', () => { 65 | const store = new Vuex.Store({ 66 | modules: { 67 | foo: { 68 | namespaced: true, 69 | state: { value: 1 } 70 | } 71 | } 72 | }) 73 | 74 | const foo = namespace('foo') 75 | 76 | @Component 77 | class MyComp extends Vue { 78 | @foo.State('value') 79 | bar: number 80 | 81 | @foo.State value: number 82 | } 83 | 84 | const c = new MyComp({ store }) 85 | assert(c.bar === 1) 86 | assert(c.value === 1) 87 | }) 88 | 89 | it('Getter: type', () => { 90 | const store = new Vuex.Store({ 91 | state: { value: 1 }, 92 | getters: { 93 | foo: state => state.value + 1 94 | } 95 | }) 96 | 97 | @Component 98 | class MyComp extends Vue { 99 | @Getter('foo') 100 | bar: number 101 | } 102 | 103 | const c = new MyComp({ store }) 104 | assert(c.bar === 2) 105 | }) 106 | 107 | it('Getter: implicit getter type', () => { 108 | const store = new Vuex.Store({ 109 | state: { value: 1 }, 110 | getters: { 111 | foo: state => state.value + 1 112 | } 113 | }) 114 | 115 | @Component 116 | class MyComp extends Vue { 117 | @Getter foo: number 118 | } 119 | 120 | const c = new MyComp({ store }) 121 | assert(c.foo === 2) 122 | }) 123 | 124 | it('Getter: namespace', () => { 125 | const store = new Vuex.Store({ 126 | modules: { 127 | foo: { 128 | namespaced: true, 129 | state: { value: 1 }, 130 | getters: { 131 | bar: state => state.value + 1 132 | } 133 | } 134 | } 135 | }) 136 | 137 | const foo = namespace('foo') 138 | 139 | @Component 140 | class MyComp extends Vue { 141 | @foo.Getter('bar') 142 | baz: number 143 | 144 | @foo.Getter bar: number 145 | } 146 | 147 | const c = new MyComp({ store }) 148 | assert(c.baz === 2) 149 | assert(c.bar === 2) 150 | }) 151 | 152 | it('Action: type', () => { 153 | const spy = sinon.spy() 154 | 155 | const store = new Vuex.Store({ 156 | actions: { 157 | foo: spy 158 | } 159 | }) 160 | 161 | @Component 162 | class MyComp extends Vue { 163 | @Action('foo') 164 | bar: (payload: { value: number }) => void 165 | } 166 | 167 | const c = new MyComp({ store }) 168 | c.bar({ value: 1 }) 169 | assert.deepStrictEqual(spy.getCall(0).args[1], { value: 1 }) 170 | }) 171 | 172 | it('Action: implicity action type', () => { 173 | const spy = sinon.spy() 174 | 175 | const store = new Vuex.Store({ 176 | actions: { 177 | foo: spy 178 | } 179 | }) 180 | 181 | @Component 182 | class MyComp extends Vue { 183 | @Action foo: () => void 184 | } 185 | 186 | const c = new MyComp({ store }) 187 | c.foo() 188 | assert(spy.called) 189 | }) 190 | 191 | it('Action: namespace', () => { 192 | const spy = sinon.spy() 193 | 194 | const store = new Vuex.Store({ 195 | modules: { 196 | foo: { 197 | namespaced: true, 198 | actions: { 199 | bar: spy 200 | } 201 | } 202 | } 203 | }) 204 | 205 | const foo = namespace('foo') 206 | 207 | @Component 208 | class MyComp extends Vue { 209 | @foo.Action('bar') 210 | baz: (payload: { value: number }) => void 211 | 212 | @foo.Action 213 | bar: (payload: { value: number }) => void 214 | } 215 | 216 | const c = new MyComp({ store }) 217 | c.baz({ value: 1 }) 218 | assert.deepStrictEqual(spy.getCall(0).args[1], { value: 1 }) 219 | c.bar({ value: 2 }) 220 | assert.deepStrictEqual(spy.getCall(1).args[1], { value: 2 }) 221 | }) 222 | 223 | it('Mutation: type', () => { 224 | const spy = sinon.spy() 225 | 226 | const store = new Vuex.Store({ 227 | mutations: { 228 | foo: spy 229 | } 230 | }) 231 | 232 | @Component 233 | class MyComp extends Vue { 234 | @Mutation('foo') 235 | bar: (payload: { value: number }) => void 236 | } 237 | 238 | const c = new MyComp({ store }) 239 | c.bar({ value: 1 }) 240 | assert.deepStrictEqual(spy.getCall(0).args[1], { value: 1 }) 241 | }) 242 | 243 | it('Mutation: implicit mutation type', () => { 244 | const spy = sinon.spy() 245 | 246 | const store = new Vuex.Store({ 247 | mutations: { 248 | foo: spy 249 | } 250 | }) 251 | 252 | @Component 253 | class MyComp extends Vue { 254 | @Mutation foo: () => void 255 | } 256 | 257 | const c = new MyComp({ store }) 258 | c.foo() 259 | assert(spy.called) 260 | }) 261 | 262 | it('Mutation: namespace', () => { 263 | const spy = sinon.spy() 264 | 265 | const store = new Vuex.Store({ 266 | modules: { 267 | foo: { 268 | namespaced: true, 269 | mutations: { 270 | bar: spy 271 | } 272 | } 273 | } 274 | }) 275 | 276 | const foo = namespace('foo') 277 | 278 | @Component 279 | class MyComp extends Vue { 280 | @foo.Mutation('bar') 281 | baz: (payload: { value: number }) => void 282 | 283 | @foo.Mutation 284 | bar: (payload: { value: number }) => void 285 | } 286 | 287 | const c = new MyComp({ store }) 288 | c.baz({ value: 1 }) 289 | assert.deepStrictEqual(spy.getCall(0).args[1], { value: 1 }) 290 | c.bar({ value: 2 }) 291 | assert.deepStrictEqual(spy.getCall(1).args[1], { value: 2 }) 292 | }) 293 | }) 294 | -------------------------------------------------------------------------------- /test/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../tsconfig.base.json", 3 | "compilerOptions": { 4 | "module": "commonjs", 5 | "experimentalDecorators": true, 6 | "sourceMap": true 7 | }, 8 | "include": [ 9 | "**/*.ts", 10 | "../src/**/*.ts", 11 | "../node_modules/@types/mocha/index.d.ts" 12 | ] 13 | } 14 | -------------------------------------------------------------------------------- /testem.yml: -------------------------------------------------------------------------------- 1 | --- 2 | framework: mocha 3 | src_files: 4 | - .tmp/test.js 5 | launch_in_dev: 6 | - Chrome 7 | browser_args: 8 | Chrome: 9 | - --headless 10 | - --disable-gpu 11 | - --remote-debugging-port=9222 -------------------------------------------------------------------------------- /tsconfig.base.json: -------------------------------------------------------------------------------- 1 | { 2 | "compileOnSave": false, 3 | "compilerOptions": { 4 | "target": "es5", 5 | "module": "es2015", 6 | "moduleResolution": "node", 7 | "lib": [ 8 | "dom", 9 | "es2015" 10 | ], 11 | "allowSyntheticDefaultImports": true, 12 | "noImplicitAny": true, 13 | "noImplicitReturns": true, 14 | "noImplicitThis": true, 15 | "strictNullChecks": true 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /tslint.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "tslint-config-ktsn" 3 | } 4 | --------------------------------------------------------------------------------