diff --git a/package.json b/package.json index d490ad4e..0008b07b 100644 --- a/package.json +++ b/package.json @@ -86,8 +86,13 @@ "devDependencies": { "@google-cloud/nodejs-repo-tools": "^2.2.3", "@google-cloud/storage": "^2.0.0", + "@types/arrify": "^1.0.4", + "@types/big.js": "^4.0.5", + "@types/extend": "^3.0.0", + "@types/is": "0.0.20", "@types/mocha": "^5.2.5", "@types/sinon": "^5.0.5", + "@types/uuid": "^3.4.4", "async": "^2.6.0", "codecov": "^3.0.0", "eslint": "^5.0.0", diff --git a/src/dataset.ts b/src/dataset.ts index dc81e519..3592e38c 100644 --- a/src/dataset.ts +++ b/src/dataset.ts @@ -16,21 +16,45 @@ 'use strict'; -import {ServiceObject, DeleteCallback} from '@google-cloud/common'; +import {ServiceObject, DeleteCallback, DecorateRequestOptions} from '@google-cloud/common'; import {paginator} from '@google-cloud/paginator'; import {promisifyAll} from '@google-cloud/promisify'; import * as extend from 'extend'; import * as is from 'is'; -import {Table} from './table'; -import * as request from 'request'; - -// tslint:disable-next-line no-any -export type TempResponse = any; +import {Table, TableOptions} from './table'; +import * as r from 'request'; +import {BigQuery, CreateQueryJobResponse, CreateQueryJobCallback} from '.'; export interface DatasetDeleteOptions { force?: boolean; } +export interface DataSetOptions { + location?: string; +} + +export interface CreateDatasetOptions {} + +export type CreateDatasetResponse = [r.Response]; +export interface CreateDatasetCallback { + (err: Error|null, apiResponse?: r.Response): void; +} + +export interface CreateQueryJobOptions {} + +export interface GetTablesOptions { + autoPaginate?: boolean; + maxApiCalls?: number; + maxResults?: number; + pageToken?: string; +} + +export type GetTablesResponse = [Table[], r.Response]; +export interface GetTablesCallback { + (err: Error|null, tables?: Table[]|null, nextQuery?: {}|null, + apiResponse?: r.Response): void; +} + /** * Interact with your BigQuery dataset. Create a Dataset instance with * {@link BigQuery#createDataset} or {@link BigQuery#dataset}. @@ -48,7 +72,9 @@ export interface DatasetDeleteOptions { * const dataset = bigquery.dataset('institutions'); */ class Dataset extends ServiceObject { - constructor(bigQuery, id, options) { + bigQuery: BigQuery; + + constructor(bigQuery: BigQuery, id: string, options?: DataSetOptions) { const methods = { /** * Create a dataset. @@ -212,15 +238,18 @@ class Dataset extends ServiceObject { baseUrl: '/datasets', id, methods, - requestModule: request, - createMethod: (id, options, callback) => { - if (is.fn(options)) { - callback = options; - options = {}; - } + requestModule: r, + createMethod: ( + id: string, + optionsOrCallback?: CreateDatasetOptions|CreateDatasetCallback, + cb?: CreateDatasetCallback) => { + let options = + typeof optionsOrCallback === 'object' ? optionsOrCallback : {}; + const callback = + typeof optionsOrCallback === 'function' ? optionsOrCallback : cb; options = extend({}, options, {location: this.location}); return bigQuery.createDataset(id, options, callback); - }, + } }); if (options && options.location) { @@ -232,7 +261,7 @@ class Dataset extends ServiceObject { // Catch all for read-modify-write cycle // https://cloud.google.com/bigquery/docs/api-performance#read-patch-write this.interceptors.push({ - request: reqOpts => { + request: (reqOpts: DecorateRequestOptions) => { if (reqOpts.method === 'PATCH' && reqOpts.json.etag) { reqOpts.headers = reqOpts.headers || {}; reqOpts.headers['If-Match'] = reqOpts.json.etag; @@ -285,9 +314,14 @@ class Dataset extends ServiceObject { * @param {function} [callback] See {@link BigQuery#createQueryJob} for full documentation of this method. * @returns {Promise} See {@link BigQuery#createQueryJob} for full documentation of this method. */ - createQueryJob(options): Promise; - createQueryJob(options, callback): void; - createQueryJob(options, callback?): void|Promise { + createQueryJob(options: string| + CreateQueryJobOptions): Promise; + createQueryJob( + options: string|CreateQueryJobOptions, + callback: CreateQueryJobCallback): void; + createQueryJob( + options: string|CreateQueryJobOptions, + callback?: CreateQueryJobCallback): void|Promise { if (is.string(options)) { options = { query: options, @@ -301,7 +335,7 @@ class Dataset extends ServiceObject { location: this.location, }); - return this.bigQuery.createQueryJob(options, callback); + return this.bigQuery.createQueryJob(options, callback!); } /** @@ -374,15 +408,15 @@ class Dataset extends ServiceObject { * const apiResponse = data[1]; * }); */ - createTable(id, options, callback) { + createTable(id: string, options, callback) { if (is.fn(options)) { callback = options; options = {}; } const body = Table.formatMetadata_(options); - - body.tableReference = { + // tslint:disable-next-line no-any + (body as any).tableReference = { datasetId: this.id, projectId: this.bigQuery.projectId, tableId: id, @@ -443,12 +477,12 @@ class Dataset extends ServiceObject { * const apiResponse = data[0]; * }); */ - delete(options?: DatasetDeleteOptions): Promise<[request.Response]>; + delete(options?: DatasetDeleteOptions): Promise<[r.Response]>; delete(options: DatasetDeleteOptions, callback: DeleteCallback): void; delete(callback: DeleteCallback): void; delete( optionsOrCallback?: DeleteCallback|DatasetDeleteOptions, - callback?: DeleteCallback): void|Promise<[request.Response]> { + callback?: DeleteCallback): void|Promise<[r.Response]> { const options = typeof optionsOrCallback === 'object' ? optionsOrCallback : {}; callback = @@ -515,16 +549,16 @@ class Dataset extends ServiceObject { * const tables = data[0]; * }); */ - getTables(options?): Promise; - getTables(options, callback): void; - getTables(callback): void; - getTables(options?, callback?): void|Promise { - if (is.fn(options)) { - callback = options; - options = {}; - } - - options = options || {}; + getTables(options?: GetTablesOptions): Promise; + getTables(options: GetTablesOptions, callback: GetTablesCallback): void; + getTables(callback: GetTablesCallback): void; + getTables( + optionsOrCallback?: GetTablesOptions|GetTablesCallback, + cb?: GetTablesCallback): void|Promise { + const options = + typeof optionsOrCallback === 'object' ? optionsOrCallback : {}; + const callback = + typeof optionsOrCallback === 'function' ? optionsOrCallback : cb; this.request( { @@ -533,11 +567,11 @@ class Dataset extends ServiceObject { }, (err, resp) => { if (err) { - callback(err, null, null, resp); + callback!(err, null, null, resp); return; } - let nextQuery = null; + let nextQuery: {}|null = null; if (resp.nextPageToken) { nextQuery = extend({}, options, { pageToken: resp.nextPageToken, @@ -551,7 +585,7 @@ class Dataset extends ServiceObject { table.metadata = tableObject; return table; }); - callback(null, tables, nextQuery, resp); + callback!(null, tables, nextQuery, resp); }); } @@ -601,13 +635,12 @@ class Dataset extends ServiceObject { * * const institutions = dataset.table('institution_data'); */ - table(id, options?) { + table(id: string, options?: TableOptions) { options = extend( { location: this.location, }, options); - return new Table(this, id, options); } } diff --git a/src/index.ts b/src/index.ts index 06f4212d..95268a4e 100644 --- a/src/index.ts +++ b/src/index.ts @@ -17,27 +17,92 @@ 'use strict'; import * as arrify from 'arrify'; -import * as Big from 'big.js'; +import Big from 'big.js'; import * as common from '@google-cloud/common'; import {promisifyAll} from '@google-cloud/promisify'; import {paginator} from '@google-cloud/paginator'; import * as extend from 'extend'; const format = require('string-format-obj'); import * as is from 'is'; -import * as request from 'request'; +import * as r from 'request'; import * as uuid from 'uuid'; -import {Dataset, TempResponse} from './dataset'; -import {Job} from './job'; -import {Table} from './table'; +import {Dataset, DataSetOptions} from './dataset'; +import {Job, JobOptions} from './job'; +import {Table, TempResponse} from './table'; import {GoogleErrorBody} from '@google-cloud/common/build/src/util'; +export type CreateQueryJobResponse = [Job, r.Response]; +export interface CreateQueryJobCallback { + (err: Error|null, job?: Job, apiResponse?: r.Response): void; +} + +export interface GetJobsOptions { + allUsers?: boolean; + autoPaginate?: boolean; + maxApiCalls?: number; + maxResults?: number; + pageToken?: string; + projection?: 'full'|'minimal'; + stateFilter?: 'done'|'pending'|'running'; +} + +export type GetJobsResponse = [Job[], r.Response]; +export interface GetJobsCallback { + (err: Error|null, jobs: Job[]|null, nextQuery?: {}|null, + apiResponse?: r.Response): void; +} + +export interface BigQueryTimeOptions { + hours?: number|string; + minutes?: number|string; + seconds?: number|string; + fractional?: number|string; +} + +export interface BigQueryDateOptions { + year?: number|string; + month?: number|string; + day?: number|string; +} + +export interface BigQueryDatetimeOptions { + year?: string|number; + month?: string|number; + day?: string|number; + hours?: string|number; + minutes?: string|number; + seconds?: string|number; + fractional?: string|number; +} + export interface QueryParameter { name?: string; parameterType: {type: string;}; parameterValue: {arrayValues?: Array<{}>; structValues?: {}; value?: {}}; } +export interface CreateQueryJobOptions { + destination?: Table; + dryRun?: boolean; + location?: string; + jobId?: string; + jobPrefix?: string; + query?: string; + useLegacySql?: boolean; +} + +export type CreateQueryResponse = [Job, r.Response]; +export interface CreateQueryJobCallback { + (err: Error|null, job?: Job, apiResponse?: r.Response): void; +} + +export interface BigQueryOptions extends common.GoogleAuthOptions { + autoRetry?: boolean; + maxRetries?: number; + location?: string; +} + /** * @typedef {object} ClientConfig * @property {string} [projectId] The project ID from the Google Developer's @@ -108,17 +173,18 @@ export interface QueryParameter { * Full quickstart example: */ export class BigQuery extends common.Service { - location: string; - createQueryStream; - getDatasetsStream; - getJobsStream; - constructor(options?) { + location?: string; + createQueryStream: Function; + getDatasetsStream: Function; + getJobsStream: Function; + + constructor(options?: BigQueryOptions) { options = options || {}; const config = { baseUrl: 'https://www.googleapis.com/bigquery/v2', scopes: ['https://www.googleapis.com/auth/bigquery'], packageJson: require('../../package.json'), - requestModule: request, + requestModule: r, }; if (options.scopes) { @@ -169,7 +235,7 @@ export class BigQuery extends common.Service { } function convert(schemaField, value) { - if (is.nil(value)) { + if (is.null(value)) { return value; } @@ -285,11 +351,11 @@ export class BigQuery extends common.Service { * day: 1 * }); */ - static date(value) { + static date(value: BigQueryDateOptions|string) { return new BigQueryDate(value); } - date(value) { + date(value: BigQueryDateOptions|string) { return BigQuery.date(value); } @@ -363,11 +429,11 @@ export class BigQuery extends common.Service { * seconds: 0 * }); */ - static datetime(value) { + static datetime(value: BigQueryDatetimeOptions|string) { return new BigQueryDatetime(value); } - datetime(value) { + datetime(value: BigQueryDatetimeOptions|string) { return BigQuery.datetime(value); } @@ -425,11 +491,11 @@ export class BigQuery extends common.Service { * seconds: 0 * }); */ - static time(value) { + static time(value: BigQueryTimeOptions|string) { return new BigQueryTime(value); } - time(value) { + time(value: BigQueryTimeOptions|string) { return BigQuery.time(value); } @@ -457,11 +523,11 @@ export class BigQuery extends common.Service { * const bigquery = new BigQuery(); * const timestamp = bigquery.timestamp(new Date()); */ - static timestamp(value) { + static timestamp(value: Date|string) { return new BigQueryTimestamp(value); } - timestamp(value) { + timestamp(value: Date|string) { return BigQuery.timestamp(value); } @@ -477,7 +543,7 @@ export class BigQuery extends common.Service { * @param {*} value The value. * @returns {string} The type detected from the value. */ - static getType_(value) { + static getType_(value: {}) { let typeName; if (value instanceof BigQueryDate) { @@ -497,10 +563,10 @@ export class BigQuery extends common.Service { type: 'ARRAY', arrayType: BigQuery.getType_(value[0]), }; - } else if (is.bool(value)) { + } else if (is.boolean(value)) { typeName = 'BOOL'; } else if (is.number(value)) { - typeName = value % 1 === 0 ? 'INT64' : 'FLOAT64'; + typeName = (value as number) % 1 === 0 ? 'INT64' : 'FLOAT64'; } else if (is.object(value)) { return { type: 'STRUCT', @@ -537,9 +603,9 @@ export class BigQuery extends common.Service { * @param {*} value The value. * @returns {object} A properly-formed `queryParameter` object. */ - static valueToQueryParameter_(value) { + static valueToQueryParameter_(value: {}) { if (is.date(value)) { - value = BigQuery.timestamp(value); + value = BigQuery.timestamp(value as Date); } const queryParameter: QueryParameter = { @@ -550,15 +616,16 @@ export class BigQuery extends common.Service { const typeName = queryParameter.parameterType.type; if (typeName.indexOf('TIME') > -1 || typeName.indexOf('DATE') > -1) { - value = value.value; + value = (value as {value}).value; } if (typeName === 'ARRAY') { - queryParameter.parameterValue.arrayValues = value.map(value => { - return { - value, - }; - }); + queryParameter.parameterValue.arrayValues = + (value as Array<{}>).map(value => { + return { + value, + }; + }); } else if (typeName === 'STRUCT') { queryParameter.parameterValue.structValues = Object.keys(value).reduce((structValues, prop) => { @@ -604,7 +671,7 @@ export class BigQuery extends common.Service { * const apiResponse = data[1]; * }); */ - createDataset(id, options, callback) { + createDataset(id: string, options, callback) { const that = this; if (is.fn(options)) { @@ -717,20 +784,21 @@ export class BigQuery extends common.Service { * return job.getQueryResults(); * }); */ - createQueryJob(options): Promise; - createQueryJob(options, callback): void; - createQueryJob(options, callback?): void|Promise { - if (is.string(options)) { - options = { - query: options, - }; - } - + createQueryJob(options: CreateQueryJobOptions| + string): Promise; + createQueryJob( + options: CreateQueryJobOptions|string, + callback: CreateQueryJobCallback): void; + createQueryJob( + opts: CreateQueryJobOptions|string, + callback?: CreateQueryJobCallback): void|Promise { + const options = typeof opts === 'object' ? opts : {query: opts}; if (!options || !options.query) { throw new Error('A SQL query string is required.'); } - const query = extend( + // tslint:disable-next-line no-any + const query: any = extend( true, { useLegacySql: false, }, @@ -961,11 +1029,10 @@ export class BigQuery extends common.Service { * const bigquery = new BigQuery(); * const dataset = bigquery.dataset('higher_education'); */ - dataset(id, options?) { + dataset(id: string, options?: DataSetOptions) { if (this.location) { options = extend({location: this.location}, options); } - return new Dataset(this, id, options); } @@ -1152,16 +1219,17 @@ export class BigQuery extends common.Service { * const jobs = data[0]; * }); */ - getJobs(options, callback?) { + getJobs(options?: GetJobsOptions): Promise; + getJobs(options: GetJobsOptions, callback: GetJobsCallback): void; + getJobs(callback: GetJobsCallback): void; + getJobs( + optionsOrCallback?: GetJobsOptions|GetJobsCallback, + cb?: GetJobsCallback): void|Promise { const that = this; - - if (is.fn(options)) { - callback = options; - options = {}; - } - - options = options || {}; - + const options = + typeof optionsOrCallback === 'object' ? optionsOrCallback : {}; + const callback = + typeof optionsOrCallback === 'function' ? optionsOrCallback : cb; this.request( { uri: '/jobs', @@ -1170,11 +1238,11 @@ export class BigQuery extends common.Service { }, (err, resp) => { if (err) { - callback(err, null, null, resp); + callback!(err, null, null, resp); return; } - let nextQuery = null; + let nextQuery: {}|null = null; if (resp.nextPageToken) { nextQuery = extend({}, options, { @@ -1182,7 +1250,8 @@ export class BigQuery extends common.Service { }); } - const jobs = (resp.jobs || []).map((jobObject) => { + // tslint:disable-next-line no-any + const jobs = (resp.jobs || []).map((jobObject: any) => { const job = that.job(jobObject.jobReference.jobId, { location: jobObject.jobReference.location, }); @@ -1191,7 +1260,7 @@ export class BigQuery extends common.Service { return job; }); - callback(null, jobs, nextQuery, resp); + callback!(null, jobs, nextQuery, resp); }); } @@ -1241,7 +1310,7 @@ export class BigQuery extends common.Service { * * const myExistingJob = bigquery.job('job-id'); */ - job(id, options?) { + job(id: string, options?: JobOptions) { if (this.location) { options = extend({location: this.location}, options); } @@ -1385,9 +1454,9 @@ promisifyAll(BigQuery, { }); export class BigQueryDate { - value; - constructor(value) { - if (is.object(value)) { + value: string; + constructor(value: BigQueryDateOptions|string) { + if (typeof value === 'object') { value = BigQuery.datetime(value).value; } this.value = value; @@ -1395,22 +1464,20 @@ export class BigQueryDate { } export class BigQueryTimestamp { - value; - constructor(value) { + value: string; + constructor(value: Date|string) { this.value = new Date(value).toJSON(); } } export class BigQueryDatetime { - value; - constructor(value) { - if (is.object(value)) { + value: string; + constructor(value: BigQueryDatetimeOptions|string) { + if (typeof value === 'object') { let time; - if (value.hours) { time = BigQuery.time(value).value; } - value = format('{y}-{m}-{d}{time}', { y: value.year, m: value.month, @@ -1420,15 +1487,14 @@ export class BigQueryDatetime { } else { value = value.replace(/^(.*)T(.*)Z$/, '$1 $2'); } - - this.value = value; + this.value = value as string; } } export class BigQueryTime { value: string; - constructor(value) { - if (is.object(value)) { + constructor(value: BigQueryTimeOptions|string) { + if (typeof value === 'object') { value = format('{h}:{m}:{s}{f}', { h: value.hours, m: value.minutes || 0, @@ -1436,7 +1502,7 @@ export class BigQueryTime { f: is.defined(value.fractional) ? '.' + value.fractional : '', }); } - this.value = value; + this.value = value as string; } } diff --git a/src/job.ts b/src/job.ts index 919db84a..2e09e20f 100644 --- a/src/job.ts +++ b/src/job.ts @@ -20,14 +20,43 @@ 'use strict'; -import {Operation, util} from '@google-cloud/common'; +import {Operation, util, GetMetadataCallback} from '@google-cloud/common'; import {promisifyAll} from '@google-cloud/promisify'; import {paginator} from '@google-cloud/paginator'; import * as extend from 'extend'; -import * as is from 'is'; -import * as request from 'request'; +import * as r from 'request'; import {BigQuery} from '../src'; +export interface JobOptions { + location?: string; +} + +export interface CancelCallback { + (err: Error|null, apiResponse?: r.Response): void; +} + +export type CancelResponse = [r.Response]; + +export type QueryResultsResponse = [Array<{}>]; +export interface QueryResultsCallback { + (error: Error|null, rows: Array<{}>|null): void; +} + +export interface QueryResultsOptions { + autoPaginate?: boolean; + maxApiCalls?: number; + maxResults?: number; + pageToken?: string; + startIndex?: number; + timeoutMs?: number; +} + +export type ManualQueryResultsResponse = [Array<{}>, {}, r.Response]; +export interface ManualQueryResultsCallback { + (err: Error|null, rows: Array<{}>|null, nextQuery?: {}|null, + apiResponse?: r.Response): void; +} + /** * Job objects are returned from various places in the BigQuery API: * @@ -81,7 +110,9 @@ import {BigQuery} from '../src'; * job.removeAllListeners(); */ class Job extends Operation { - constructor(bigQuery, id, options) { + bigQuery: BigQuery; + + constructor(bigQuery: BigQuery, id: string, options?: JobOptions) { let location; if (options && options.location) { location = options.location; @@ -226,7 +257,7 @@ class Job extends Operation { baseUrl: '/jobs', id, methods, - requestModule: request, + requestModule: r, }); this.bigQuery = bigQuery; @@ -291,7 +322,9 @@ class Job extends Operation { * const apiResponse = data[0]; * }); */ - cancel(callback) { + cancel(): Promise; + cancel(callback: CancelCallback): void; + cancel(callback?: CancelCallback): void|Promise { let qs; if (this.location) { @@ -304,7 +337,7 @@ class Job extends Operation { uri: '/cancel', qs, }, - callback); + callback!); } /** @@ -389,11 +422,22 @@ class Job extends Operation { * const rows = data[0]; * }); */ - getQueryResults(options, callback) { - if (is.fn(options)) { - callback = options; - options = {}; - } + getQueryResults(options?: QueryResultsOptions): + Promise; + getQueryResults( + options: QueryResultsOptions, + callback: ManualQueryResultsCallback|QueryResultsCallback): void; + getQueryResults(callback: ManualQueryResultsCallback| + QueryResultsCallback): void; + getQueryResults( + optionsOrCallback?: QueryResultsOptions|ManualQueryResultsCallback| + QueryResultsCallback, + cb?: ManualQueryResultsCallback|QueryResultsCallback): + void|Promise { + let options = + typeof optionsOrCallback === 'object' ? optionsOrCallback : {}; + const callback = + typeof optionsOrCallback === 'function' ? optionsOrCallback : cb; options = extend( { @@ -408,17 +452,18 @@ class Job extends Operation { }, (err, resp) => { if (err) { - callback(err, null, null, resp); + callback!(err, null, null, resp); return; } - let rows = []; + // tslint:disable-next-line no-any + let rows: any = []; if (resp.schema && resp.rows) { rows = BigQuery.mergeSchemaWithRows_(resp.schema, resp.rows); } - let nextQuery = null; + let nextQuery: {}|null = null; if (resp.jobComplete === false) { // Query is still running. nextQuery = extend({}, options); @@ -429,7 +474,7 @@ class Job extends Operation { }); } - callback(null, rows, nextQuery, resp); + callback!(null, rows, nextQuery, resp); }); } @@ -439,7 +484,9 @@ class Job extends Operation { * * @private */ - getQueryResultsAsStream_(options, callback) { + getQueryResultsAsStream_( + options: QueryResultsOptions, + callback: ManualQueryResultsCallback): void { options = extend({autoPaginate: false}, options); this.getQueryResults(options, callback); } @@ -455,7 +502,7 @@ class Job extends Operation { * * @param {function} callback */ - poll_(callback) { + poll_(callback: GetMetadataCallback): void { this.getMetadata((err, metadata, apiResponse) => { // tslint:disable-next-line no-any if (!err && (apiResponse as any).status && @@ -471,7 +518,7 @@ class Job extends Operation { } if (metadata.status.state !== 'DONE') { - callback(); + callback(null); return; } diff --git a/src/table.ts b/src/table.ts index 738b5a70..5b15059c 100644 --- a/src/table.ts +++ b/src/table.ts @@ -17,30 +17,83 @@ 'use strict'; import * as arrify from 'arrify'; -import * as Big from 'big.js'; +import Big from 'big.js'; import * as common from '@google-cloud/common'; import {paginator} from '@google-cloud/paginator'; import {promisifyAll} from '@google-cloud/promisify'; -const duplexify = require('duplexify'); +import * as duplexify from 'duplexify'; import * as extend from 'extend'; const format = require('string-format-obj'); import * as fs from 'fs'; import * as is from 'is'; import * as path from 'path'; -import * as request from 'request'; -const streamEvents = require('stream-events'); +import * as r from 'request'; +import * as streamEvents from 'stream-events'; import * as uuid from 'uuid'; -import {BigQuery} from '../src'; +import {BigQuery, Job, Dataset} from '../src'; import {GoogleErrorBody} from '@google-cloud/common/build/src/util'; +import {Writable} from 'stream'; -// tslint:disable-next-line no-any -export type TempResponse = any; +export type CreateCopyJobResponse = [Job, r.Response]; +export interface CreateCopyJobCallback { + (err: Error|null, job?: Job, apiResponse?: r.Response): void; +} + +export interface CreateCopyJobMetadata extends CopyTableMetadata { + createDisposition?: 'CREATE_IF_NEEDED'|'CREATE_NEVER'; + destinationEncryptionConfiguration?: {kmsKeyName?: string;}; + destinationTable?: {datasetId: string; projectId: string; tableId: string;}; + sourceTable?: {datasetId: string; projectId: string; tableId: string;}; + sourceTables: Array<{datasetId: string; projectId: string; tableId: string;}>; + writeDisposition?: 'WRITE_TRUNCATE'|'WRITE_APPEND'|'WRITE_EMPTY'; +} export interface SetTableMetadataOptions { description?: string; schema?: string|{}; } +export interface CopyTableMetadata { + jobId?: string; + jobPrefix?: string; +} + +interface FormatMetadataOptions { + name?: string; + friendlyName: string; + schema?: string|TableField[]; + partitioning?: string; + view?: string; +} + +interface FormattedMetadata { + schema?: TableSchema; + friendlyName: string; + name?: string; + partitioning?: string; + timePartitioning?: {type: string}; + view: {query: string; useLegacySql: boolean;}; +} + +export interface TableSchema { + fields: TableField[]; +} + +export interface TableField { + name: string; + type: string; + mode?: string; + fields?: TableField[]; +} + +export type CopyTableResponse = [r.Response]; +export interface CopyTableCallback { + (err: Error|null, apiResponse?: r.Response): void; +} + +// tslint:disable-next-line no-any +export type TempResponse = any; + /** * The file formats accepted by BigQuery. * @@ -55,6 +108,10 @@ const FORMATS = { parquet: 'PARQUET', }; +export interface TableOptions { + location?: string; +} + /** * Table objects are returned by methods such as * {@link BigQuery/dataset#table}, {@link BigQuery/dataset#createTable}, and @@ -79,7 +136,7 @@ const FORMATS = { * const table = dataset.table('my-table'); */ class Table extends common.ServiceObject { - constructor(dataset, id, options?) { + constructor(dataset: Dataset, id: string, options?: TableOptions) { const methods = { /** * Create a table. @@ -253,7 +310,7 @@ class Table extends common.ServiceObject { id, createMethod: dataset.createTable.bind(dataset), methods, - requestModule: request, + requestModule: r, }); if (options && options.location) { @@ -266,7 +323,7 @@ class Table extends common.ServiceObject { // Catch all for read-modify-write cycle // https://cloud.google.com/bigquery/docs/api-performance#read-patch-write this.interceptors.push({ - request: reqOpts => { + request: (reqOpts: common.DecorateRequestOptions) => { if (reqOpts.method === 'PATCH' && reqOpts.json.etag) { reqOpts.headers = reqOpts.headers || {}; reqOpts.headers['If-Match'] = reqOpts.json.etag; @@ -317,17 +374,18 @@ class Table extends common.ServiceObject { * @param {string} str Comma-separated schema string. * @returns {object} Table schema in the format the API expects. */ - static createSchemaFromString_(str) { - return str.split(/\s*,\s*/).reduce((acc, pair) => { - acc.fields.push({ - name: pair.split(':')[0].trim(), - type: (pair.split(':')[1] || 'STRING').toUpperCase().trim(), - }); - - return acc; - }, { - fields: [], - }); + static createSchemaFromString_(str: string): TableSchema { + return str.split(/\s*,\s*/).reduce( + (acc: {fields: Array<{name: string, type: string}>}, pair) => { + acc.fields.push({ + name: pair.split(':')[0].trim(), + type: (pair.split(':')[1] || 'STRING').toUpperCase().trim(), + }); + return acc; + }, + { + fields: [], + }); } /** @@ -340,8 +398,8 @@ class Table extends common.ServiceObject { * @param {*} value The value to be converted. * @returns {*} The converted value. */ - static encodeValue_(value) { - if (is.undefined(value) || is.null(value)) { + static encodeValue_(value?: {}|null): {}|null { + if (typeof value === 'undefined' || value === null) { return null; } @@ -364,22 +422,25 @@ class Table extends common.ServiceObject { customTypeConstructorNames.indexOf(constructorName) > -1; if (isCustomType) { - return value.value; + return (value as {value: {}}).value; } if (is.date(value)) { - return value.toJSON(); + return (value as Date).toJSON(); } if (is.array(value)) { - return value.map(Table.encodeValue_); + return (value as []).map(Table.encodeValue_); } - if (is.object(value)) { - return Object.keys(value).reduce((acc, key) => { - acc[key] = Table.encodeValue_(value[key]); - return acc; - }, {}); + if (typeof value === 'object') { + return Object.keys(value).reduce( + (acc: {[index: string]: {}|null}, key) => { + acc[key] = Table.encodeValue_( + (value as {[index: string]: {} | null})[key]); + return acc; + }, + {}); } return value; } @@ -387,8 +448,8 @@ class Table extends common.ServiceObject { /** * @private */ - static formatMetadata_(options) { - const body = extend(true, {}, options); + static formatMetadata_(options: FormatMetadataOptions): FormattedMetadata { + const body = extend(true, {}, options) as {} as FormattedMetadata; if (options.name) { body.friendlyName = options.name; @@ -396,12 +457,12 @@ class Table extends common.ServiceObject { } if (is.string(options.schema)) { - body.schema = Table.createSchemaFromString_(options.schema); + body.schema = Table.createSchemaFromString_(options.schema as string); } if (is.array(options.schema)) { body.schema = { - fields: options.schema, + fields: options.schema as [], }; } @@ -414,15 +475,15 @@ class Table extends common.ServiceObject { }); } - if (is.string(body.partitioning)) { + if (is.string(options.partitioning)) { body.timePartitioning = { - type: body.partitioning.toUpperCase(), + type: body.partitioning!.toUpperCase(), }; } - if (is.string(body.view)) { + if (is.string(options.view)) { body.view = { - query: body.view, + query: options.view!, useLegacySql: false, }; } @@ -477,22 +538,31 @@ class Table extends common.ServiceObject { * const apiResponse = data[0]; * }); */ - copy(destination, metadata, callback) { - if (is.fn(metadata)) { - callback = metadata; - metadata = {}; - } - - this.createCopyJob(destination, metadata, (err, job, resp) => { - if (err) { - callback(err, resp); - return; - } + copy(destination: Table, metadata?: CopyTableMetadata): + Promise; + copy( + destination: Table, metadata: CopyTableMetadata, + callback: CopyTableCallback): void; + copy(destination: Table, callback: CopyTableCallback): void; + copy( + destination: Table, + metadataOrCallback?: CopyTableMetadata|CopyTableCallback, + cb?: CopyTableCallback): void|Promise { + const metadata = + typeof metadataOrCallback === 'object' ? metadataOrCallback : {}; + const callback = + typeof metadataOrCallback === 'function' ? metadataOrCallback : cb; + this.createCopyJob( + destination, metadata as CreateCopyJobMetadata, (err, job, resp) => { + if (err) { + callback!(err, resp); + return; + } - job.on('error', callback).on('complete', (metadata) => { - callback(null, metadata); - }); - }); + job!.on('error', callback!).on('complete', (metadata) => { + callback!(null, metadata); + }); + }); } /** @@ -546,7 +616,7 @@ class Table extends common.ServiceObject { * const apiResponse = data[0]; * }); */ - copyFrom(sourceTables, metadata, callback) { + copyFrom(sourceTables: Table|Table[], metadata, callback) { if (is.fn(metadata)) { callback = metadata; metadata = {}; @@ -616,18 +686,24 @@ class Table extends common.ServiceObject { * const apiResponse = data[1]; * }); */ - createCopyJob(destination, metadata?): Promise; - createCopyJob(destination, metadata, callback): void; - createCopyJob(destination, callback): void; - createCopyJob(destination, metadata?, callback?): void|Promise { + createCopyJob(destination: Table, metadata?: CreateCopyJobMetadata): + Promise; + createCopyJob( + destination: Table, metadata: CreateCopyJobMetadata, + callback: CreateCopyJobCallback): void; + createCopyJob(destination: Table, callback: CreateCopyJobCallback): void; + createCopyJob( + destination: Table, + metadataOrCallback?: CreateCopyJobMetadata|CreateCopyJobCallback, + cb?: CreateCopyJobCallback): void|Promise { if (!(destination instanceof Table)) { throw new Error('Destination must be a Table object.'); } - - if (is.fn(metadata)) { - callback = metadata; - metadata = {}; - } + const metadata = typeof metadataOrCallback === 'object' ? + metadataOrCallback : + {} as CreateCopyJobMetadata; + const callback = + typeof metadataOrCallback === 'function' ? metadataOrCallback : cb; // tslint:disable-next-line no-any const body: any = { @@ -723,9 +799,8 @@ class Table extends common.ServiceObject { * const apiResponse = data[1]; * }); */ - createCopyFromJob(sourceTables, metadata, callback) { - sourceTables = arrify(sourceTables); - + createCopyFromJob(source: Table|Table[], metadata, callback) { + const sourceTables = arrify(source); sourceTables.forEach((sourceTable) => { if (!(sourceTable instanceof Table)) { throw new Error('Source must be a Table object.'); @@ -1013,7 +1088,7 @@ class Table extends common.ServiceObject { * const apiResponse = data[1]; * }); */ - createLoadJob(source, metadata?, callback?) { + createLoadJob(source, metadata?, callback?): void|Writable { if (is.fn(metadata)) { callback = metadata; metadata = {}; @@ -1138,7 +1213,7 @@ class Table extends common.ServiceObject { * @param {string} [metadata.jobPrefix] Prefix to apply to the job id. * @returns {WritableStream} */ - createWriteStream_(metadata) { + createWriteStream_(metadata): Writable { metadata = metadata || {}; const fileTypes = Object.keys(FORMATS).map((key) => { return FORMATS[key]; @@ -1178,13 +1253,13 @@ class Table extends common.ServiceObject { throw new Error(`Source format not recognized: ${metadata.sourceFormat}`); } - const dup = streamEvents(duplexify()); + const dup = streamEvents(duplexify()) as duplexify.Duplexify; dup.once('writing', () => { common.util.makeWritableStream( dup, { makeAuthenticatedRequest: this.bigQuery.makeAuthenticatedRequest, - requestModule: request, + requestModule: r, metadata: { configuration: { load: metadata, @@ -1872,7 +1947,7 @@ class Table extends common.ServiceObject { setMetadata( metadata: SetTableMetadataOptions, callback?: common.ResponseCallback): void|Promise { - const body = Table.formatMetadata_(metadata); + const body = Table.formatMetadata_(metadata as FormatMetadataOptions); super.setMetadata(body, callback!); } } diff --git a/system-test/bigquery.ts b/system-test/bigquery.ts index 4515e5a2..3c9ee7e3 100644 --- a/system-test/bigquery.ts +++ b/system-test/bigquery.ts @@ -18,7 +18,7 @@ import * as assert from 'assert'; import * as async from 'async'; -import * as Big from 'big.js'; +import Big from 'big.js'; import * as fs from 'fs'; import * as uuid from 'uuid'; import * as exec from 'methmeth'; @@ -371,7 +371,7 @@ describe('BigQuery', () => { it('should get a list of jobs', done => { bigquery.getJobs((err, jobs) => { assert.ifError(err); - assert(jobs[0] instanceof Job); + assert(jobs![0] instanceof Job); done(); }); }); @@ -460,7 +460,7 @@ describe('BigQuery', () => { it('should get tables', done => { dataset.getTables((err, tables) => { assert.ifError(err); - assert(tables[0] instanceof Table); + assert(tables![0] instanceof Table); done(); }); }); @@ -559,7 +559,7 @@ describe('BigQuery', () => { return table.createLoadJob(TEST_DATA_FILE); }) .then(data => { - job = data[0]; + job = data![0]; return job.promise(); }); }); @@ -611,7 +611,7 @@ describe('BigQuery', () => { const badJob = bigquery.job(job.id, {location: 'US'}); badJob.cancel(err => { - assert.strictEqual(err.code, 404); + assert.strictEqual((err as ApiError).code, 404); done(); }); }); @@ -623,7 +623,7 @@ describe('BigQuery', () => { describe('job.getQueryResults', () => { it('should fail if the job location is incorrect', done => { - const badDataset = bigquery.dataset(dataset.id, {location: 'US'}); + const badDataset = bigquery.dataset(dataset.id!, {location: 'US'}); badDataset.createQueryJob( { @@ -662,10 +662,10 @@ describe('BigQuery', () => { const otherTable = dataset.table(generateName('table')); it('should fail if the job location is incorrect', done => { - const badTable = dataset.table(table.id, {location: 'US'}); + const badTable = dataset.table(table.id!, {location: 'US'}); badTable.createCopyJob(otherTable, err => { - assert.strictEqual(err.code, 404); + assert.strictEqual((err as ApiError).code, 404); done(); }); }); @@ -689,7 +689,7 @@ describe('BigQuery', () => { }); it('should fail if the job location is incorrect', done => { - const badTable = dataset.table(table.id, {location: 'US'}); + const badTable = dataset.table(table.id!, {location: 'US'}); badTable.createExtractJob(extractFile, err => { assert.strictEqual(err.code, 404); diff --git a/test/dataset.ts b/test/dataset.ts index 31eb0764..7ed13d9f 100644 --- a/test/dataset.ts +++ b/test/dataset.ts @@ -610,7 +610,7 @@ describe('BigQuery/Dataset', () => { done(); }; - ds.getTables(null, assert.ifError); + ds.getTables(assert.ifError); }); it('should return error to callback', done => { diff --git a/test/index.ts b/test/index.ts index cf391a75..acccff6f 100644 --- a/test/index.ts +++ b/test/index.ts @@ -18,7 +18,7 @@ import * as arrify from 'arrify'; import * as assert from 'assert'; -import * as Big from 'big.js'; +import Big from 'big.js'; import * as extend from 'extend'; import * as proxyquire from 'proxyquire'; import * as uuid from 'uuid'; @@ -26,6 +26,7 @@ import * as pfy from '@google-cloud/promisify'; import {Service, util} from '@google-cloud/common'; import * as sinon from 'sinon'; import {BigQueryDate, Table} from '../src'; +import {TableField} from '../src/table'; const fakeUuid = extend(true, {}, uuid); @@ -213,7 +214,7 @@ describe('BigQuery', () => { {name: 'teeth_count', type: 'FLOAT64'}, {name: 'numeric_col', type: 'NUMERIC'}, ], - }; + } as {fields: TableField[]}; beforeEach(() => { sandbox.stub(BigQuery, 'date').callsFake(input => { @@ -1438,8 +1439,7 @@ describe('BigQuery', () => { assert.deepStrictEqual(reqOpts.qs, {}); done(); }; - - bq.getJobs(null, assert.ifError); + bq.getJobs(assert.ifError); }); it('should return error to callback', done => { diff --git a/test/table.ts b/test/table.ts index 5418f468..c136cf7b 100644 --- a/test/table.ts +++ b/test/table.ts @@ -18,7 +18,7 @@ import * as arrify from 'arrify'; import * as assert from 'assert'; -import * as Big from 'big.js'; +import Big from 'big.js'; import {EventEmitter} from 'events'; import * as extend from 'extend'; import * as proxyquire from 'proxyquire'; @@ -68,7 +68,8 @@ const fakePaginator = { } }; -let fakeUuid = extend(true, {}, uuid); +// tslint:disable-next-line no-any +let fakeUuid: any = extend(true, {}, uuid); class FakeServiceObject extends ServiceObject { calledWith_;