├── .circleci
└── config.yml
├── .gitignore
├── .npmignore
├── .prettierrc
├── CNAME
├── README.md
├── example.ts
├── jest.config.js
├── logo.png
├── logo.sketch
├── package-lock.json
├── package.json
├── policy.png
├── src
├── db.ts
├── errors.ts
├── index.ts
├── storage.ts
├── types.ts
└── utils
│ └── error.ts
├── tests
├── main-test.ts
└── public-test.ts
├── tsconfig.json
└── tslint.json
/.circleci/config.yml:
--------------------------------------------------------------------------------
1 | version: 2.1
2 | orbs:
3 | node: circleci/node@1.1.6
4 | jobs:
5 | build-and-test:
6 | executor:
7 | name: node/default
8 | steps:
9 | - checkout
10 | - node/with-cache:
11 | steps:
12 | - run: npm install
13 | - run: npm build
14 | workflows:
15 | build-and-test:
16 | jobs:
17 | - build-and-test
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | google-serviceaccount.json
2 |
3 | dist
4 | node_modules
5 | build
6 | test
7 | src/**.js
8 | .idea/*
9 |
10 | coverage
11 | .nyc_output
12 | *.log
13 |
14 | yarn.lock
15 |
--------------------------------------------------------------------------------
/.npmignore:
--------------------------------------------------------------------------------
1 | tests
2 | jest.config.js
3 | .*.swp
4 | ._*
5 | .DS_Store
6 | .git
7 | .hg
8 | .npmrc
9 | .lock-wscript
10 | .svn
11 | .wafpickle-*
12 | config.gypi
13 | CVS
14 | npm-debug.log
--------------------------------------------------------------------------------
/.prettierrc:
--------------------------------------------------------------------------------
1 | {
2 | "printWidth": 100,
3 | "semi": false,
4 | "singleQuote": true,
5 | "trailingComma": "all"
6 | }
--------------------------------------------------------------------------------
/CNAME:
--------------------------------------------------------------------------------
1 | sheetsql.joway.io
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # SheetSQL
2 |
3 |
4 |
5 | [](https://www.npmjs.com/package/sheetsql)
6 | [](https://circleci.com/gh/joway/sheetsql)
7 |
8 | Google Spreadsheet as a Database.
9 |
10 | ## Purpose
11 |
12 | In the past, I often asked by non-technical colleagues to do some DB scripts jobs to mapping their spreadsheets data to the production database. And when their data changes the same work needs to be done again. Since these data are not changed too frequently(compared with other online data), it's also not worth to make a content management system for them.
13 |
14 | But why don't make their spreadsheets as a real production database? That's what ["Single source of truth"](https://en.wikipedia.org/wiki/Single_source_of_truth) means. What's more, you even could write back some statistical data like "Page View" to the spreadsheets, so they could see the feedback clearly and continue to optimize the content.
15 |
16 | ## Requirements
17 |
18 | 1. Create a Google Spreadsheet and populate the first row with the columns names, here is an [Example Sheet](https://docs.google.com/spreadsheets/d/1ya2Tl2ev9M80xYwspv7FJaoWq0oVOMBk3VF0f0MXv2s/edit?usp=sharing).
19 | 2. Create a [Google Cloud Service Account](https://cloud.google.com/docs/authentication/production) and download the JSON file that contains your key.
20 | 3. Find your service account email in [credentials console](https://console.cloud.google.com/apis/credentials) which similar with `account-name@project-name.iam.gserviceaccount.com`.
21 | 4. Share your sheets to the above email, and make sure you have assigned it as an editor.
22 |
23 | 
24 |
25 | ## Usage
26 |
27 | ### Concepts
28 |
29 | #### db
30 |
31 | `db` means the Google Spreadsheet ID. You can find it in your sheet's URL: `https://docs.google.com/spreadsheets/d/${YOUR_SHEETS_ID}/edit`
32 |
33 | #### table
34 |
35 | `table` means the Sheet Name in your Spreadsheet. The default is `Sheet1`.
36 |
37 | #### data type
38 |
39 | Every data in sheetsql will be set/get as a string. You need to handle the type mapping on your side.
40 |
41 | #### keyFile
42 |
43 | Your service account JSON key file.
44 |
45 | ### Install
46 |
47 | ```
48 | npm i sheetsql -S
49 | ```
50 |
51 | ### Example
52 |
53 | ```typescript
54 | const db = new Database({
55 | db: '1ya2Tl2ev9M80xYwspv7FJaoWq0oVOMBk3VF0f0MXv2s',
56 | table: 'Sheet1', // optional, default = Sheet1
57 | keyFile: './google-serviceaccount.json',
58 | cacheTimeoutMs: 5000, // optional, default = 5000
59 | })
60 |
61 | // load schema and data from google spreadsheet
62 | await db.load()
63 |
64 | // insert multiple documents
65 | let docs = await db.insert([
66 | {
67 | name: 'joway',
68 | age: 18,
69 | },
70 | ])
71 |
72 | // find documents and update them
73 | docs = await db.update(
74 | {
75 | name: 'joway',
76 | },
77 | {
78 | age: 100,
79 | },
80 | )
81 |
82 | // find documents
83 | docs = await db.find({
84 | name: 'joway',
85 | })
86 |
87 | // find all documents
88 | docs = await db.find({})
89 |
90 | // find documents and remove them
91 | docs = await db.remove({
92 | name: 'joway',
93 | })
94 | ```
95 |
96 | ### Using a Proxy
97 |
98 | sheetsql depend on `googleapis` lib in which you can set the following environment variables to proxy http/https requests:
99 |
100 | - `HTTP_PROXY` / `http_proxy`
101 | - `HTTPS_PROXY` / `https_proxy`
102 |
103 | The two environment variables could let your all requests using the proxy. If that is not your expected behavior and you only need to proxy google APIs, set `NO_PROXY=*`.
104 |
105 | Here is the [discuss](https://github.com/joway/sheetsql/issues/4).
106 |
--------------------------------------------------------------------------------
/example.ts:
--------------------------------------------------------------------------------
1 | import * as bluebird from 'bluebird'
2 | import Database from './src'
3 |
4 | const DB = '1gSb-_vI8jk53UPaCXU9YGfeq2C9v2_k9tYC4ibdut98'
5 |
6 | const main = async () => {
7 | const db = new Database({ db: DB, table: 'Sheet1', keyFile: './google-serviceaccount.json' })
8 | let count = 0
9 | while (true) {
10 | await db.insert([
11 | {
12 | name: `user-${count}`,
13 | age: count,
14 | },
15 | ])
16 | if (count % 3 === 1) {
17 | await db.remove({
18 | age: count - 1,
19 | })
20 | }
21 | count++
22 |
23 | console.log(`processed item ${count}`)
24 | await bluebird.delay(100)
25 | }
26 | }
27 |
28 | main()
29 | .then(() => process.exit(0))
30 | .catch((err) => {
31 | console.error(err)
32 | process.exit(-1)
33 | })
34 |
--------------------------------------------------------------------------------
/jest.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | transform: { '^.+\\.ts?$': 'ts-jest' },
3 | testEnvironment: 'node',
4 | testRegex: '(/tests/.*|(\\.|/)(test|spec))\\.tsx?$',
5 | moduleFileExtensions: ['ts', 'tsx', 'js', 'jsx', 'json', 'node']
6 | }
7 |
--------------------------------------------------------------------------------
/logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/joway/sheetsql/f551ab82a96bea54155ac35305b0b21eda5b5582/logo.png
--------------------------------------------------------------------------------
/logo.sketch:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/joway/sheetsql/f551ab82a96bea54155ac35305b0b21eda5b5582/logo.sketch
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "sheetsql",
3 | "version": "0.1.7",
4 | "description": "Google Spreadsheet as a Database",
5 | "main": "dist/src/index.js",
6 | "types": "dist/src/index.d.ts",
7 | "scripts": {
8 | "setup-test": "node ./gen-serviceaccount.js",
9 | "test": "npm run setup-test && npm run clean && jest",
10 | "clean": "rm -rf ./dist",
11 | "build": "npm run clean && tsc",
12 | "prepublish": "npm run build"
13 | },
14 | "precommit": [
15 | "prettier"
16 | ],
17 | "author": "Joway",
18 | "license": "MIT",
19 | "devDependencies": {
20 | "@types/bluebird": "^3.5.26",
21 | "@types/jest": "^26.0.0",
22 | "@types/lodash": "^4.14.158",
23 | "@types/nedb": "^1.8.10",
24 | "jest": "^24.9.0",
25 | "ts-jest": "^26.1.4",
26 | "tslint-jike-node": "0.0.18",
27 | "typescript": "^3.3.4000"
28 | },
29 | "dependencies": {
30 | "bluebird": "^3.5.3",
31 | "googleapis": "^55.0.0",
32 | "lodash": "^4.17.19"
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/policy.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/joway/sheetsql/f551ab82a96bea54155ac35305b0b21eda5b5582/policy.png
--------------------------------------------------------------------------------
/src/db.ts:
--------------------------------------------------------------------------------
1 | import { Query, Document } from './types'
2 | import GoogleStorage, { IStorageOptions, IStorage } from './storage'
3 |
4 | export interface IDatabaseOptions extends IStorageOptions {}
5 |
6 | export default class Database {
7 | private storage: IStorage
8 |
9 | constructor(opts: IDatabaseOptions) {
10 | this.storage = new GoogleStorage(opts)
11 | }
12 |
13 | async load() {
14 | return this.storage.load()
15 | }
16 |
17 | async insert(docs: Document[]): Promise {
18 | return this.storage.insert(docs)
19 | }
20 |
21 | async find(query?: Query): Promise {
22 | return this.storage.find(query)
23 | }
24 |
25 | async findOne(query?: Query): Promise {
26 | const docs = await this.storage.find(query)
27 | return docs.length ? docs[0] : null
28 | }
29 |
30 | async update(query: Query, toUpdate: Document): Promise {
31 | return this.storage.update(query, toUpdate)
32 | }
33 |
34 | async updateOne(query: Query, toUpdate: Partial): Promise {
35 | const newDocs = await this.storage.update(query, toUpdate, { updatedOnce: true })
36 | return newDocs.length ? newDocs[0] : null
37 | }
38 |
39 | async remove(query: Query): Promise {
40 | return this.storage.remove(query)
41 | }
42 |
43 | async removeOne(query: Query): Promise {
44 | const removedDocs = await this.storage.remove(query)
45 | return removedDocs.length ? removedDocs[0] : null
46 | }
47 | }
48 |
--------------------------------------------------------------------------------
/src/errors.ts:
--------------------------------------------------------------------------------
1 | import { getErrors } from './utils/error'
2 |
3 | const errors = [
4 | 'StorageOptionsError',
5 | 'StorageFormatError',
6 | 'StorageOptionError',
7 | ]
8 |
9 | export const Errors = getErrors(errors)
10 |
--------------------------------------------------------------------------------
/src/index.ts:
--------------------------------------------------------------------------------
1 | import Database from './db'
2 |
3 | export default Database
4 | export { Database }
5 |
--------------------------------------------------------------------------------
/src/storage.ts:
--------------------------------------------------------------------------------
1 | import { google, sheets_v4 } from 'googleapis'
2 | import * as _ from 'lodash'
3 | import { Errors } from './errors'
4 | import { Query, Document } from './types'
5 | import * as bluebird from 'bluebird'
6 |
7 | export interface IStorage {
8 | insert(docs: Document[]): Promise
9 | find(query?: Query): Promise
10 | update(
11 | query: Query,
12 | toUpdate: Partial,
13 | opts?: { updatedOnce: boolean },
14 | ): Promise
15 | remove(query: Query): Promise
16 | load(): Promise
17 | }
18 |
19 | export interface IStorageOptions {
20 | db: string // google spreadsheet id
21 | table?: string
22 |
23 | // google spreadsheet settings
24 | apiKey?: string
25 | keyFile?: string
26 |
27 | // storage settings
28 | cacheTimeoutMs?: number // ms
29 | }
30 |
31 | export default class GoogleStorage implements IStorage {
32 | private sheets: sheets_v4.Sheets
33 | private db: string
34 | private table: string
35 | private schema: string[] = []
36 | private schemaMetaStore: { [name: string]: { col: number } } = {}
37 | private data: string[][] = new Array()
38 | private lastUpdated: Date | null = null
39 | private cacheTimeoutMs: number
40 |
41 | constructor(opts: IStorageOptions) {
42 | if (!opts.apiKey && !opts.keyFile) {
43 | throw new Errors.StorageOptionsError()
44 | }
45 |
46 | const auth = opts.apiKey
47 | ? opts.apiKey
48 | : new google.auth.GoogleAuth({
49 | scopes: ['https://www.googleapis.com/auth/spreadsheets'],
50 | keyFile: opts.keyFile,
51 | })
52 | this.sheets = google.sheets({
53 | version: 'v4',
54 | auth: auth || '',
55 | })
56 | this.db = opts.db
57 | this.table = opts.table || 'Sheet1'
58 | this.cacheTimeoutMs = opts.cacheTimeoutMs || 5000
59 | }
60 |
61 | _findRows(query?: Query): number[] {
62 | const queryKeys = _.keys(query)
63 | const nums: number[] = []
64 |
65 | _.each(this.data, (row, rowNum) => {
66 | if (!query || !queryKeys.length) {
67 | nums.push(rowNum)
68 | return
69 | }
70 |
71 | for (const fieldName of queryKeys) {
72 | if (!this.schemaMetaStore[fieldName]) {
73 | return
74 | }
75 | const colNum = this.schemaMetaStore[fieldName].col
76 | const field = row[colNum]
77 | if (_.toString(field) !== _.toString(query[fieldName])) {
78 | return
79 | }
80 | }
81 |
82 | nums.push(rowNum)
83 | })
84 |
85 | return nums
86 | }
87 |
88 | _rowToDoc = (row: string[]): Document => {
89 | const doc: Document = {}
90 | for (let colNum = 0; colNum < row.length; ++colNum) {
91 | const field = row[colNum]
92 | const fieldName = this.schema[colNum]
93 | doc[fieldName] = _.toString(field)
94 | }
95 | return doc
96 | }
97 |
98 | _docToRow = (doc: Document): string[] => {
99 | const row = new Array(this.schema.length)
100 | for (const fieldName of _.keys(doc)) {
101 | if (this.schemaMetaStore[fieldName]) {
102 | row[this.schemaMetaStore[fieldName].col] = _.toString(doc[fieldName])
103 | }
104 | }
105 | return row
106 | }
107 |
108 | async find(query?: Query): Promise {
109 | await this._checkCacheTimeout()
110 |
111 | const rowNums = this._findRows(query)
112 | const docs = _.map(rowNums, (rowNum) => this._rowToDoc(this.data[rowNum]))
113 | return docs
114 | }
115 |
116 | async insert(docs: Document[]): Promise {
117 | await this._checkCacheTimeout()
118 |
119 | const rows = _.map(docs, this._docToRow)
120 | const range = this.table.includes('!') ? this.table : `${this.table}!A:A`
121 | await this.sheets.spreadsheets.values.append({
122 | spreadsheetId: this.db,
123 | range,
124 | valueInputOption: 'USER_ENTERED',
125 | requestBody: {
126 | values: rows,
127 | },
128 | })
129 | this.data.push(...rows)
130 |
131 | return _.map(rows, this._rowToDoc)
132 | }
133 |
134 | async update(
135 | query: Query,
136 | toUpdate: Document,
137 | opts: { updatedOnce: boolean } = { updatedOnce: false },
138 | ): Promise {
139 | await this._checkCacheTimeout()
140 |
141 | const rowNums = this._findRows(query)
142 | const { updatedOnce } = opts
143 | if (!rowNums.length) {
144 | return []
145 | }
146 |
147 | const docs: Document[] = []
148 | await bluebird.map(
149 | updatedOnce ? [rowNums[0]] : rowNums,
150 | async (rowNum) => {
151 | const oldDoc = this._rowToDoc(this.data[rowNum])
152 | const newDoc = { ...oldDoc, ...toUpdate }
153 | const row = this._docToRow(newDoc)
154 |
155 | docs.push(this._rowToDoc(row))
156 |
157 | await this.sheets.spreadsheets.values.update({
158 | spreadsheetId: this.db,
159 | range: `${this.table}!A${rowNum + 2}`,
160 | valueInputOption: 'USER_ENTERED',
161 | requestBody: {
162 | values: [row],
163 | },
164 | })
165 | this.data[rowNum] = row
166 | },
167 | { concurrency: 10 },
168 | )
169 |
170 | return docs
171 | }
172 |
173 | async remove(query: Query): Promise {
174 | await this._checkCacheTimeout()
175 |
176 | const rowNums = this._findRows(query)
177 | const emptyDoc = _.mapValues(this.schemaMetaStore, () => '')
178 | const emptyRow = this._docToRow(emptyDoc)
179 | const oldDoc: Document[] = []
180 | await bluebird.map(
181 | rowNums,
182 | async (rowNum) => {
183 | oldDoc.push(this._rowToDoc(this.data[rowNum]))
184 |
185 | await this.sheets.spreadsheets.values.update({
186 | spreadsheetId: this.db,
187 | range: `${this.table}!A${rowNum + 2}`,
188 | valueInputOption: 'USER_ENTERED',
189 | requestBody: {
190 | values: [emptyRow],
191 | },
192 | })
193 | this.data[rowNum] = emptyRow
194 | },
195 | { concurrency: 10 },
196 | )
197 |
198 | return oldDoc
199 | }
200 |
201 | async _checkCacheTimeout(): Promise {
202 | if (!this.lastUpdated || Date.now() - this.lastUpdated.getTime() >= this.cacheTimeoutMs) {
203 | await this.load()
204 | return true
205 | }
206 | return false
207 | }
208 |
209 | async load() {
210 | const res = await this.sheets.spreadsheets.values.get({
211 | range: this.table,
212 | spreadsheetId: this.db,
213 | })
214 | const rows = res.data.values
215 | if (!rows || rows.length === 0) {
216 | throw new Errors.StorageFormatError()
217 | }
218 |
219 | const schema = rows[0]
220 | const schemaMetaStore: { [key: string]: { col: number } } = _.zipObject(
221 | rows[0],
222 | _.map(rows[0], (_, colNum) => ({
223 | col: colNum,
224 | })),
225 | )
226 |
227 | this.schema = schema
228 | this.schemaMetaStore = schemaMetaStore
229 | this.data = _.slice(rows, 1)
230 | this.lastUpdated = new Date()
231 | }
232 | }
233 |
--------------------------------------------------------------------------------
/src/types.ts:
--------------------------------------------------------------------------------
1 | export interface Document {
2 | [key: string]: any
3 | }
4 | export interface Query {
5 | [key: string]: any
6 | }
7 |
--------------------------------------------------------------------------------
/src/utils/error.ts:
--------------------------------------------------------------------------------
1 | export interface ErrorInput {}
2 |
3 | export function getErrors(errors: string[]) {
4 | const Err: { [key: string]: new () => Error } = {}
5 | for (const errName of errors) {
6 | Err[errName] = class extends Error {
7 | constructor() {
8 | super()
9 | this.name = errName
10 | }
11 | }
12 | }
13 | return Err
14 | }
15 |
--------------------------------------------------------------------------------
/tests/main-test.ts:
--------------------------------------------------------------------------------
1 | import Database from '../src'
2 |
3 | // https://docs.google.com/spreadsheets/d/1ya2Tl2ev9M80xYwspv7FJaoWq0oVOMBk3VF0f0MXv2s/edit#gid=0
4 | const DB = '1ya2Tl2ev9M80xYwspv7FJaoWq0oVOMBk3VF0f0MXv2s'
5 |
6 | function getDB(table = 'Sheet1') {
7 | return new Database({ db: DB, table, keyFile: './google-serviceaccount.json' })
8 | }
9 |
10 | beforeEach(async () => {
11 | const db = getDB()
12 | await db.load()
13 |
14 | await db.remove({})
15 | })
16 |
17 | afterAll(async () => {
18 | const db = getDB()
19 | await db.load()
20 |
21 | await db.remove({})
22 | })
23 |
24 | test('db simple', async () => {
25 | const db = getDB()
26 | await db.load()
27 |
28 | let docs = await db.insert([
29 | {
30 | name: 'joway',
31 | age: 18,
32 | },
33 | ])
34 | expect(docs.length).toBe(1)
35 | expect(docs[0].name).toBe('joway')
36 | expect(docs[0].age).toBe('18')
37 |
38 | docs = await db.update(
39 | {
40 | name: 'joway',
41 | },
42 | {
43 | age: 100,
44 | },
45 | )
46 | expect(docs[0].name).toBe('joway')
47 | expect(docs[0].age).toBe('100')
48 |
49 | docs = await db.find({
50 | name: 'joway',
51 | })
52 | expect(docs[0].name).toBe('joway')
53 |
54 | docs = await db.remove({
55 | name: 'joway',
56 | })
57 | expect(docs[0].name).toBe('joway')
58 |
59 | docs = await db.find({
60 | name: 'joway',
61 | })
62 | expect(docs.length).toBe(0)
63 | }, 30000)
64 |
65 | test('db find', async () => {
66 | const db = getDB()
67 | await db.load()
68 |
69 | let docs = await db.insert([
70 | {
71 | name: 'jack',
72 | age: 18,
73 | },
74 | {
75 | name: 'mark',
76 | age: 21,
77 | },
78 | {
79 | name: 'jason',
80 | age: 18,
81 | no: 1,
82 | },
83 | ])
84 | expect(docs.length).toBe(3)
85 |
86 | docs = await db.find({
87 | age: 18,
88 | })
89 | expect(docs.length).toBe(2)
90 |
91 | docs = await db.find({
92 | age: '18',
93 | })
94 | expect(docs.length).toBe(2)
95 |
96 | docs = await db.find({})
97 | expect(docs.length).toBe(3)
98 |
99 | docs = await db.find({ no: 1 })
100 | expect(docs.length).toBe(0)
101 |
102 | docs = await db.find({ name: '' })
103 | expect(docs.length).toBe(0)
104 |
105 | let doc = await db.updateOne({ name: 'jack' }, { name: '' })
106 | expect(doc!.name).toBe('')
107 |
108 | docs = await db.find({ name: '' })
109 | expect(docs.length).toBe(1)
110 | }, 30000)
111 |
--------------------------------------------------------------------------------
/tests/public-test.ts:
--------------------------------------------------------------------------------
1 | import Database from '../src'
2 |
3 | const DB = '1J9CLF2GZroBpxMkjCoDXiXHL9o9kREv44q3Ia8Z6nGQ'
4 |
5 | function getDB() {
6 | return new Database({ db: DB, table: 'Sheet1', keyFile: './google-serviceaccount.json' })
7 | }
8 |
9 | beforeEach(async () => {
10 | const db = getDB()
11 | await db.load()
12 | await db.remove({})
13 | })
14 |
15 | afterAll(async () => {
16 | const db = getDB()
17 | await db.load()
18 | await db.remove({})
19 | })
20 |
21 | test('db simple', async () => {
22 | const db = getDB()
23 | await db.load()
24 |
25 | let docs = await db.insert([
26 | {
27 | name: 'joway',
28 | age: 18,
29 | },
30 | ])
31 | expect(docs.length).toBe(1)
32 | expect(docs[0].name).toBe('joway')
33 | expect(docs[0].age).toBe('18')
34 |
35 | docs = await db.update(
36 | {
37 | name: 'joway',
38 | },
39 | {
40 | age: 100,
41 | },
42 | )
43 | expect(docs[0].name).toBe('joway')
44 | expect(docs[0].age).toBe('100')
45 |
46 | docs = await db.find({
47 | name: 'joway',
48 | })
49 | expect(docs[0].name).toBe('joway')
50 |
51 | docs = await db.remove({
52 | name: 'joway',
53 | })
54 | expect(docs[0].name).toBe('joway')
55 |
56 | docs = await db.find({
57 | name: 'joway',
58 | })
59 | expect(docs.length).toBe(0)
60 | }, 30000)
61 |
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "exclude": [
3 | "dist",
4 | "node_modules"
5 | ],
6 | "compileOnSave": true,
7 | "compilerOptions": {
8 | "outDir": "dist",
9 | "rootDir": "./",
10 | "module": "commonjs",
11 | "sourceMap": true,
12 | "declaration": true,
13 | "target": "es5",
14 | "strictNullChecks": true,
15 | "typeRoots": [
16 | "node_modules/@types"
17 | ],
18 | "strict": true
19 | }
20 | }
--------------------------------------------------------------------------------
/tslint.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "tslint-jike-node"
3 | }
--------------------------------------------------------------------------------