├── .editorconfig ├── .gitignore ├── .travis.yml ├── .vscode ├── keybindings-mac.json ├── keybindings-win.json ├── launch.json └── tasks.json ├── LICENSE ├── README.md ├── jest.config.js ├── package-lock.json ├── package.json ├── rollup.config.js ├── src ├── deleted-entitites.ts ├── entity-notify-info.ts ├── models │ ├── category.spec.ts │ └── product.spec.ts ├── property-notify-info.ts ├── trackable-collection.ts ├── trackable-entities.ts ├── trackable-entitiy.ts ├── trackable-entity.spec.ts ├── trackable-helper.ts ├── trackable-map.spec.ts ├── trackable-map.ts ├── trackable-set.spec.ts ├── trackable-set.ts ├── trackable.ts └── tracking-state.ts ├── tools ├── gh-pages-publish.ts └── semantic-release-prepare.ts ├── tsconfig.json ├── tslint.json └── yarn.lock /.editorconfig: -------------------------------------------------------------------------------- 1 | #root = true 2 | 3 | [*] 4 | indent_style = space 5 | end_of_line = lf 6 | charset = utf-8 7 | trim_trailing_whitespace = true 8 | insert_final_newline = true 9 | max_line_length = 100 10 | indent_size = 2 11 | 12 | [*.md] 13 | trim_trailing_whitespace = false 14 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # See http://help.github.com/ignore-files/ for more about ignoring files. 2 | 3 | # compiled output 4 | /dist 5 | /tmp 6 | /out-tsc 7 | 8 | # dependencies 9 | /node_modules 10 | 11 | # IDEs and editors 12 | /.idea 13 | .project 14 | .classpath 15 | .c9/ 16 | *.launch 17 | .settings/ 18 | *.sublime-workspace 19 | 20 | # IDE - VSCode 21 | .vscode/* 22 | !.vscode/keybindings*.json 23 | !.vscode/settings.json 24 | !.vscode/tasks.json 25 | !.vscode/launch.json 26 | !.vscode/extensions.json 27 | 28 | # misc 29 | /.sass-cache 30 | /connect.lock 31 | /coverage 32 | /libpeerconnection.log 33 | npm-debug.log 34 | testem.log 35 | /typings 36 | yarn-error.log 37 | .nyc_output 38 | .awcache 39 | 40 | # e2e 41 | /e2e/*.js 42 | /e2e/*.map 43 | 44 | # System Files 45 | .DS_Store 46 | Thumbs.db 47 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | branches: 3 | only: 4 | - master 5 | - /^greenkeeper/.*$/ 6 | cache: 7 | yarn: true 8 | directories: 9 | - node_modules 10 | notifications: 11 | email: false 12 | node_js: 13 | - '10.16.0' 14 | script: 15 | - npm run test:prod && npm run build 16 | after_success: 17 | - npm run deploy-docs 18 | -------------------------------------------------------------------------------- /.vscode/keybindings-mac.json: -------------------------------------------------------------------------------- 1 | // Place your key bindings in this file to overwrite the defaults 2 | [ 3 | // End a running background task 4 | { 5 | "key": "shift+cmd+x", 6 | "command": "workbench.action.tasks.terminate" 7 | }, 8 | // Show Source Control 9 | { 10 | "key": "shift+cmd+g", 11 | "command": "workbench.view.scm" 12 | }, 13 | { 14 | "key": "ctrl+shift+g", 15 | "command": "-workbench.view.scm" 16 | }, 17 | // Lint 18 | { 19 | "key": "ctrl+shift+l", 20 | "command": "workbench.action.tasks.runTask", 21 | "args": "lint" 22 | }, 23 | // Tests 24 | { 25 | "key": "shift+cmd+t", 26 | "command": "workbench.action.tasks.test" 27 | }, 28 | // E2E Tests 29 | { 30 | "key": "ctrl+shift+e", 31 | "command": "workbench.action.tasks.runTask", 32 | "args": "e2e" 33 | }, 34 | // Serve app 35 | { 36 | "key": "shift+cmd+s", 37 | "command": "workbench.action.tasks.runTask", 38 | "args": "serve" 39 | }, 40 | // Open app 41 | { 42 | "key": "shift+cmd+o", 43 | "command": "workbench.action.tasks.runTask", 44 | "args": "open" 45 | }, 46 | // Go to symbol (remapped) 47 | { 48 | "key": "ctrl+shift+o", 49 | "command": "workbench.action.gotoSymbol" 50 | } 51 | ] -------------------------------------------------------------------------------- /.vscode/keybindings-win.json: -------------------------------------------------------------------------------- 1 | // Place your key bindings in this file to overwrite the defaults 2 | [ 3 | // End a running background task 4 | { 5 | "key": "ctrl+shift+x", 6 | "command": "workbench.action.tasks.terminate" 7 | }, 8 | // Lint 9 | { 10 | "key": "ctrl+shift+l", 11 | "command": "workbench.action.tasks.runTask", 12 | "args": "lint" 13 | }, 14 | // Tests 15 | { 16 | "key": "ctrl+shift+t", 17 | "command": "workbench.action.tasks.test" 18 | }, 19 | // E2E Tests 20 | { 21 | "key": "ctrl+shift+e", 22 | "command": "workbench.action.tasks.runTask", 23 | "args": "e2e" 24 | }, 25 | // Serve app 26 | { 27 | "key": "ctrl+shift+s", 28 | "command": "workbench.action.tasks.runTask", 29 | "args": "serve" 30 | }, 31 | // Open app 32 | { 33 | "key": "ctrl+shift+o", 34 | "command": "workbench.action.tasks.runTask", 35 | "args": "open" 36 | } 37 | ] -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "0.2.0", 3 | "configurations": [ 4 | { 5 | "name": "Tests - Current File", 6 | "type": "node", 7 | "request": "launch", 8 | "program": "${workspaceFolder}/node_modules/.bin/jest", 9 | "sourceMaps": true, 10 | "args": [ 11 | "${fileBasenameNoExtension}", 12 | "--config", 13 | "jest.config.js" 14 | ], 15 | "console": "integratedTerminal", 16 | "internalConsoleOptions": "neverOpen", 17 | "disableOptimisticBPs": true, 18 | "windows": { 19 | "program": "${workspaceFolder}/node_modules/jest/bin/jest", 20 | } 21 | }, 22 | { 23 | "type": "node", 24 | "request": "launch", 25 | "name": "Test - All Files", 26 | "program": "${workspaceFolder}/node_modules/.bin/jest", 27 | "args": ["--runInBand"], 28 | "console": "integratedTerminal", 29 | "internalConsoleOptions": "neverOpen", 30 | "disableOptimisticBPs": true, 31 | "windows": { 32 | "program": "${workspaceFolder}/node_modules/jest/bin/jest", 33 | } 34 | } 35 | ] 36 | } 37 | -------------------------------------------------------------------------------- /.vscode/tasks.json: -------------------------------------------------------------------------------- 1 | { 2 | // See https://go.microsoft.com/fwlink/?LinkId=733558 3 | // for the documentation about the tasks.json format 4 | "version": "2.0.0", 5 | "tasks": [ 6 | { 7 | "label": "build", 8 | "type": "npm", 9 | "script": "build", 10 | "presentation": { 11 | "reveal": "always" 12 | }, 13 | "group": { 14 | "kind": "build", 15 | "isDefault": true 16 | }, 17 | "problemMatcher": [ 18 | "$tsc" 19 | ] 20 | }, 21 | { 22 | "label": "test", 23 | "type": "npm", 24 | "script": "test:watch", 25 | "presentation": { 26 | "reveal": "always" 27 | }, 28 | "group": { 29 | "kind": "test", 30 | "isDefault": true 31 | } 32 | }, 33 | { 34 | "taskName": "lint", 35 | "type": "shell", 36 | "command": "npm run lint", 37 | "problemMatcher": [ 38 | "$tsc" 39 | ] 40 | } 41 | ] 42 | } -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright 2017 Anthony Sneed 2 | 3 | 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: 4 | 5 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 6 | 7 | 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. 8 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # trackable-entities-js 2 | 3 | Base classes that track change state when properties are updated and objects are added or removed objects from collections. 4 | 5 | [![Build Status](https://travis-ci.org/TrackableEntities/trackable-entities-js.svg)](https://travis-ci.org/TrackableEntities/trackable-entities-js) 6 | [![npm version](https://badge.fury.io/js/trackable-entities.svg)](https://badge.fury.io/js/trackable-entities) 7 | 8 | Docs: 9 | 10 | Sample application: 11 | 12 | > Note: You must change the TypeScript compiler target to "es2015" in **ts.config.json**. 13 | > - Apps using trackable-entities can support most modern browsers (Chrome, Firefox, Safari, Edge, etc), but they will not be able to support legacy browsers (Internet Explorer). 14 | 15 | ## Setup 16 | 17 | Install **trackable-entities** as a runtime dependency from npm. 18 | 19 | ``` 20 | npm i --save trackable-entities 21 | ``` 22 | 23 | ## Usage 24 | 25 | To track property changes on an object, create a class that extends `TrackableEntity`. Then add a `constructor` that returns `super.proxify(this)`. For example: 26 | 27 | ```js 28 | export class Product extends TrackableEntity { 29 | productId: number; 30 | productName: string; 31 | unitPrice: number; 32 | 33 | constructor() { 34 | super(); 35 | return super.proxify(this); 36 | } 37 | } 38 | ``` 39 | 40 | Then set the `tracking` property to `true`. Modifying property values will change the `trackingState` from `Unchanged` to `Modified` and will populate the `modifiedProperties` collection with the names of properties that were changed, so that partial updates (PATCH) may be performed by an API that knows how to apply changes to a persistence store such as a database. 41 | 42 | ```js 43 | // Product extends TrackableEntity 44 | product = new Product(1, 'Bacon', 1); 45 | 46 | // Turn on change tracking 47 | product.tracking = true; 48 | 49 | // Modify a property 50 | product.productName = 'Peas'; 51 | 52 | // Tracking state is set to Modified 53 | expect(product.trackingState).toEqual(TrackingState.Modified); 54 | 55 | // Name of modified properties are tracked 56 | expect(product.modifiedProperties.has('productName')).toBeTruthy(); 57 | ``` 58 | 59 | There are two collections that support change tracking: `TrackableSet` which extends `Set`, and `TrackableMap` which extends `Map`. When items are added their `trackingState` is set to `Added`, and when items are deleted their `trackingState` is set to `Deleted`. 60 | 61 | ```js 62 | // Create a new TrackableSet of products 63 | const products = [ 64 | new Product(1, 'Bacon', 1), 65 | new Product(2, 'Lettuce', 2), 66 | new Product(3, 'Tomatoes', 3), 67 | ]; 68 | const trackableProducts = new TrackableSet(...products); 69 | 70 | // Turn on change tracking 71 | product.tracking = true; 72 | 73 | // Add an entity 74 | const addedProduct = new Product(4, 'Carrots', 4); 75 | trackableProducts.add(addedProduct); 76 | 77 | // Tracking state is set to Added 78 | expect(addedProduct.trackingState).toEqual(TrackingState.Added); 79 | 80 | // Remove an entity 81 | const removedProduct = new Product(4, 'Carrots', 4); 82 | trackableProducts.delete(removedProduct); 83 | 84 | // Tracking state is set to Added 85 | expect(removedProduct.trackingState).toEqual(TrackingState.Deleted); 86 | ``` 87 | 88 | ## Persistence 89 | 90 | Once the tracking state of entities have been set, you can pass them to a Web API where they can be persisted to a data store. [Trackable Entities](http://trackableentities.github.io) has a server-side [NuGet package](https://www.nuget.org/packages/TrackableEntities.EF.6) for persisting changes to a relational data store using [Entity Framework](https://docs.microsoft.com/en-us/ef/). The current version supports the full .NET Framework with EF 6, and a future version will support [.NET Core](https://www.microsoft.com/net/core) and EF Core as a [NetStandard](https://docs.microsoft.com/en-us/dotnet/standard/net-standard) library. 91 | 92 | ## Roadmap 93 | 94 | We are currently developing the full API for **trackable-entities**. See the project [roadmap](https://github.com/TrackableEntities/trackable-entities-js/wiki/Roadmap) for more information. 95 | 96 | ## Contributing 97 | 98 | If you'd like to help with this project, please contact @tonysneed via email (tony@tonysneed.com) or twitter (@tonysneed). -------------------------------------------------------------------------------- /jest.config.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @type {Partial} 3 | */ 4 | const config = { 5 | preset: 'ts-jest', 6 | rootDir: '.', 7 | testEnvironment: 'node', 8 | testMatch: [ 9 | '/src/**/?(*.)+(spec|test).ts?(x)', 10 | ], 11 | testPathIgnorePatterns: ['dist', 'src/models'], 12 | coverageThreshold: { 13 | global: { 14 | branches: 50, 15 | functions: 50, 16 | lines: 50, 17 | statements: 50, 18 | }, 19 | } 20 | } 21 | 22 | module.exports = config 23 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "trackable-entities", 3 | "version": "1.0.0-alpha.2", 4 | "description": "Base classes that track change state when properties are updated and objects are added or removed objects from collections.", 5 | "keywords": [ 6 | "change-tracking", 7 | "observables", 8 | "proxies", 9 | "set", 10 | "map", 11 | "angular" 12 | ], 13 | "main": "dist/bundles/trackable-entities.umd.js", 14 | "module": "dist/bundles/trackable-entities.es2015.js", 15 | "typings": "dist/types/trackable-entities.d.ts", 16 | "files": [ 17 | "dist/bundles", 18 | "dist/types" 19 | ], 20 | "author": "Anthony Sneed ", 21 | "repository": { 22 | "type": "git", 23 | "url": "https://github.com/TrackableEntities/trackable-entities-js.git" 24 | }, 25 | "license": "MIT", 26 | "engines": { 27 | "node": ">=8.5", 28 | "npm": ">=6.0" 29 | }, 30 | "scripts": { 31 | "lint": "tslint -p 'tsconfig.json' 'src/**/*.ts'", 32 | "prebuild": "rimraf dist", 33 | "build": "tsc && rollup -c && typedoc --out dist/docs --target es6 --theme minimal --exclude '**/*.spec.ts' --mode modules src", 34 | "start": "tsc-watch --onSuccess \"rollup -c\"", 35 | "test": "jest -c ./jest.config.js", 36 | "test:watch": "jest --watch", 37 | "test:prod": "npm run lint && npm run test -- --coverage --no-cache", 38 | "deploy-docs": "ts-node tools/gh-pages-publish", 39 | "semantic-release-prepare": "ts-node tools/semantic-release-prepare", 40 | "prepush": "npm run test:prod && npm run build" 41 | }, 42 | "dependencies": { 43 | "observable-entities": "^1.0.0" 44 | }, 45 | "peerDependencies": { 46 | "tslib": ">=1.9.0" 47 | }, 48 | "devDependencies": { 49 | "@angular/compiler": "^8.0.2", 50 | "@angular/core": "^8.0.2", 51 | "@types/jest": "^24.0.15", 52 | "@types/node": "^12.0.10", 53 | "codelyzer": "^5.1.0", 54 | "colors": "^1.3.3", 55 | "cross-env": "^5.2.0", 56 | "husky": "^2.4.1", 57 | "jest": "^24.8.0", 58 | "lint-staged": "^8.2.1", 59 | "lodash.camelcase": "^4.3.0", 60 | "prompt": "^1.0.0", 61 | "replace-in-file": "^4.1.0", 62 | "rimraf": "^2.6.3", 63 | "rollup": "^1.16.1", 64 | "rollup-plugin-commonjs": "^10.0.0", 65 | "rollup-plugin-json": "^4.0.0", 66 | "rollup-plugin-node-resolve": "^5.0.3", 67 | "rollup-plugin-replace": "^2.2.0", 68 | "rollup-plugin-sourcemaps": "^0.4.2", 69 | "rollup-plugin-terser": "^5.0.0", 70 | "rollup-plugin-uglify": "^6.0.2", 71 | "ts-jest": "^24.0.2", 72 | "ts-node": "^8.3.0", 73 | "tsc-watch": "^2.2.1", 74 | "tslint": "^5.18.0", 75 | "typedoc": "^0.14.2", 76 | "typescript": "^3.5.2", 77 | "webpack-config-utils": "^2.3.1", 78 | "zone.js": "^0.9.1", 79 | "semantic-release": "^15.13.16" 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /rollup.config.js: -------------------------------------------------------------------------------- 1 | import sourceMaps from 'rollup-plugin-sourcemaps' 2 | import nodeResolve from 'rollup-plugin-node-resolve' 3 | import json from 'rollup-plugin-json' 4 | import commonjs from 'rollup-plugin-commonjs' 5 | import replace from 'rollup-plugin-replace' 6 | import { uglify } from 'rollup-plugin-uglify' 7 | import { terser } from 'rollup-plugin-terser' 8 | import { getIfUtils, removeEmpty } from 'webpack-config-utils' 9 | import pkg from './package.json' 10 | 11 | /** 12 | * @typedef {import('./types').RollupConfig} Config 13 | */ 14 | /** 15 | * @typedef {import('./types').RollupPlugin} Plugin 16 | */ 17 | 18 | const env = process.env.NODE_ENV || 'development' 19 | const { ifProduction } = getIfUtils(env) 20 | const libraryName = 'trackable-entities' 21 | 22 | /** 23 | * @type {string[]} 24 | */ 25 | const external = Object.keys(pkg.peerDependencies) || [] 26 | 27 | /** 28 | * @type {Plugin[]} 29 | */ 30 | const plugins = /** @type {Plugin[]} */ ([ 31 | // Allow json resolution 32 | json(), 33 | 34 | // Allow bundling cjs modules (unlike webpack, rollup doesn't understand cjs) 35 | commonjs(), 36 | 37 | // Allow node_modules resolution, so you can use 'external' to control 38 | // which external modules to include in the bundle 39 | // https://github.com/rollup/rollup-plugin-node-resolve#usage 40 | nodeResolve(), 41 | 42 | // Resolve source maps to the original source 43 | sourceMaps(), 44 | 45 | // properly set process.env.NODE_ENV within `./environment.ts` 46 | replace({ 47 | exclude: 'node_modules/**', 48 | 'process.env.NODE_ENV': JSON.stringify(env), 49 | }), 50 | ]) 51 | 52 | /** 53 | * @type {Config} 54 | */ 55 | const CommonConfig = { 56 | input: {}, 57 | output: {}, 58 | inlineDynamicImports: true, 59 | // Indicate here external modules you don't wanna include in your bundle (i.e.: 'lodash') 60 | external, 61 | } 62 | 63 | /** 64 | * @type {Config} 65 | */ 66 | const UMDconfig = { 67 | ...CommonConfig, 68 | // input: resolve(PATHS.entry.compiled, `${libraryName}.js`), 69 | input: `dist/compiled/${libraryName}.js`, 70 | output: { 71 | file: `dist/bundles/${libraryName}.umd.js`, 72 | format: 'umd', 73 | name: libraryName, 74 | sourcemap: true, 75 | globals: { 76 | 'tslib': 'TypeScriptLib' 77 | } 78 | }, 79 | plugins: removeEmpty( 80 | /** @type {Plugin[]} */ ([...plugins, ifProduction(uglify())]) 81 | ), 82 | } 83 | 84 | /** 85 | * @type {Config} 86 | */ 87 | const FESMconfig = { 88 | ...CommonConfig, 89 | // input: resolve(PATHS.entry.compiled, `${libraryName}.js`), 90 | input: `dist/compiled/${libraryName}.js`, 91 | output: [ 92 | { 93 | file: `dist/bundles/${libraryName}.es2015.js`, 94 | format: 'es', 95 | sourcemap: true, 96 | globals: { 97 | 'tslib': 'TypeScriptLib' 98 | } 99 | }, 100 | ], 101 | plugins: removeEmpty( 102 | /** @type {Plugin[]} */ ([...plugins, ifProduction(terser())]) 103 | ), 104 | } 105 | 106 | export default [UMDconfig, FESMconfig] 107 | -------------------------------------------------------------------------------- /src/deleted-entitites.ts: -------------------------------------------------------------------------------- 1 | export interface IDeletedEntities { 2 | add?(value: any); 3 | set?(key: any, value: any); 4 | clear(); 5 | } 6 | -------------------------------------------------------------------------------- /src/entity-notify-info.ts: -------------------------------------------------------------------------------- 1 | import { INotifyInfo } from 'observable-entities'; 2 | import { TrackableEntity } from './trackable-entitiy'; 3 | 4 | export interface IEntityNotifyInfo extends INotifyInfo { 5 | key?: any; 6 | origValue?: TEntity; 7 | currentValue?: TEntity; 8 | } 9 | -------------------------------------------------------------------------------- /src/models/category.spec.ts: -------------------------------------------------------------------------------- 1 | import { TrackableEntity } from '../trackable-entitiy'; 2 | import { Product } from './product.spec'; 3 | 4 | export class Category extends TrackableEntity { 5 | categoryId: number; 6 | categoryName: string; 7 | products: Product[]; 8 | 9 | constructor(); 10 | constructor(categoryId: number, categoryName: string, ...products: Product[]); 11 | constructor(categoryId?: number, categoryName?: string, ...products: Product[]) { 12 | super(); 13 | this.categoryId = categoryId; 14 | this.categoryName = categoryName; 15 | this.products = products; 16 | return super.proxify(this); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/models/product.spec.ts: -------------------------------------------------------------------------------- 1 | import { TrackableEntity } from '../trackable-entitiy'; 2 | import { Category } from './category.spec'; 3 | 4 | export class Product extends TrackableEntity { 5 | productId: number; 6 | productName: string; 7 | unitPrice: number; 8 | categoryId: number; 9 | category: Category; 10 | 11 | constructor(); 12 | constructor(productId: number, productName: string, unitPrice: number, category?: Category); 13 | constructor(productId?: number, productName?: string, unitPrice?: number, category?: Category) { 14 | super(); 15 | this.productId = productId; 16 | this.productName = productName; 17 | this.unitPrice = unitPrice; 18 | this.category = category; 19 | return super.proxify(this); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/property-notify-info.ts: -------------------------------------------------------------------------------- 1 | import { INotifyInfo } from 'observable-entities'; 2 | 3 | export interface IPropertyNotifyInfo extends INotifyInfo { 4 | key?: string; 5 | origValue?: any; 6 | currentValue?: any; 7 | } 8 | -------------------------------------------------------------------------------- /src/trackable-collection.ts: -------------------------------------------------------------------------------- 1 | import { IObservableCollection } from 'observable-entities'; 2 | 3 | export interface ITrackableCollection extends IObservableCollection { 4 | tracking: boolean; 5 | } 6 | -------------------------------------------------------------------------------- /src/trackable-entities.ts: -------------------------------------------------------------------------------- 1 | export { IDeletedEntities } from './deleted-entitites'; 2 | export { IEntityNotifyInfo } from './entity-notify-info'; 3 | export { IPropertyNotifyInfo } from './property-notify-info'; 4 | export { ITrackableCollection } from './trackable-collection'; 5 | export { TrackableEntity } from './trackable-entitiy'; 6 | export { TrackableHelper } from './trackable-helper'; 7 | export { TrackableMap } from './trackable-map'; 8 | export { TrackableSet } from './trackable-set'; 9 | export { ITrackable } from './trackable'; 10 | export { TrackingState } from './tracking-state'; 11 | -------------------------------------------------------------------------------- /src/trackable-entitiy.ts: -------------------------------------------------------------------------------- 1 | import { Subject } from 'rxjs'; 2 | 3 | import { ObservableEntity } from 'observable-entities'; 4 | import { IPropertyNotifyInfo } from './property-notify-info'; 5 | import { ITrackable } from './trackable'; 6 | import { TrackingState } from './tracking-state'; 7 | 8 | export abstract class TrackableEntity extends ObservableEntity implements ITrackable { 9 | 10 | public trackingState = TrackingState.Unchanged; 11 | public modifiedProperties = new Set(); 12 | 13 | private _tracking: boolean; 14 | private _modifyListener = new Subject(); 15 | 16 | constructor() { 17 | super(); 18 | super.addExcludedProperties('tracking', 'TrackingState', 'ModifiedProperties'); 19 | } 20 | 21 | get tracking(): boolean { 22 | return this._tracking; 23 | } 24 | 25 | set tracking(value: boolean) { 26 | this._tracking = value; 27 | this.setTracking(); 28 | } 29 | 30 | private setTracking(): void { 31 | if (this.tracking) { 32 | const modifyIndex = this.modifyListeners.indexOf(this._modifyListener); 33 | if (modifyIndex < 0) { 34 | this.modifyListeners.push(this._modifyListener); 35 | this._modifyListener.subscribe(propInfo => { 36 | if (this._tracking === true && (propInfo.origValue !== propInfo.currentValue)) { 37 | this.trackingState = TrackingState.Modified; 38 | this.modifiedProperties.add(propInfo.key); 39 | } 40 | }); 41 | } 42 | } else { 43 | const modifyIndex = this.modifyListeners.indexOf(this._modifyListener); 44 | if (modifyIndex >= 0) { 45 | this.modifyListeners.splice(modifyIndex, this.modifyListeners.length); 46 | } 47 | } 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /src/trackable-entity.spec.ts: -------------------------------------------------------------------------------- 1 | import { Product } from './models/product.spec'; 2 | import { TrackableSet } from './trackable-set'; 3 | import { TrackingState } from './tracking-state'; 4 | 5 | describe('TrackableEntity', () => { 6 | 7 | let product: Product; 8 | 9 | beforeEach(() => { 10 | product = new Product(1, 'Bacon', 1); 11 | }); 12 | 13 | it('should be created', () => { 14 | expect(product).toBeTruthy(); 15 | }); 16 | 17 | it('should set entity TrackingState to Modified when tracking', (done) => { 18 | 19 | // Arrange 20 | product.tracking = true; 21 | 22 | // Act 23 | product.productName = 'Peas'; 24 | 25 | // Assert 26 | expect(product.trackingState).toEqual(TrackingState.Modified); 27 | done(); 28 | }); 29 | 30 | it('should not set entity TrackingState to Modified when tracking but not changed', (done) => { 31 | 32 | // Arrange 33 | product.tracking = true; 34 | 35 | // Act 36 | product.productName = product.productName; 37 | 38 | // Assert 39 | expect(product.trackingState).toEqual(TrackingState.Unchanged); 40 | done(); 41 | }); 42 | 43 | it('should not set entity TrackingState to Modified when not tracking', (done) => { 44 | 45 | // Arrange 46 | product.tracking = true; 47 | 48 | // Act 49 | product.tracking = false; 50 | product.productName = 'Peas'; 51 | 52 | // Assert 53 | expect(product.trackingState).toEqual(TrackingState.Unchanged); 54 | done(); 55 | }); 56 | 57 | it('should add to entity ModifiedProperties when tracking', (done) => { 58 | 59 | // Arrange 60 | product.tracking = true; 61 | 62 | // Act 63 | product.productName = 'Peas'; 64 | product.unitPrice = 5; 65 | 66 | // Assert 67 | expect(product.trackingState).toEqual(TrackingState.Modified); 68 | expect(product.modifiedProperties.size).toEqual(2); 69 | expect(product.modifiedProperties.has('productName')).toBeTruthy(); 70 | expect(product.modifiedProperties.has('unitPrice')).toBeTruthy(); 71 | done(); 72 | }); 73 | }); 74 | -------------------------------------------------------------------------------- /src/trackable-helper.ts: -------------------------------------------------------------------------------- 1 | import { Subject } from 'rxjs'; 2 | 3 | import { IDeletedEntities } from './deleted-entitites'; 4 | import { IEntityNotifyInfo } from './entity-notify-info'; 5 | import { IPropertyNotifyInfo } from './property-notify-info'; 6 | import { TrackingState } from './tracking-state'; 7 | import { ITrackableCollection } from './trackable-collection'; 8 | import { TrackableEntity } from './trackable-entitiy'; 9 | 10 | export abstract class TrackableHelper { 11 | 12 | public static setTracking( 13 | trackable: ITrackableCollection, 14 | deletedEntities: IDeletedEntities, 15 | modifyListener: Subject, 16 | addListener: Subject>, 17 | removeListener: Subject>): void { 18 | 19 | if (trackable.tracking === true) { 20 | const addIndex = trackable.addListeners.indexOf(addListener); 21 | if (addIndex < 0) { 22 | addListener.subscribe(notifyInfo => { 23 | if (notifyInfo && notifyInfo.currentValue) { 24 | notifyInfo.currentValue.trackingState = TrackingState.Added; 25 | } 26 | }); 27 | trackable.addListeners.push(addListener); 28 | } 29 | const removeIndex = trackable.removeListeners.indexOf(removeListener); 30 | if (removeIndex < 0) { 31 | removeListener.subscribe(notifyInfo => { 32 | if (notifyInfo && notifyInfo.currentValue) { 33 | notifyInfo.currentValue.trackingState = TrackingState.Deleted; 34 | if (deletedEntities.add) { 35 | deletedEntities.add(notifyInfo.currentValue); 36 | } else if (deletedEntities.set) { 37 | deletedEntities.set(notifyInfo.key, notifyInfo.currentValue); 38 | } 39 | } 40 | }); 41 | trackable.removeListeners.push(removeListener); 42 | } 43 | } else { 44 | const addIndex = trackable.addListeners.indexOf(addListener); 45 | if (addIndex >= 0) { 46 | trackable.addListeners.splice(addIndex, trackable.addListeners.length); 47 | } 48 | const removeIndex = trackable.removeListeners.indexOf(removeListener); 49 | if (removeIndex >= 0) { 50 | deletedEntities.clear(); 51 | trackable.removeListeners.splice(addIndex, trackable.removeListeners.length); 52 | } 53 | } 54 | [...trackable].forEach(item => { 55 | if (item instanceof TrackableEntity) { 56 | item.tracking = trackable.tracking; 57 | } else { 58 | const entity = item as [any, TrackableEntity]; 59 | entity[1].tracking = trackable.tracking; 60 | } 61 | }); 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /src/trackable-map.spec.ts: -------------------------------------------------------------------------------- 1 | import { Product } from './models/product.spec'; 2 | import { TrackableMap } from './trackable-map'; 3 | import { TrackingState } from './tracking-state'; 4 | 5 | describe('TrackableMap', () => { 6 | 7 | let trackableMap: TrackableMap; 8 | 9 | beforeEach(() => { 10 | const entries: [string, Product][] = [ 11 | ['Bacon', new Product(1, 'Bacon', 1)], 12 | ['Lettuce', new Product(2, 'Lettuce', 2)], 13 | ['Tomatoes', new Product(3, 'Tomatoes', 3)], 14 | ]; 15 | trackableMap = new TrackableMap(...entries); 16 | }); 17 | 18 | it('should be created', () => { 19 | expect(trackableMap).toBeTruthy(); 20 | }); 21 | 22 | it('should contain items', () => { 23 | expect(trackableMap.size).toBe(3); 24 | }); 25 | 26 | it('should set entity TrackingState to Added when tracking', (done) => { 27 | 28 | // Arrange 29 | trackableMap.tracking = true; 30 | const product = new Product(4, 'Carrots', 4); 31 | 32 | // Act 33 | trackableMap.add(product.productName, product); 34 | 35 | // Assert 36 | expect(product.trackingState).toEqual(TrackingState.Added); 37 | done(); 38 | }); 39 | 40 | it('should not set entity TrackingState to Added when not tracking', (done) => { 41 | 42 | // Arrange 43 | trackableMap.tracking = true; 44 | const product = new Product(5, 'Carrots', 4); 45 | 46 | // Act 47 | trackableMap.tracking = false; 48 | trackableMap.add(product.productName, product); 49 | 50 | // Assert 51 | expect(product.trackingState).toEqual(TrackingState.Unchanged); 52 | done(); 53 | }); 54 | 55 | it('should set entity TrackingState to Deleted when tracking', (done) => { 56 | 57 | // Arrange 58 | trackableMap.tracking = true; 59 | const entry = [...trackableMap][0]; 60 | 61 | // Act 62 | trackableMap.delete(entry[0]); 63 | 64 | // Assert 65 | expect(entry[1].trackingState).toEqual(TrackingState.Deleted); 66 | done(); 67 | }); 68 | 69 | it('should not set entity TrackingState to Deleted when not tracking', (done) => { 70 | 71 | // Arrange 72 | trackableMap.tracking = true; 73 | const entry = [...trackableMap][0]; 74 | 75 | // Act 76 | trackableMap.tracking = false; 77 | trackableMap.delete(entry[0]); 78 | 79 | // Assert 80 | expect(entry[1].trackingState).toEqual(TrackingState.Unchanged); 81 | done(); 82 | }); 83 | 84 | it('should cache Deleted entities when tracking', (done) => { 85 | 86 | // Arrange 87 | trackableMap.tracking = true; 88 | const entry = [...trackableMap][0]; 89 | 90 | // Act 91 | trackableMap.delete(entry[0]); 92 | 93 | // Assert 94 | expect(entry[1].trackingState).toEqual(TrackingState.Deleted); 95 | const deletedEntities = [...(trackableMap as any).deletedEntities]; 96 | expect(deletedEntities[0][0]).toBe(entry[0]); 97 | expect(deletedEntities[0][1]).toBe(entry[1]); 98 | done(); 99 | }); 100 | 101 | it('should clear Deleted entities when not tracking', (done) => { 102 | 103 | // Arrange 104 | trackableMap.tracking = true; 105 | const entry = [...trackableMap][0]; 106 | 107 | // Act 108 | trackableMap.delete(entry[0]); 109 | trackableMap.tracking = false; 110 | 111 | // Assert 112 | expect(entry[1].trackingState).toEqual(TrackingState.Deleted); 113 | const deletedEntities = [...(trackableMap as any).deletedEntities]; 114 | expect(deletedEntities.length).toEqual(0); 115 | done(); 116 | }); 117 | 118 | it('should set entity TrackingState to Modified when tracking', (done) => { 119 | 120 | // Arrange 121 | trackableMap.tracking = true; 122 | const entry = [...trackableMap][0]; 123 | 124 | // Act 125 | entry[1].productName = 'Peas'; 126 | 127 | // Assert 128 | expect(entry[1].trackingState).toEqual(TrackingState.Modified); 129 | done(); 130 | }); 131 | 132 | it('should not set entity TrackingState to Modified when tracking but not changed', (done) => { 133 | 134 | // Arrange 135 | trackableMap.tracking = true; 136 | const entry = [...trackableMap][0]; 137 | 138 | // Act 139 | entry[1].productName = entry[1].productName; 140 | 141 | // Assert 142 | expect(entry[1].trackingState).toEqual(TrackingState.Unchanged); 143 | done(); 144 | }); 145 | 146 | it('should not set entity TrackingState to Modified when not tracking', (done) => { 147 | 148 | // Arrange 149 | trackableMap.tracking = true; 150 | const entry = [...trackableMap][0]; 151 | 152 | // Act 153 | trackableMap.tracking = false; 154 | entry[1].productName = 'Peas'; 155 | 156 | // Assert 157 | expect(entry[1].trackingState).toEqual(TrackingState.Unchanged); 158 | done(); 159 | }); 160 | 161 | it('should add to entity ModifiedProperties when tracking', (done) => { 162 | 163 | // Arrange 164 | trackableMap.tracking = true; 165 | const entry = [...trackableMap][0]; 166 | 167 | // Act 168 | entry[1].productName = 'Peas'; 169 | entry[1].unitPrice = 5; 170 | 171 | // Assert 172 | expect(entry[1].trackingState).toEqual(TrackingState.Modified); 173 | expect(entry[1].modifiedProperties.size).toEqual(2); 174 | expect(entry[1].modifiedProperties.has('productName')).toBeTruthy(); 175 | expect(entry[1].modifiedProperties.has('unitPrice')).toBeTruthy(); 176 | done(); 177 | }); 178 | }); 179 | -------------------------------------------------------------------------------- /src/trackable-map.ts: -------------------------------------------------------------------------------- 1 | import { Subject } from 'rxjs'; 2 | 3 | import { IEntityNotifyInfo } from './entity-notify-info'; 4 | import { ObservableMap } from 'observable-entities'; 5 | import { IPropertyNotifyInfo } from './property-notify-info'; 6 | import { ITrackableCollection } from './trackable-collection'; 7 | import { TrackableEntity } from './trackable-entitiy'; 8 | import { TrackableHelper } from './trackable-helper'; 9 | 10 | export class TrackableMap 11 | extends ObservableMap implements ITrackableCollection { 12 | 13 | private _tracking: boolean; 14 | private _modifyListener = new Subject(); 15 | private _addListener = new Subject>(); 16 | private _removeListener = new Subject>(); 17 | 18 | protected deletedEntities = new Map(); 19 | 20 | constructor(...entries: [TKey, TEntity][]) { 21 | super(...entries); 22 | } 23 | 24 | get tracking(): boolean { 25 | return this._tracking; 26 | } 27 | 28 | set tracking(value: boolean) { 29 | this._tracking = value; 30 | this.setTracking(); 31 | } 32 | 33 | private setTracking() { 34 | TrackableHelper.setTracking(this, this.deletedEntities, this._modifyListener, this._addListener, this._removeListener); 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/trackable-set.spec.ts: -------------------------------------------------------------------------------- 1 | import { Product } from './models/product.spec'; 2 | import { TrackableSet } from './trackable-set'; 3 | import { TrackingState } from './tracking-state'; 4 | 5 | describe('TrackableSet', () => { 6 | 7 | let trackableSet: TrackableSet; 8 | 9 | beforeEach(() => { 10 | const products = [ 11 | new Product(1, 'Bacon', 1), 12 | new Product(2, 'Lettuce', 2), 13 | new Product(3, 'Tomatoes', 3), 14 | ]; 15 | trackableSet = new TrackableSet(...products); 16 | }); 17 | 18 | it('should be created', () => { 19 | expect(trackableSet).toBeTruthy(); 20 | }); 21 | 22 | it('should contain items', () => { 23 | expect(trackableSet.size).toBe(3); 24 | }); 25 | 26 | it('should set entity TrackingState to Added when tracking', (done) => { 27 | 28 | // Arrange 29 | trackableSet.tracking = true; 30 | const product = new Product(4, 'Carrots', 4); 31 | 32 | // Act 33 | trackableSet.add(product); 34 | 35 | // Assert 36 | expect(product.trackingState).toEqual(TrackingState.Added); 37 | done(); 38 | }); 39 | 40 | it('should not set entity TrackingState to Added when not tracking', (done) => { 41 | 42 | // Arrange 43 | trackableSet.tracking = true; 44 | const product = new Product(4, 'Carrots', 4); 45 | 46 | // Act 47 | trackableSet.tracking = false; 48 | trackableSet.add(product); 49 | 50 | // Assert 51 | expect(product.trackingState).toEqual(TrackingState.Unchanged); 52 | done(); 53 | }); 54 | 55 | it('should set entity TrackingState to Deleted when tracking', (done) => { 56 | 57 | // Arrange 58 | trackableSet.tracking = true; 59 | const product = [...trackableSet][0]; 60 | 61 | // Act 62 | trackableSet.delete(product); 63 | 64 | // Assert 65 | expect(product.trackingState).toEqual(TrackingState.Deleted); 66 | done(); 67 | }); 68 | 69 | it('should not set entity TrackingState to Deleted when not tracking', (done) => { 70 | 71 | // Arrange 72 | trackableSet.tracking = true; 73 | const product = [...trackableSet][0]; 74 | 75 | // Act 76 | trackableSet.tracking = false; 77 | trackableSet.delete(product); 78 | 79 | // Assert 80 | expect(product.trackingState).toEqual(TrackingState.Unchanged); 81 | done(); 82 | }); 83 | 84 | it('should cache Deleted entities when tracking', (done) => { 85 | 86 | // Arrange 87 | trackableSet.tracking = true; 88 | const product = [...trackableSet][0]; 89 | 90 | // Act 91 | trackableSet.delete(product); 92 | 93 | // Assert 94 | expect(product.trackingState).toEqual(TrackingState.Deleted); 95 | const deletedEntities = [...(trackableSet as any).deletedEntities]; 96 | expect(deletedEntities[0]).toBe(product); 97 | done(); 98 | }); 99 | 100 | it('should clear Deleted entities when not tracking', (done) => { 101 | 102 | // Arrange 103 | trackableSet.tracking = true; 104 | const product = [...trackableSet][0]; 105 | 106 | // Act 107 | trackableSet.delete(product); 108 | trackableSet.tracking = false; 109 | 110 | // Assert 111 | expect(product.trackingState).toEqual(TrackingState.Deleted); 112 | const deletedEntities = [...(trackableSet as any).deletedEntities]; 113 | expect(deletedEntities.length).toEqual(0); 114 | done(); 115 | }); 116 | 117 | it('should set entity TrackingState to Modified when tracking', (done) => { 118 | 119 | // Arrange 120 | trackableSet.tracking = true; 121 | const product = [...trackableSet][0]; 122 | 123 | // Act 124 | product.productName = 'Peas'; 125 | 126 | // Assert 127 | expect(product.trackingState).toEqual(TrackingState.Modified); 128 | done(); 129 | }); 130 | 131 | it('should not set entity TrackingState to Modified when tracking but not changed', (done) => { 132 | 133 | // Arrange 134 | trackableSet.tracking = true; 135 | const product = [...trackableSet][0]; 136 | 137 | // Act 138 | product.productName = product.productName; 139 | 140 | // Assert 141 | expect(product.trackingState).toEqual(TrackingState.Unchanged); 142 | done(); 143 | }); 144 | 145 | it('should not set entity TrackingState to Modified when not tracking', (done) => { 146 | 147 | // Arrange 148 | trackableSet.tracking = true; 149 | const product = [...trackableSet][0]; 150 | 151 | // Act 152 | trackableSet.tracking = false; 153 | product.productName = 'Peas'; 154 | 155 | // Assert 156 | expect(product.trackingState).toEqual(TrackingState.Unchanged); 157 | done(); 158 | }); 159 | 160 | it('should add to entity ModifiedProperties when tracking', (done) => { 161 | 162 | // Arrange 163 | trackableSet.tracking = true; 164 | const product = [...trackableSet][0]; 165 | 166 | // Act 167 | product.productName = 'Peas'; 168 | product.unitPrice = 5; 169 | 170 | // Assert 171 | expect(product.trackingState).toEqual(TrackingState.Modified); 172 | expect(product.modifiedProperties.size).toEqual(2); 173 | expect(product.modifiedProperties.has('productName')).toBeTruthy(); 174 | expect(product.modifiedProperties.has('unitPrice')).toBeTruthy(); 175 | done(); 176 | }); 177 | }); 178 | -------------------------------------------------------------------------------- /src/trackable-set.ts: -------------------------------------------------------------------------------- 1 | import { Subject } from 'rxjs'; 2 | 3 | import { IEntityNotifyInfo } from './entity-notify-info'; 4 | import { ObservableSet } from 'observable-entities'; 5 | import { IPropertyNotifyInfo } from './property-notify-info'; 6 | import { ITrackable } from './trackable'; 7 | import { ITrackableCollection } from './trackable-collection'; 8 | import { TrackableEntity } from './trackable-entitiy'; 9 | import { TrackableHelper } from './trackable-helper'; 10 | 11 | export class TrackableSet 12 | extends ObservableSet implements ITrackableCollection { 13 | 14 | private _tracking: boolean; 15 | private _modifyListener = new Subject(); 16 | private _addListener = new Subject>(); 17 | private _removeListener = new Subject>(); 18 | 19 | protected deletedEntities = new Set(); 20 | 21 | constructor(...items: TEntity[]) { 22 | super(...items); 23 | } 24 | 25 | get tracking(): boolean { 26 | return this._tracking; 27 | } 28 | 29 | set tracking(value: boolean) { 30 | this._tracking = value; 31 | this.setTracking(); 32 | } 33 | 34 | private setTracking(): void { 35 | TrackableHelper.setTracking(this, this.deletedEntities, this._modifyListener, this._addListener, this._removeListener); 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/trackable.ts: -------------------------------------------------------------------------------- 1 | import { TrackingState } from './tracking-state'; 2 | 3 | export interface ITrackable { 4 | trackingState: TrackingState; 5 | modifiedProperties: Set; 6 | } 7 | -------------------------------------------------------------------------------- /src/tracking-state.ts: -------------------------------------------------------------------------------- 1 | export enum TrackingState { 2 | Unchanged, 3 | Added, 4 | Modified, 5 | Deleted 6 | } 7 | -------------------------------------------------------------------------------- /tools/gh-pages-publish.ts: -------------------------------------------------------------------------------- 1 | const { cd, exec, echo, touch } = require("shelljs") 2 | const { readFileSync } = require("fs") 3 | const url = require("url") 4 | 5 | let repoUrl 6 | let pkg = JSON.parse(readFileSync("package.json") as any) 7 | if (typeof pkg.repository === "object") { 8 | if (!pkg.repository.hasOwnProperty("url")) { 9 | throw new Error("URL does not exist in repository section") 10 | } 11 | repoUrl = pkg.repository.url 12 | } else { 13 | repoUrl = pkg.repository 14 | } 15 | 16 | let parsedUrl = url.parse(repoUrl) 17 | let repository = (parsedUrl.host || "") + (parsedUrl.path || "") 18 | let ghToken = process.env.GH_TOKEN 19 | 20 | echo("Deploying docs!!!") 21 | cd("dist/docs") 22 | touch(".nojekyll") 23 | exec("git init") 24 | exec("git add .") 25 | exec('git config user.name "Anthony Sneed"') 26 | exec('git config user.email "tony@tonysneed.com"') 27 | exec('git commit -m "docs(docs): update gh-pages"') 28 | exec( 29 | `git push --force --quiet "https://${ghToken}@${repository}" master:gh-pages` 30 | ) 31 | echo("Docs deployed!!") 32 | -------------------------------------------------------------------------------- /tools/semantic-release-prepare.ts: -------------------------------------------------------------------------------- 1 | const path = require("path") 2 | const { fork } = require("child_process") 3 | const colors = require("colors") 4 | 5 | const { readFileSync, writeFileSync } = require("fs") 6 | const pkg = JSON.parse( 7 | readFileSync(path.resolve(__dirname, "..", "package.json")) 8 | ) 9 | 10 | pkg.scripts.prepush = "npm run test:prod && npm run build" 11 | // pkg.scripts.commitmsg = "validate-commit-msg" 12 | 13 | writeFileSync( 14 | path.resolve(__dirname, "..", "package.json"), 15 | JSON.stringify(pkg, null, 2) 16 | ) 17 | 18 | // Call husky to set up the hooks 19 | fork(path.resolve(__dirname, "..", "node_modules", "husky", "bin", "install")) 20 | 21 | console.log() 22 | console.log(colors.green("Done!!")) 23 | console.log() 24 | 25 | if (pkg.repository.url.trim()) { 26 | console.log(colors.cyan("Now run:")) 27 | console.log(colors.cyan(" npm install -g semantic-release-cli")) 28 | console.log(colors.cyan(" semantic-release-cli setup")) 29 | console.log() 30 | console.log( 31 | colors.cyan('Important! Answer NO to "Generate travis.yml" question') 32 | ) 33 | console.log() 34 | console.log( 35 | colors.gray( 36 | 'Note: Make sure "repository.url" in your package.json is correct before' 37 | ) 38 | ) 39 | } else { 40 | console.log( 41 | colors.red( 42 | 'First you need to set the "repository.url" property in package.json' 43 | ) 44 | ) 45 | console.log(colors.cyan("Then run:")) 46 | console.log(colors.cyan(" npm install -g semantic-release-cli")) 47 | console.log(colors.cyan(" semantic-release-cli setup")) 48 | console.log() 49 | console.log( 50 | colors.cyan('Important! Answer NO to "Generate travis.yml" question') 51 | ) 52 | } 53 | 54 | console.log() 55 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "moduleResolution": "node", 4 | "target": "es2015", 5 | "module":"es2015", 6 | "lib": ["es2015", "es2016", "es2017", "dom"], 7 | "sourceMap": true, 8 | "declaration": true, 9 | "allowSyntheticDefaultImports": true, 10 | "experimentalDecorators": true, 11 | "emitDecoratorMetadata": true, 12 | "declarationDir": "dist/types", 13 | "outDir": "dist/compiled", 14 | "typeRoots": [ 15 | "node_modules/@types" 16 | ] 17 | }, 18 | "include": [ 19 | "src" 20 | ] 21 | } 22 | -------------------------------------------------------------------------------- /tslint.json: -------------------------------------------------------------------------------- 1 | { 2 | "rulesDirectory": [ 3 | "node_modules/codelyzer" 4 | ], 5 | "rules": { 6 | "arrow-return-shorthand": true, 7 | "callable-types": false, 8 | "class-name": true, 9 | "comment-format": [ 10 | true, 11 | "check-space" 12 | ], 13 | "curly": true, 14 | "eofline": true, 15 | "forin": true, 16 | "import-spacing": true, 17 | "indent": [ 18 | true, 19 | "spaces" 20 | ], 21 | "interface-over-type-literal": true, 22 | "label-position": true, 23 | "max-line-length": [ 24 | true, 25 | 140 26 | ], 27 | "member-access": false, 28 | "member-ordering": [ 29 | true, 30 | { 31 | "order": [ 32 | "static-field", 33 | "instance-field", 34 | "static-method", 35 | "instance-method" 36 | ] 37 | } 38 | ], 39 | "no-arg": true, 40 | "no-bitwise": true, 41 | "no-console": [ 42 | true, 43 | "debug", 44 | "info", 45 | "time", 46 | "timeEnd", 47 | "trace" 48 | ], 49 | "no-construct": true, 50 | "no-debugger": true, 51 | "no-duplicate-super": true, 52 | "no-empty": false, 53 | "no-empty-interface": true, 54 | "no-eval": true, 55 | "no-inferrable-types": [ 56 | true, 57 | "ignore-params" 58 | ], 59 | "no-misused-new": true, 60 | "no-non-null-assertion": true, 61 | "no-shadowed-variable": true, 62 | "no-string-literal": false, 63 | "no-string-throw": true, 64 | "no-switch-case-fall-through": true, 65 | "no-trailing-whitespace": true, 66 | "no-unnecessary-initializer": true, 67 | "no-unused-expression": true, 68 | "no-var-keyword": true, 69 | "object-literal-sort-keys": false, 70 | "one-line": [ 71 | true, 72 | "check-open-brace", 73 | "check-catch", 74 | "check-else", 75 | "check-whitespace" 76 | ], 77 | "prefer-const": true, 78 | "quotemark": [ 79 | true, 80 | "single" 81 | ], 82 | "radix": true, 83 | "semicolon": [ 84 | true, 85 | "always" 86 | ], 87 | "triple-equals": [ 88 | true, 89 | "allow-null-check" 90 | ], 91 | "typedef-whitespace": [ 92 | true, 93 | { 94 | "call-signature": "nospace", 95 | "index-signature": "nospace", 96 | "parameter": "nospace", 97 | "property-declaration": "nospace", 98 | "variable-declaration": "nospace" 99 | } 100 | ], 101 | "unified-signatures": true, 102 | "variable-name": false, 103 | "whitespace": [ 104 | true, 105 | "check-branch", 106 | "check-decl", 107 | "check-operator", 108 | "check-separator", 109 | "check-type" 110 | ], 111 | "directive-selector": [ 112 | true, 113 | "attribute", 114 | "app", 115 | "camelCase" 116 | ], 117 | "component-selector": [ 118 | true, 119 | "element", 120 | "app", 121 | "kebab-case" 122 | ], 123 | "no-input-rename": true, 124 | "no-output-rename": true, 125 | "use-life-cycle-interface": true, 126 | "use-pipe-transform-interface": true, 127 | "component-class-suffix": true, 128 | "directive-class-suffix": true 129 | } 130 | } 131 | --------------------------------------------------------------------------------