9 Commits

Author SHA1 Message Date
034134a08f Updated documentation
All checks were successful
Build / Build NPM Project (push) Successful in 36s
Build / Tag Version (push) Successful in 7s
Build / Publish (push) Successful in 16s
2024-01-07 19:11:26 -05:00
7a9ab68402 Updated libraries & testing 2023-03-20 20:42:32 +00:00
4e6ab26f0d Update .gitlab/.gitlab-ci.yml file 2023-03-20 20:28:07 +00:00
c3d6c79fe3 Update .gitlab/.gitlab-ci.yml file 2023-03-20 20:26:36 +00:00
2b1e7d6816 Added coverage 2022-09-23 11:40:46 -04:00
e14e1ffa74 Added build pipeline 2022-09-23 11:38:55 -04:00
414128db90 Bump to 4.1.0, removed lib 2021-05-15 20:18:48 -04:00
132f23a028 Removed crypto-js 2021-05-15 19:57:12 -04:00
f27014af14 Updated docs 2021-02-02 00:05:08 -05:00
16 changed files with 4088 additions and 278 deletions

86
.github/workflows/build.yaml vendored Normal file
View File

@ -0,0 +1,86 @@
name: Build
run-name: Build
on:
- push
- pull_request
jobs:
build:
name: Build NPM Project
runs-on: ubuntu-latest
container:
image: node
volumes:
- '/mnt/swarm/gitea/runner/cache:/cache'
steps:
- name: Clone Repository
uses: ztimson/actions/clone@develop
- name: Restore node_modules
uses: ztimson/actions/cache/restore@develop
with:
key: node_modules
- name: Install Dependencies
run: npm i
- name: Build Project
run: npm run build
- name: Test
run: npm run test:coverage
- name: Cache node_modules
uses: ztimson/actions/cache@develop
with:
key: node_modules
pattern: node_modules
- name: Cache Artifacts
uses: ztimson/actions/cache@develop
with:
pattern: dist
tag:
name: Tag Version
needs: build
if: ${{github.ref_name}} == 'release'
runs-on: ubuntu-latest
container: node
steps:
- name: Clone Repository
uses: ztimson/actions/clone@develop
- name: Get Version Number
run: echo "VERSION=$(cat package.json | grep version | grep -Eo ':.+' | grep -Eo '[[:alnum:]\.\/\-]+')" >> $GITHUB_ENV
- name: Tag Version
uses: ztimson/actions/tag@develop
with:
tag: ${{env.VERSION}}
publish:
name: Publish
needs: build
if: ${{github.ref_name}} == 'release'
runs-on: ubuntu-latest
container:
image: node
volumes:
- '/mnt/swarm/gitea/runner/cache:/cache'
steps:
- name: Clone Repository
uses: ztimson/actions/clone@develop
- name: Restore Artifacts
uses: ztimson/actions/cache/restore@develop
- name: Upload to Registry
uses: ztimson/actions/npm/publish@develop
- name: Upload to NPM
uses: ztimson/actions/npm/publish@develop
with:
owner: ztimson
registry: https://registry.npmjs.org/
token: ${{secrets.NPM_TOKEN}}

2
.gitignore vendored
View File

@ -2,7 +2,7 @@
# compiled output
/tmp
/out-tsc
/lib
# dependencies
/node_modules

7
LICENSE Normal file
View File

@ -0,0 +1,7 @@
Copyright (c) 2023 Zakary Timson
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

158
README.md
View File

@ -1,26 +1,126 @@
# WebStorage Decorators
A Javascript library that adds property decorators to sync a class property with the local or session storage. Useful
for persisting themes or local settings over reloads or maintaining filters/search options in the current user session.
<!-- Header -->
<div id="top" align="center">
<br />
<!-- Logo -->
<img src="https://git.zakscode.com/repo-avatars/60b47463c877db0d03d41f265a24df35882f246e95df786de38a19fbb617d310" alt="Logo" width="200" height="200">
<!-- Title -->
### webstorage-decorators
<!-- Description -->
TypeScript: Sync variables with localStorage
<!-- Repo badges -->
[![Version](https://img.shields.io/badge/dynamic/json.svg?label=Version&style=for-the-badge&url=https://git.zakscode.com/api/v1/repos/ztimson/webstorage-decorators/tags&query=$[0].name)](https://git.zakscode.com/ztimson/webstorage-decorators/tags)
[![Pull Requests](https://img.shields.io/badge/dynamic/json.svg?label=Pull%20Requests&style=for-the-badge&url=https://git.zakscode.com/api/v1/repos/ztimson/webstorage-decorators&query=open_pr_counter)](https://git.zakscode.com/ztimson/webstorage-decorators/pulls)
[![Issues](https://img.shields.io/badge/dynamic/json.svg?label=Issues&style=for-the-badge&url=https://git.zakscode.com/api/v1/repos/ztimson/webstorage-decorators&query=open_issues_count)](https://git.zakscode.com/ztimson/webstorage-decorators/issues)
<!-- Links -->
---
<div>
<a href="https://git.zakscode.com/ztimson/webstorage-decorators/releases" target="_blank">Release Notes</a>
<a href="https://git.zakscode.com/ztimson/webstorage-decorators/issues/new?template=.github%2fissue_template%2fbug.md" target="_blank">Report a Bug</a>
<a href="https://git.zakscode.com/ztimson/webstorage-decorators/issues/new?template=.github%2fissue_template%2fenhancement.md" target="_blank">Request a Feature</a>
</div>
---
</div>
## Table of Contents
- [WebStorage-Decorators](#top)
- [About](#about)
- [Examples](#examples)
- [Built With](#built-with)
- [Setup](#setup)
- [Production](#production)
- [Development](#development)
- [Documentation](#documentation)
- [Decorators](#decorators)
- [WebStorageOptions](#webstorageoptions)
- [License](#license)
## About
**Deprecated:** Please use the replacement library [var-persist](https://git.zakscode.com/ztimson/var-persist).
WebStorage-Decorators is a library that adds property decorators to sync a class property with the local or session storage. It is useful for persisting state through reloads without making any changes to existing code.
This library has some caveats that [var-persist](https://git.zakscode.com/ztimson/var-persist) rectifies:
- Only supports decorators
- Impure functions can make changes to data which do not get synced
- [Proto]types and functions are lost
**Disclaimer:** JavaScript's decorators are currently undergoing changes to the API overseen by [TC39](https://tc39.es) and currently have no support for property decorators. [Experimental decorators](https://www.typescriptlang.org/tsconfig#experimentalDecorators) must be enabled to work properly.
### Examples
## Quick Setup
1. Install with: `npm install --save webstorage-decorators`
2. Add the decorator to your property and use as normal!
```typescript
import {LocalStorage, SessionStorage} from 'webstorage-decorators';
export class MyCustomClass {
@LocalStorage({key: 'site_theme', default: 'light_theme'}) theme: string;
@SessionStorage({encryptWith: config.entryptionKey}) thisUser: User;
@LocalStorage('light_theme', {key: 'site_theme'}) theme: string;
@SessionStorage(null, {encryptWith: config.entryptionKey}) thisUser: User;
@SessionStorage() searchBar: string;
constructor() {
constructor() {
console.log(this.theme, localStorage.getItem('theme')); // Output: 'light_theme', 'light_theme'
console.log(this.user, localStorage.getItem('user')); // Output: null, undefined
user = {first: 'John', last: 'Smith', ...}
console.log(this.user, this.user == localStorage.getItem('user')); // Output: {first: 'John', last: 'Smith', ...}, true
console.log(this.user, localStorage.getItem('user')); // Output: {first: 'John', last: 'Smith', ...}, **Some encrypted value**
}
}
```
### Built With
[![TypeScript](https://img.shields.io/badge/TypeScript-3178C6?style=for-the-badge&logo=typescript&logoColor=white)](https://typescriptlang.org/)
## Setup
<details>
<summary>
<h3 id="production" style="display: inline">
Production
</h3>
</summary>
#### Prerequisites
- [Node.js](https://nodejs.org/en/download)
#### Instructions
1. Install persist: `npm i webstorage-decorators`
2. Enable decorators inside `tsconfig.json`:
```json
{
"compilerOptions": {
"experimentalDecorators": true,
...
},
...
}
```
3. Import & use, see [examples above](#examples)
</details>
<details>
<summary>
<h3 id="development" style="display: inline">
Development
</h3>
</summary>
#### Prerequisites
- [Node.js](https://nodejs.org/en/download)
#### Instructions
1. Install the dependencies: `npm i`
2. Build library & docs: `npm build`
3. Run unit tests: `npm test`
</details>
## Documentation
### Decorators
@ -35,42 +135,10 @@ export class MyCustomClass {
| Options | Description |
|---------|-------------|
| default | Default value, same as decorator's first argument |
| encryptWith | Secret key to encrypt stored values with |
| key | Key to reference value inside local/session storage (Defaults to the property name) |
## Caveats
## License
### Custom Functions
You can technically store anything inside local/session storage however everything is serialized using javascript's JSON,
so anything extra (prototypes, functions, etc) will be lost. However if you provide a default value, it will be copied &
the data injected, giving you a workaround to accessing static properties (Does not work with arrays).
Copyright © 2023 Zakary Timson | Available under MIT Licensing
```typescript
class Person {
constructor(public first: string, public last: string) { }
fullName() { return `${this.last}, ${this.first}`; }
}
LocalStorage.setItem('example', '{"first": "John", "last": "Smith"}');
@LocalStorage(null) example!: Person;
console.log(example.fullName()) // ERROR: fullName function doesn't exist
LocalStorage.setItem('example2', '{"first": "John", "last": "Smith"}');
@LocalStorage(new Person(null, null)) example2!: Person;
console.log(example2.fullName()) // Works because we have a default object to copy type from
```
### Impure Functions
Impure functions don't use the Object's setter preventing the storage from being updated. To prevent this use a pure
function or save it manually by reading the variable. (Reading triggers change detection & save if there are differences)
```typescript
@LocalStorage([1, 2]) example: number[];
example.push(3) // Impure & won't update storage
console.log(localStorage.getItem('example')) // Output: [1, 2];
example; // Trigger save
console.log(localStorage.getItem('example')) // Output: [1, 2, 3];
// OR
example = example.concat([3]); // Pure function requires you to use the setter triggering automatic saving
```
See the [license](./LICENSE) for more information.

View File

@ -1,12 +1,16 @@
module.exports = {
"reporters": ["default"],
"roots": [
"<rootDir>/tests"
],
"testMatch": [
"**/?(*.)+(spec|test).+(ts|tsx|js)"
],
"transform": {
".+\\.(ts)$": "ts-jest"
},
}
"reporters": ["default", "jest-junit"],
"roots": [
"<rootDir>/tests"
],
"testMatch": [
"**/?(*.)+(spec|test).+(ts|tsx|js)"
],
"transform": {
".+\\.(ts)$": "ts-jest"
},
collectCoverageFrom: [
'src/**/*.ts',
'!src/**/*.d.ts'
],
};

1
lib/index.d.ts vendored
View File

@ -1 +0,0 @@
export * from './webstorage';

View File

@ -1,2 +0,0 @@
export * from './webstorage';
//# sourceMappingURL=index.js.map

View File

@ -1 +0,0 @@
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,cAAc,CAAC"}

41
lib/webstorage.d.ts vendored
View File

@ -1,41 +0,0 @@
/**
* Options to be used with WebStorage decorators
*/
export interface WebStorageOptions {
/** Default value to provide if storage is empty */
default?: any;
/** Key to prevent plain text storage **/
encryptWith?: string;
/** Key to save under */
key?: string;
}
/**
* Automatically syncs localStorage with the decorated property.
*
* **Example**
* ```
* class Example {
* @LocalStorage() lastLogin: string;
* @LocalStorage(false, {key: '_hideMenu'}) hideMenu: boolean;
* }
* ```
*
* @param defaultValue Default value to return if property does no exist inside localStorage.
* @param opts Any additional options
*/
export declare function LocalStorage(defaultValue?: any, opts?: WebStorageOptions): (target: object, key: string) => void;
/**
* Automatically syncs sessionStorage with the decorated property.
*
* **Example**
* ```
* class Example {
* @SessionStorage() lastLogin: string;
* @SessionStorage(false, {key: '_hideMenu'}) hideMenu: boolean;
* }
* ```
*
* @param defaultValue Default value to return if property does no exist inside sessionStorage.
* @param opts Any additional options
*/
export declare function SessionStorage(defaultValue?: any, opts?: WebStorageOptions): (target: object, key: string) => void;

View File

@ -1,98 +0,0 @@
import * as crypto from 'crypto-js';
/**
* Automatically syncs localStorage with the decorated property.
*
* **Example**
* ```
* class Example {
* @LocalStorage() lastLogin: string;
* @LocalStorage(false, {key: '_hideMenu'}) hideMenu: boolean;
* }
* ```
*
* @param defaultValue Default value to return if property does no exist inside localStorage.
* @param opts Any additional options
*/
export function LocalStorage(defaultValue, opts = {}) {
opts.default = defaultValue;
return decoratorBuilder(localStorage, opts);
}
/**
* Automatically syncs sessionStorage with the decorated property.
*
* **Example**
* ```
* class Example {
* @SessionStorage() lastLogin: string;
* @SessionStorage(false, {key: '_hideMenu'}) hideMenu: boolean;
* }
* ```
*
* @param defaultValue Default value to return if property does no exist inside sessionStorage.
* @param opts Any additional options
*/
export function SessionStorage(defaultValue, opts = {}) {
opts.default = defaultValue;
return decoratorBuilder(sessionStorage, opts);
}
/**
* **Internal use only**
*
* Fetch variable from storage & take care of any defaults, object definitions, encryption & serialization
*
* @param storage Web Storage API
* @param opts Any additional options
*/
function fromStorage(storage, opts) {
let storedVal = storage.getItem(opts.key);
if (storedVal == null || storedVal == "" || storedVal == "undefined") {
if (opts.default == null)
return null;
if (opts.default != 'object')
return opts.default;
if (opts.default.constructor != null)
return Object.assign(new opts.default.constructor(), opts.default);
let temp = Object.assign({}, opts.default);
Object.setPrototypeOf(temp, Object.getPrototypeOf(opts.default));
return temp;
}
storedVal = JSON.parse(storedVal);
if (opts.encryptWith != null)
storedVal = JSON.parse(crypto.AES.decrypt(storedVal, opts.encryptWith).toString(crypto.enc.Utf8));
if (typeof storedVal != 'object' || !Array.isArray(storedVal))
return storedVal;
if (opts.default != null && opts.default.constructor != null)
return Object.assign(new opts.default.constructor(), opts.default, storedVal);
return Object.assign({}, opts.default, storedVal);
}
/**
* **Internal use only**
*
* Overrides the properties getter/setter methods to read/write from the provided storage endpoint.
*
* @param storage Web Storage API
* @param opts Any additional options
*/
function decoratorBuilder(storage, opts) {
return function (target, key) {
if (!opts.key)
opts.key = key;
let field = fromStorage(storage, opts);
Object.defineProperty(target, key, {
get: function () {
if (field != fromStorage(storage, { key: opts.key, encryptWith: opts.encryptWith }))
target[key] = field;
return field;
},
set: function (value) {
field = value;
if (value == null)
storage.removeItem(opts.key);
if (opts.encryptWith != null)
value = crypto.AES.encrypt(JSON.stringify(value), opts.encryptWith).toString();
storage.setItem(opts.key, JSON.stringify(value));
}
});
};
}
//# sourceMappingURL=webstorage.js.map

View File

@ -1 +0,0 @@
{"version":3,"file":"webstorage.js","sourceRoot":"","sources":["../src/webstorage.ts"],"names":[],"mappings":"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"}

3789
package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

View File

@ -1,34 +1,36 @@
{
"name": "webstorage-decorators",
"version": "3.2.5",
"description": "Decorators to sync class properties to Local/Session storage",
"repository": "https://github.com/ztimson/WebstorageDecorators",
"main": "./lib/index.js",
"typings": "./lib/index.d.ts",
"scripts": {
"build": "tsc",
"test": "jest --verbose",
"watch": "npm run build && tsc --watch"
},
"files": [
"lib"
],
"keywords": [
"Decorators",
"LocalStorage",
"SessionStorage",
"WebStorage"
],
"author": "Zak Timson",
"license": "ISC",
"dependencies": {
"@types/crypto-js": "^4.0.1",
"crypto-js": "^4.0.0"
},
"devDependencies": {
"@types/jest": "^26.0.15",
"jest": "^26.6.0",
"ts-jest": "^26.4.1",
"typescript": "^4.0.3"
}
"name": "webstorage-decorators",
"version": "4.3.1",
"description": "Decorators to sync class properties to Local/Session storage",
"repository": {
"type": "git",
"url": "https://git.zakscode.com/ztimson/webstorage-decorators"
},
"author": "Zak Timson",
"license": "MIT",
"main": "dist/index.js",
"types": "dist/index.d.ts",
"scripts": {
"build": "tsc",
"test": "jest --verbose",
"test:coverage": "npx jest --verbose --coverage",
"watch": "npm run build && tsc --watch"
},
"keywords": [
"Decorators",
"LocalStorage",
"SessionStorage",
"WebStorage"
],
"dependencies": {},
"devDependencies": {
"@types/jest": "^29.5.11",
"jest": "^29.7.0",
"jest-junit": "^16.0.0",
"ts-jest": "^29.1.1",
"typescript": "^5.0.4"
},
"files": [
"dist"
]
}

View File

@ -1,13 +1,9 @@
import * as crypto from 'crypto-js';
/**
* Options to be used with WebStorage decorators
*/
export interface WebStorageOptions {
/** Default value to provide if storage is empty */
default?: any;
/** Key to prevent plain text storage **/
encryptWith?: string;
/** Key to save under */
key?: string;
}
@ -45,7 +41,7 @@ export function LocalStorage(defaultValue?: any, opts: WebStorageOptions = {}) {
* @param defaultValue Default value to return if property does no exist inside sessionStorage.
* @param opts Any additional options
*/
export function SessionStorage(defaultValue?, opts: WebStorageOptions = {}) {
export function SessionStorage(defaultValue?: any, opts: WebStorageOptions = {}) {
opts.default = defaultValue;
return decoratorBuilder(sessionStorage, opts);
}
@ -69,9 +65,9 @@ function fromStorage(storage: Storage, opts: WebStorageOptions) {
return temp;
}
storedVal = JSON.parse(<string>storedVal);
if(opts.encryptWith != null) storedVal = JSON.parse(crypto.AES.decrypt(<string>storedVal, opts.encryptWith).toString(crypto.enc.Utf8));
if(typeof storedVal != 'object' || !Array.isArray(storedVal)) return storedVal;
if(opts.default != null && opts.default.constructor != null) return Object.assign(new opts.default.constructor(), opts.default, storedVal);
if(opts.default != null && opts.default.constructor != null)
return Object.assign(new opts.default.constructor(), opts.default, storedVal);
return Object.assign({}, opts.default, storedVal);
}
@ -89,13 +85,12 @@ function decoratorBuilder(storage: Storage, opts: WebStorageOptions) {
let field = fromStorage(storage, opts);
Object.defineProperty(target, key, {
get: function() {
if(field != fromStorage(storage, {key: opts.key, encryptWith: opts.encryptWith})) target[key] = field;
if(field != fromStorage(storage, {key: opts.key})) (<any>target)[key] = field;
return field;
},
set: function(value?) {
field = value;
if(value == null) storage.removeItem(<string>opts.key);
if(opts.encryptWith != null) value = <any>crypto.AES.encrypt(JSON.stringify(value), opts.encryptWith).toString();
storage.setItem(<string>opts.key, JSON.stringify(value));
}
});

View File

@ -1,8 +1,27 @@
import {LocalStorage, SessionStorage} from "../src";
const CUSTOM_KEY = '_MY_KEY'
const ENCRYPTION_KEY = 'abc123';
// Mocks ===============================================================================================================
class StorageMock implements Storage {
private store: {[key: string]: string} = {}
get length() { return Object.keys(this.store).length; }
clear() { this.store = {}; }
getItem(key: string) { return this.store[key]; }
setItem(key: string, value: string) { this.store[key] = value; }
removeItem(key: string) { delete this.store[key]; }
key(index: number) { return Object.keys(this.store)[index]; }
}
(<any>global).localStorage = new StorageMock();
(<any>global).sessionStorage = new StorageMock()
var localStorage: StorageMock;
localStorage = (<any>global).localStorage;
var sessionStorage: StorageMock;
sessionStorage = (<any>global).sessionStorage;
// Test Data ===========================================================================================================
const CUSTOM_KEY = '_MY_KEY'
class TestType {
constructor(public first: string, public last: string) { }
fullName() { return `${this.last}, ${this.first}`; }
@ -12,16 +31,15 @@ class TestStorage {
@LocalStorage() localStorage: any;
@LocalStorage({a: true, b: 'test', c: 3.14}) defaultedLocalStorage: any;
@LocalStorage(null, {key: CUSTOM_KEY}) customLocalStorage: any;
@LocalStorage(null, {encryptWith: ENCRYPTION_KEY}) encryptedLocalStorage: any;
@LocalStorage(new TestType('John', 'Smith')) objectLocalStorage!: TestType;
@SessionStorage() sessionStorage: any;
@SessionStorage({a: true, b: 'test', c: 3.14}) defaultedSessionStorage: any;
@SessionStorage(null, {key: CUSTOM_KEY}) customSessionStorage: any;
@SessionStorage(null, {encryptWith: ENCRYPTION_KEY}) encryptedSessionStorage: any;
@SessionStorage(new TestType('John', 'Smith')) objectSessionStorage!: TestType;
}
describe('Webstorage Decorators', () => {
// Tests ===============================================================================================================
describe('WebStorage Decorators', () => {
let testComponent: TestStorage;
beforeEach(() => {
localStorage.clear();
@ -67,12 +85,6 @@ describe('Webstorage Decorators', () => {
expect(localStorage.getItem(CUSTOM_KEY)).toBe(JSON.stringify(testValue));
expect(testComponent.customLocalStorage).toBe(testValue);
});
test('Encrypted', () => {
const testValue = Math.random();
testComponent.encryptedLocalStorage = testValue;
expect(localStorage.getItem('encryptedLocalStorage')).not.toBe(JSON.stringify(testValue));
expect(testComponent.encryptedLocalStorage).toBe(testValue);
});
test('Impure Functions', () => {
testComponent.localStorage = [1];
testComponent.localStorage.push(2);
@ -128,12 +140,6 @@ describe('Webstorage Decorators', () => {
expect(sessionStorage.getItem(CUSTOM_KEY)).toBe(JSON.stringify(testValue));
expect(testComponent.customSessionStorage).toBe(testValue);
});
test('Encrypted', () => {
const testValue = Math.random();
testComponent.encryptedSessionStorage = testValue;
expect(sessionStorage.getItem('encryptedSessionStorage')).not.toBe(JSON.stringify(testValue));
expect(testComponent.encryptedSessionStorage).toBe(testValue);
});
test('Impure Functions', () => {
testComponent.sessionStorage = [1];
testComponent.sessionStorage.push(2);

View File

@ -1,21 +1,18 @@
{
"compilerOptions": {
"target": "es2015",
"module": "es2020",
"declaration": true,
"sourceMap": true,
"outDir": "lib",
"strict": true,
"noImplicitAny": false,
"moduleResolution": "node",
"typeRoots": [
"./node_modules/@types"
],
"esModuleInterop": true,
"experimentalDecorators": true,
"emitDecoratorMetadata": true
},
"include": [
"./src"
]
"compilerOptions": {
"declaration": true,
"declarationMap": true,
"experimentalDecorators": true,
"lib": [
"ESNext",
"DOM"
],
"module": "commonjs",
"outDir": "./dist",
"strict": true,
"target": "es2015"
},
"include": [
"src/**/*"
]
}