This commit is contained in:
Fred Zhang
2018-12-06 10:54:34 -08:00
parent 9a4b42f111
commit ce1b892c03
17 changed files with 61 additions and 698 deletions

View File

@@ -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");

View File

@@ -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 <githubRepo>")
.option("-b, --branch <branch>", "repository branch to download from. Defaults to master")
.option("-p, --path <path>", "custom path to kit configuration file. Defaults to kits.json")
.option("--id <releaseId>", "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);
});
});

View File

@@ -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);
});
});

View File

@@ -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);
});
};

View File

@@ -1,7 +0,0 @@
"use strict";
module.exports = {
prepareKitsConfig: require("./prepareKitsConfig"),
prepareKitsUpload: require("./prepareKitsUpload"),
deploy: require("./deploy"),
};

View File

@@ -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);
});
};

View File

@@ -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,
};

View File

@@ -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
// <org>-<repo>-<hash>
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,
};

View File

@@ -6,7 +6,6 @@ var configstore = require("./configstore");
var previews = _.assign(
{
// insert previews here...
kits: false,
},
configstore.get("previews")
);

View File

@@ -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<boolean> {
deletePath(path: string): Promise<boolean> {
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<NodeSize> {
prefetchTest(path: string): Promise<NodeSize> {
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<string[]> {
listPath(path: string): Promise<string[]> {
const d = this._dataAtpath(path);
if (d) {
return Promise.resolve(Object.keys(d));

View File

@@ -11,7 +11,7 @@ describe("Queue", () => {
it("should be first-in-first-out", async () => {
const order: string[] = [];
const queue = new Queue<Task>({
const queue = new Queue<Task, void>({
handler: createHandler(order),
concurrency: 1,
});

View File

@@ -11,7 +11,7 @@ describe("Stack", () => {
it("should be first-in-last-out", async () => {
const order: string[] = [];
const queue = new Stack<Task>({
const queue = new Stack<Task, void>({
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<Task>({
const queue = new Stack<Task, void>({
handler: createHandler(order),
concurrency: 1,
});

View File

@@ -8,7 +8,7 @@ import { Throttler, ThrottlerOptions } from "../../throttler/throttler";
const TEST_ERROR = new Error("foobar");
interface ThrottlerConstructor {
new <T>(options: ThrottlerOptions<T>): Throttler<T>;
new <T, R>(options: ThrottlerOptions<T, R>): Throttler<T, R>;
}
const throttlerTest = (throttlerConstructor: ThrottlerConstructor) => {

View File

@@ -1,18 +1,18 @@
import { Throttler, ThrottlerOptions } from "./throttler";
export class Queue<T> extends Throttler<T> {
public cursor: number = 0;
export class Queue<T, R> extends Throttler<T, R> {
cursor: number = 0;
constructor(options: ThrottlerOptions<T>) {
constructor(options: ThrottlerOptions<T, R>) {
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");
}

View File

@@ -1,19 +1,19 @@
import { Throttler, ThrottlerOptions } from "./throttler";
export class Stack<T> extends Throttler<T> {
public lastTotal: number = 0;
public stack: number[] = [];
export class Stack<T, R> extends Throttler<T, R> {
lastTotal: number = 0;
stack: number[] = [];
constructor(options: ThrottlerOptions<T>) {
constructor(options: ThrottlerOptions<T, R>) {
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++;

View File

@@ -6,14 +6,14 @@ function backoff(retryNumber: number, delay: number): Promise<void> {
});
}
function DEFAULT_HANDLER(task: any): Promise<any> {
return (task as () => Promise<any>)();
function DEFAULT_HANDLER<R>(task: any): Promise<R> {
return (task as () => Promise<R>)();
}
export interface ThrottlerOptions<T> {
export interface ThrottlerOptions<T, R> {
name?: string;
concurrency?: number;
handler?: (task: T) => Promise<any>;
handler?: (task: T) => Promise<R>;
retries?: number;
backoff?: number;
}
@@ -31,34 +31,34 @@ export interface ThrottlerStats {
elapsed: number;
}
interface TaskData<T> {
interface TaskData<T, R> {
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<T> {
public name: string = "";
public concurrency: number = 200;
public handler: (task: T) => Promise<any> = 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<number, TaskData<T>>();
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<T, R> {
name: string = "";
concurrency: number = 200;
handler: (task: T) => Promise<any> = DEFAULT_HANDLER;
active: number = 0;
complete: number = 0;
success: number = 0;
errored: number = 0;
retried: number = 0;
total: number = 0;
taskDataMap = new Map<number, TaskData<T, R>>();
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<T>) {
constructor(options: ThrottlerOptions<T, R>) {
if (options.name) {
this.name = options.name;
}
@@ -82,14 +82,14 @@ export abstract class Throttler<T> {
/**
* @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<void> {
wait(): Promise<void> {
const p = new Promise<void>((resolve, reject) => {
this.waits.push({ resolve, reject });
});
@@ -101,7 +101,7 @@ export abstract class Throttler<T> {
* 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<T> {
* 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<R>(task: T): Promise<R> {
return new Promise<R>((resolve, reject) => {
run(task: T): Promise<R> {
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<T> {
this.handle(this.nextWaitingTaskIndex());
}
public async handle(cursorIndex: number): Promise<void> {
async handle(cursorIndex: number): Promise<void> {
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<T> {
}
}
public stats(): ThrottlerStats {
stats(): ThrottlerStats {
return {
max: this.max,
min: this.min,
@@ -198,7 +198,7 @@ export abstract class Throttler<T> {
};
}
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<T> {
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.");

View File

@@ -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" }],