@ -0,0 +1,43 @@ | |||
{ | |||
"name": "@jaquiz/server", | |||
"version": "0.1.0", | |||
"main": "lib/index.js", | |||
"private": true, | |||
"repository": "git@git.polv.cc:jaquiz/server.git", | |||
"author": "Pacharapol Withayasakpunt <polv@polv.cc>", | |||
"license": "MIT", | |||
"scripts": { | |||
"ts": "ts-node", | |||
"build": "rm -r lib && tsc --rootDir src --outDir lib" | |||
}, | |||
"dependencies": { | |||
"@databases/pg": "^5.1.1", | |||
"axios": "^0.23.0", | |||
"connect-pg-simple": "^7.0.0", | |||
"dayjs": "^1.10.7", | |||
"fastify": "^3.22.0", | |||
"fastify-cookie": "^5.3.1", | |||
"fastify-rate-limit": "^5.6.2", | |||
"fastify-session": "^5.2.1", | |||
"fastify-static": "^4.4.2", | |||
"fastify-swagger": "^4.12.4", | |||
"jsonschema-definer": "^1.3.2", | |||
"short-uuid": "^4.2.0" | |||
}, | |||
"devDependencies": { | |||
"@types/better-sqlite3": "^7.4.0", | |||
"@types/connect-pg-simple": "^4.2.4", | |||
"@types/node": "^16.10.9", | |||
"@types/pino": "^6.3.11", | |||
"better-sqlite3": "^7.4.3", | |||
"import-sort-parser-typescript": "^6.0.0", | |||
"ts-node": "^10.3.0", | |||
"typescript": "^4.4.4" | |||
}, | |||
"importSort": { | |||
".js, .ts": { | |||
"parser": "typescript", | |||
"style": "module" | |||
} | |||
} | |||
} |
@ -0,0 +1,160 @@ | |||
import qs from 'querystring' | |||
import sql from '@databases/sql' | |||
import axios from 'axios' | |||
import ConnectPG from 'connect-pg-simple' | |||
import fastify, { FastifyPluginAsync } from 'fastify' | |||
import fCookie from 'fastify-cookie' | |||
import rateLimit from 'fastify-rate-limit' | |||
import fSession from 'fastify-session' | |||
import fastifySwagger from 'fastify-swagger' | |||
import { db, isDev } from '../shared' | |||
const apiRouter: FastifyPluginAsync = async (f) => { | |||
f.register(fCookie) | |||
f.register(fSession, { | |||
secret: process.env['SECRET']!, | |||
// @ts-ignore | |||
store: new (ConnectPG(fSession))( | |||
process.env['DATABASE_URL'] | |||
? { | |||
conString: process.env['DATABASE_URL'], | |||
} | |||
: { | |||
conObject: { | |||
user: process.env['POSTGRES_USER'], | |||
password: process.env['POSTGRES_PASSWORD'], | |||
database: process.env['POSTGRES_DB'], | |||
host: process.env['POSTGRES_HOST'], | |||
}, | |||
} | |||
), | |||
}) | |||
f.register(rateLimit, { | |||
max: 10, | |||
timeWindow: '1 second', | |||
allowList: (req) => { | |||
if (req.routerPath === '/api/quiz/getSrsLevel') { | |||
return true | |||
} | |||
return false | |||
}, | |||
}) | |||
f.register(fastifySwagger, { | |||
openapi: { | |||
security: [ | |||
{ | |||
BearerAuth: [], | |||
}, | |||
], | |||
components: { | |||
securitySchemes: { | |||
BearerAuth: { | |||
type: 'http', | |||
scheme: 'bearer', | |||
}, | |||
}, | |||
}, | |||
}, | |||
exposeRoute: isDev, | |||
routePrefix: '/doc', | |||
}) | |||
f.addHook('preHandler', async (req) => { | |||
const { body, log } = req | |||
if (body && typeof body === 'object' && body.constructor === Object) { | |||
log.info({ body }, 'parsed body') | |||
} | |||
}) | |||
f.addHook('preHandler', async (req) => { | |||
if (['/api/doc', '/api/settings'].some((s) => req.url.startsWith(s))) { | |||
return | |||
} | |||
if (req.session['userId']) { | |||
return | |||
} | |||
const [, apiKey] = | |||
/^Bearer (.+)$/.exec(req.headers.authorization || '') || [] | |||
if (!apiKey) { | |||
throw { statusCode: 401, message: 'no apiKey in header' } | |||
} | |||
const userData = await axios | |||
.create({ | |||
baseURL: 'https://api.wanikani.com/v2/', | |||
headers: { | |||
Authorization: `Bearer ${apiKey}`, | |||
}, | |||
validateStatus: function () { | |||
return true | |||
}, | |||
}) | |||
.get('/user') | |||
.then( | |||
(r) => | |||
r.data as { | |||
data: { | |||
id: string | |||
username: string | |||
level: number | |||
subscription: { | |||
active: boolean | |||
type: string | |||
} | |||
} | |||
} | |||
) | |||
const userId = userData.data.id | |||
await db.query(sql` | |||
INSERT INTO "user" ("id", "level") | |||
VALUES (${userId}, ${userData.data.level}) | |||
ON CONFLICT ("id") DO UPDATE SET | |||
"level" = EXCLUDED."level" | |||
`) | |||
req.session['userId'] = userId | |||
}) | |||
} | |||
export default apiRouter | |||
async function main() { | |||
const app = fastify({ | |||
logger: { | |||
prettyPrint: isDev, | |||
serializers: { | |||
req(req) { | |||
if (!req.url) { | |||
return { method: req.method } | |||
} | |||
const [url = '', q] = req.url.split(/\?(.+)$/) | |||
const query = q ? qs.parse(q) : undefined | |||
return { method: req.method, url, query } | |||
}, | |||
}, | |||
}, | |||
}) | |||
app.register(apiRouter, { | |||
prefix: '/api', | |||
}) | |||
return app.listen(process.env['PORT']!, '0.0.0.0') | |||
} | |||
if (require.main === module) { | |||
main() | |||
} |
@ -0,0 +1,51 @@ | |||
import path from 'path' | |||
import qs from 'querystring' | |||
import fastify from 'fastify' | |||
import fastifyStatic from 'fastify-static' | |||
import apiRouter from './api' | |||
import { gCloudLogger } from './logger' | |||
import { isDev } from './shared' | |||
async function main() { | |||
const port = parseInt(process.env['PORT']!) || 35594 | |||
process.env['PORT'] = port.toString() | |||
const app = fastify({ | |||
logger: gCloudLogger({ | |||
prettyPrint: isDev, | |||
serializers: { | |||
req(req) { | |||
if (!req.url) { | |||
return { method: req.method } | |||
} | |||
const [url, q] = req.url.split(/\?(.+)$/) | |||
const query = q ? qs.parse(q) : undefined | |||
return { method: req.method, url, query } | |||
}, | |||
}, | |||
}), | |||
}) | |||
app.register(apiRouter, { | |||
prefix: '/api', | |||
}) | |||
app.register(fastifyStatic, { | |||
root: path.join(__dirname, '../public'), | |||
redirect: true, | |||
}) | |||
app.setNotFoundHandler((_, reply) => { | |||
reply.redirect(200, '/') | |||
}) | |||
await app.listen(port, '0.0.0.0') | |||
} | |||
if (require.main === module) { | |||
main() | |||
} |
@ -0,0 +1,30 @@ | |||
import pino from 'pino' | |||
const SeverityLookup = { | |||
default: 'DEFAULT', | |||
silly: 'DEFAULT', | |||
verbose: 'DEBUG', | |||
debug: 'DEBUG', | |||
http: 'notice', | |||
info: 'INFO', | |||
warn: 'WARNING', | |||
error: 'ERROR', | |||
} | |||
const defaultPinoConfig: pino.LoggerOptions = { | |||
messageKey: 'message', | |||
formatters: { | |||
level(label, number) { | |||
return { | |||
severity: SeverityLookup[label as keyof typeof SeverityLookup], | |||
level: number, | |||
} | |||
}, | |||
}, | |||
} | |||
export const gCloudLogger = (pinoConfigOverrides: pino.LoggerOptions = {}) => | |||
pino({ | |||
...defaultPinoConfig, | |||
...pinoConfigOverrides, | |||
}) |
@ -0,0 +1,23 @@ | |||
import createConnectionPool, { ConnectionPool } from '@databases/pg' | |||
export const isDev = process.env['NODE_ENV'] === 'development' | |||
export let db: ConnectionPool | |||
// @ts-ignore | |||
if (!db) { | |||
db = createConnectionPool({ | |||
...(process.env['DATABASE_URL'] | |||
? { | |||
connectionString: process.env['DATABASE_URL'], | |||
} | |||
: { | |||
user: process.env['POSTGRES_USER']!, | |||
password: process.env['POSTGRES_PASSWORD']!, | |||
database: process.env['POSTGRES_DB']!, | |||
host: process.env['POSTGRES_HOST']!, | |||
port: parseInt(process.env['POSTGRES_PORT']!), | |||
}), | |||
bigIntMode: 'number', | |||
}) | |||
} |
@ -0,0 +1,299 @@ | |||
import { SQLQuery, sql } from '@databases/pg' | |||
import dayjs from 'dayjs' | |||
export class QSplit { | |||
constructor( | |||
private opts: { | |||
default: (v: string) => SQLQuery | null | |||
fields: { | |||
[name: string]: { | |||
[op: string]: (v: string) => SQLQuery | |||
} | |||
} | |||
} | |||
) {} | |||
parse(q: string) { | |||
const $and: SQLQuery[] = [] | |||
const $or: SQLQuery[] = [] | |||
const $not: SQLQuery[] = [] | |||
const ops = new Set( | |||
Object.values(this.opts.fields).flatMap((def) => Object.keys(def)) | |||
) | |||
for (let kv of this.doSplit(q, ' ')) { | |||
let $current = $and | |||
if (kv[0] === '-') { | |||
$current = $not | |||
kv = kv.substr(1) | |||
} else if (kv[0] === '?') { | |||
$current = $or | |||
kv = kv.substr(1) | |||
} | |||
for (const op of ops) { | |||
const segs = this.doSplit(kv, op) | |||
if (segs.length === 1) { | |||
const cond = this.opts.default(segs[0]!) | |||
if (cond) { | |||
$current.push(cond) | |||
} | |||
} else if (segs.length === 2) { | |||
const fnMap = this.opts.fields[segs[0]!] | |||
if (!fnMap) continue | |||
const fn = fnMap[op] | |||
if (!fn) continue | |||
$current.push(fn(segs[1]!)) | |||
} | |||
} | |||
} | |||
let cond: SQLQuery | null = null | |||
if ($not.length) { | |||
$and.push(sql`NOT ((${sql.join($not, ') AND (')}))`) | |||
} | |||
if ($and.length) { | |||
$or.push(sql`(${sql.join($and, ') AND (')})`) | |||
} | |||
if ($or.length) { | |||
cond = sql`((${sql.join($or, ') OR (')}))` | |||
} | |||
return cond | |||
} | |||
/** | |||
* ```js | |||
* > this.doSplit('') | |||
* [] | |||
* > this.doSplit('a:b "c:d e:f"') | |||
* ['a:b', 'c:d e:f'] | |||
* > this.doSplit('a "b c" "d e"') | |||
* ['a', 'b c', 'd e'] | |||
* ``` | |||
*/ | |||
private doSplit(ss: string, splitter: string) { | |||
const brackets = [ | |||
['"', '"'], | |||
["'", "'"], | |||
] as const | |||
const keepBraces = false | |||
const bracketStack = { | |||
data: [] as string[], | |||
push(c: string) { | |||
this.data.push(c) | |||
}, | |||
pop() { | |||
return this.data.pop() | |||
}, | |||
peek() { | |||
return this.data.length > 0 | |||
? this.data[this.data.length - 1] | |||
: undefined | |||
}, | |||
} | |||
const tokenStack = { | |||
data: [] as string[], | |||
currentChars: [] as string[], | |||
addChar(c: string) { | |||
this.currentChars.push(c) | |||
}, | |||
flush() { | |||
const d = this.currentChars.join('') | |||
if (d) { | |||
this.data.push(d) | |||
} | |||
this.currentChars = [] | |||
}, | |||
} | |||
let prev = '' | |||
ss.split('').map((c) => { | |||
if (prev === '\\') { | |||
tokenStack.addChar(c) | |||
} else { | |||
let canAddChar = true | |||
for (const [op, cl] of brackets) { | |||
if (c === cl) { | |||
if (bracketStack.peek() === op) { | |||
bracketStack.pop() | |||
canAddChar = false | |||
break | |||
} | |||
} | |||
if (c === op) { | |||
bracketStack.push(c) | |||
canAddChar = false | |||
break | |||
} | |||
} | |||
if (c === splitter && !bracketStack.peek()) { | |||
tokenStack.flush() | |||
} else { | |||
if (keepBraces || canAddChar) { | |||
tokenStack.addChar(c) | |||
} | |||
} | |||
} | |||
prev = c | |||
}) | |||
tokenStack.flush() | |||
return tokenStack.data.map((s) => s.trim()).filter((s) => s) | |||
} | |||
} | |||
export const qParseNum: ( | |||
k: SQLQuery | |||
) => Record<string, (v: string) => SQLQuery> = (k) => ({ | |||
':': (v) => { | |||
switch (v.toLocaleLowerCase()) { | |||
case 'null': | |||
return sql`${k} IS NULL` | |||
case 'any': | |||
return sql`${k} IS NOT NULL` | |||
} | |||
const m = /^([[\(])(\d+),(\d+)([\])])$/.exec(v) | |||
if (m) { | |||
let gt = sql`>` | |||
let lt = sql`<` | |||
if (m[1] === '[') gt = sql`>=` | |||
if (m[4] === ']') gt = sql`<=` | |||
return sql`${k} ${gt} ${parseInt(m[2])} OR ${k} ${lt} ${parseInt(m[3])}` | |||
} | |||
return sql`${k} = ${parseInt(v)}` | |||
}, | |||
'>': (v) => { | |||
return v[0] === '=' | |||
? sql`${k} >= ${parseInt(v.substr(1))}` | |||
: sql`${k} > ${parseInt(v)}` | |||
}, | |||
'<': (v) => { | |||
return v[0] === '=' | |||
? sql`${k} <= ${parseInt(v.substr(1))}` | |||
: sql`${k} < ${parseInt(v)}` | |||
}, | |||
}) | |||
const reDur = /^([+-]?\d+(?:\.\d+)?)([A-Z]+)$/i | |||
const toDate = (s: string) => { | |||
const m = reDur.exec(s) | |||
let d = dayjs(s) | |||
if (m) { | |||
d = dayjs().add(parseFloat(m[1]!), m[2] as any) | |||
if (d.isValid()) { | |||
return d.toDate() | |||
} | |||
} | |||
if (d.isValid()) { | |||
return d.toDate() | |||
} | |||
return null | |||
} | |||
const toBetween = (s: string) => { | |||
const m = reDur.exec(s) | |||
if (m && toDate(s) instanceof Date) { | |||
return [ | |||
dayjs() | |||
.add(parseFloat(m[1]!) - 0.5, m[2] as any) | |||
.toDate(), | |||
dayjs() | |||
.add(parseFloat(m[1]!) + 0.5, m[2] as any) | |||
.toDate(), | |||
] | |||
} | |||
return [] | |||
} | |||
export const qParseDate: ( | |||
k: SQLQuery | |||
) => Record<string, (v: string) => SQLQuery> = (k) => ({ | |||
':': (v) => { | |||
let b: Date[] = [] | |||
switch (v.toLocaleLowerCase()) { | |||
case 'null': | |||
return sql`${k} IS NULL` | |||
case 'any': | |||
return sql`${k} IS NOT NULL` | |||
case 'now': | |||
b = toBetween('-0.5d') | |||
} | |||
const reBetween = | |||
/^([[\(])([+-]?\d+(?:\.\d+)?[A-Z]+),([+-]?\d+(?:\.\d+)?[A-Z]+)([\])])$/i | |||
const m = reBetween.exec(v) | |||
if (m) { | |||
let gt = sql`>` | |||
let lt = sql`<` | |||
if (m[1] === '[') gt = sql`>=` | |||
if (m[4] === ']') gt = sql`<=` | |||
return sql`${k} ${gt} ${toDate(m[2]!)} OR ${k} ${lt} ${toDate(m[3]!)}` | |||
} | |||
if (reDur.test(v)) { | |||
b = toBetween(v) | |||
} | |||
if (b.length === 2) { | |||
return sql`${k} > ${b[0]} AND ${k} > ${b[1]}` | |||
} | |||
return sql`FALSE` | |||
}, | |||
'>': (v) => sql`${k} > ${toDate(v)}`, | |||
'<': (v) => sql`${k} < ${toDate(v)}`, | |||
}) | |||
export const makeQuiz = new QSplit({ | |||
default: () => null, | |||
fields: { | |||
srsLevel: qParseNum(sql`quiz."srsLevel"`), | |||
nextReview: qParseDate(sql`quiz."nextReview"`), | |||
lastRight: qParseDate(sql`quiz."lastRight"`), | |||
lastWrong: qParseDate(sql`quiz."lastWrong"`), | |||
maxRight: qParseNum(sql`quiz."maxRight"`), | |||
maxWrong: qParseNum(sql`quiz."maxWrong"`), | |||
rightStreak: qParseNum(sql`quiz."rightStreak"`), | |||
wrongStreak: qParseNum(sql`quiz."wrongStreak"`), | |||
}, | |||
}) | |||
export const makeTag = new QSplit({ | |||
default: () => null, | |||
fields: { | |||
tag: { ':': (v) => sql`${v} = ANY("entry"."tag")` }, | |||
type: { | |||
':': (v) => sql`"entry"."type" = ${v.replace(/hanzi/gi, 'character')}`, | |||
}, | |||
}, | |||
}) | |||
export const makeLevel = new QSplit({ | |||
default: () => null, | |||
fields: { | |||
level: qParseNum(sql`"entry"."level"`), | |||
hLevel: qParseNum(sql`"entry"."hLevel"`), | |||
}, | |||
}) |
@ -0,0 +1,100 @@ | |||
{ | |||
"compilerOptions": { | |||
/* Visit https://aka.ms/tsconfig.json to read more about this file */ | |||
/* Projects */ | |||
// "incremental": true, /* Enable incremental compilation */ | |||
// "composite": true, /* Enable constraints that allow a TypeScript project to be used with project references. */ | |||
// "tsBuildInfoFile": "./", /* Specify the folder for .tsbuildinfo incremental compilation files. */ | |||
// "disableSourceOfProjectReferenceRedirect": true, /* Disable preferring source files instead of declaration files when referencing composite projects */ | |||
// "disableSolutionSearching": true, /* Opt a project out of multi-project reference checking when editing. */ | |||
// "disableReferencedProjectLoad": true, /* Reduce the number of projects loaded automatically by TypeScript. */ | |||
/* Language and Environment */ | |||
"target": "es2019", /* Set the JavaScript language version for emitted JavaScript and include compatible library declarations. */ | |||
// "lib": [], /* Specify a set of bundled library declaration files that describe the target runtime environment. */ | |||
// "jsx": "preserve", /* Specify what JSX code is generated. */ | |||
// "experimentalDecorators": true, /* Enable experimental support for TC39 stage 2 draft decorators. */ | |||
// "emitDecoratorMetadata": true, /* Emit design-type metadata for decorated declarations in source files. */ | |||
// "jsxFactory": "", /* Specify the JSX factory function used when targeting React JSX emit, e.g. 'React.createElement' or 'h' */ | |||
// "jsxFragmentFactory": "", /* Specify the JSX Fragment reference used for fragments when targeting React JSX emit e.g. 'React.Fragment' or 'Fragment'. */ | |||
// "jsxImportSource": "", /* Specify module specifier used to import the JSX factory functions when using `jsx: react-jsx*`.` */ | |||
// "reactNamespace": "", /* Specify the object invoked for `createElement`. This only applies when targeting `react` JSX emit. */ | |||
// "noLib": true, /* Disable including any library files, including the default lib.d.ts. */ | |||
// "useDefineForClassFields": true, /* Emit ECMAScript-standard-compliant class fields. */ | |||
/* Modules */ | |||
"module": "commonjs", /* Specify what module code is generated. */ | |||
// "rootDir": "./", /* Specify the root folder within your source files. */ | |||
// "moduleResolution": "node", /* Specify how TypeScript looks up a file from a given module specifier. */ | |||
// "baseUrl": "./", /* Specify the base directory to resolve non-relative module names. */ | |||
// "paths": {}, /* Specify a set of entries that re-map imports to additional lookup locations. */ | |||
// "rootDirs": [], /* Allow multiple folders to be treated as one when resolving modules. */ | |||
// "typeRoots": [], /* Specify multiple folders that act like `./node_modules/@types`. */ | |||
// "types": [], /* Specify type package names to be included without being referenced in a source file. */ | |||
// "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */ | |||
// "resolveJsonModule": true, /* Enable importing .json files */ | |||
// "noResolve": true, /* Disallow `import`s, `require`s or `<reference>`s from expanding the number of files TypeScript should add to a project. */ | |||
/* JavaScript Support */ | |||
// "allowJs": true, /* Allow JavaScript files to be a part of your program. Use the `checkJS` option to get errors from these files. */ | |||
// "checkJs": true, /* Enable error reporting in type-checked JavaScript files. */ | |||
// "maxNodeModuleJsDepth": 1, /* Specify the maximum folder depth used for checking JavaScript files from `node_modules`. Only applicable with `allowJs`. */ | |||
/* Emit */ | |||
// "declaration": true, /* Generate .d.ts files from TypeScript and JavaScript files in your project. */ | |||
// "declarationMap": true, /* Create sourcemaps for d.ts files. */ | |||
// "emitDeclarationOnly": true, /* Only output d.ts files and not JavaScript files. */ | |||
"sourceMap": true, /* Create source map files for emitted JavaScript files. */ | |||
// "outFile": "./", /* Specify a file that bundles all outputs into one JavaScript file. If `declaration` is true, also designates a file that bundles all .d.ts output. */ | |||
// "outDir": "./", /* Specify an output folder for all emitted files. */ | |||
// "removeComments": true, /* Disable emitting comments. */ | |||
// "noEmit": true, /* Disable emitting files from a compilation. */ | |||
// "importHelpers": true, /* Allow importing helper functions from tslib once per project, instead of including them per-file. */ | |||
// "importsNotUsedAsValues": "remove", /* Specify emit/checking behavior for imports that are only used for types */ | |||
// "downlevelIteration": true, /* Emit more compliant, but verbose and less performant JavaScript for iteration. */ | |||
// "sourceRoot": "", /* Specify the root path for debuggers to find the reference source code. */ | |||
// "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */ | |||
// "inlineSourceMap": true, /* Include sourcemap files inside the emitted JavaScript. */ | |||
// "inlineSources": true, /* Include source code in the sourcemaps inside the emitted JavaScript. */ | |||
// "emitBOM": true, /* Emit a UTF-8 Byte Order Mark (BOM) in the beginning of output files. */ | |||
// "newLine": "crlf", /* Set the newline character for emitting files. */ | |||
// "stripInternal": true, /* Disable emitting declarations that have `@internal` in their JSDoc comments. */ | |||
// "noEmitHelpers": true, /* Disable generating custom helper functions like `__extends` in compiled output. */ | |||
// "noEmitOnError": true, /* Disable emitting files if any type checking errors are reported. */ | |||
// "preserveConstEnums": true, /* Disable erasing `const enum` declarations in generated code. */ | |||
// "declarationDir": "./", /* Specify the output directory for generated declaration files. */ | |||
/* Interop Constraints */ | |||
// "isolatedModules": true, /* Ensure that each file can be safely transpiled without relying on other imports. */ | |||
// "allowSyntheticDefaultImports": true, /* Allow 'import x from y' when a module doesn't have a default export. */ | |||
"esModuleInterop": true, /* Emit additional JavaScript to ease support for importing CommonJS modules. This enables `allowSyntheticDefaultImports` for type compatibility. */ | |||
// "preserveSymlinks": true, /* Disable resolving symlinks to their realpath. This correlates to the same flag in node. */ | |||
"forceConsistentCasingInFileNames": true, /* Ensure that casing is correct in imports. */ | |||
/* Type Checking */ | |||
"strict": true, /* Enable all strict type-checking options. */ | |||
"noImplicitAny": true, /* Enable error reporting for expressions and declarations with an implied `any` type.. */ | |||
"strictNullChecks": true, /* When type checking, take into account `null` and `undefined`. */ | |||
"strictFunctionTypes": true, /* When assigning functions, check to ensure parameters and the return values are subtype-compatible. */ | |||
"strictBindCallApply": true, /* Check that the arguments for `bind`, `call`, and `apply` methods match the original function. */ | |||
"strictPropertyInitialization": true, /* Check for class properties that are declared but not set in the constructor. */ | |||
"noImplicitThis": true, /* Enable error reporting when `this` is given the type `any`. */ | |||
"useUnknownInCatchVariables": true, /* Type catch clause variables as 'unknown' instead of 'any'. */ | |||
"alwaysStrict": true, /* Ensure 'use strict' is always emitted. */ | |||
"noUnusedLocals": true, /* Enable error reporting when a local variables aren't read. */ | |||
"noUnusedParameters": true, /* Raise an error when a function parameter isn't read */ | |||
"exactOptionalPropertyTypes": true, /* Interpret optional property types as written, rather than adding 'undefined'. */ | |||
"noImplicitReturns": true, /* Enable error reporting for codepaths that do not explicitly return in a function. */ | |||
"noFallthroughCasesInSwitch": true, /* Enable error reporting for fallthrough cases in switch statements. */ | |||
"noUncheckedIndexedAccess": true, /* Include 'undefined' in index signature results */ | |||
"noImplicitOverride": true, /* Ensure overriding members in derived classes are marked with an override modifier. */ | |||
"noPropertyAccessFromIndexSignature": true, /* Enforces using indexed accessors for keys declared using an indexed type */ | |||
// "allowUnusedLabels": true, /* Disable error reporting for unused labels. */ | |||
// "allowUnreachableCode": true, /* Disable error reporting for unreachable code. */ | |||
/* Completeness */ | |||
// "skipDefaultLibCheck": true, /* Skip type checking .d.ts files that are included with TypeScript. */ | |||
"skipLibCheck": true /* Skip type checking all .d.ts files. */ | |||
} | |||
} |