initial commit

This commit is contained in:
Matt Low 2021-02-19 18:30:02 -07:00
parent e13b4d737b
commit 7f9cffbfdd
16 changed files with 2865 additions and 0 deletions

8
.dockerignore Normal file
View File

@ -0,0 +1,8 @@
**/.git
.gitignore
.vscode
.env*
*.log
node_modules
dist

122
.gitignore vendored Normal file
View File

@ -0,0 +1,122 @@
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
lerna-debug.log*
# Diagnostic reports (https://nodejs.org/api/report.html)
report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json
# Runtime data
pids
*.pid
*.seed
*.pid.lock
# Directory for instrumented libs generated by jscoverage/JSCover
lib-cov
# Coverage directory used by tools like istanbul
coverage
*.lcov
# nyc test coverage
.nyc_output
# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files)
.grunt
# Bower dependency directory (https://bower.io/)
bower_components
# node-waf configuration
.lock-wscript
# Compiled binary addons (https://nodejs.org/api/addons.html)
build/Release
# Dependency directories
node_modules/
jspm_packages/
# Snowpack dependency directory (https://snowpack.dev/)
web_modules/
# TypeScript cache
*.tsbuildinfo
# Optional npm cache directory
.npm
# Optional eslint cache
.eslintcache
# Microbundle cache
.rpt2_cache/
.rts2_cache_cjs/
.rts2_cache_es/
.rts2_cache_umd/
# Optional REPL history
.node_repl_history
# Output of 'npm pack'
*.tgz
# Yarn Integrity file
.yarn-integrity
# dotenv environment variables file
.env
.env.test
# parcel-bundler cache (https://parceljs.org/)
.cache
.parcel-cache
# Next.js build output
.next
out
# Nuxt.js build / generate output
.nuxt
dist
# Gatsby files
.cache/
# Comment in the public line in if your project uses Gatsby and not Next.js
# https://nextjs.org/blog/next-9-1#public-directory-support
# public
# vuepress build output
.vuepress/dist
# Serverless directories
.serverless/
# FuseBox cache
.fusebox/
# DynamoDB Local files
.dynamodb/
# TernJS port file
.tern-port
# Stores VSCode versions used for testing VSCode extensions
.vscode-test
# yarn v2
.yarn/cache
.yarn/unplugged
.yarn/build-state.yml
.yarn/install-state.gz
.pnp.*
# deno
.deno_plugins
# vscode
.vscode

34
Dockerfile Normal file
View File

@ -0,0 +1,34 @@
# dev stage
FROM node:14-alpine as dev
WORKDIR /app
RUN apk update && apk add --no-cache python3 make gcc g++
COPY package*.json ./
RUN npm ci
COPY . .
# build stage
FROM dev as build
RUN npx tsc && npm prune --production
# production stage
FROM node:14-alpine
WORKDIR /app
RUN printf "%b" '#!'"/bin/sh\n\
set -e\n\
if [ ! -z \"\$RUN_MIGRATIONS\" ]; then\n\
echo \"Running migrations.\"\n\
npm run knex:migrate:latest\n\
fi\n\
exec \"\$@\"\n" > docker-entrypoint.sh && chmod +x docker-entrypoint.sh
# Copy over production modules and dist folder
COPY --from=build /app/package*.json ./
COPY --from=build /app/node_modules ./node_modules
COPY --from=build /app/dist ./dist
COPY --from=build /app/db ./db
EXPOSE 4000
ENTRYPOINT [ "./docker-entrypoint.sh" ]
CMD [ "node", "dist/main.js" ]

33
README.md Normal file
View File

@ -0,0 +1,33 @@
# BlueBlog API
A GraphQL blogging API.
## Features
* Username/Password JWT based authentication
* Blog posts and client-side encrypted journal entries
* Obfuscated IDs via [hashids](https://www.npmjs.com/package/hashids)
* Drafts for both of the above
* Blog post edit history
## Environment Variables:
```
# The secret used for JWT signatures creation and verification
SECRET=my-super-secret
# So hashids are unique
HASHIDS_SALT=salty
# PostgreSQL connection params
DB_HOST=localhost
DB_NAME=blueblog
DB_USER=blueblog
DB_PASSWORD=password
DB_PORT=5432
# Application startup PostgresSQL connection attempts & retry delay
DB_CONNECT_ATTEMPTS=6
DB_CONNECT_RETRY_DELAY=5
# Service responds at http://localhost:$LISTEN_PORT/graphql
LISTEN_PORT=4000
```

1899
package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

43
package.json Normal file
View File

@ -0,0 +1,43 @@
{
"name": "sudoku-api",
"version": "0.1.0",
"description": "Sudoku generating and solving API",
"main": "dist/main.js",
"dependencies": {
"@graphql-tools/merge": "^6.2.9",
"@graphql-tools/schema": "^6.2.4",
"dotenv": "^8.2.0",
"graphql": "^15.5.0",
"graphql-tag": "^2.11.0",
"graphql-type-json": "^0.3.2",
"koa": "^2.13.1",
"koa-bodyparser": "^4.3.0",
"koa-router": "^9.4.0",
"stoppable": "^1.1.0"
},
"devDependencies": {
"@types/koa": "^2.13.0",
"@types/koa-bodyparser": "^4.3.0",
"@types/koa-router": "^7.4.1",
"@types/markdown-it": "^10.0.3",
"@types/stoppable": "^1.1.0",
"nodemon": "^2.0.7",
"ts-node": "^8.10.2",
"typescript": "^3.9.9"
},
"scripts": {
"build": "tsc -p tsconfig.json",
"start": "node -r dotenv/config -r ts-node/register src/main.ts",
"serve": "nodemon"
},
"nodemonConfig": {
"watch": [
"src"
],
"exec": "npm start",
"ext": "ts"
},
"author": "Matt Low <matt@mlow.ca>",
"license": "UNLICENSED",
"private": true
}

83
src/graphql.ts Normal file
View File

@ -0,0 +1,83 @@
import {
Application,
Router,
RouterContext,
graphql,
makeExecutableSchema,
} from "./mods";
export interface ResolversProps {
Query?: any;
Mutation?: any;
[dynamicProperty: string]: any;
}
export interface ApplyGraphQLOptions {
app: Application;
path?: string;
typeDefs: any;
resolvers: ResolversProps;
context?: (ctx: RouterContext) => any;
}
export const applyGraphQL = ({
app,
path = "/graphql",
typeDefs,
resolvers,
context,
}: ApplyGraphQLOptions) => {
const schema = makeExecutableSchema({
typeDefs,
resolvers,
logger: {
log: (err: any) => console.log(err),
},
});
const router = new Router();
router.post(path, async (ctx) => {
const { response, request } = ctx;
if (!request.is("application/json") || !request.body) {
response.status = 415;
response.body = {
error: { message: "Request body must be in json format." },
};
return;
}
const contextResult = context ? await context(ctx) : ctx;
const { query, variables, operationName } = request.body;
try {
if (!query) {
response.status = 422;
response.body = {
error: { message: "Body missing 'query' parameter." },
};
return;
}
const result: any = await graphql(
schema,
query,
resolvers,
contextResult,
variables,
operationName
);
response.body = result;
} catch (error) {
response.status = 500;
response.body = {
error: error.message,
};
}
});
app.use(router.routes());
app.use(router.allowedMethods());
};

6
src/graphql/index.ts Normal file
View File

@ -0,0 +1,6 @@
import { mergeTypeDefs, mergeResolvers } from "../mods";
const modules = [require("./sudoku")];
export const typeDefs = mergeTypeDefs(modules.map((mod) => mod.typeDefs));
export const resolvers = mergeResolvers(modules.map((mod) => mod.resolvers));

55
src/graphql/sudoku.ts Normal file
View File

@ -0,0 +1,55 @@
import { gql } from "../mods";
import { generate } from "../sudoku/index";
export const typeDefs = gql`
type Cell {
value: Int
}
"""
A sudoku
"""
type Sudoku {
"The width of each region."
regionWidth: Int!
"The height of each region."
regionHeight: Int!
"The total number of cells in the board."
cells: Int!
"The 'raw' board, an array of cells in region-first order."
raw: [Cell!]!
"The rows of the board, from top to bottom."
rows: [[Cell!]!]!
"The columns of the board, from left to right."
columns: [[Cell!]!]!
"The regions of the board, book-ordered."
regions: [[Cell!]!]!
}
type Query {
"Generates a new sudoku."
generate(regionWidth: Int = 3, regionHeight: Int = 3, clues: Int!): Sudoku!
}
`;
type GenerateArguments = {
regionWidth: number;
regionHeight: number;
clues: number;
};
export const resolvers = {
Query: {
generate: (
obj: any,
{ regionWidth, regionHeight, clues }: GenerateArguments
) => {
return generate(regionWidth, regionHeight, clues);
},
},
};

69
src/main.ts Normal file
View File

@ -0,0 +1,69 @@
import { Application, bodyParser } from "./mods";
import { applyGraphQL } from "./graphql";
import { typeDefs, resolvers } from "./graphql/index";
import stoppable from "stoppable";
const runtime: { server: undefined | stoppable.StoppableServer } = {
server: undefined,
};
async function main() {
const app = new Application();
app.use(
bodyParser({
enableTypes: ["json"],
})
);
app.use(async (ctx, next) => {
const start = Date.now();
await next();
console.log(
`\x1b[32m%s\x1b[0m %s \x1b[32m%dms\x1b[0m\n---`,
ctx.request.method,
ctx.request.url,
Date.now() - start
);
});
applyGraphQL({
app,
typeDefs: typeDefs,
resolvers: resolvers,
});
runtime.server = stoppable(
app.listen({ port: parseInt(process.env.LISTEN_PORT!) || 4000 }, () => {
console.log(
`Server listening at http://localhost:${
process.env.LISTEN_PORT || 4000
}\n---`
);
})
);
}
["SIGINT", "SIGTERM"].forEach((sig) => {
process.on(sig, async () => {
console.log("\nCaught", sig);
console.log("Shutting down...");
if (runtime.server) {
runtime.server.stop((error, gracefully) => {
if (error) {
console.error(error);
process.exit(2);
} else if (!gracefully) {
console.warn("Server was not shut down gracefully.");
process.exit(1);
} else {
process.exit(0);
}
});
} else {
process.exit();
}
});
});
main();

20
src/mods.ts Normal file
View File

@ -0,0 +1,20 @@
// Koa
import Application from "koa";
export { Application };
export { Context } from "koa";
import Router from "koa-router";
export { Router };
export { RouterContext } from "koa-router";
import bodyParser from "koa-bodyparser";
export { bodyParser };
// graphql
export { graphql, GraphQLScalarType } from "graphql";
// graphql-tag
import gql from "graphql-tag";
export { gql };
// graphql-tools
export { makeExecutableSchema } from "@graphql-tools/schema";
export { mergeTypeDefs, mergeResolvers } from "@graphql-tools/merge";

37
src/sudoku/index.ts Normal file
View File

@ -0,0 +1,37 @@
import { SudokuMath } from "./math";
export type Cell = { value: number | null };
export type CellArray = Cell[];
export type Row = CellArray;
export type Column = CellArray;
export type Region = CellArray;
export type Puzzle = CellArray;
export type Sudoku = {
regionWidth: number;
regionHeight: number;
cells: number;
raw: Puzzle;
rows: () => Row[];
columns: () => Column[];
regions: () => Region[];
};
export function generate(
regionWidth: number,
regionHeight: number,
clues: number
): Sudoku {
const math = new SudokuMath(regionWidth, regionHeight);
const puzzle = math.generatePuzzle(clues);
return {
regionWidth,
regionHeight,
cells: math.boardCells,
raw: puzzle,
rows: () => math.regionsToRows(puzzle, true),
columns: () => math.regionsToCols(puzzle, true),
regions: () => math.chunkRegions(puzzle),
};
}

327
src/sudoku/math.ts Normal file
View File

@ -0,0 +1,327 @@
import { Cell, Row, Column, Region, Puzzle } from "./index";
import {
clone,
range,
shuffle,
chunkify,
mapArray,
addCellValuesToSet,
} from "./util";
function getTakenValues(region: Region, row: Row, col: Column) {
const filter = new Set<number>();
addCellValuesToSet(filter, region);
addCellValuesToSet(filter, row);
addCellValuesToSet(filter, col);
return filter;
}
export class SudokuMath {
regionWidth: number;
regionHeight: number;
boardWidth: number;
boardHeight: number;
width: number;
height: number;
boardCells: number;
regionCells: number;
legalValues: number[];
_regionsFromIndex: number[];
_rowsFromIndex: number[];
_colsFromIndex: number[];
_rowIndexToRegionIndex: number[];
_colIndexToRegionIndex: number[];
_regionIndexToRowIndex: number[];
_regionIndexToColIndex: number[];
constructor(regionWidth: number, regionHeight: number) {
this.regionWidth = regionWidth;
this.regionHeight = regionHeight;
this.boardWidth = regionHeight;
this.boardHeight = regionWidth;
this.width = regionWidth * this.boardWidth;
this.height = regionHeight * this.boardHeight;
this.boardCells = this.width * this.height;
this.regionCells = regionWidth * regionHeight;
this.legalValues = range(1, this.regionCells);
this._regionsFromIndex = Array(this.boardCells);
this._rowsFromIndex = Array(this.boardCells);
this._colsFromIndex = Array(this.boardCells);
this._rowIndexToRegionIndex = Array(this.boardCells);
this._colIndexToRegionIndex = Array(this.boardCells);
this._regionIndexToRowIndex = Array(this.boardCells);
this._regionIndexToColIndex = Array(this.boardCells);
for (let i = 0; i < this.boardCells; i++) {
this._regionsFromIndex[i] = this._regionFromRegionIndex(i);
const [row, col] = this._rowColFromRegionIndex(i);
this._rowsFromIndex[i] = row;
this._colsFromIndex[i] = col;
const rowIndex = row * this.width + col;
const colIndex = col * this.height + row;
this._rowIndexToRegionIndex[rowIndex] = i;
this._colIndexToRegionIndex[i] = rowIndex;
this._regionIndexToRowIndex[i] = rowIndex;
this._regionIndexToColIndex[colIndex] = i;
}
}
_regionFromRegionIndex(i: number) {
return Math.trunc(i / this.regionCells);
}
regionFromRegionIndex(i: number) {
return this._regionsFromIndex[i];
}
_rowColFromRegionIndex(i: number) {
const region = this.regionFromRegionIndex(i);
const cell = i % this.regionCells;
const regionRow = Math.trunc(region / this.boardWidth);
const regionCol = region % this.boardWidth;
const cellRow = Math.trunc(cell / this.regionWidth);
const cellCol = cell % this.regionWidth;
return [
regionRow * this.regionHeight + cellRow,
regionCol * this.regionWidth + cellCol,
];
}
rowColFromRegionIndex(i: number) {
return [this._rowsFromIndex[i], this._colsFromIndex[i]];
}
regionIndexToRowIndex(i: number) {
return this._regionIndexToRowIndex[i];
}
rowIndexToRegionIndex(i: number) {
return this._rowIndexToRegionIndex[i];
}
regionIndexToColIndex(i: number) {
return this._regionIndexToColIndex[i];
}
colIndexToRegionIndex(i: number) {
return this._colIndexToRegionIndex[i];
}
chunkRegions(cells: Cell[]) {
return chunkify(cells, this.regionCells);
}
regionsToRows(cells: Cell[], split = false) {
const rows = mapArray(cells, this._regionIndexToRowIndex);
return split ? chunkify(rows, this.width) : rows;
}
regionsToCols(cells: Cell[], split = false) {
const cols = mapArray(cells, this._regionIndexToColIndex);
return split ? chunkify(cols, this.height) : cols;
}
rowsToRegions(cells: Cell[], split = false) {
const regions = mapArray(cells, this._rowIndexToRegionIndex);
return split ? chunkify(regions, this.regionCells) : regions;
}
colsToRegions(cells: Cell[], split = false) {
const regions = mapArray(cells, this._colIndexToRegionIndex);
return split ? chunkify(regions, this.regionCells) : regions;
}
getBlankPuzzle(): Puzzle {
return Array(this.boardCells)
.fill(null)
.map((value) => ({ value }));
}
/**
* Returns the remaining legal values given a set of taken values.
*
* @param taken a set of taken values
*/
getLegalValues(taken: Set<number>) {
return this.legalValues.filter((value) => !taken.has(value));
}
/**
* Returns which values are unavailable at a given location, looking at the
* row, column, and region of the given cell index.
*
* @param cell
* @param puzzle
* @param regions
*/
getTakenValues(cell: number, puzzle: Puzzle, regions: Region[]) {
const rows = this.regionsToRows(puzzle, true);
const cols = this.regionsToCols(puzzle, true);
const [row, col] = this.rowColFromRegionIndex(cell);
const region = this.regionFromRegionIndex(cell);
return getTakenValues(regions[region], rows[row], cols[col]);
}
/**
* Returns whether a puzzle has only one solution.
*
* @param puzzle
*/
hasOneSolution(puzzle: Cell[]) {
const optimistic = clone(puzzle);
if (this.optimisticSolver(optimistic)) {
return true;
}
return this.backTrackingSolver(optimistic, 2) === 1;
}
/**
* Generates a single-solution sudoku puzzle with
*
* @param clues the number of cells to have pre-filled
*/
generatePuzzle(clues: number) {
const puzzle = this.getBlankPuzzle();
this.backTrackingSolver(puzzle, 1, shuffle);
if (clues === -1 || clues >= puzzle.length) return puzzle;
const orig = clone(puzzle);
const toRemove = puzzle.length - clues;
let removed: number[] = [];
let removeNext: number[] = shuffle(puzzle.map((_, i) => i));
const remove = () => {
const x = removeNext.shift() as any;
removed.push(x);
puzzle[x].value = null;
};
const replace = () => {
const x = removed.pop() as any;
removeNext.push(x);
puzzle[x].value = orig[x].value;
};
const removeCell = () => {
remove();
if (this.hasOneSolution(puzzle)) {
return true;
}
replace();
return false;
};
let fails = 0;
while (removed.length < toRemove) {
if (!removeCell()) {
fails++;
} else {
console.log(`Removed ${removed.length} cells.`);
fails = 0;
}
if (fails > removeNext.length) {
fails = 0;
console.log("Backstepping..");
Array(removed.length)
.fill(null)
.forEach(() => replace());
shuffle(removeNext);
}
}
return puzzle;
}
/**
* Attempt to solve the puzzle "optimistically". Only sets values which are
* certain, i.e. no guesses are made.
*
* Useful as a first pass.
*
* @param puzzle a region-ordered array of cells (each cell an object with
* a `value` key.
* @returns whether the puzzle was completely solved
*/
optimisticSolver(puzzle: Puzzle) {
const regions = this.chunkRegions(puzzle);
const rows = this.regionsToRows(puzzle, true);
const cols = this.regionsToCols(puzzle, true);
const solve = (): boolean => {
let foundValue = false;
let foundEmpty = false;
for (let i = 0, len = puzzle.length; i < len; i++) {
const cell = puzzle[i];
if (!!cell.value) continue;
foundEmpty = true;
const region = this.regionFromRegionIndex(i);
const [row, col] = this.rowColFromRegionIndex(i);
const taken = getTakenValues(regions[region], rows[row], cols[col]);
if (taken.size === this.regionCells - 1) {
cell.value = this.getLegalValues(taken)[0];
foundValue = true;
}
}
return foundValue && foundEmpty ? solve() : !foundEmpty;
};
return solve();
}
/**
* Backtracking solver. Mutates the puzzle during solve but eventually returns
* it to its initial state.
*
* @param puzzle see optimisticSolver
* @param stopAfter stop looking after this many solutions
* @param guessStrategy a function which takes an array of possible
* values for a cell, and returns the same values (in any order)
* @returns the number of solutions found
*/
backTrackingSolver(
puzzle: Puzzle,
stopAfter: number = -1,
guessStrategy: (values: number[]) => number[] = (values: number[]) => values
) {
const regions = this.chunkRegions(puzzle);
const rows = this.regionsToRows(puzzle, true);
const cols = this.regionsToCols(puzzle, true);
let solutions = 0;
const solve = (): boolean => {
for (let i = 0, len = puzzle.length; i < len; i++) {
const cell = puzzle[i];
if (!cell.value) {
const region = this.regionFromRegionIndex(i);
const [row, col] = this.rowColFromRegionIndex(i);
const avail = guessStrategy(
this.getLegalValues(
getTakenValues(regions[region], rows[row], cols[col])
)
);
for (let j = 0; j < avail.length; j++) {
cell.value = avail[j];
if (solve() && solutions === stopAfter) {
return true;
}
cell.value = null;
}
return false;
}
}
solutions++;
return true;
};
solve();
return solutions;
}
}

59
src/sudoku/util.ts Normal file
View File

@ -0,0 +1,59 @@
import { Cell } from "./index";
function randInt(lower: number, upper: number) {
return lower + Math.trunc(Math.random() * (upper - lower + 1));
}
export function shuffle(arr: any[]) {
const length = arr.length;
let lastIndex = length - 1;
for (let i = 0; i < length; i++) {
const rand = randInt(i, lastIndex);
const tmp = arr[rand];
arr[rand] = arr[i];
arr[i] = tmp;
}
return arr;
}
export function chunkify(arr: any[], chunkSize: number) {
const chunks = Array(arr.length / chunkSize);
for (let i = 0, len = chunks.length; i < len; i++) {
const start = i * chunkSize;
chunks[i] = arr.slice(start, start + chunkSize);
}
return chunks;
}
export function range(start: number, end: number) {
return Array(1 + end - start)
.fill(null)
.map((_, i) => start + i);
}
/**
* Re-orders an array. Given an array of elements, return a new array of those
* elements ordered by the indexes array.
*
* @param arr
* @param indexes
*/
export function mapArray(arr: any[], indexes: number[]) {
const newArr = Array(indexes.length);
for (let i = 0, len = arr.length; i < len; i++) {
newArr[i] = arr[indexes[i]];
}
return newArr;
}
export function clone(puzz: Cell[]): Cell[] {
return puzz.map(({ value }) => ({ value }));
}
export function addCellValuesToSet(set: Set<number>, cells: Cell[]) {
cells.forEach((cell) => {
if (!!cell.value) set.add(cell.value);
});
return set;
}

0
src/utils.ts Normal file
View File

70
tsconfig.json Normal file
View File

@ -0,0 +1,70 @@
{
"compilerOptions": {
/* Visit https://aka.ms/tsconfig.json to read more about this file */
/* Basic Options */
// "incremental": true, /* Enable incremental compilation */
"target": "es2020" /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017', 'ES2018', 'ES2019', 'ES2020', or 'ESNEXT'. */,
"module": "commonjs" /* Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', 'es2020', or 'ESNext'. */,
// "lib": [], /* Specify library files to be included in the compilation. */
// "allowJs": true, /* Allow javascript files to be compiled. */
// "checkJs": true, /* Report errors in .js files. */
// "jsx": "preserve", /* Specify JSX code generation: 'preserve', 'react-native', or 'react'. */
// "declaration": true, /* Generates corresponding '.d.ts' file. */
// "declarationMap": true, /* Generates a sourcemap for each corresponding '.d.ts' file. */
// "sourceMap": true, /* Generates corresponding '.map' file. */
// "outFile": "./", /* Concatenate and emit output to single file. */
"outDir": "./dist" /* Redirect output structure to the directory. */,
// "rootDir": "./", /* Specify the root directory of input files. Use to control the output directory structure with --outDir. */
// "composite": true, /* Enable project compilation */
// "tsBuildInfoFile": "./", /* Specify file to store incremental compilation information */
// "removeComments": true, /* Do not emit comments to output. */
// "noEmit": true, /* Do not emit outputs. */
// "importHelpers": true, /* Import emit helpers from 'tslib'. */
// "downlevelIteration": true, /* Provide full support for iterables in 'for-of', spread, and destructuring when targeting 'ES5' or 'ES3'. */
// "isolatedModules": true, /* Transpile each file as a separate module (similar to 'ts.transpileModule'). */
/* Strict Type-Checking Options */
"strict": true /* Enable all strict type-checking options. */,
// "noImplicitAny": true, /* Raise error on expressions and declarations with an implied 'any' type. */
// "strictNullChecks": true, /* Enable strict null checks. */
// "strictFunctionTypes": true, /* Enable strict checking of function types. */
// "strictBindCallApply": true, /* Enable strict 'bind', 'call', and 'apply' methods on functions. */
// "strictPropertyInitialization": true, /* Enable strict checking of property initialization in classes. */
// "noImplicitThis": true, /* Raise error on 'this' expressions with an implied 'any' type. */
// "alwaysStrict": true, /* Parse in strict mode and emit "use strict" for each source file. */
/* Additional Checks */
// "noUnusedLocals": true, /* Report errors on unused locals. */
// "noUnusedParameters": true, /* Report errors on unused parameters. */
// "noImplicitReturns": true, /* Report error when not all code paths in function return a value. */
// "noFallthroughCasesInSwitch": true, /* Report errors for fallthrough cases in switch statement. */
/* Module Resolution Options */
"moduleResolution": "node" /* Specify module resolution strategy: 'node' (Node.js) or 'classic' (TypeScript pre-1.6). */,
// "baseUrl": "./", /* Base directory to resolve non-absolute module names. */
// "paths": {}, /* A series of entries which re-map imports to lookup locations relative to the 'baseUrl'. */
// "rootDirs": [], /* List of root folders whose combined content represents the structure of the project at runtime. */
// "typeRoots": [], /* List of folders to include type definitions from. */
// "types": [], /* Type declaration files to be included in compilation. */
// "allowSyntheticDefaultImports": true, /* Allow default imports from modules with no default export. This does not affect code emit, just typechecking. */
"esModuleInterop": true /* Enables emit interoperability between CommonJS and ES Modules via creation of namespace objects for all imports. Implies 'allowSyntheticDefaultImports'. */,
// "preserveSymlinks": true, /* Do not resolve the real path of symlinks. */
// "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */
/* Source Map Options */
// "sourceRoot": "", /* Specify the location where debugger should locate TypeScript files instead of source locations. */
// "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */
// "inlineSourceMap": true, /* Emit a single file with source maps instead of having a separate file. */
// "inlineSources": true, /* Emit the source alongside the sourcemaps within a single file; requires '--inlineSourceMap' or '--sourceMap' to be set. */
/* Experimental Options */
// "experimentalDecorators": true, /* Enables experimental support for ES7 decorators. */
// "emitDecoratorMetadata": true, /* Enables experimental support for emitting type metadata for decorators. */
/* Advanced Options */
"skipLibCheck": true /* Skip type checking of declaration files. */,
"forceConsistentCasingInFileNames": true /* Disallow inconsistently-cased references to the same file. */
},
"include": ["./src"]
}