diff --git a/src/commands/index.js b/src/commands/index.js index d6758b3e..13bb138c 100644 --- a/src/commands/index.js +++ b/src/commands/index.js @@ -1,6 +1,7 @@ "use strict"; -var previews = require("../previews"); +var previews = require("../previews"); //eslint-disable-line + module.exports = function(client) { var loadCommand = function(name) { var cmd = require("./" + name); @@ -53,13 +54,6 @@ module.exports = function(client) { client.help = loadCommand("help"); - if (previews.kits) { - client.kits = { - install: loadCommand("kits-install"), - uninstall: loadCommand("kits-uninstall"), - }; - } - client.init = loadCommand("init"); client.list = loadCommand("list"); diff --git a/src/commands/kits-install.js b/src/commands/kits-install.js deleted file mode 100644 index a687e286..00000000 --- a/src/commands/kits-install.js +++ /dev/null @@ -1,74 +0,0 @@ -"use strict"; - -var clc = require("cli-color"); - -var Command = require("../command"); -var getProjectId = require("../getProjectId"); -var logger = require("../logger"); -var requirePermissions = require("../requirePermissions"); -var utils = require("../utils"); -var kits = require("../kits"); - -// TODO: add option for urlPath to be inserted or parse urlPath for name if needed -module.exports = new Command("kits:install ") - .option("-b, --branch ", "repository branch to download from. Defaults to master") - .option("-p, --path ", "custom path to kit configuration file. Defaults to kits.json") - .option("--id ", "release version to be installed. Defaults to latest") - .before(requirePermissions, [ - /* TODO */ - ]) - .action(function(githubRepo, options) { - var projectId = getProjectId(options); - var kit = githubRepo.split("/"); - - var kitOwner; - var kitRepo; - - if (kit.length > 1) { - kitOwner = kit[0]; - kitRepo = kit[1]; - } else { - kitOwner = "function-kits"; - kitRepo = kit[0]; - } - - var githubConfig = { - id: options.releaseId || "latest", - owner: kitOwner, - manifestPath: options.path || "kits.json", - ref: options.branch || "master", - repo: kitRepo, - }; - - var gitRepo = kitOwner + "/" + kitRepo; - var kitFunctions; - var runtimeConfig; - - return kits.prepareKitsUpload - .retrieveFile(githubConfig) - .then(function(result) { - var kitConfig = JSON.parse(result); - kitFunctions = kitConfig.functions; - - utils.logSuccess( - clc.green.bold("kits: ") + "Fetched configuration file from " + clc.bold(gitRepo) - ); - utils.logBullet(clc.bold("We will now ask a series of questions to help set up your kit.")); - - return kits.prepareKitsConfig.prompt(githubConfig.repo, kitConfig.config); - }) - .then(function(result) { - runtimeConfig = result; - - return kits.prepareKitsUpload.upload(projectId, githubConfig, runtimeConfig); - }) - .then(function(sourceUploadUrl) { - utils.logSuccess(clc.green.bold("kits: ") + "Completed configuration setup."); - logger.debug(clc.bold("kits: ") + "Source uploaded to GCS bucket"); - utils.logBullet( - "Deploying kit " + gitRepo + " as " + clc.bold(runtimeConfig.kitname + "...") - ); - - return kits.deploy(kitFunctions, options, runtimeConfig, sourceUploadUrl); - }); - }); diff --git a/src/commands/kits-uninstall.js b/src/commands/kits-uninstall.js deleted file mode 100644 index 7b64d740..00000000 --- a/src/commands/kits-uninstall.js +++ /dev/null @@ -1,135 +0,0 @@ -"use strict"; -var clc = require("cli-color"); -var _ = require("lodash"); - -var Command = require("../command"); -var gcp = require("../gcp"); -var pollKits = require("../kits/pollKits"); -var getProjectId = require("../getProjectId"); -var prompt = require("../prompt"); -var requirePermissions = require("../requirePermissions"); -var utils = require("../utils"); - -var DEFAULT_REGION = gcp.cloudfunctions.DEFAULT_REGION; - -function _getFunctions(dict, kitName) { - return _.reduce( - dict[kitName], - function(funcs, func) { - return _.concat(funcs, func.functions); - }, - [] - ); -} - -function _listKits(projectId) { - return gcp.cloudfunctions.list(projectId, DEFAULT_REGION).then(function(functions) { - return _.chain(functions) - .filter(function(func) { - return _.has(func, "labels.goog-kit-name"); - }) - .map(function(funcInfo) { - return { - kit: funcInfo.labels["goog-kit-name"], - source: funcInfo.labels["goog-kit-source"], - functions: funcInfo.functionName, - }; - }) - .groupBy("kit") - .value(); - }); -} - -function _promptForKitsUninstall(choices, dict) { - return prompt({}, [ - { - type: "checkbox", - name: "kitNames", - message: - "Which kits would you like to delete? " + - "The source of each kit is listed after the kit name.", - choices: prompt.convertLabeledListChoices(choices), - }, - ]).then(function(list) { - if (_.isEmpty(list.kitNames)) { - return utils.reject("Please select at least one kit to delete", { - exit: 1, - }); - } - return _.chain(list.kitNames) - .map(function(key) { - return prompt.listLabelToValue(key, choices); - }) - .map(function(kit) { - return _getFunctions(dict, kit); - }) - .value(); - }); -} - -function _deleteKitFunctions(projectId, functions) { - return Promise.all( - _.map(functions, function(funcName) { - return gcp.cloudfunctions.delete({ - projectId: projectId, - region: DEFAULT_REGION, - functionName: funcName, - }); - }) - ); -} - -module.exports = new Command("kits:uninstall [kitName]") - .description("Command to uninstall function kit") - .before(requirePermissions, [ - /* TODO */ - ]) - .action(function(kitName, options) { - var projectId = getProjectId(options); - return _listKits(projectId) - .then(function(dict) { - if (_.isEmpty(dict)) { - return utils.reject("There are no kits asssociated with your project.", { exit: 1 }); - } - - if (kitName) { - if (!dict[kitName]) { - return utils.reject("Could not find kit named " + clc.bold(kitName), { exit: 1 }); - } - return _getFunctions(dict, kitName); - } - var choices = _.map(dict, function(kit, key) { - return { - name: key, - label: key + ": " + kit[0].source, - checked: false, - }; - }); - return _promptForKitsUninstall(choices, dict); - }) - .then(function(funcsToDelete) { - utils.logBullet(clc.cyan.bold("kits: ") + "Deleting kits now..."); - return _deleteKitFunctions(projectId, _.flatten(funcsToDelete)); - }) - .then(function(operations) { - utils.logBullet( - clc.cyan.bold("kits: ") + "Checking to make sure kits have been deleted safely..." - ); - - var printSuccess = function(kits) { - return utils.logSuccess( - clc.green.bold("kits: ") + - "Successfully deleted the following kit(s): " + - clc.bold(_.uniq(kits)) - ); - }; - - var printFail = function(reason) { - return utils.logWarning( - clc.yellow.bold("kits: ") + "Failed to delete the following kit: " + reason - ); - }; - - return pollKits(operations, printSuccess, printFail); - }); - }); diff --git a/src/kits/deploy.js b/src/kits/deploy.js deleted file mode 100644 index be9ef1cf..00000000 --- a/src/kits/deploy.js +++ /dev/null @@ -1,74 +0,0 @@ -"use strict"; - -var _ = require("lodash"); -var clc = require("cli-color"); - -var FirebaseError = require("../error"); -var functionsConfig = require("../functionsConfig"); -var gcp = require("../gcp"); -var getProjectId = require("../getProjectId"); -var pollKits = require("./pollKits"); -var utils = require("../utils"); - -var DEFAULT_REGION = gcp.cloudfunctions.DEFAULT_REGION; - -function _getFunctionTrigger(cloudfunction, firebaseconfig, config) { - if (cloudfunction.httpsTrigger) { - return _.pick(cloudfunction, "httpsTrigger"); - } else if (cloudfunction.eventTrigger) { - var trigger = cloudfunction.eventTrigger; - var resource = "projects/_/buckets/" + firebaseconfig.storageBucket; - if (config.OBJECT_PREFIX) { - resource = resource + "/" + config.OBJECT_PREFIX; - } - trigger.resource = resource; - return { eventTrigger: trigger }; - } - return new FirebaseError("Could not parse function trigger, unknown trigger type."); -} - -function _deployKitFunctions(functions, options, config, sourceUploadUrl) { - var projectId = getProjectId(options); - - if (functions.constructor !== Array) { - functions = [functions]; - } - - return functionsConfig.getFirebaseConfig(options).then(function(firebaseconfig) { - // TODO: Do we deal with nested functions? How would we deal with nested functions - return Promise.all( - _.map(functions, function(cloudfunction) { - var functionTrigger = _getFunctionTrigger(cloudfunction, firebaseconfig, config); - return gcp.cloudfunctions.create({ - entryPoint: cloudfunction.entryPoint, - functionName: config.kitname + "-" + cloudfunction.name, - labels: { - "goog-kit-source": config.kitsource, - "goog-kit-name": config.kitname, - }, - projectId: projectId, - region: DEFAULT_REGION, - sourceUploadUrl: sourceUploadUrl, - trigger: functionTrigger, - }); - }) - ); - }); -} - -module.exports = function(functions, options, config, sourceUploadUrl) { - return _deployKitFunctions(functions, options, config, sourceUploadUrl).then(function( - operations - ) { - var printSuccess = function() { - return utils.logSuccess(clc.green.bold("kits: ") + "Your kit has successfully been deployed"); - }; - - var printFail = function(reason) { - utils.logWarning("Your kit could not be deployed."); - utils.logWarning(reason); - return new FirebaseError("Your kit could not be deployed."); - }; - return pollKits(operations, printSuccess, printFail); - }); -}; diff --git a/src/kits/index.js b/src/kits/index.js deleted file mode 100644 index bf5f8d05..00000000 --- a/src/kits/index.js +++ /dev/null @@ -1,7 +0,0 @@ -"use strict"; - -module.exports = { - prepareKitsConfig: require("./prepareKitsConfig"), - prepareKitsUpload: require("./prepareKitsUpload"), - deploy: require("./deploy"), -}; diff --git a/src/kits/pollKits.js b/src/kits/pollKits.js deleted file mode 100644 index 6ade6797..00000000 --- a/src/kits/pollKits.js +++ /dev/null @@ -1,62 +0,0 @@ -/** - * Wrapper around pollOperations.js specifically for polling functions deployed through kits - */ - -"use strict"; - -var _ = require("lodash"); - -var gcp = require("../gcp"); -var pollOperations = require("../pollOperations"); - -function _pollKitFunctions(operations) { - var pollFunction = gcp.cloudfunctions.check; - var interval = 2 * 1000; - - // Code from functions/release.js - var retryCondition = function(result) { - // The error codes from a Google.LongRunning operation follow google.rpc.Code format. - - var retryableCodes = [ - 1, // cancelled by client - 4, // deadline exceeded - 10, // aborted (typically due to concurrency issue) - 14, // unavailable - ]; - - if (_.includes(retryableCodes, result.error.code)) { - return true; - } - return false; - }; - - var success = function(op) { - return Promise.resolve(_.last(op.func.split("/")).match(/[^-]*/)[0]); - }; - var fail = function(op) { - return Promise.reject({ - kit: _.last(op.func.split("/")).match(/[^-]*/)[0], - reason: op.error.message, - }); - }; - - return pollOperations.pollAndRetry( - operations, - pollFunction, - interval, - success, - fail, - retryCondition - ); -} - -module.exports = function(operations, printSuccess, printFail) { - return _pollKitFunctions(operations) - .then(function(successes) { - return printSuccess(successes); - }) - .catch(function(reason) { - // since poll operations uses Promise.all, will catch immediately on first failure - return printFail(reason); - }); -}; diff --git a/src/kits/prepareKitsConfig.js b/src/kits/prepareKitsConfig.js deleted file mode 100644 index 459c2135..00000000 --- a/src/kits/prepareKitsConfig.js +++ /dev/null @@ -1,94 +0,0 @@ -"use strict"; -var _ = require("lodash"); -var clc = require("cli-color"); - -var FirebaseError = require("../error"); -var prompt = require("../prompt"); -var utils = require("../utils"); - -function _promptForKitConfig(kitName, kitConfig) { - var prompts = []; - // TODO: name does not allow for dashes - prompts.push({ - name: "kitname", - type: "input", - default: kitName, - message: "What would you like to name this kit?", - }); - - for (var i = 0; i < kitConfig.length; i++) { - prompts.push({ - name: kitConfig[i].name, - type: "input", - default: kitConfig[i].default, - message: kitConfig[i].label, - }); - } - return prompt({ kitsource: kitName }, prompts) - .then(function(answers) { - // prompting again if needed - var promptAgains = _.chain(kitConfig) - .filter(function(question) { - var value = answers[question.name]; - return !("default" in question) && !value; - }) - .map(function(needsAnswer) { - utils.logWarning( - clc.yellow.bold("kits: ") + clc.bold(needsAnswer.name) + " requires a value." - ); - return { - name: needsAnswer.name, - type: "input", - message: needsAnswer.label, - }; - }) - .value(); - return prompt(answers, promptAgains); - }) - .then(function(configList) { - // checking that all values that need to be set are set and of the correct type - return utils.promiseAllSettled( - _.map(kitConfig, function(question) { - return new Promise(function(resolve, reject) { - var configValue = configList[question.name]; - // if value still isn't set - if (!("default" in question) && !configValue) { - reject(new FirebaseError("The following value needs to be set: " + question.name)); - } - switch (question.type) { - case "number": - configList[question.name] = _.toNumber(configValue); - break; - case "boolean": - // why is there no toBoolean in lodash - configList[question.name] = configValue === "true"; - break; - default: - // keep as a string - configList[question.name] = _.toString(configValue); - } - resolve(configList); - }); - }) - ); - }) - .then(function(allPrompts) { - return new Promise(function(resolve, reject) { - var failed = _.chain(allPrompts) - .filter({ state: "rejected" }) - .map("reason") - .value(); - var config = _.find(allPrompts, function(succeeded) { - return succeeded.state === "fulfilled"; - }); - if (failed.length > 0) { - return reject(new FirebaseError("The following values need to be set.\n" + failed)); - } - return resolve(config.value); - }); - }); -} - -module.exports = { - prompt: _promptForKitConfig, -}; diff --git a/src/kits/prepareKitsUpload.js b/src/kits/prepareKitsUpload.js deleted file mode 100644 index 53a59c05..00000000 --- a/src/kits/prepareKitsUpload.js +++ /dev/null @@ -1,185 +0,0 @@ -"use strict"; - -var _ = require("lodash"); -var archiver = require("archiver"); -var clc = require("cli-color"); -var filesize = require("filesize"); -var fs = require("fs"); -var tar = require("tar"); -var path = require("path"); -var request = require("request"); -var tmp = require("tmp"); - -var fsAsync = require("../fsAsync"); -var api = require("../api"); -var FirebaseError = require("../error"); -var gcp = require("../gcp"); -var utils = require("../utils"); - -var DEFAULT_REGION = gcp.cloudfunctions.DEFAULT_REGION; -var CONFIG_DEST_FILE = ".runtimeconfig.json"; - -var _pipeAsync = function(from, to) { - return new Promise(function(resolve, reject) { - to.on("finish", resolve); - to.on("error", reject); - from.pipe(to); - }); -}; - -function _retrieveFile(githubConfig) { - var endpoint = - "/repos/" + - githubConfig.owner + - "/" + - githubConfig.repo + - "/contents/" + - githubConfig.manifestPath; - return api - .request("GET", endpoint, { - auth: false, - origin: "https://api.github.com", - headers: { - Accept: "application/vnd.github.v3+json", - "User-Agent": githubConfig.repo + "-kitsIntaller", - }, - }) - .then(function(result) { - if (result.status !== 200) { - return Promise.reject( - new FirebaseError( - githubConfig.path + " could not be retrieved for kit at " + githubConfig.repo - ) - ); - } - var buf = Buffer.from(result.body.content, "base64"); - return Promise.resolve(buf); - }) - .catch(function(error) { - return Promise.reject(error); - }); -} - -function _downloadSource(githubConfig) { - var owner = githubConfig.owner; - var repo = githubConfig.repo; - var ref = githubConfig.ref; - var tmpDir = tmp.dirSync({ prefix: "kits-source-" }).name; - - var endpoint = "/repos/" + owner + "/" + repo + "/tarball/" + ref; - var download = request({ - url: "https://api.github.com" + endpoint, - headers: { - Accept: "application/vnd.github.v3.sha", - "User-Agent": repo + "-kitsIntaller", - }, - }); - var untar = tar.x({ - cwd: tmpDir, - // GitHub embeds everything in a folder named as the git - // -- - strip: 1, - }); - return _pipeAsync(download, untar).then( - function() { - utils.logSuccess(clc.green.bold("kits: ") + "Fetched kits source code."); - return tmpDir; - }, - function(err) { - throw new FirebaseError("There was an error with fetching the kit", { - original: err, - exit: 2, - }); - } - ); -} - -/** - * Scaffolding code. Adapted from prepareFunctionsUpload.js - **/ -var _packageSource = function(sourceDir, githubContext, configValues) { - var tmpFile = tmp.fileSync({ prefix: "kits-upload-", postfix: ".zip" }).name; - var fileStream = fs.createWriteStream(tmpFile, { - flags: "w", - defaultEncoding: "binary", - }); - var archive = archiver("zip"); - var archiveDone = _pipeAsync(archive, fileStream); - - return fsAsync - .readdirRecursive({ - path: sourceDir, - ignore: [ - githubContext.manfiestPath /* kit.json */, - CONFIG_DEST_FILE /* .runtimeconfig.json */, - "node_modules", - ], - }) - .then(function(files) { - _.forEach(files, function(file) { - archive.file(file.name, { - name: path.relative(sourceDir, file.name), - mode: file.mode, - }); - }); - archive.append(JSON.stringify(configValues, null, 2), { - name: CONFIG_DEST_FILE, - mode: 420 /* 0o644 */, - }); - archive.finalize(); - return archiveDone; - }) - .then( - function() { - utils.logBullet( - clc.cyan.bold("kits:") + - " packaged kit source (" + - filesize(archive.pointer()) + - ") for uploading" - ); - return { - file: tmpFile, - stream: fs.createReadStream(tmpFile), - size: archive.pointer(), - }; - }, - function(err) { - throw new FirebaseError( - "Could not read source directory. Remove links and shortcuts and try again.", - { - original: err, - exit: 1, - } - ); - } - ); -}; - -function _uploadSourceCode(projectId, source) { - var fullUrl; - return gcp.cloudfunctions - .generateUploadUrl(projectId, DEFAULT_REGION) - .then(function(uploadUrl) { - fullUrl = uploadUrl; - uploadUrl = _.replace(uploadUrl, "https://storage.googleapis.com", ""); - return gcp.storage.upload(source, uploadUrl); - }) - .then(function() { - return fullUrl; - }); -} - -function _upload(projectId, githubContext, options) { - return _downloadSource(githubContext) - .then(function(sourceDir) { - return _packageSource(sourceDir, githubContext, options); - }) - .then(function(source) { - return _uploadSourceCode(projectId, source); - }); -} - -module.exports = { - retrieveFile: _retrieveFile, - upload: _upload, -}; diff --git a/src/previews.js b/src/previews.js index 6d3795ea..ac388df8 100644 --- a/src/previews.js +++ b/src/previews.js @@ -6,7 +6,6 @@ var configstore = require("./configstore"); var previews = _.assign( { // insert previews here... - kits: false, }, configstore.get("previews") ); diff --git a/src/test/database/remove.spec.ts b/src/test/database/remove.spec.ts index 971043e3..c66fd9f1 100644 --- a/src/test/database/remove.spec.ts +++ b/src/test/database/remove.spec.ts @@ -5,13 +5,13 @@ import DatabaseRemove from "../../database/remove"; import { NodeSize, RemoveRemote } from "../../database/removeRemote"; class TestRemoveRemote implements RemoveRemote { - public data: any; + data: any; constructor(data: any) { this.data = data; } - public deletePath(path: string): Promise { + deletePath(path: string): Promise { if (path === "/") { this.data = null; return Promise.resolve(true); @@ -25,7 +25,7 @@ class TestRemoveRemote implements RemoveRemote { return Promise.resolve(true); } - public prefetchTest(path: string): Promise { + prefetchTest(path: string): Promise { const d = this._dataAtpath(path); if (!d) { return Promise.resolve(NodeSize.EMPTY); @@ -39,7 +39,7 @@ class TestRemoveRemote implements RemoveRemote { } } - public listPath(path: string): Promise { + listPath(path: string): Promise { const d = this._dataAtpath(path); if (d) { return Promise.resolve(Object.keys(d)); diff --git a/src/test/throttler/queue.spec.ts b/src/test/throttler/queue.spec.ts index 1597504d..6b0659c2 100644 --- a/src/test/throttler/queue.spec.ts +++ b/src/test/throttler/queue.spec.ts @@ -11,7 +11,7 @@ describe("Queue", () => { it("should be first-in-first-out", async () => { const order: string[] = []; - const queue = new Queue({ + const queue = new Queue({ handler: createHandler(order), concurrency: 1, }); diff --git a/src/test/throttler/stack.spec.ts b/src/test/throttler/stack.spec.ts index f8f7cd1d..311ca79e 100644 --- a/src/test/throttler/stack.spec.ts +++ b/src/test/throttler/stack.spec.ts @@ -11,7 +11,7 @@ describe("Stack", () => { it("should be first-in-last-out", async () => { const order: string[] = []; - const queue = new Stack({ + const queue = new Stack({ handler: createHandler(order), concurrency: 1, }); @@ -30,7 +30,7 @@ describe("Stack", () => { it("should not repeat completed tasks", async () => { const order: string[] = []; - const queue = new Stack({ + const queue = new Stack({ handler: createHandler(order), concurrency: 1, }); diff --git a/src/test/throttler/throttler.spec.ts b/src/test/throttler/throttler.spec.ts index 67bc9d5a..b0076f8f 100644 --- a/src/test/throttler/throttler.spec.ts +++ b/src/test/throttler/throttler.spec.ts @@ -8,7 +8,7 @@ import { Throttler, ThrottlerOptions } from "../../throttler/throttler"; const TEST_ERROR = new Error("foobar"); interface ThrottlerConstructor { - new (options: ThrottlerOptions): Throttler; + new (options: ThrottlerOptions): Throttler; } const throttlerTest = (throttlerConstructor: ThrottlerConstructor) => { diff --git a/src/throttler/queue.ts b/src/throttler/queue.ts index bc2f0b62..b5105d31 100644 --- a/src/throttler/queue.ts +++ b/src/throttler/queue.ts @@ -1,18 +1,18 @@ import { Throttler, ThrottlerOptions } from "./throttler"; -export class Queue extends Throttler { - public cursor: number = 0; +export class Queue extends Throttler { + cursor: number = 0; - constructor(options: ThrottlerOptions) { + constructor(options: ThrottlerOptions) { super(options); this.name = this.name || "queue"; } - public hasWaitingTask(): boolean { + hasWaitingTask(): boolean { return this.cursor !== this.total; } - public nextWaitingTaskIndex(): number { + nextWaitingTaskIndex(): number { if (this.cursor >= this.total) { throw new Error("There is no more task in queue"); } diff --git a/src/throttler/stack.ts b/src/throttler/stack.ts index afd3fb26..e688014f 100644 --- a/src/throttler/stack.ts +++ b/src/throttler/stack.ts @@ -1,19 +1,19 @@ import { Throttler, ThrottlerOptions } from "./throttler"; -export class Stack extends Throttler { - public lastTotal: number = 0; - public stack: number[] = []; +export class Stack extends Throttler { + lastTotal: number = 0; + stack: number[] = []; - constructor(options: ThrottlerOptions) { + constructor(options: ThrottlerOptions) { super(options); this.name = this.name || "stack"; } - public hasWaitingTask(): boolean { + hasWaitingTask(): boolean { return this.lastTotal !== this.total || this.stack.length > 0; } - public nextWaitingTaskIndex(): number { + nextWaitingTaskIndex(): number { while (this.lastTotal < this.total) { this.stack.push(this.lastTotal); this.lastTotal++; diff --git a/src/throttler/throttler.ts b/src/throttler/throttler.ts index 1235c365..5b966dc4 100644 --- a/src/throttler/throttler.ts +++ b/src/throttler/throttler.ts @@ -6,14 +6,14 @@ function backoff(retryNumber: number, delay: number): Promise { }); } -function DEFAULT_HANDLER(task: any): Promise { - return (task as () => Promise)(); +function DEFAULT_HANDLER(task: any): Promise { + return (task as () => Promise)(); } -export interface ThrottlerOptions { +export interface ThrottlerOptions { name?: string; concurrency?: number; - handler?: (task: T) => Promise; + handler?: (task: T) => Promise; retries?: number; backoff?: number; } @@ -31,34 +31,34 @@ export interface ThrottlerStats { elapsed: number; } -interface TaskData { +interface TaskData { task: T; retryCount: number; - wait?: { resolve: (result: any) => void; reject: (err: Error) => void }; + wait?: { resolve: (R: any) => void; reject: (err: Error) => void }; } -export abstract class Throttler { - public name: string = ""; - public concurrency: number = 200; - public handler: (task: T) => Promise = DEFAULT_HANDLER; - public active: number = 0; - public complete: number = 0; - public success: number = 0; - public errored: number = 0; - public retried: number = 0; - public total: number = 0; - public taskDataMap = new Map>(); - public waits: Array<{ resolve: () => void; reject: (err: Error) => void }> = []; - public min: number = 9999999999; - public max: number = 0; - public avg: number = 0; - public retries: number = 0; - public backoff: number = 200; - public closed: boolean = false; - public finished: boolean = false; - public startTime: number = 0; +export abstract class Throttler { + name: string = ""; + concurrency: number = 200; + handler: (task: T) => Promise = DEFAULT_HANDLER; + active: number = 0; + complete: number = 0; + success: number = 0; + errored: number = 0; + retried: number = 0; + total: number = 0; + taskDataMap = new Map>(); + waits: Array<{ resolve: () => void; reject: (err: Error) => void }> = []; + min: number = 9999999999; + max: number = 0; + avg: number = 0; + retries: number = 0; + backoff: number = 200; + closed: boolean = false; + finished: boolean = false; + startTime: number = 0; - constructor(options: ThrottlerOptions) { + constructor(options: ThrottlerOptions) { if (options.name) { this.name = options.name; } @@ -82,14 +82,14 @@ export abstract class Throttler { /** * @return `true` if there are unscheduled task waiting to be scheduled. */ - public abstract hasWaitingTask(): boolean; + abstract hasWaitingTask(): boolean; /** * @return the index of the next task to schedule. */ - public abstract nextWaitingTaskIndex(): number; + abstract nextWaitingTaskIndex(): number; - public wait(): Promise { + wait(): Promise { const p = new Promise((resolve, reject) => { this.waits.push({ resolve, reject }); }); @@ -101,7 +101,7 @@ export abstract class Throttler { * When the task is completed, resolve will be called with handler's result. * If this task fails after retries, reject will be called with the error. */ - public add(task: T): void { + add(task: T): void { this.addHelper(task); } @@ -109,18 +109,18 @@ export abstract class Throttler { * Add the task to the throttler and return a promise of handler's result. * If the task failed, both the promised returned by throttle and wait will reject. */ - public run(task: T): Promise { - return new Promise((resolve, reject) => { + run(task: T): Promise { + return new Promise((resolve, reject) => { this.addHelper(task, { resolve, reject }); }); } - public close(): boolean { + close(): boolean { this.closed = true; return this.finishIfIdle(); } - public process(): void { + process(): void { if (this.finishIfIdle() || this.active >= this.concurrency || !this.hasWaitingTask()) { return; } @@ -129,7 +129,7 @@ export abstract class Throttler { this.handle(this.nextWaitingTaskIndex()); } - public async handle(cursorIndex: number): Promise { + async handle(cursorIndex: number): Promise { const taskData = this.taskDataMap.get(cursorIndex); if (!taskData) { throw new Error(`taskData.get(${cursorIndex}) does not exist`); @@ -183,7 +183,7 @@ export abstract class Throttler { } } - public stats(): ThrottlerStats { + stats(): ThrottlerStats { return { max: this.max, min: this.min, @@ -198,7 +198,7 @@ export abstract class Throttler { }; } - public taskName(cursorIndex: number): string { + taskName(cursorIndex: number): string { const taskData = this.taskDataMap.get(cursorIndex); if (!taskData) { return "finished task"; @@ -208,7 +208,7 @@ export abstract class Throttler { private addHelper( task: T, - wait?: { resolve: (result: any) => void; reject: (err: Error) => void } + wait?: { resolve: (result: R) => void; reject: (err: Error) => void } ): void { if (this.closed) { throw new Error("Cannot add a task to a closed throttler."); diff --git a/tslint.json b/tslint.json index 3c1d8d57..8a338d3b 100644 --- a/tslint.json +++ b/tslint.json @@ -5,6 +5,7 @@ "rules": { "arrow-parens": true, "interface-name": false, + "member-access": [true, "no-public"], "object-literal-key-quotes": [true, "as-needed"], "object-literal-sort-keys": false, "ordered-imports": [true, { "import-sources-order": "any" }],