├── .editorconfig ├── .gitignore ├── .travis.yml ├── LICENSE ├── README.md ├── package-lock.json ├── package.json ├── src ├── index.spec.ts └── index.ts ├── tsconfig.json └── tslint.json /.editorconfig: -------------------------------------------------------------------------------- 1 | # EditorConfig is awesome: http://EditorConfig.org 2 | 3 | root = true 4 | 5 | [*] 6 | indent_size = 2 7 | indent_style = space 8 | end_of_line = lf 9 | charset = utf-8 10 | trim_trailing_whitespace = true 11 | insert_final_newline = true 12 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | coverage/ 3 | .DS_Store 4 | npm-debug.log 5 | dist/ 6 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | sudo: false 2 | 3 | language: node_js 4 | 5 | notifications: 6 | email: 7 | on_success: never 8 | on_failure: change 9 | 10 | node_js: 11 | - "6" 12 | - stable 13 | 14 | after_script: 15 | - npm install coveralls@2 16 | - cat ./coverage/lcov.info | coveralls 17 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2019 Blake Embrey (hello@blakeembrey.com) 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # SQL Type 2 | 3 | [![NPM version][npm-image]][npm-url] 4 | [![NPM downloads][downloads-image]][downloads-url] 5 | [![Build status][travis-image]][travis-url] 6 | [![Test coverage][coveralls-image]][coveralls-url] 7 | 8 | > Type-safe SQL builder using [ES2015 template tags](https://github.com/blakeembrey/sql-template-tag). 9 | 10 | ## Installation 11 | 12 | ``` 13 | npm install sql-type --save 14 | ``` 15 | 16 | ## Usage 17 | 18 | ```ts 19 | import { 20 | query, 21 | Table, 22 | Column, 23 | DefaultColumn, 24 | CreateExpression, 25 | ResultExpression 26 | } from "sql-type"; 27 | 28 | export const table = new Table("users", { 29 | id: new Column("id"), 30 | username: new Column("username"), 31 | createdAt: new DefaultColumn("created_at") 32 | }); 33 | 34 | export type CreateData = Omit, "id">; 35 | 36 | export type Model = ResultExpression; 37 | 38 | export async function one(query: Query) { 39 | const result = await conn.query(query); 40 | const { length } = result.rows; 41 | if (length !== 1) throw new TypeError(`Expected 1 row, got ${length}`); 42 | return query.decode(result.rows[0]); 43 | } 44 | 45 | export async function create(data: CreateData) { 46 | return one(table.create(data).return(table.keys)); 47 | } 48 | ``` 49 | 50 | ## License 51 | 52 | MIT 53 | 54 | [npm-image]: https://img.shields.io/npm/v/sql-type.svg?style=flat 55 | [npm-url]: https://npmjs.org/package/sql-type 56 | [downloads-image]: https://img.shields.io/npm/dm/sql-type.svg?style=flat 57 | [downloads-url]: https://npmjs.org/package/sql-type 58 | [travis-image]: https://img.shields.io/travis/blakeembrey/sql-type.svg?style=flat 59 | [travis-url]: https://travis-ci.org/blakeembrey/sql-type 60 | [coveralls-image]: https://img.shields.io/coveralls/blakeembrey/sql-type.svg?style=flat 61 | [coveralls-url]: https://coveralls.io/r/blakeembrey/sql-type?branch=master 62 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "sql-type", 3 | "version": "1.0.0", 4 | "description": "Type-safe SQL builder using ES2015 template tags", 5 | "main": "dist/index.js", 6 | "typings": "dist/index.d.ts", 7 | "files": [ 8 | "dist/" 9 | ], 10 | "scripts": { 11 | "prettier": "prettier --write", 12 | "lint": "tslint \"src/**/*.ts\" --project tsconfig.json", 13 | "format": "npm run prettier -- \"{.,src/**}/*.{js,jsx,ts,tsx,json,css,md,yml,yaml}\"", 14 | "build": "rimraf dist && tsc", 15 | "specs": "jest --coverage", 16 | "test": "npm run -s lint && npm run -s build && npm run -s specs", 17 | "prepare": "npm run build" 18 | }, 19 | "repository": { 20 | "type": "git", 21 | "url": "git://github.com/blakeembrey/sql-type.git" 22 | }, 23 | "keywords": [ 24 | "sql", 25 | "template", 26 | "string", 27 | "tag", 28 | "es2015", 29 | "es6", 30 | "model", 31 | "object", 32 | "type", 33 | "safe" 34 | ], 35 | "author": { 36 | "name": "Blake Embrey", 37 | "email": "hello@blakeembrey.com", 38 | "url": "http://blakeembrey.me" 39 | }, 40 | "license": "MIT", 41 | "bugs": { 42 | "url": "https://github.com/blakeembrey/sql-type/issues" 43 | }, 44 | "homepage": "https://github.com/blakeembrey/sql-type", 45 | "jest": { 46 | "roots": [ 47 | "/src/" 48 | ], 49 | "transform": { 50 | "\\.tsx?$": "ts-jest" 51 | }, 52 | "testRegex": "(/__tests__/.*|\\.(test|spec))\\.(tsx?|jsx?)$", 53 | "moduleFileExtensions": [ 54 | "ts", 55 | "tsx", 56 | "js", 57 | "jsx", 58 | "json", 59 | "node" 60 | ] 61 | }, 62 | "husky": { 63 | "hooks": { 64 | "pre-commit": "lint-staged" 65 | } 66 | }, 67 | "lint-staged": { 68 | "*.{js,jsx,ts,tsx,json,css,md,yml,yaml}": [ 69 | "npm run prettier", 70 | "git add" 71 | ] 72 | }, 73 | "publishConfig": { 74 | "access": "public" 75 | }, 76 | "engines": { 77 | "node": ">=6" 78 | }, 79 | "devDependencies": { 80 | "@types/jest": "^24.0.11", 81 | "@types/node": "^13.1.2", 82 | "husky": "^3.0.0", 83 | "jest": "^24.9.0", 84 | "lint-staged": "^9.2.0", 85 | "prettier": "^1.17.0", 86 | "rimraf": "^3.0.0", 87 | "ts-jest": "^24.0.2", 88 | "tslint": "^5.20.1", 89 | "tslint-config-prettier": "^1.18.0", 90 | "tslint-config-standard": "^9.0.0", 91 | "typescript": "^3.7.4" 92 | }, 93 | "dependencies": { 94 | "decorator-cache-getter": "^1.0.0", 95 | "sql-template-tag": "^3.3.0" 96 | } 97 | } 98 | -------------------------------------------------------------------------------- /src/index.spec.ts: -------------------------------------------------------------------------------- 1 | import { Table, Column, DefaultColumn } from "./index"; 2 | 3 | describe("sql type", () => { 4 | describe("table", () => { 5 | const table = new Table("users", { 6 | id: new DefaultColumn("id"), 7 | username: new Column("username"), 8 | createdAt: new DefaultColumn("created_at") 9 | }); 10 | 11 | it("should generate an insert statement", () => { 12 | const sql = table.create({ username: "test" }); 13 | 14 | expect(sql.text).toEqual( 15 | "INSERT INTO users (id,username,created_at) VALUES ($1,$2,$3)" 16 | ); 17 | expect(sql.values).toEqual([undefined, "test", undefined]); 18 | }); 19 | }); 20 | }); 21 | -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | import { cache } from "decorator-cache-getter"; 2 | import { 3 | Sql, 4 | RawValue, 5 | Value, 6 | join as rJoin, 7 | raw as rRaw 8 | } from "sql-template-tag"; 9 | 10 | /** 11 | * Export SQL template tag utilities. 12 | */ 13 | export { RawValue, Value, Sql }; 14 | 15 | /** 16 | * Support typed SQL expressions. 17 | */ 18 | export class TSql extends Sql { 19 | __TYPE_OF__!: T; 20 | } 21 | 22 | /** 23 | * Raw SQL types. 24 | */ 25 | export type TRawValue = T | TSql; 26 | 27 | /** 28 | * Get type of a SQL expression. 29 | */ 30 | export type TypeOfSql = T extends TSql ? U : never; 31 | 32 | /** 33 | * Typed SQL expressions. 34 | */ 35 | export function sql( 36 | strings: TemplateStringsArray, 37 | ...values: RawValue[] 38 | ) { 39 | return new TSql(strings, values); 40 | } 41 | 42 | /** 43 | * Typed raw value. 44 | */ 45 | export function raw(value: string) { 46 | return rRaw(value) as TSql; 47 | } 48 | 49 | /** 50 | * Typed join utility. 51 | */ 52 | export function join( 53 | values: { [K in keyof T]: TRawValue }, 54 | separator?: string 55 | ) { 56 | return rJoin(values, separator) as TSql; 57 | } 58 | 59 | /** 60 | * Quote an identifier. 61 | */ 62 | export function quote(name: string) { 63 | return /^[a-z0-9_]+$/.test(name) ? name : `"${name}"`; 64 | } 65 | 66 | /** 67 | * A column is a typed SQL expression. 68 | */ 69 | export class Column extends TSql { 70 | constructor(public name: string) { 71 | super([quote(name)], []); 72 | } 73 | } 74 | 75 | /** 76 | * A default column extends `Column` with type information for default values. 77 | */ 78 | export class DefaultColumn extends Column { 79 | hasDefaultValue = true; 80 | } 81 | 82 | /** 83 | * all values of an interface. 84 | */ 85 | export type ValueOf = T[keyof T]; 86 | 87 | /** 88 | * Pick the default table columns from a dictionary. 89 | */ 90 | export type DefaultColumns>> = ValueOf< 91 | { [K in keyof T]: T[K] extends DefaultColumn ? K : never } 92 | >; 93 | 94 | /** 95 | * Pick nullable columns from a dictionary. 96 | */ 97 | export type NullableColumns>> = ValueOf< 98 | { [K in keyof T]: Extract, null> extends never ? never : K } 99 | >; 100 | 101 | /** 102 | * Get not required columns for create. 103 | */ 104 | export type NotRequiredColumns>> = 105 | | NullableColumns 106 | | DefaultColumns; 107 | 108 | /** 109 | * Map columns to expected values. 110 | */ 111 | export type ColumnValues> = { 112 | [K in keyof T]: TRawValue> 113 | }; 114 | 115 | /** 116 | * Generate the interface for `INSERT` subtracting default columns. 117 | */ 118 | export type CreateExpression> = ColumnValues< 119 | Omit> 120 | > & 121 | Partial>>>; 122 | 123 | /** 124 | * Get the partial expression (e.g. `UPDATE`) for a table. 125 | */ 126 | export type UpdateExpression> = Partial< 127 | ColumnValues 128 | >; 129 | 130 | /** 131 | * Extract raw types of table columns. 132 | */ 133 | export type TypeOfColumns> = { 134 | [K in keyof T]: TypeOfSql 135 | }; 136 | 137 | /** 138 | * Get the result expression from a table. 139 | */ 140 | export type ResultExpression> = TypeOfColumns< 141 | T["columns"] 142 | >; 143 | 144 | /** 145 | * SQL row type. 146 | */ 147 | export type Row = Record; 148 | 149 | /** 150 | * Create a table instance. 151 | */ 152 | export class Table>> extends TSql { 153 | constructor(public tableName: string, public columns: C) { 154 | super([quote(tableName)], []); 155 | } 156 | 157 | @cache 158 | get keys(): { [K in keyof C]: TSql> } { 159 | const result = Object.create(null); 160 | for (const [key, col] of Object.entries(this.columns)) { 161 | result[key] = e.label(this, col); 162 | } 163 | return result; 164 | } 165 | 166 | create(...items: CreateExpression[]) { 167 | const keys = Object.keys(this.columns) as (keyof this["columns"])[]; 168 | const columns = e.arg(join(keys.map(key => this.columns[key]))); 169 | const values = join( 170 | items.map(item => { 171 | return e.arg(join(keys.map(key => (item as any)[key]))); 172 | }) 173 | ); 174 | 175 | return query.insertInto(this).append(sql`${columns} VALUES ${values}`); 176 | } 177 | 178 | update(data: UpdateExpression) { 179 | const expression = Object.entries(data) 180 | .filter(([_, value]) => value !== undefined) 181 | .map(([key, value]) => { 182 | return e.eq(this.columns[key], value!); 183 | }); 184 | 185 | return query.update(this).set(join(expression)); 186 | } 187 | 188 | get() { 189 | return query.get(this.keys).from(this); 190 | } 191 | 192 | delete() { 193 | return query.deleteFrom(this); 194 | } 195 | } 196 | 197 | /** 198 | * Single SQL mapping value. 199 | */ 200 | export type SqlMappingValue = Sql | Record; 201 | 202 | /** 203 | * Dictionary selector of SQL. 204 | */ 205 | export type SqlMapping = SqlMappingValue | SqlMappingValue[]; 206 | 207 | /** 208 | * Extract type mapping of a SQL object. 209 | */ 210 | export type TypeOfSqlMappingValue = T extends TSql 211 | ? TypeOfSql 212 | : T extends Record> 213 | ? { [K in keyof T]: TypeOfSql } 214 | : never; 215 | 216 | /** 217 | * Single result for a row mapping. 218 | */ 219 | export type RowMappingValue = string | Record; 220 | 221 | /** 222 | * Type of the value for `exec()` result mapping. 223 | */ 224 | export type RowMapping = RowMappingValue | RowMappingValue[]; 225 | 226 | /** 227 | * Query builder class for programmatically building SQL queries. 228 | */ 229 | export class Query extends TSql< 230 | U extends [any] ? U[0] : U 231 | > { 232 | constructor( 233 | strings: string[], 234 | values: RawValue[], 235 | public index: number, 236 | public mapping?: RowMapping 237 | ) { 238 | super(strings, values); 239 | } 240 | 241 | get( 242 | value: Exclude extends never ? R : never 243 | ): Query, never> { 244 | return appendMappedQuery>( 245 | this, 246 | sql`SELECT`, 247 | value 248 | ); 249 | } 250 | 251 | getAll( 252 | ...values: Exclude extends never ? R : never 253 | ): Query<{ [K in keyof R]: TypeOfSqlMappingValue }, never> { 254 | return appendMappedQuery<{ [K in keyof R]: TypeOfSqlMappingValue }>( 255 | this, 256 | sql`SELECT`, 257 | values 258 | ); 259 | } 260 | 261 | select(values: TSql): Query; 262 | select(values: TSql): Query; 263 | select(values: TSql) { 264 | return appendQuery( 265 | this, 266 | sql`SELECT ${values}`, 267 | this.index, 268 | undefined 269 | ); 270 | } 271 | 272 | returning( 273 | value: Exclude extends never ? R : never 274 | ): Query, never> { 275 | return appendMappedQuery>( 276 | this, 277 | sql`RETURNING`, 278 | value 279 | ); 280 | } 281 | 282 | append( 283 | value: Sql | TemplateStringsArray, 284 | ...values: RawValue[] 285 | ): Query { 286 | const child = value instanceof Sql ? value : new Sql(value, values); 287 | return appendQuery(this, child, this.index, this.mapping); 288 | } 289 | 290 | insertInto(value: Sql) { 291 | return this.append`INSERT INTO ${value}`; 292 | } 293 | 294 | deleteFrom(value: Sql) { 295 | return this.append`DELETE FROM ${value}`; 296 | } 297 | 298 | update(value: Sql) { 299 | return this.append`UPDATE ${value}`; 300 | } 301 | 302 | set(value: Sql) { 303 | return this.append`SET ${value}`; 304 | } 305 | 306 | from(...values: Sql[]) { 307 | return this.append`FROM ${join(values)}`; 308 | } 309 | 310 | leftJoin(table: Sql, condition: Sql) { 311 | return this.append`LEFT JOIN ${table} ON ${condition}`; 312 | } 313 | 314 | join(table: Sql, condition: Sql) { 315 | return this.append`JOIN ${table} ON ${condition}`; 316 | } 317 | 318 | groupBy(value: Sql) { 319 | return this.append`GROUP BY ${value}`; 320 | } 321 | 322 | where(value: Sql) { 323 | return this.append`WHERE ${value}`; 324 | } 325 | 326 | orderBy(value: Sql, direction?: "ASC" | "DESC") { 327 | if (direction) return this.append`ORDER BY ${value} ${raw(direction)}`; 328 | 329 | return this.append`ORDER BY ${value}`; 330 | } 331 | 332 | limit(value?: TRawValue) { 333 | if (value === undefined) return this; 334 | const limit = typeof value === "number" ? raw(String(value)) : value; 335 | return this.append`LIMIT ${limit}`; 336 | } 337 | 338 | offset(value?: number) { 339 | if (value === undefined) return this; 340 | return this.append`OFFSET ${raw(String(value))}`; 341 | } 342 | 343 | tap(fn: (value: this) => void) { 344 | fn(this); 345 | return this; 346 | } 347 | 348 | decode(row: Row) { 349 | return unpack(row, this.mapping) as T; 350 | } 351 | } 352 | 353 | /** 354 | * Query builder interface. 355 | */ 356 | export const query = new Query([""], [], 0, undefined); 357 | 358 | /** 359 | * SQL expression collection. 360 | */ 361 | export const e = { 362 | begin: sql`BEGIN`, 363 | commit: sql`COMMIT`, 364 | rollback: sql`ROLLBACK`, 365 | now: sql`NOW()`, 366 | label: (label: TSql, element: TSql) => { 367 | return sql`${label}.${element}`; 368 | }, 369 | extend: (...values: RawValue[]) => { 370 | return join(values, " "); 371 | }, 372 | distinct: (values: TRawValue, condition?: Sql) => { 373 | if (condition === undefined) return sql`DISTINCT ${values}`; 374 | return sql`DISTINCT ${condition} ${values}`; 375 | }, 376 | on: (value: TSql) => { 377 | return sql`ON ${value}`; 378 | }, 379 | list: ( 380 | ...values: { [K in keyof T]: TRawValue } 381 | ): TSql => { 382 | return join(values) as TSql; 383 | }, 384 | arg: ( 385 | ...values: { [K in keyof T]: TRawValue } 386 | ): TSql => { 387 | return sql`(${join(values)})`; 388 | }, 389 | and: (...values: TRawValue[]) => { 390 | return sql`(${join(values, " AND ")})`; 391 | }, 392 | or: (...values: TRawValue[]) => { 393 | return sql`(${join(values, " OR ")})`; 394 | }, 395 | eq: (a: TRawValue, b: TRawValue) => { 396 | return sql`${a} = ${b}`; 397 | }, 398 | ne: (a: TRawValue, b: TRawValue) => { 399 | return sql`${a} != ${b}`; 400 | }, 401 | lt: (a: TRawValue, b: TRawValue) => { 402 | return sql`${a} < ${b}`; 403 | }, 404 | gt: (a: TRawValue, b: TRawValue) => { 405 | return sql`${a} > ${b}`; 406 | }, 407 | lte: (a: TRawValue, b: TRawValue) => { 408 | return sql`${a} <= ${b}`; 409 | }, 410 | gte: (a: TRawValue, b: TRawValue) => { 411 | return sql`${a} >= ${b}`; 412 | }, 413 | mod: (a: TRawValue, b: TRawValue) => { 414 | return sql`${a} % ${b}`; 415 | }, 416 | between: ( 417 | a: TRawValue, 418 | b: TRawValue, 419 | c: TRawValue 420 | ) => { 421 | return sql`${a} BETWEEN ${b} AND ${c}`; 422 | }, 423 | isNull: (a: TRawValue) => { 424 | return sql`${a} IS NULL`; 425 | }, 426 | isNotNull: (a: TRawValue) => { 427 | sql`${a} IS NOT NULL`; 428 | }, 429 | in: ( 430 | item: TRawValue, 431 | values: TRawValue[] | TSql 432 | ) => { 433 | const value = Array.isArray(values) ? join(values) : values; 434 | return sql`${item} IN (${value})`; 435 | }, 436 | notIn: ( 437 | item: TRawValue, 438 | values: TRawValue[] | TSql 439 | ) => { 440 | const value = Array.isArray(values) ? join(values) : values; 441 | return sql`${item} NOT IN (${value})`; 442 | }, 443 | concat: (...values: TRawValue[]) => { 444 | return sql`CONCAT(${join(values)})`; 445 | }, 446 | as: (value: TRawValue, key: TSql) => { 447 | return sql`${value} AS ${key}`; 448 | }, 449 | count: (value: TRawValue) => { 450 | return sql`COUNT(${value})`; 451 | } 452 | }; 453 | 454 | /** 455 | * Append a value to the current query. 456 | */ 457 | function appendQuery( 458 | parent: Sql, 459 | child: Sql, 460 | index: number, 461 | mapping?: RowMapping 462 | ) { 463 | const query = parent.text ? sql`${parent} ${child}` : child; 464 | return new Query(query.strings, query.values, index, mapping); 465 | } 466 | 467 | /** 468 | * Append a SQL mapped query to the existing query. 469 | */ 470 | function appendMappedQuery( 471 | query: Query, 472 | keyword: Sql, 473 | values: SqlMapping 474 | ) { 475 | const [items, mapping, index] = mapQuery(values, query.index); 476 | const selects = join(items.map(([key, value]) => e.as(value, raw(key)))); 477 | 478 | return appendQuery( 479 | query, 480 | sql`${keyword} ${selects}`, 481 | index, 482 | mapping 483 | ); 484 | } 485 | 486 | /** 487 | * Convert a SQL mapping input to `exec` result. 488 | */ 489 | function mapQuery( 490 | query: T, 491 | index: number 492 | ): [Array<[string, Sql]>, RowMapping, number] { 493 | const items: Array<[string, Sql]> = []; 494 | let nameIndex = index; 495 | 496 | function mapValue(value: SqlMappingValue): RowMappingValue { 497 | if (value instanceof Sql) { 498 | const name = `x${nameIndex++}`; 499 | items.push([name, value]); 500 | return name; 501 | } 502 | 503 | const record: Record = {}; 504 | for (const [key, sql] of Object.entries(value)) { 505 | const name = `x${nameIndex++}`; 506 | items.push([name, sql]); 507 | record[key] = name; 508 | } 509 | return record; 510 | } 511 | 512 | if (!Array.isArray(query)) { 513 | return [items, mapValue(query as SqlMappingValue), nameIndex]; 514 | } 515 | 516 | return [items, query.map(x => mapValue(x)), nameIndex]; 517 | } 518 | 519 | /** 520 | * Unpack a single value to mapping. 521 | */ 522 | function unpackValue(mapping: RowMappingValue, row: Row) { 523 | if (typeof mapping === "string") return row[mapping]; 524 | 525 | const record: Row = {}; 526 | for (const [key, prop] of Object.entries(mapping)) record[key] = row[prop]; 527 | return record; 528 | } 529 | 530 | /** 531 | * Convert a SQL row back into the mapping. 532 | */ 533 | function unpack(row: Row, mapping?: RowMappingValue | RowMappingValue[]) { 534 | if (!mapping) return undefined; 535 | if (!Array.isArray(mapping)) return unpackValue(mapping, row); 536 | return mapping.map(x => unpackValue(x, row)); 537 | } 538 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es2015", 4 | "lib": ["es2015"], 5 | "outDir": "dist", 6 | "rootDir": "src", 7 | "module": "commonjs", 8 | "strict": true, 9 | "declaration": true, 10 | "sourceMap": true, 11 | "inlineSources": true, 12 | "experimentalDecorators": true 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /tslint.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": ["tslint-config-standard", "tslint-config-prettier"] 3 | } 4 | --------------------------------------------------------------------------------