diff --git a/packages/lib/package.json b/packages/lib/package.json
index c0a121c0..40f530a5 100644
--- a/packages/lib/package.json
+++ b/packages/lib/package.json
@@ -36,6 +36,7 @@
"devDependencies": {
"@types/fs-extra": "^11.0.1",
"@types/node": "^20.4.5",
+ "@types/triple-beam": "^1.3.2",
"typescript": "^5.1.6"
}
}
diff --git a/packages/lib/src/ExponentialBackoff.js b/packages/lib/src/ExponentialBackoff.ts
similarity index 70%
rename from packages/lib/src/ExponentialBackoff.js
rename to packages/lib/src/ExponentialBackoff.ts
index 5af3033f..1e5550b5 100644
--- a/packages/lib/src/ExponentialBackoff.js
+++ b/packages/lib/src/ExponentialBackoff.ts
@@ -1,6 +1,3 @@
-"use strict";
-
-
/**
* Helper class for implementing the exponential backoff algorithm
*
@@ -14,49 +11,48 @@
* the passed value of `max`. The value for the delay will reset back to
* `base` if more time than the value for `reset` has passed between two
* invocations of delay.
- *
- * @alias module:lib.ExponentialBackoff
*/
-class ExponentialBackoff {
+export default class ExponentialBackoff {
+ /**
+ * Base delay in seconds. The first delay will on average be this
+ * long, but can be anywhere between 0 and 2 times this value.
+ */
+ base = 1;
+ /**
+ * Maximum delay in seconds. A delay will not take more than this
+ * value.
+ */
+ max = 60;
+ /**
+ * Time in seconds between two invocations of delay which will cause
+ * it to start over with `base` instead of doubling.
+ */
+ reset: number;
+
+ private _exp = 0;
+ private _lastInvocationTime = Date.now();
+
/**
* Construct ExponentialBackoff helper
*
* The options passed may also be modified on the instance at runtime.
*
- * @param {object=} options -
+ * @param options -
* Options for this exponential backoff helper
- * @param {number=} [options.base=1] -
+ * @param [options.base=1] -
* Base delay in seconds. The first delay will on average be this
* long, but can be anywhere between 0 and 2 times this value.
- * @param {number=} [options.max=60] -
+ * @param [options.max=60] -
* Maximum delay in seconds. A delay will not take more than this
* value.
- * @param {number=} [options.reset=2*max] -
+ * @param [options.reset=2*max] -
* Time in seconds between two invocations of delay which will cause
* it to start over with `base` instead of doubling.
*/
- constructor(options = {}) {
- /**
- * Base delay in seconds. The first delay will on average be this
- * long, but can be anywhere between 0 and 2 times this value.
- * @type {number}
- */
+ constructor(options: { base?: number, max?: number, reset?: number } = {}) {
this.base = options.base || 1;
- /**
- * Maximum delay in seconds. A delay will not take more than this
- * value.
- * @type {number}
- */
this.max = options.max || 60;
- /**
- * Time in seconds between two invocations of delay which will cause
- * it to start over with `base` instead of doubling.
- * @type {number}
- */
this.reset = options.reset || 2 * this.max;
-
- this._exp = 0;
- this._lastInvocationTime = Date.now();
}
/**
@@ -66,7 +62,7 @@ class ExponentialBackoff {
* algorithm. This is a value between 0 and min(2 * options.base *
* 2retries , optinos.max).
*
- * @returns {number} time in milliseconds to wait.
+ * @returns time in milliseconds to wait.
*/
delay() {
let invocationTime = Date.now();
diff --git a/packages/lib/src/PlayerStats.js b/packages/lib/src/PlayerStats.ts
similarity index 85%
rename from packages/lib/src/PlayerStats.js
rename to packages/lib/src/PlayerStats.ts
index 1c7c873b..ed1bb244 100644
--- a/packages/lib/src/PlayerStats.js
+++ b/packages/lib/src/PlayerStats.ts
@@ -1,39 +1,31 @@
-"use strict";
-
/**
* Stastics collected about a player.
- * @alias module:lib.PlayerStats
*/
-class PlayerStats {
+export default class PlayerStats {
/**
* Count of the number of times this player has been seen joining this server.
- * @type {number}
*/
joinCount = 0;
/**
* Time in ms this player has been seen online on this instance.
- * @type {number}
*/
onlineTimeMs = 0;
/**
* Timestamp the player was last seen joining.
- * @type {Date=}
*/
- lastJoinAt;
+ lastJoinAt?: Date;
/**
* Timestamp the player was last seen leaving.
- * @type {Date=}
*/
- lastLeaveAt;
+ lastLeaveAt?: Date;
/**
* Reason the player was last seen leaving with.
- * @type {string=}
*/
- lastLeaveReason;
+ lastLeaveReason?: string;
static jsonSchema = {
type: "object",
@@ -62,7 +54,7 @@ class PlayerStats {
}
}
- static fromJSON(json) {
+ static fromJSON(json: any) {
return new this(json);
}
@@ -84,5 +76,3 @@ class PlayerStats {
return json;
}
}
-
-module.exports = PlayerStats;
diff --git a/packages/lib/src/RateLimiter.js b/packages/lib/src/RateLimiter.ts
similarity index 77%
rename from packages/lib/src/RateLimiter.js
rename to packages/lib/src/RateLimiter.ts
index 7a82d0c1..c7362881 100644
--- a/packages/lib/src/RateLimiter.js
+++ b/packages/lib/src/RateLimiter.ts
@@ -1,6 +1,3 @@
-"use strict";
-
-
/**
* Helper class for rate limiting actions
*
@@ -8,6 +5,7 @@
* limited on invocations.
*
* @example
+ * ```ts
* const rateLimiter = RateLimiter({
* action: () => { console.log("action"); },
* maxRate: 1,
@@ -19,36 +17,36 @@
* // and one more time 1 second later for 99 other activations that were
* // exceeding the rate limit.
* }
- * @alias module:lib.RateLimiter
+ * ```
*/
-class RateLimiter {
+export default class RateLimiter {
+ /**
+ * Maximum rate of activations per second.
+ */
+ maxRate: number;
+ /**
+ * Callback invoked at most `maxRate` per second on activation.
+ */
+ action: Function | null;
+
+ private _lastRun = 0;
+ private _runTimeout = null;
+ private lastProgressBroadcast = Date.now();
+
/**
* Construct RateLimiter helper
*
* The options passed may also be modified on the instance at runtime.
*
- * @param {object=} options - Options for this rate limiter
- * @param {number=} options.maxRate -
+ * @param options - Options for this rate limiter
+ * @param options.maxRate -
* Maximum rate of activations per second.
- * @param {function()=} options.action -
+ * @param options.action -
* Callback invoked at maste `maxRate` per second on activation.
*/
- constructor(options = {}) {
- /**
- * Maximum rate of activations per second.
- * @type {number}
- */
+ constructor(options: { maxRate?: number, action?: Function } = {}) {
this.maxRate = options.maxRate || 1;
-
- /**
- * Callback invoked at most `maxRate` per second on activation.
- * @type {function()}
- */
this.action = options.action || null;
-
- this._lastRun = 0;
- this._runTimeout = null;
- this.lastProgressBroadcast = Date.now();
}
/**
@@ -59,7 +57,7 @@ class RateLimiter {
* invoked immediatly, otherwise a timeout is registered to invoke the
* action at next point in time where it will be within the rate limit.
*
- * @returns {boolean} true if activation was within the rate limit.
+ * @returns true if activation was within the rate limit.
*/
activate() {
let now = Date.now();
@@ -100,5 +98,3 @@ class RateLimiter {
}
}
}
-
-module.exports = RateLimiter;
diff --git a/packages/lib/src/command.js b/packages/lib/src/command.ts
similarity index 69%
rename from packages/lib/src/command.js
rename to packages/lib/src/command.ts
index 972f6969..f0caf2f5 100644
--- a/packages/lib/src/command.js
+++ b/packages/lib/src/command.ts
@@ -6,29 +6,37 @@
* @author Hornwitser
* @module lib/command
*/
-"use strict";
-const libData = require("./data");
-const libLink = require("./link");
-const libErrors = require("./errors");
+import * as libData from "./data";
+import * as libErrors from "./errors";
+import type { Link } from "./link";
+
+
+export type CommandHandler = (args: Record, control: Link) => Promise;
+export type CommandDefinition = [string | string[], string?, Function?];
/**
* Represents a command that can be runned by clusterioctl
- * @static
*/
-class Command {
+export class Command {
+ private _handler: CommandHandler;
+ private _definition: CommandDefinition;
+
+ name: string;
+ alias: string[];
+
/**
* Define an executable command.
*
- * @param {Object} cmd - Command definiton.
- * @param {Array} cmd.definiton -
+ * @param cmd - Command definiton.
+ * @param cmd.definiton -
* Arguments to pass to yargs .command method to define this
* command.
- * @param {function(args, control)} handler -
+ * @param cmd.handler -
* Async function invoked when the command is executed. Is given
* the parsed args and a reference to the {@link
* module:ctl/ctl.Control} instance.
*/
- constructor({ definition, handler }) {
+ constructor({ definition, handler }: { definition: CommandDefinition, handler: CommandHandler }) {
if (definition[0] instanceof Array) {
this.name = definition[0][0].split(" ")[0];
this.alias = definition[0].slice(1);
@@ -40,11 +48,11 @@ class Command {
this._handler = handler;
}
- register(yargs) {
+ register(yargs: any) {
yargs.command(...this._definition);
}
- async run(args, control) {
+ async run(args: Record, control: Link) {
await this._handler(args, control);
}
}
@@ -56,20 +64,23 @@ class Command {
* CommandTrees. This is used by clusterioctl to hold the full tree of
* available commands, you may extend this tree by using a control plugin,
* see {@link module:lib.BaseControlPlugin#addCommands}
- *
- * @static
*/
-class CommandTree {
+export class CommandTree {
+ public name: string;
+ public alias: string[];
+ public description: string;
+ public subCommands: Map;
+
/**
* Define a command containing sub commands.
*
- * @param {Object} cmd - Command definition
- * @param {string} cmd.name - Name of the command.
- * @param {Array} cmd.alias - Aliases for this tree.
- * @param {description} cmd.description -
+ * @param cmd - Command definition
+ * @param cmd.name - Name of the command.
+ * @param cmd.alias - Aliases for this tree.
+ * @param cmd.description -
* Descripton to provide for this command tree node.
*/
- constructor({ name, alias, description }) {
+ constructor({ name, alias, description }: { name: string, alias: string[], description: string }) {
if (typeof name !== "string") {
throw new Error("name must be a string");
}
@@ -83,10 +94,10 @@ class CommandTree {
/**
* Add a command or command tree to this tree
*
- * @param {module:lib.Command|module:lib.CommandTree} command -
+ * @param command -
* The command to add to this command tree.
*/
- add(command) {
+ add(command: Command | CommandTree) {
if (this.subCommands.has(command.name)) {
throw new Error(`Command ${command.name} already exists`);
}
@@ -102,13 +113,13 @@ class CommandTree {
/**
* Get a command or command tree from this tree
*
- * @param {string} name -
+ * @param name -
* The name of the command or command tree to retrieve.
- * @return {?module:lib.Command|module:lib.CommandTree}
+ * @return
* The command to add to this command tree.
*/
- get(name) {
- return this.subCommands.get(name) || null;
+ get(name: string) {
+ return this.subCommands.get(name);
}
/**
@@ -117,10 +128,10 @@ class CommandTree {
* Traverses the the tree of sub-commands recursively and adds all of
* them to the given yargs parser.
*
- * @param {Object} yargs - yargs parser to register with.
+ * @param yargs - yargs parser to register with.
*/
- register(yargs) {
- yargs.command([this.name].concat(this.alias), this.description, (yargs) => {
+ register(yargs: any) {
+ yargs.command([this.name].concat(this.alias), this.description, (yargs: any) => {
for (let [name, command] of this.subCommands) {
// Check if the entry is not an alias.
if (name === command.name) {
@@ -141,13 +152,12 @@ class CommandTree {
* Resolves a string with either an host name or an id into an integer with
* the host ID.
*
- * @param {module:lib.Link} client - link to controller to query host on.
- * @param {string} hostName - string with name or id of host.
- * @returns {Promise} host ID.
- * @static
+ * @param client - link to controller to query host on.
+ * @param hostName - string with name or id of host.
+ * @returns host ID.
*/
-async function resolveHost(client, hostName) {
- let hostId;
+export async function resolveHost(client: Link, hostName: string) {
+ let hostId: number;
if (/^-?\d+$/.test(hostName)) {
hostId = parseInt(hostName, 10);
} else {
@@ -173,13 +183,12 @@ async function resolveHost(client, hostName) {
* Resolves a string with either an instance name or an id into an integer
* with the instance ID.
*
- * @param {module:lib.Link} client - link to controller to query instance on.
- * @param {string} instanceName - string with name or id of instance.
- * @returns {Promise} instance ID.
- * @static
+ * @param client - link to controller to query instance on.
+ * @param instanceName - string with name or id of instance.
+ * @returns instance ID.
*/
-async function resolveInstance(client, instanceName) {
- let instanceId;
+export async function resolveInstance(client: Link, instanceName: string) {
+ let instanceId: number;
if (/^-?\d+$/.test(instanceName)) {
instanceId = parseInt(instanceName, 10);
} else {
@@ -205,14 +214,13 @@ async function resolveInstance(client, instanceName) {
* Resolevs a string with either a mod pack name or an id into an integer
* with the mod pack ID.
*
- * @param {module:lib.Link} client -
+ * @param client -
* link to controller to query mod pack on.
- * @param {string} modPackName - string with name or id of mod pack.
- * @returns {Promise} mod pack ID.
- * @static
+ * @param modPackName - string with name or id of mod pack.
+ * @returns mod pack ID.
*/
-async function resolveModPack(client, modPackName) {
- let modPackId;
+export async function resolveModPack(client: Link, modPackName: string) {
+ let modPackId: number;
if (/^-?\d+/.test(modPackName)) {
modPackId = parseInt(modPackName, 10);
} else {
@@ -238,15 +246,14 @@ async function resolveModPack(client, modPackName) {
* Resolves a string with either a role name or an id into an object
* representing the role.
*
- * @param {module:lib.Link} client - link to controller to query role on.
- * @param {string} roleName - string with name or id of role.
- * @returns {Promise} Role info.
- * @static
+ * @param client - link to controller to query role on.
+ * @param roleName - string with name or id of role.
+ * @returns Role info.
*/
-async function retrieveRole(client, roleName) {
+export async function retrieveRole(client: Link, roleName: string) {
let roles = await client.sendTo("controller", new libData.RoleListRequest());
- let resolvedRole;
+ let resolvedRole: libData.RawRole;
if (/^-?\d+$/.test(roleName)) {
let roleId = parseInt(roleName, 10);
for (let role of roles) {
@@ -271,13 +278,3 @@ async function retrieveRole(client, roleName) {
return resolvedRole;
}
-
-module.exports = {
- Command,
- CommandTree,
-
- resolveHost,
- resolveInstance,
- resolveModPack,
- retrieveRole,
-};
diff --git a/packages/lib/src/data/messages_instance.js b/packages/lib/src/data/messages_instance.js
index 311bf0cf..da661418 100644
--- a/packages/lib/src/data/messages_instance.js
+++ b/packages/lib/src/data/messages_instance.js
@@ -1,5 +1,5 @@
"use strict";
-const PlayerStats = require("../PlayerStats");
+const PlayerStats = require("../PlayerStats").default;
const { JsonString, jsonArray } = require("./composites");
/** @memberof module:lib */
diff --git a/packages/lib/src/data/messages_user.js b/packages/lib/src/data/messages_user.js
index 47bc1ff3..edd391cd 100644
--- a/packages/lib/src/data/messages_user.js
+++ b/packages/lib/src/data/messages_user.js
@@ -1,5 +1,5 @@
"use strict";
-const PlayerStats = require("../PlayerStats");
+const PlayerStats = require("../PlayerStats").default;
const { jsonArray, JsonNumber } = require("./composites");
/* @memberof module:lib */
diff --git a/packages/lib/src/database.js b/packages/lib/src/database.ts
similarity index 74%
rename from packages/lib/src/database.js
rename to packages/lib/src/database.ts
index 4c2b8520..3bc65235 100644
--- a/packages/lib/src/database.js
+++ b/packages/lib/src/database.ts
@@ -4,23 +4,21 @@
* @module lib/database
* @author Hornwitser
*/
+import fs from "fs-extra";
-"use strict";
-const fs = require("fs-extra");
-
-const libFileOps = require("./file_ops");
-const { basicType } = require("./helpers");
+import * as libFileOps from "./file_ops";
+import { basicType } from "./helpers";
/**
* Converts a Map with only string keys to a JavaScript object.
*
- * @param {Map} map - the Map to convert to an object.
- * @return {object} - Object with the mapping's key-values as properties.
+ * @param map - the Map to convert to an object.
+ * @return Object with the mapping's key-values as properties.
* @throws {Error} if there are non-string keys in map.
*/
-function mapToObject(map) {
- let obj = {};
+export function mapToObject(map: Map) {
+ let obj: Record = {};
for (let [key, value] of map) {
if (typeof key !== "string") {
throw new Error(
@@ -40,14 +38,15 @@ function mapToObject(map) {
* properties as key value pairs and converts it into a JavaScript Map.
* Returns an empty Map if the file does not exist.
*
- * @param {string} filePath - The path to the JSON file to load.
+ * @param filePath - The path to the JSON file to load.
+ * @returns promise resolving to the loaded map
* @throws {Error} If JSON file did not contain an object.
* @throws {Error} If an error occured reading the file.
*/
-async function loadJsonAsMap(filePath) {
+export async function loadJsonAsMap(filePath: string): Promise> {
try {
let content = await fs.readFile(filePath);
- let parsed = JSON.parse(content);
+ let parsed = JSON.parse(content.toString());
if (basicType(parsed) !== "object") {
throw new Error(`Expected object but got ${basicType(parsed)}`);
@@ -72,12 +71,12 @@ async function loadJsonAsMap(filePath) {
* If the directory the file is to be saved into doesn't exist it will be
* created.
*
- * @param {string} filePath - The path to the JSON file that will be saved.
- * @param {Map} map - Map with only string keys to save.
+ * @param filePath - The path to the JSON file that will be saved.
+ * @param map - Map with only string keys to save.
* @throws {Error} if there are non-string keys in map.
* @throws {Error} if an error occured writing to the file.
*/
-async function saveMapAsJson(filePath, map) {
+export async function saveMapAsJson(filePath: string, map: Map) {
let obj = mapToObject(map);
await libFileOps.safeOutputFile(filePath, JSON.stringify(obj, null, 4));
}
@@ -88,15 +87,15 @@ async function saveMapAsJson(filePath, map) {
* Loads the JSON file specified by path containing an array of objects that
* each have an id property into a Map from the id to the object.
*
- * @param {string} filePath - The path to the JSON file to load.
+ * @param filePath - The path to the JSON file to load.
* @throws {Error} If JSON file did not contain an array.
* @throws {Error} If there are objects in the array without an id property.
* @throws {Error} If an error occured reading the file.
*/
-async function loadJsonArrayAsMap(filePath) {
+export async function loadJsonArrayAsMap(filePath: string): Promise> {
try {
let content = await fs.readFile(filePath);
- let parsed = JSON.parse(content);
+ let parsed = JSON.parse(content.toString());
if (basicType(parsed) !== "array") {
throw new Error(`Expected array but got ${basicType(parsed)}`);
@@ -132,24 +131,24 @@ async function loadJsonArrayAsMap(filePath) {
* Save the values of a Map with objects containing an id property that is
* equal to the key the objects are stored at into a JSON file.
*
- * @param {string} filePath - The path to the JSON file that will be saved.
- * @param {Map} map -
+ * @param filePath - The path to the JSON file that will be saved.
+ * @param map -
* Map with objects containing an id property equal to the key they are
* stored under.
* @throws {Error} if an error occured writing to the file.
*/
-async function saveMapAsJsonArray(filePath, map) {
+export async function saveMapAsJsonArray(filePath: string, map: Map) {
await libFileOps.safeOutputFile(filePath, JSON.stringify([...map.values()], null, 4));
}
-function checkName(name) {
+function checkName(name: unknown): asserts name is string {
if (typeof name !== "string") {
throw new Error("name must be a string");
}
}
-function checkCount(count) {
+function checkCount(count: unknown): asserts count is number {
if (typeof count !== "number" || isNaN(count)) {
throw new Error("count must be a number");
}
@@ -164,7 +163,8 @@ function checkCount(count) {
* serialized the database discards entries with a zero counts, and when
* deserialized the content is verified.
*/
-class ItemDatabase {
+export class ItemDatabase {
+ private _items: Map = new Map();
/**
* Create a new item database
@@ -174,9 +174,7 @@ class ItemDatabase {
* database from. An empty database will be created if this parameter
* is left undefined.
*/
- constructor(serialized) {
- this._items = new Map();
-
+ constructor(serialized?: object) {
// Verify the content of the serialized database
if (serialized !== undefined) {
for (let [name, count] of Object.entries(serialized)) {
@@ -194,10 +192,10 @@ class ItemDatabase {
* Serialize the item database into a plain JavaScript object that can be
* turned inta a string with JSON.stringify().
*
- * @returns {object} Serialized representation of the database
+ * @returns Serialized representation of the database
*/
serialize() {
- let obj = {};
+ let obj: Record = {};
for (let [name, count] of this._items) {
if (count !== 0) {
obj[name] = count;
@@ -223,10 +221,10 @@ class ItemDatabase {
* database. If the item has not been previously stored into the database
* then 0 is returned.
*
- * @param {string} name - The name of the item to get the count of.
- * @returns {number} The count of the item stored.
+ * @param name - The name of the item to get the count of.
+ * @returns The count of the item stored.
*/
- getItemCount(name) {
+ getItemCount(name: string) {
checkName(name);
if (!this._items.has(name)) {
@@ -241,10 +239,10 @@ class ItemDatabase {
*
* Add count copies of the item specified by name to the item database.
*
- * @param {string} name - The name of the item to add.
- * @param {number} count - The count of item to remove.
+ * @param name - The name of the item to add.
+ * @param count - The count of item to remove.
*/
- addItem(name, count) {
+ addItem(name: string, count: number) {
checkName(name);
checkCount(count);
@@ -262,10 +260,10 @@ class ItemDatabase {
* If count is greater than the currently stored count the stored item
* count will become negative.
*
- * @param {string} name - The name of the item to remove.
- * @param {number} count - The count of items to remove.
+ * @param name - The name of the item to remove.
+ * @param count - The count of items to remove.
*/
- removeItem(name, count) {
+ removeItem(name: string, count: number) {
checkName(name);
checkCount(count);
@@ -277,12 +275,3 @@ class ItemDatabase {
this._items.set(name, count);
};
}
-
-module.exports = {
- mapToObject,
- loadJsonAsMap,
- saveMapAsJson,
- loadJsonArrayAsMap,
- saveMapAsJsonArray,
- ItemDatabase,
-};
diff --git a/packages/lib/src/errors.js b/packages/lib/src/errors.js
deleted file mode 100644
index e7de55df..00000000
--- a/packages/lib/src/errors.js
+++ /dev/null
@@ -1,92 +0,0 @@
-/**
- * Errors thrown in Clusterio
- * @module lib/errors
- */
-"use strict";
-
-/**
- * Thrown by commands when they fail
- * @static
- */
-class CommandError extends Error { }
-
-/**
- * Thrown from requests sent when an error occured handling it
- * @static
- */
-class RequestError extends Error {
- constructor(message, code, stack) {
- super(message);
- if (code) { this.code = code; }
- if (stack) { this.stack = stack; }
- }
-}
-
-/**
- * Thrown when a permission check fails.
- *
- * Is a subclass of RequestError to prevent logging stack traces when
- * requests fail due to permission denied.
- *
- * @static
- */
-class PermissionError extends RequestError { }
-
-/**
- * Signal for messages that fail validation
- * @static
- */
-class InvalidMessage extends Error {
- constructor(msg, errors) {
- super(msg);
- this.errors = errors || null;
- }
-}
-
-/**
- * Thrown from requests when the session it was sent on was lost
- * @static
- */
-class SessionLost extends Error { }
-
-/**
- * Thrown when WebSocket authentication failed
- * @static
- */
-class AuthenticationFailed extends Error { }
-
-/**
- * Errror class for known errors occuring during startup
- * @static
- */
-class StartupError extends Error { }
-
-/**
- * Errors outside of our control
- * @static
- */
-class EnvironmentError extends Error { }
-
-/**
- * Errors caused by plugins
- * @static
- */
-class PluginError extends Error {
- constructor(pluginName, original) {
- super(`PluginError: ${original.message}`);
- this.pluginName = pluginName;
- this.original = original;
- }
-}
-
-module.exports = {
- CommandError,
- RequestError,
- PermissionError,
- InvalidMessage,
- SessionLost,
- AuthenticationFailed,
- StartupError,
- EnvironmentError,
- PluginError,
-};
diff --git a/packages/lib/src/errors.ts b/packages/lib/src/errors.ts
new file mode 100644
index 00000000..30b26cf3
--- /dev/null
+++ b/packages/lib/src/errors.ts
@@ -0,0 +1,75 @@
+/**
+ * Errors thrown in Clusterio
+ * @module lib/errors
+ */
+
+/**
+ * Thrown by commands when they fail
+ */
+export class CommandError extends Error { }
+
+/**
+ * Thrown from requests sent when an error occured handling it
+ */
+export class RequestError extends Error {
+ constructor(
+ message: string,
+ public code?: string,
+ public stack?: string,
+ ) {
+ super(message);
+ }
+}
+
+/**
+ * Thrown when a permission check fails.
+ *
+ * Is a subclass of RequestError to prevent logging stack traces when
+ * requests fail due to permission denied.
+ *
+ */
+export class PermissionError extends RequestError { }
+
+/**
+ * Signal for messages that fail validation
+ */
+export class InvalidMessage extends Error {
+ constructor(
+ message: string,
+ public errors: object = null,
+ ) {
+ super(message);
+ }
+}
+
+/**
+ * Thrown from requests when the session it was sent on was lost
+ */
+export class SessionLost extends Error { }
+
+/**
+ * Thrown when WebSocket authentication failed
+ */
+export class AuthenticationFailed extends Error { }
+
+/**
+ * Errror class for known errors occuring during startup
+ */
+export class StartupError extends Error { }
+
+/**
+ * Errors outside of our control
+ */
+export class EnvironmentError extends Error { }
+
+/**
+ * Errors caused by plugins
+ */
+export class PluginError extends Error {
+ constructor(
+ public pluginName: string,
+ public original: Error
+ ) {
+ super(`PluginError: ${original.message}`);
+ }
+}
diff --git a/packages/lib/src/file_ops.js b/packages/lib/src/file_ops.ts
similarity index 73%
rename from packages/lib/src/file_ops.js
rename to packages/lib/src/file_ops.ts
index 3a19ba2f..0a67ebb3 100644
--- a/packages/lib/src/file_ops.js
+++ b/packages/lib/src/file_ops.ts
@@ -1,25 +1,24 @@
/**
* @module lib/file_ops
*/
-"use strict";
-const fs = require("fs-extra");
-const path = require("path");
-const crypto = require("crypto"); // needed for getTempFile
+import fs from "fs-extra";
+import path from "path";
+import crypto from "crypto"; // needed for getTempFile
/**
* Returns the newest file in a directory
*
- * @param {string} directory - The directory to look for the newest file in.
- * @param {function(string): boolean} filter -
+ * @param directory - The directory to look for the newest file in.
+ * @param filter -
* Optional function to filter out files to ignore. Should return true
* if the file is to be considered.
- * @returns {Promise}
+ * @returns
* Name of the file with the newest timestamp or null if the
* directory contains no files.
*/
-async function getNewestFile(directory, filter = (name) => true) {
+export async function getNewestFile(directory: string, filter = (_name: string) => true) {
let newestTime = new Date(0);
- let newestFile;
+ let newestFile: string | undefined;
for (let entry of await fs.readdir(directory, { withFileTypes: true })) {
if (entry.isFile()) {
let stat = await fs.stat(path.join(directory, entry.name));
@@ -40,11 +39,11 @@ async function getNewestFile(directory, filter = (name) => true) {
* Error reading the size of files are ignored, and error reading the
* directory will result in 0 being returned.
*
- * @param {string} directory - The directory to sum files in.
- * @returns {Promise} The size in bytes of all files in the directory.
+ * @param directory - The directory to sum files in.
+ * @returns The size in bytes of all files in the directory.
*/
-async function directorySize(directory) {
- let dirEntries;
+export async function directorySize(directory: string) {
+ let dirEntries: fs.Dirent[];
try {
dirEntries = await fs.readdir(directory, { withFileTypes: true });
} catch (err) {
@@ -53,7 +52,7 @@ async function directorySize(directory) {
}
throw err;
}
- let statTasks = [];
+ let statTasks: Promise[] = [];
for (let entry of dirEntries) {
if (entry.isFile()) {
statTasks.push(fs
@@ -77,13 +76,13 @@ async function directorySize(directory) {
* racy and a file or folder may have been created in the folder by the time
* this function returns.
*
- * @param {string} directory - directory to check in.
- * @param {string} name - file name to check for, may have extension.
- * @param {string} extension - dot extension used for the file name.
- * @returns {Promise} modified name with extension that likely does
+ * @param directory - directory to check in.
+ * @param name - file name to check for, may have extension.
+ * @param extension - dot extension used for the file name.
+ * @returns modified name with extension that likely does
* not exist in the folder
*/
-async function findUnusedName(directory, name, extension = "") {
+export async function findUnusedName(directory: string, name: string, extension = "") {
if (extension && name.endsWith(extension)) {
name = name.slice(0, -extension.length);
}
@@ -101,25 +100,27 @@ async function findUnusedName(directory, name, extension = "") {
}
}
}
+
/**
+ * Generate collision resistante temporary file name
+ *
* Warning: this function should not be relied upon to be accurate for
* security sensitive applications. The selection process is inherently
* racy and a file or folder may have been created in the folder by the time
* this function returns.
*
- * @param {string} prefix - Prefix for file
- * @param {string} suffix - Suffix for file
- * @param {string} tmpdir - Directory for temp file
+ * @param prefix - Prefix for file
+ * @param suffix - Suffix for file
+ * @param tmpdir - Directory for temp file
+ * @returns Temporary file name
*/
-async function getTempFile(prefix, suffix, tmpdir) {
- prefix = (typeof prefix !== "undefined") ? prefix : "tmp.";
- suffix = (typeof suffix !== "undefined") ? suffix : "";
- tmpdir = (typeof tmpdir !== "undefined") ? tmpdir : "./";
+export async function getTempFile(prefix = "tmp.", suffix = "", tmpdir = "./") {
let fileName = path.join(prefix + crypto.randomBytes(16).toString("hex") + suffix);
let freeFile = await findUnusedName(tmpdir, fileName);
let fullPath = path.join(tmpdir, freeFile);
return fullPath;
}
+
/**
* Safely write data to a file
*
@@ -131,11 +132,11 @@ async function getTempFile(prefix, suffix, tmpdir) {
* should not be too much of an issue as the next time the same file is
* written the temporary will be overwritten and renamed to the target file.
*
- * @param {string} file - Path to file to write.
- * @param {string|Buffer} data - Content to write.
- * @param {object|string} options - see fs.writeFile, `flag` must not be set.
+ * @param file - Path to file to write.
+ * @param data - Content to write.
+ * @param options - see fs.writeFile, `flag` must not be set.
*/
-async function safeOutputFile(file, data, options={}) {
+export async function safeOutputFile(file: string, data: string | Buffer, options: fs.WriteFileOptions ={}) {
let directory = path.dirname(file);
if (!await fs.pathExists(directory)) {
await fs.mkdirs(directory);
@@ -153,7 +154,7 @@ async function safeOutputFile(file, data, options={}) {
}
-// Reserved names by allmost all filesystems
+// Reserved names by almost all filesystems
const badNames = [".", ".."];
// Reserved namespaces in Windows
@@ -167,10 +168,10 @@ const badWinNamespaces = [
/**
* Check if a string is a valid file name
*
- * @param {string} name - Name to check
+ * @param name - Name to check
* @throws Error if the name is unsuitable.
*/
-function checkFilename(name) {
+export function checkFilename(name: string) {
// All of these are bad in Windows only, except for /, . and ..
// See: https://docs.microsoft.com/en-us/windows/win32/fileio/naming-a-file
const badChars = /[<>:"\/\\|?*\x00-\x1f]/g;
@@ -208,10 +209,10 @@ function checkFilename(name) {
/**
* Clean up string to be suitable for use as filename
*
- * @param {string} name - Arbitrary name string
- * @returns {string} Filename suitable to use in the filesystem
+ * @param name - Arbitrary name string
+ * @returns Filename suitable to use in the filesystem
*/
-function cleanFilename(name) {
+export function cleanFilename(name: string) {
// copied from checkFilename due to RegExp with global flag containing state.
const badChars = /[<>:"\/\\|?*\x00-\x1f]/g;
const badEnd = /[. ]$/;
@@ -233,14 +234,3 @@ function cleanFilename(name) {
return name;
}
-
-
-module.exports = {
- getNewestFile,
- directorySize,
- findUnusedName,
- getTempFile,
- safeOutputFile,
- checkFilename,
- cleanFilename,
-};
diff --git a/packages/lib/src/hash.js b/packages/lib/src/hash.ts
similarity index 60%
rename from packages/lib/src/hash.js
rename to packages/lib/src/hash.ts
index 12359602..a2542b9b 100644
--- a/packages/lib/src/hash.js
+++ b/packages/lib/src/hash.ts
@@ -2,18 +2,17 @@
* Hashing functions
* @module lib/hash
*/
-"use strict";
-const crypto = require("crypto");
-const fs = require("fs-extra");
+import crypto from "crypto";
+import fs from "fs-extra";
/**
* Returns a promise that resolves to the SHA1 hash of the stream given
*
- * @param {Stream} stream - Node stream of the content to hash.
- * @returns {Promise} hash of the stream.
+ * @param stream - Node stream of the content to hash.
+ * @returns hash of the stream.
*/
-function hashStream(stream) {
+export function hashStream(stream: NodeJS.ReadableStream): Promise {
return new Promise((resolve, reject) => {
let hasher = crypto.createHash("sha1");
hasher.setEncoding("hex");
@@ -33,14 +32,9 @@ function hashStream(stream) {
/**
* Returns a promise that resolves to the SHA1 hash of the file given by path
*
- * @param {string} path - Path to the file to hash.
- * @returns {Promise} hash of the file given.
+ * @param path - Path to the file to hash.
+ * @returns hash of the file given.
*/
-function hashFile(path) {
+export function hashFile(path: string) {
return hashStream(fs.createReadStream(path));
}
-
-module.exports = {
- hashStream,
- hashFile,
-};
diff --git a/packages/lib/src/ini.js b/packages/lib/src/ini.ts
similarity index 81%
rename from packages/lib/src/ini.js
rename to packages/lib/src/ini.ts
index 413aeb92..592d7fb1 100644
--- a/packages/lib/src/ini.js
+++ b/packages/lib/src/ini.ts
@@ -5,17 +5,14 @@
* @author Hornwitser
*/
-"use strict";
-
/**
* Parse INI file into object of sections
- * @param {string} input - content of INI file to parse.
- * @returns {object>}
- * sections parsed from the ini file.
+ * @param input - content of INI file to parse.
+ * @returns sections parsed from the ini file.
*/
-function parse(input) {
- const sections = {};
- let currentSection = sections;
+export function parse(input: string) {
+ const sections: Record> = {};
+ let currentSection: Record = sections as Record;
const lines = input.split(/\r\n|\n/);
for (let number = 1; number <= lines.length; number++) {
let line = lines[number - 1].replace(/^[\t ]+/, "");
@@ -56,11 +53,11 @@ function parse(input) {
/**
* Serialize an object of section definitions into an INI file
- * @param {object>} sections -
+ * @param sections -
* Sections to encode into ini format
- * @returns {string} sections serialized into INI format
+ * @returns sections serialized into INI format
*/
-function stringify(sections) {
+export function stringify(sections: Record>) {
let serialized = [];
let first = true;
@@ -87,8 +84,3 @@ function stringify(sections) {
if (serialized.length) { serialized.push(""); }
return serialized.join("\n");
}
-
-module.exports = {
- parse,
- stringify,
-};
diff --git a/packages/lib/src/is_deep_strict_equal.d.ts b/packages/lib/src/is_deep_strict_equal.d.ts
new file mode 100644
index 00000000..ec5e4c72
--- /dev/null
+++ b/packages/lib/src/is_deep_strict_equal.d.ts
@@ -0,0 +1 @@
+export default function isDeepStrictEqual(a: any, b: any): boolean
diff --git a/packages/lib/src/logging.js b/packages/lib/src/logging.ts
similarity index 53%
rename from packages/lib/src/logging.js
rename to packages/lib/src/logging.ts
index f4fa9c4c..0a24cfb7 100644
--- a/packages/lib/src/logging.js
+++ b/packages/lib/src/logging.ts
@@ -1,9 +1,9 @@
"use strict";
-const winston = require("winston");
-const Transport = require("winston-transport");
-const { LEVEL, MESSAGE } = require("triple-beam");
+import winston from "winston";
+import Transport from "winston-transport";
+import { LEVEL, MESSAGE } from "triple-beam";
-const levels = Object.freeze({
+export const levels = Object.freeze({
fatal: 0,
error: 1,
warn: 2,
@@ -13,7 +13,7 @@ const levels = Object.freeze({
verbose: 6,
});
-const colors = {
+export const colors = {
fatal: "inverse red",
error: "bold brightRed",
warn: "bold brightYellow",
@@ -21,21 +21,31 @@ const colors = {
info: "bold brightBlue",
server: "bold",
verbose: "bold grey",
-};
+} as const;
winston.addColors(colors);
// The Console Transport is replicated here because it's overly complicated
// in winston, writes to stdout/err instead of console and doesn't work in
// the browser.
-class ConsoleTransport extends Transport {
- constructor(options = {}) {
+export class ConsoleTransport extends Transport {
+ errorLevels: Set;
+ warnLevels: Set;
+ filter: (info: object) => boolean;
+
+ constructor(
+ options: Transport.TransportStreamOptions & {
+ errorLevels?: string[],
+ warnLevels?: string[],
+ filter?: ConsoleTransport["filter"]
+ } = {}
+ ) {
super(options);
this.errorLevels = new Set(options.errorLevels || ["fatal", "error"]);
this.warnLevels = new Set(options.warnLevels || ["warn"]);
this.filter = options.filter || (() => true);
}
- log(info, callback) {
+ log(info: any, callback: Function) {
if (!this.filter(info)) {
return callback();
}
@@ -56,12 +66,12 @@ class ConsoleTransport extends Transport {
/**
* Formats winston log messages for the web console
*/
-class WebConsoleFormat {
- constructor(options = {}) {
- this.options = options;
- }
+export class WebConsoleFormat {
+ constructor(
+ public options = {}
+ ) { }
- transform(info, options) {
+ transform(info: any, _options: {}) {
let src = " ";
if (info.host_id !== undefined) {
src += `s:${info.host_name} - `;
@@ -84,31 +94,41 @@ class WebConsoleFormat {
/**
* Filter object for logs
- * @typedef {Object} LogFilter
- * @property {string} [maxLevel] -
- * Maximum log level to include. Higher levels are more verbose.
- * @property {boolean} [all] -
- * Include log entries from controller, all hosts and all instances.
- * @property {boolean} [controller] -
- * Include log entries from the controller.
- * @property {Array} [hostIds] -
- * Include log entries for the given hosts and instances of those
- * hosts by id.
- * @property {Array} [instanceIds] -
- * Include log entries for the given instances by id.
*/
+export interface LogFilter {
+ /**
+ * Maximum log level to include. Higher levels are more verbose.
+ */
+ maxLevel?: keyof typeof levels;
+ /**
+ * Include log entries from controller, all hosts and all instances.
+ */
+ all?: boolean;
+ /**
+ * Include log entries from the controller.
+ */
+ controller?: boolean;
+ /**
+ * Include log entries for the given hosts and instances of those
+ * hosts by id.
+ */
+ hostIds?: number[];
+ /**
+ * Include log entries for the given instances by id.
+ */
+ instanceIds?: number[];
+}
/**
* Create log filter by level and source.
*
- * @param {LogFilter} filter -
+ * @param filter -
* Filter to filter log entries by.
- * @returns {function(object): boolean}
+ * @returns
* filter returning true for log entries that match it.
- * @static
*/
-function logFilter({ all, controller, hostIds, instanceIds, maxLevel }) {
- return info => {
+export function logFilter({ all, controller, hostIds, instanceIds, maxLevel }: LogFilter) {
+ return (info: any) => {
// Note: reversed to filter out undefined levels
if (maxLevel && !(levels[info.level] <= levels[maxLevel])) {
return false;
@@ -135,17 +155,23 @@ function logFilter({ all, controller, hostIds, instanceIds, maxLevel }) {
};
}
-const logger = winston.createLogger({
+export const logger = winston.createLogger({
level: "verbose",
levels,
format: winston.format.timestamp(),
-});
+}) as unknown as Logger;
-module.exports = {
- ConsoleTransport,
- WebConsoleFormat,
- colors,
- levels,
- logFilter,
- logger,
+// Who in their right mind thought that log levels should be hard coded
+// into the type when you can define custom levels you want to support?
+export type Logger = Omit & {
+ fatal: winston.LeveledLogMethod,
+ error: winston.LeveledLogMethod,
+ warn: winston.LeveledLogMethod,
+ audit: winston.LeveledLogMethod,
+ info: winston.LeveledLogMethod,
+ server: winston.LeveledLogMethod,
+ verbose: winston.LeveledLogMethod,
};
diff --git a/packages/lib/src/logging_utils.js b/packages/lib/src/logging_utils.ts
similarity index 59%
rename from packages/lib/src/logging_utils.js
rename to packages/lib/src/logging_utils.ts
index fe627044..0d075e5c 100644
--- a/packages/lib/src/logging_utils.js
+++ b/packages/lib/src/logging_utils.ts
@@ -5,24 +5,95 @@
* @module lib/logging_utils
*/
"use strict";
-const events = require("events");
-const fs = require("fs-extra");
-const path = require("path");
-const stream = require("stream");
-const util = require("util");
-const winston = require("winston");
-const Transport = require("winston-transport");
-const { LEVEL, MESSAGE } = require("triple-beam");
-const chalk = require("chalk");
+import fs from "fs-extra";
+import path from "path";
+import stream from "stream";
+import util from "util";
+import winston from "winston";
+import Transport from "winston-transport";
+import { LEVEL, MESSAGE } from "triple-beam";
+import chalk from "chalk";
-const libData = require("./data");
-const libErrors = require("./errors");
-const libFileOps = require("./file_ops");
-const { levels, logFilter, logger } = require("./logging");
-const libStream = require("./stream");
+import * as libData from "./data";
+import * as libErrors from "./errors";
+import * as libFileOps from "./file_ops";
+import { type LogFilter, levels, logFilter, logger } from "./logging";
+import * as libStream from "./stream";
+import type { Link } from "./link";
const finished = util.promisify(stream.finished);
+interface SecondsLogOutput {
+ /** Where the message came from, one of "stdout" and "stderr". */
+ source: "stdout" | "stderr";
+ /** Timestamp format, one of "date", "seconds" and "none". */
+ format: "seconds";
+ /** Timestamp of the message. Not present if format is "none". */
+ time: string;
+ /** Type of message, one of "log", "action" and "generic". */
+ type: "log";
+ /** Log level for "log" type. i.e "Info" normally. */
+ level: string;
+ /** File reported for "log" type. */
+ file: string;
+ /** Main content of the line. */
+ message: string;
+}
+interface SecondsGenericOutput {
+ /** Where the message came from, one of "stdout" and "stderr". */
+ source: "stdout" | "stderr";
+ /** Timestamp format, one of "date", "seconds" and "none". */
+ format: "seconds";
+ /** Timestamp of the message. Not present if format is "none". */
+ time: string;
+ /** Type of message, one of "log", "action" and "generic". */
+ type: "generic";
+ /** Main content of the line. */
+ message: string;
+}
+interface DateActionOutput {
+ /** Where the message came from, one of "stdout" and "stderr". */
+ source: "stdout" | "stderr";
+ /** Timestamp format, one of "date", "seconds" and "none". */
+ format: "date";
+ /** Timestamp of the message. Not present if format is "none". */
+ time: string;
+ /** Type of message, one of "log", "action" and "generic". */
+ type: "action";
+ /** Kind of action for "action" type. i.e "CHAT" for chat. */
+ action: string;
+ /** Main content of the line. */
+ message: string;
+}
+interface DateGenericOutput {
+ /** Where the message came from, one of "stdout" and "stderr". */
+ source: "stdout" | "stderr";
+ /** Timestamp format, one of "date", "seconds" and "none". */
+ format: "date";
+ /** Timestamp of the message. Not present if format is "none". */
+ time: string;
+ /** Type of message, one of "log", "action" and "generic". */
+ type: "generic";
+ /** Main content of the line. */
+ message: string;
+}
+interface UnformattedOutput {
+ /** Where the message came from, one of "stdout" and "stderr". */
+ source: "stdout" | "stderr";
+ /** Timestamp format, one of "date", "seconds" and "none". */
+ format: "none";
+ /** Type of message, one of "log", "action" and "generic". */
+ type: "generic";
+ /** Main content of the line. */
+ message: string;
+}
+export type ParsedFactorioOutput =
+ | SecondsLogOutput
+ | SecondsGenericOutput
+ | DateActionOutput
+ | DateGenericOutput
+ | UnformattedOutput
+;
/**
* Format a parsed Factorio message with colors
@@ -30,11 +101,11 @@ const finished = util.promisify(stream.finished);
* Formats a parsed Factorio output from lib/factorio into a readable
* colorized output using terminal escape codes that can be printed.
*
- * @param {Object} parsed - Parsed Factorio server output.
- * @returns {string} terminal colorized message.
+ * @param parsed - Parsed Factorio server output.
+ * @returns terminal colorized message.
* @private
*/
-function formatServerOutput(parsed) {
+function formatServerOutput(parsed: ParsedFactorioOutput) {
let time = "";
if (parsed.format === "seconds") {
time = `${chalk.yellow(parsed.time.padStart(8))} `;
@@ -71,15 +142,17 @@ function formatServerOutput(parsed) {
/**
* Formats winston log messages for a character terminal.
- * @static
*/
-class TerminalFormat {
- constructor(options = {}) {
- this.options = options;
+export class TerminalFormat {
+ colorize: winston.Logform.Colorizer;
+
+ constructor(
+ public options: object = {}
+ ) {
this.colorize = winston.format.colorize(options);
}
- transform(info, options) {
+ transform(info: any, options: { showTimestamp: boolean }) {
info = this.colorize.transform(info, this.colorize.options);
let ts = "";
if (options.showTimestamp && info.timestamp) {
@@ -112,17 +185,24 @@ class TerminalFormat {
/**
* Sends logs over a lib/link connection.
- * @static
*/
-class LinkTransport extends Transport {
- constructor(options) {
+export class LinkTransport extends Transport {
+ link: Link;
+ filter?: (info: any) => boolean;
+
+ constructor(
+ options: Transport.TransportStreamOptions & {
+ link: Link,
+ filter?: (info: any) => boolean,
+ }
+ ) {
super(options);
this.link = options.link;
- this.filter = options.filter || null;
+ this.filter = options.filter;
}
- log(info, callback) {
+ log(info: any, callback: Function) {
if (this.filter && !this.filter(info)) {
return callback();
}
@@ -144,18 +224,26 @@ const logIndexVersion = 2;
/**
* Keeps an index over a log directory to speed up queries to it
- * @static
*/
-class LogIndex {
- constructor(logDirectory, serialized) {
- this.logDirectory = logDirectory;
+export class LogIndex {
+ index: Map,
+ controller: boolean,
+ hostIds: Set,
+ instanceIds: Set,
+ }>;
+
+ constructor(
+ public logDirectory: string,
+ serialized: any,
+ ) {
this.index = new Map();
if (serialized.version !== logIndexVersion) {
return;
}
- for (let [file, serializedEntry] of Object.entries(serialized.files)) {
+ for (let [file, serializedEntry] of Object.entries(serialized.files) as [string, any][]) {
this.index.set(file, {
levels: new Set(serializedEntry.levels),
controller: serializedEntry.controller,
@@ -190,13 +278,13 @@ class LogIndex {
* data about previously indexed files saved with {@link
* module:lib.LogIndex#save}.
*
- * @param {string} logDirectory - Path to directory to load from.
- * @returns {Promise} loaded or created log index.
+ * @param logDirectory - Path to directory to load from.
+ * @returns loaded or created log index.
*/
- static async load(logDirectory) {
+ static async load(logDirectory: string) {
try {
let content = await fs.readFile(path.join(logDirectory, "index.json"));
- return new LogIndex(logDirectory, JSON.parse(content));
+ return new LogIndex(logDirectory, JSON.parse(content.toString()));
} catch (err) {
if (err.code !== "ENOENT") {
logger.warn(`Failed to load ${path.join(logDirectory, "index.json")}: ${err}`);
@@ -218,19 +306,19 @@ class LogIndex {
);
}
- async indexFile(file) {
+ async indexFile(file: string) {
let filePath = path.join(this.logDirectory, file);
let lineStream = new libStream.LineSplitter({ readableObjectMode: true });
let fileStream = fs.createReadStream(filePath);
fileStream.pipe(lineStream);
let entry = {
- levels: new Set(),
+ levels: new Set() as Set,
controller: false,
- hostIds: new Set(),
- instanceIds: new Set(),
+ hostIds: new Set() as Set,
+ instanceIds: new Set() as Set,
};
for await (let line of lineStream) {
- let info;
+ let info: any;
try {
info = JSON.parse(line);
} catch (err) {
@@ -273,14 +361,14 @@ class LogIndex {
/**
* Check if a file includes entries for the given filter
*
- * @param {string} file -
+ * @param file -
* Name of file in the log directory to check for.
- * @param {LogFilter} filter -
+ * @param filter -
* Filter to check index if file contains entries for.
- * @returns {boolean}
+ * @returns
* true if the file may contain entries included by the filter.
*/
- filterIncludesFile(file, { maxLevel, all, controller, instanceIds, hostIds }) {
+ filterIncludesFile(file: string, { maxLevel, all, controller, instanceIds, hostIds }: LogFilter) {
let entry = this.index.get(file);
if (!entry) {
return true;
@@ -314,35 +402,51 @@ class LogIndex {
/**
* Filter object for querying logs
- * @typedef {Object} QueryLogFilter
- * @property {number} [limit=100] - Maximum number of entries to return.
- * @property {string} [order="asc"] -
- * Return entries in ascending ("asc") date order or desceding ("desc")
- * date order.
- * @property {string} [maxLevel] -
- * Maximum log level to include. Higher levels are more verbose.
- * @property {boolean} [all] -
- * Include log entries from controller, all hosts and all instances.
- * @property {boolean} [controller] -
- * Include log entries from the controller.
- * @property {Array} [hostIds] -
- * Include log entries for the given hosts and instances of those
- * hosts by id.
- * @property {Array} [instanceIds] -
- * Include log entries for the given instances by id.
*/
+export interface QueryLogFilter {
+ /**
+ * Maximum number of entries to return. Defaults to 100.
+ */
+ limit?: number;
+ /**
+ * Return entries in ascending ("asc") date order or desceding ("desc")
+ * date order. Defaults to "asc".
+ */
+ order: "asc" | "desc";
+ /**
+ * Maximum log level to include. Higher levels are more verbose.
+ */
+ maxLevel: keyof typeof levels;
+ /**
+ * Include log entries from controller, all hosts and all instances.
+ */
+ all: boolean;
+ /**
+ * Include log entries from the controller.
+ */
+ controller: boolean;
+ /**
+ * Include log entries for the given hosts and instances of those
+ * hosts by id.
+ */
+ hostIds: number[];
+ /**
+ * Include log entries for the given instances by id.
+ */
+ instanceIds: number[];
+}
/**
* Query log directory
*
- * @param {string} logDirectory - path to directory with logs.
- * @param {QueryLogFilter} filter -
+ * @param logDirectory - path to directory with logs.
+ * @param filter -
* Filter to limit logs by.
- * @param {module:lib.LogIndex=} index -
+ * @param index -
* Index to speed up query with.
- * @returns {Promise>} log entries matching the filter
+ * @returns log entries matching the filter
*/
-async function queryLog(logDirectory, filter, index) {
+export async function queryLog(logDirectory: string, filter: QueryLogFilter, index: LogIndex) {
let files = (await fs.readdir(logDirectory)).filter(entry => logFileGlob.test(entry));
filter = {
@@ -363,8 +467,8 @@ async function queryLog(logDirectory, filter, index) {
continue;
}
let filePath = path.join(logDirectory, file);
- let fileStream;
- let lineStream;
+ let fileStream: fs.ReadStream;
+ let lineStream: libStream.LineSplitter;
if (filter.order === "asc") {
lineStream = new libStream.LineSplitter({ readableObjectMode: true });
fileStream = fs.createReadStream(filePath);
@@ -373,7 +477,7 @@ async function queryLog(logDirectory, filter, index) {
fileStream = await libStream.createReverseReadStream(filePath);
}
lineStream.on("data", line => {
- let info;
+ let info: any;
try {
info = JSON.parse(line);
} catch (err) {
@@ -403,27 +507,18 @@ async function queryLog(logDirectory, filter, index) {
return log;
}
-function handleUnhandledErrors() {
+export function handleUnhandledErrors() {
/* eslint-disable node/no-process-exit */
process.on("uncaughtException", err => {
logger.fatal(`Uncaught exception:\n${err.stack}`);
process.exit(1);
});
- process.on("unhandledRejection", (reason, promise) => {
+ process.on("unhandledRejection", (reason: Error, _promise) => {
logger.fatal(`Unhandled rejection:\n${reason.stack ? reason.stack : reason}`);
process.exit(1);
});
/* eslint-enable node/no-process-exit */
}
-const _formatServerOutput = formatServerOutput;
-module.exports = {
- TerminalFormat,
- LinkTransport,
- LogIndex,
- handleUnhandledErrors,
- queryLog,
-
- // for testing only
- _formatServerOutput,
-};
+// for testing only
+export const _formatServerOutput = formatServerOutput;
diff --git a/packages/lib/src/lua_tools.js b/packages/lib/src/lua_tools.ts
similarity index 64%
rename from packages/lib/src/lua_tools.js
rename to packages/lib/src/lua_tools.ts
index 086af446..b9dbba13 100644
--- a/packages/lib/src/lua_tools.js
+++ b/packages/lib/src/lua_tools.ts
@@ -2,15 +2,14 @@
* Utilities for dealing with Lua
* @module lib/lua_tools
*/
-"use strict";
/**
* Escapes a string for inclusion into a lua string
*
- * @param {string} content - String to escape.
- * @returns {string} Escaped string.
+ * @param content - String to escape.
+ * @returns Escaped string.
*/
-function escapeString(content) {
+export function escapeString(content: string) {
return content
.replace(/\\/g, "\\\\")
.replace(/"/g, '\\"')
@@ -20,7 +19,3 @@ function escapeString(content) {
.replace(/\r/g, "\\r")
;
}
-
-module.exports = {
- escapeString,
-};
diff --git a/packages/lib/src/plugin.js b/packages/lib/src/plugin.ts
similarity index 60%
rename from packages/lib/src/plugin.js
rename to packages/lib/src/plugin.ts
index 74accdbb..7ccf4b20 100644
--- a/packages/lib/src/plugin.js
+++ b/packages/lib/src/plugin.ts
@@ -2,18 +2,61 @@
* Plugin interfaces and utilities.
* @module lib/plugin
*/
-"use strict";
+import * as libHelpers from "./helpers";
+import type { Logger } from "./logging";
+import type { ConfigGroup } from "./config";
+import type { CollectorResult } from "./prometheus";
+import type { ParsedFactorioOutput } from "./logging_utils";
+import type { ModPack, ModInfo } from "./data";
+import type { Link } from "./link";
+import type { CommandTree } from "./command";
-const helpers = require("./helpers");
-
-/* TODO fix when converting to ts
+/**
* Conceptual base for controller and instance plugins.
- * @typedef {
- * BaseControllerPlugin
- * |BaseInstancePlugin
- * |BaseControlPlugin
- * } BasePlugin
*/
+export type BasePlugin =
+ | BaseControllerPlugin
+ | BaseInstancePlugin
+ | BaseControlPlugin
+ | BaseWebPlugin
+;
+
+// TODO Add proper typing for plugins
+export type PluginInfo = any;
+type Controller = any;
+type Instance = any;
+type Host = any;
+type InstanceInfo = any;
+type HostConnection = any;
+type ControlConnection = any;
+type Event = any;
+type React = any;
+
+/**
+ * Information about the event.
+ */
+interface PlayerEvent {
+ type: "join" | "leave" | "import";
+ /** Name of the player that joined/left */
+ name: string,
+ /**
+ * Only present for type "leave". Reason for player leaving the
+ * game, one of the possible reasons in defines.disconnect_reason
+ * or "server_quit" if the server exits while the player is online.
+ */
+ reason?: string;
+}
+type InstanceStatus =
+ | "unassigned"
+ | "unknown"
+ | "stopped"
+ | "starting"
+ | "running"
+ | "stopping"
+ | "creating_save"
+ | "exporting_data"
+ | "deleted"
+;
/**
* Base class for instance plugins
@@ -27,40 +70,43 @@ const helpers = require("./helpers");
* Instances may be started and stopped many times, and many instances may
* be running at the same time, each of which will have their own instance
* of the InstancePlugin class.
- *
- * @static
*/
-class BaseInstancePlugin {
- constructor(info, instance, host) {
+export class BaseInstancePlugin {
+ /**
+ * Logger for this plugin
+ *
+ * Instance of winston Logger for sending log messages from this
+ * plugin. Supported methods and their corresponding log levels are
+ * `error`, `warn`, `audit`, `info` and `verbose`.
+ */
+ logger: Logger;
+
+ private _pendingRconMessages: {
+ resolve: (result: string) => void,
+ reject: (err: Error) => void,
+ message: string,
+ expectEmpty: boolean,
+ }[] = [];
+ private _sendingRconMessages = false;
+
+ constructor(
/**
* The plugin's own info module
*/
- this.info = info;
-
+ public info: PluginInfo,
/**
* Instance the plugin started for
- * @type {module:host/src/Instance}
*/
- this.instance = instance;
-
+ public instance: Instance,
/**
* Host running the instance
*
* With the exepction of accessing the host's config you should
* avoid ineracting with the host object directly.
- *
- * @type {module:host/src/Host}
*/
- this.host = host;
-
- /**
- * Logger for this plugin
- *
- * Instance of winston Logger for sending log messages from this
- * plugin. Supported methods and their corresponding log levels are
- * `error`, `warn`, `audit`, `info` and `verbose`.
- */
- this.logger = instance.logger.child({ plugin: this.info.name });
+ public host: Host,
+ ) {
+ this.logger = instance.logger.child({ plugin: this.info.name }) as unknown as Logger;
this._pendingRconMessages = [];
this._sendingRconMessages = false;
@@ -77,12 +123,12 @@ class BaseInstancePlugin {
* Invoked after the value of the config field given by `field` has
* changed.
*
- * @param {module:lib.ConfigGroup} group -
+ * @param group -
* The group who's field got changed.
- * @param {string} field - Name of the field that changed.
- * @param {*} prev - The previous value of the field.
+ * @param field - Name of the field that changed.
+ * @param prev - The previous value of the field.
*/
- async onInstanceConfigFieldChanged(group, field, prev) { }
+ async onInstanceConfigFieldChanged(group: ConfigGroup, field: string, prev: unknown) { }
/**
* Called before collecting Prometheus metrics
@@ -97,9 +143,9 @@ class BaseInstancePlugin {
* the Gauges/Counters/etc in onMetric and let Clusterio deal will
* collecting the values.
*
- * @returns {*} an async iterator of prometheus metric results or undefined.
+ * @returns an async iterator of prometheus metric results or undefined.
*/
- async onMetrics() { }
+ async onMetrics(): Promise> { }
/**
* Called after the Factorio server is started
@@ -130,24 +176,10 @@ class BaseInstancePlugin {
* stderr. The output is a parsed form of the line and contains the
* following properties:
*
- * @param {Object} parsed - parsed server output.
- * @param {string} parsed.source -
- * Where the message came from, one of "stdout" and "stderr".
- * @param {string} parsed.format -
- * Timestamp format, one of "date", "seconds" and "none".
- * @param {string=} parsed.time -
- * Timestamp of the message. Not present if format is "none".
- * @param {string} parsed.type -
- * Type of message, one of "log", "action" and "generic".
- * @param {string=} parsed.level -
- * Log level for "log" type. i.e "Info" normally.
- * @param {string=} parsed.file - File reported for "log" type.
- * @param {string=} parsed.action -
- * Kind of action for "action" type. i.e "CHAT" for chat.
- * @param {string} parsed.message - Main content of the line.
- * @param {string} line - raw line of server output.
+ * @param parsed - parsed server output.
+ * @param line - raw line of server output.
*/
- async onOutput(parsed, line) { }
+ async onOutput(parsed: ParsedFactorioOutput, line: string) { }
/**
* Called when an event on the controller connection happens
@@ -181,9 +213,9 @@ class BaseInstancePlugin {
* send any messages that goes to or via the controller after the
* connection has been closed and before a new one is established.
*
- * @param {string} event - one of connect, drop, resume and close
+ * @param event - one of connect, drop, resume and close
*/
- onControllerConnectionEvent(event) { }
+ onControllerConnectionEvent(event: "connect" | "drop" | "resume" | "close") { }
/**
* Called when the controller is preparing to disconnect from the host
@@ -195,25 +227,19 @@ class BaseInstancePlugin {
* Plugins must stop sending messages to the controller, or are forwarded
* via the controller after the prepare disconnect has been handled.
*
- * @param {module:controller/src/HostConnection} connection -
+ * @param connection -
* The connection to the host preparing to disconnect.
*/
- async onPrepareControllerDisconnect(connection) { }
+ async onPrepareControllerDisconnect(connection: HostConnection) { }
/**
* Called when a player joins or leaves the game
*
* Invoked when a player either joins or leaves the instance.
*
- * @param {Object} event - Information about the event.
- * @param {string} event.type - Either "join", "leave" or "import".
- * @param {string} event.name - Name of the player that joined/left.
- * @param {string=} event.reason -
- * Only present for type "leave". Reason for player leaving the
- * game, one of the possible reasons in defines.disconnect_reason
- * or "server_quit" if the server exits while the player is online.
+ * @param event - Information about the event.
*/
- async onPlayerEvent(event) { }
+ async onPlayerEvent(event: PlayerEvent) { }
/**
* Send RCON message to instance
@@ -227,13 +253,13 @@ class BaseInstancePlugin {
* running some time after `onStart` and some time before `onStop` is
* invoked.
*
- * @param {string} message - message to send to server over RCON.
- * @param {boolean=} [expectEmpty=false] -
+ * @param message - message to send to server over RCON.
+ * @param expectEmpty -
* if true throw if the response is not empty. Useful for detecting
* errors that might have been sent in response.
- * @returns {Promise} response from server.
+ * @returns response from server.
*/
- async sendRcon(message, expectEmpty=false) {
+ async sendRcon(message: string, expectEmpty=false): Promise {
return await this.instance.sendRcon(message, expectEmpty, this.info.name);
}
@@ -252,14 +278,14 @@ class BaseInstancePlugin {
* running some time after `onStart` and some time before `onStop` is
* invoked.
*
- * @param {string} message - message to send to server over RCON.
- * @param {boolean=} [expectEmpty=false] -
+ * @param message - message to send to server over RCON.
+ * @param [expectEmpty=false] -
* if true throw if the response is not empty. Useful for detecting
* errors that might have been sent in response.
- * @returns {Promise} response from server.
+ * @returns response from server.
*/
- async sendOrderedRcon(message, expectEmpty=false) {
- let promise = new Promise((resolve, reject) => {
+ async sendOrderedRcon(message: string, expectEmpty=false) {
+ let promise = new Promise((resolve, reject) => {
this._pendingRconMessages.push({resolve, reject, message, expectEmpty});
});
if (!this._sendingRconMessages) {
@@ -291,28 +317,24 @@ class BaseInstancePlugin {
* To be discovered the class must be exported under the name `ControllerPlugin`
* in the module specified by the `controllerEntrypoint` in the plugin's info.js
* file.
- *
- * @static
*/
-class BaseControllerPlugin {
- constructor(info, controller, metrics, logger) {
- this.info = info;
+export class BaseControllerPlugin {
+ /**
+ * Logger for this plugin
+ *
+ * Instance of winston Logger for sending log messages from this
+ * plugin. Supported methods and their corresponding log levels are
+ * `error`, `warn`, `audit`, `info` and `verbose`.
+ */
+ logger: Logger;
- /**
- * Controller
- * @type {module:controller/src/Controller}
- */
- this.controller = controller;
- this.metrics = metrics;
-
- /**
- * Logger for this plugin
- *
- * Instance of winston Logger for sending log messages from this
- * plugin. Supported methods and their corresponding log levels are
- * `error`, `warn`, `audit`, `info` and `verbose`.
- */
- this.logger = logger.child({ plugin: this.info.name });
+ constructor(
+ public info: PluginInfo,
+ public controller: Controller,
+ public metrics: any[],
+ logger: Logger
+ ) {
+ this.logger = logger.child({ plugin: this.info.name }) as unknown as Logger;
}
/**
@@ -354,11 +376,11 @@ class BaseControllerPlugin {
* the case of network outages if reconnect fails to re-establish the
* session between the controller and the host.
*
- * @param {module:controller/src/InstanceInfo} instance -
+ * @param instance -
* The instance that changed.
- * @param {?string} prev - the previous status of the instance.
+ * @param prev - the previous status of the instance.
*/
- async onInstanceStatusChanged(instance, prev) { }
+ async onInstanceStatusChanged(instance: InstanceInfo, prev?: InstanceStatus) { }
/**
* Called when the value of a controller config field changed.
@@ -366,12 +388,12 @@ class BaseControllerPlugin {
* Invoked after the value of the config field given by `field` has
* changed on the controller.
*
- * @param {module:lib.ConfigGroup} group -
+ * @param group -
* The group who's field got changed.
- * @param {string} field - Name of the field that changed.
- * @param {*} prev - The previous value of the field.
+ * @param field - Name of the field that changed.
+ * @param prev - The previous value of the field.
*/
- async onControllerConfigFieldChanged(group, field, prev) { }
+ async onControllerConfigFieldChanged(group: ConfigGroup, field: string, prev: unknown) { }
/**
* Called when the value of an instance config field changed.
@@ -379,14 +401,14 @@ class BaseControllerPlugin {
* Invoked after the value of the config field given by `field` has
* changed on an instance.
*
- * @param {module:controller/src/InstanceInfo} instance -
+ * @param instance -
* The instance the config changed on.
- * @param {module:lib.ConfigGroup} group -
+ * @param group -
* The group who's field got changed.
- * @param {string} field - Name of the field that changed.
- * @param {*} prev - The previous value of the field.
+ * @param field - Name of the field that changed.
+ * @param prev - The previous value of the field.
*/
- async onInstanceConfigFieldChanged(instance, group, field, prev) { }
+ async onInstanceConfigFieldChanged(instance: InstanceInfo, group: ConfigGroup, field: string, prev: unknown) { }
/**
* Called before collecting Prometheus metrics
@@ -401,9 +423,9 @@ class BaseControllerPlugin {
* the Gauges/Counters/etc in onMetric and let Clusterio deal will
* collecting the values.
*
- * @returns {*} an async iterator of prometheus metric results or undefined.
+ * @returns an async iterator of prometheus metric results or undefined.
*/
- async onMetrics() { }
+ async onMetrics(): Promise> { }
/**
* Called when the controller is shutting down
@@ -439,11 +461,11 @@ class BaseControllerPlugin {
*
* Invoked when the connection to the host has been closed.
*
- * @param {module:controller/src/HostConnection} connection -
+ * @param connection -
* The connection the event occured on.
- * @param {string} event - one of connect, drop, resume and close
+ * @param event - one of connect, drop, resume and close
*/
- onHostConnectionEvent(connection, event) { }
+ onHostConnectionEvent(connection: HostConnection, event: "connect" | "drop" | "resume" | "close") { }
/**
* Called when an avent on a control connection happens
@@ -475,11 +497,11 @@ class BaseControllerPlugin {
*
* Invoked when the connection to the control has been closed.
*
- * @param {module:controller/src/ControlConnection} connection -
+ * @param connection -
* The connection the event occured on.
- * @param {string} event - one of connect, drop, resume, and close.
+ * @param event - one of connect, drop, resume, and close.
*/
- onControlConnectionEvent(connection, event) { }
+ onControlConnectionEvent(connection: ControlConnection, event: "connect" | "drop" | "resume" | "close") { }
/**
* Called when a host is preparing to disconnect from the controller
@@ -490,10 +512,10 @@ class BaseControllerPlugin {
* Plugins must stop sending messages to the host in question after the
* prepare disconnect has been handled.
*
- * @param {module:controller/src/HostConnection} connection -
+ * @param connection -
* The connection to the host preparing to disconnect.
*/
- async onPrepareHostDisconnect(connection) { }
+ async onPrepareHostDisconnect(connection: HostConnection) { }
/**
* Called when a mod pack is updated
@@ -504,9 +526,9 @@ class BaseControllerPlugin {
* If the mod pack has been deleted its `.isDeleted` property will be
* true.
*
- * @param {module:lib.ModPack} modPack - Mod pack that updated.
+ * @param modPack - Mod pack that updated.
*/
- async onModPackUpdated(modPack) { }
+ async onModPackUpdated(modPack: ModPack) { }
/**
* Called when a mod stored on the controller is updated
@@ -516,9 +538,9 @@ class BaseControllerPlugin {
*
* If a mod has been deleted its `.isDeleted` property will be true.
*
- * @param {module:lib.ModInfo} mod - Mod that updated.
+ * @param mod - Mod that updated.
*/
- async onModUpdated(mod) { }
+ async onModUpdated(mod: ModInfo) { }
/**
* Called when a player joins or leaves an instance
@@ -526,17 +548,11 @@ class BaseControllerPlugin {
* Invoked when a player either joins or leaves an instance in the
* cluster.
*
- * @param {module:controller/src/InstanceInfo} instance -
+ * @param instance -
* The instance it occured on.
- * @param {Object} event - Information about the event.
- * @param {string} event.type - Either "join", "leave" or "import".
- * @param {string} event.name - Name of the player that joined/left.
- * @param {string=} event.reason -
- * Only present for type "leave". Reason for player leaving the
- * game, one of the possible reasons in defines.disconnect_reason
- * or "server_quit" if the server exits while the player is online.
+ * @param event - Information about the event.
*/
- async onPlayerEvent(instance, event) { }
+ async onPlayerEvent(instance: InstanceInfo, event: PlayerEvent) { }
/**
* Broadcast event to all connected hosts
@@ -545,20 +561,15 @@ class BaseControllerPlugin {
* This does not include hosts that are in the process of closing the
* connection, which typically happens when they are shutting down.
*
- * @param {*} event - Event to send
- * @param {Object} data - Data ta pass with the event.
+ * @param event - Event to send
*/
- broadcastEventToHosts(event, data={}) {
- if (event.type !== "event") {
- this.logger.warn(`Ignoring broadcast of ${event.type}`);
- return;
- }
+ broadcastEventToHosts(event: Event) {
for (let hostConnection of this.controller.wsServer.hostConnections.values()) {
if (
!hostConnection.connector.closing
- && (!event.plugin || hostConnection.plugins.has(event.plugin))
+ && (!event.constructor.plugin || hostConnection.plugins.has(event.constructor.plugin))
) {
- event.send(hostConnection, data);
+ hostConnection.send(event);
}
}
}
@@ -571,24 +582,25 @@ class BaseControllerPlugin {
* clusterioctl in order to extend its functionallity. To be discovered the
* class must be exported under the name `ControlPlugin` in the module
* specified by the `controlEntrypoint` in the plugin's info.js file.
- *
- * @static
*/
-class BaseControlPlugin {
- constructor(info, logger) {
+export class BaseControlPlugin {
+ /**
+ * Logger for this plugin
+ *
+ * Instance of winston Logger for sending log messages from this
+ * plugin. Supported methods and their corresponding log levels are
+ * `error`, `warn`, `audit`, `info` and `verbose`.
+ */
+ logger: Logger;
+
+ constructor(
/**
* The plugin's own info module
*/
- this.info = info;
-
- /**
- * Logger for this plugin
- *
- * Instance of winston Logger for sending log messages from this
- * plugin. Supported methods and their corresponding log levels are
- * `error`, `warn`, `audit`, `info` and `verbose`.
- */
- this.logger = logger.child({ plugin: this.info.name });
+ public info: PluginInfo,
+ logger: Logger,
+ ) {
+ this.logger = logger.child({ plugin: this.info.name }) as unknown as Logger;
}
/**
@@ -601,136 +613,153 @@ class BaseControlPlugin {
*
* Invoked by clusterioctl to let plugins add commands. `rootCommand` is
* the top level command node which the plugin should add its own {@link
- * module:lib.CommandTree} to.
+ * CommandTree} to.
*
- * @param {module:lib.CommandTree} rootCommand -
+ * @param rootCommand -
* Root of the clusterioctl command tree.
*/
- async addCommands(rootCommand) { }
+ async addCommands(rootCommand: CommandTree) { }
}
+
/**
* Plugin supplied login form
- * @typedef {Object} LoginForm
- * @property {string} name -
- * Internal name of the login form, this should start with the
- * plugin name followed by a dot.
- * @property {string} title -
- * Name displayed above this form in the login window.
- * @property {React.Component} Component -
- * React component that's rendered for this login form. This is
- * supplied the setToken function via its props which should be
- * called when an authentication token is aquired via this
- * form.
*/
+export interface PluginLoginForm {
+ /**
+ * Internal name of the login form, this should start with the
+ * plugin name followed by a dot.
+ */
+ name: string;
+
+ /** Name displayed above this form in the login window. */
+ title: string;
+
+ /**
+ * React component that's rendered for this login form. This is
+ * supplied the setToken function via its props which should be called
+ * when an authentication token is aquired via this form.
+ */
+ Component: React["Component"];
+};
+
/**
* Plugin supplied pages
- * @typedef {Object} Page
- * @property {string} path - URL path to this page.
- * @property {string=} sidebarPath -
- * If present and this path matches one of the pages in the sidebar it
- * will cause that sidebar entry to be highlighted as active.
- * @property {string=} sidebarGroup -
- * If present group this entry under a group of the given name in the
- * sidebar.
- * @property {string=} sidebarName -
- * If present creates an entry in the sidebar for this page with the
- * given text.
- * @property {ReactNode} content -
- * A react node which is rendered when this page is navigated to.
- * Should render a PageLayout.
*/
+export interface PluginPage {
+ /** URL path to this page. */
+ path: string;
+ /**
+ * If present and this path matches one of the pages in the sidebar it
+ * will cause that sidebar entry to be highlighted as active.
+ */
+ sidebarPath?: string;
+ /**
+ * If present group this entry under a group of the given name in the
+ * sidebar.
+ */
+ sidebarGroup?: string;
+ /**
+ * If present creates an entry in the sidebar for this page with the
+ * given text.
+ */
+ sidebarName?: string;
+ /**
+ * A react node which is rendered when this page is navigated to.
+ * Should render a PageLayout.
+ */
+ content?: React["ReactNode"];
+};
/**
* Base class for web interface plugins
- *
- * @static
*/
-class BaseWebPlugin {
- constructor(container, packageData, info, logger) {
+export class BaseWebPlugin {
+ /**
+ * Contents of the plugin's package.json file
+ */
+ package: any;
+ /**
+ * Control link to the controller, not available until the
+ * connect event in onControllerConnectionEvent is signaled.
+ */
+ control?: Link;
+ /**
+ * Logger for this plugin
+ *
+ * Instance of winston Logger for sending log messages from this
+ * plugin. Supported methods and their corresponding log levels are
+ * `error`, `warn`, `audit`, `info` and `verbose`.
+ */
+ logger: Logger;
+ /**
+ * List of login forms provided by this plugin
+ */
+ loginForms: PluginLoginForm[] = [];
+ /**
+ * List of pages provided by this plugin
+ */
+ pages: PluginPage[] = [];
+ /**
+ * Extra react component to add to core components
+ *
+ * Interface to augment core components of the web UI. Setting a
+ * component as one of the supported properties of this object will
+ * cause the web UI to render it when displaying that component,
+ * usually at the end. Each component will receive a `plugin` param
+ * which is the instance of the web plugin that contained the
+ * component extra.
+ */
+ componentExtra: {
+ /** Placed at the end of the controller page. */
+ ControllerPage?: React["ComponentType"],
+ /** Placed at the end of the hosts list page. */
+ HostsPage?: React["ComponentType"],
+ /**
+ * Placed at the end of each host page. Takes a `host` param which
+ * is the host the page is displayed for.
+ */
+ HostViewPage?: React["ComponentType"],
+ /** Placed at the end of the instance list page. */
+ InstancesPage?: React["ComponentType"],
+ /**
+ * Placed at the end of each instance page. Takes an `instance`
+ * param which is the instance the page is displayed for.
+ */
+ InstanceViewPage?: React["ComponentType"],
+ /** Placed at the end of the users list page. */
+ UsersPage?: React["ComponentType"],
+ /**
+ * Placed at the end of each user page. Takes a `user` param which
+ * is the user object the page is displayed for.
+ */
+ UserViewPage?: React["ComponentType"],
+ /** Placed at the end of the roles list page. */
+ RolesPage?: React["ComponentType"],
+ /**
+ * Placed at the end of each role page. Takes a `role` param which
+ * is the role object the page is displayed for.
+ */
+ RoleViewPage?: React["ComponentType"],
+ } = {};
+
+
+ constructor(
/**
* Webpack container for this plugin
*/
- this.container = container;
-
- /**
- * Contents of the plugin's package.json file
- * @type {Object}
- */
- this.package = packageData;
-
+ public container: any,
+ packageData: any,
/**
* The plugin's own info module
*/
- this.info = info;
-
- /**
- * Control link to the controller, not available until the
- * connect event in onControllerConnectionEvent is signaled.
- * @type {?module:lib.Link}
- */
- this.control = null;
-
- /**
- * Logger for this plugin
- *
- * Instance of winston Logger for sending log messages from this
- * plugin. Supported methods and their corresponding log levels are
- * `error`, `warn`, `audit`, `info` and `verbose`.
- */
- this.logger = logger.child({ plugin: this.info.name });
-
- /**
- * List of login forms provided by this plugin
- *
- * @type {Array}
- */
- this.loginForms = [];
-
- /**
- * List of pages provided by this plugin
- *
- * @type {Array}
- */
- this.pages = [];
-
- /**
- * Extra react component to add to core components
- *
- * Interface to augment core components of the web UI. Setting a
- * component as one of the supported properties of this object will
- * cause the web UI to render it when displaying that component,
- * usually at the end. Each component will receive a `plugin` param
- * which is the instance of the web plugin that contained the
- * component extra.
- *
- * @type {object}
- * @property {React.ComponentType} ControllerPage -
- * Placed at the end of the controller page.
- * @property {React.ComponentType} HostsPage -
- * Placed at the end of the hosts list page.
- * @property {React.ComponentType} HostViewPage -
- * Placed at the end of each host page. Takes a `host` param
- * which is the host the page is displayed for.
- * @property {React.ComponentType} InstancesPage -
- * Placed at the end of the instance list page.
- * @property {React.ComponentType} InstanceViewPage -
- * Placed at the end of each instance page. Takes an `instance`
- * param which is the instance the page is displayed for.
- * @property {React.ComponentType} UsersPage -
- * Placed at the end of the users list page.
- * @property {React.ComponentType} UserViewPage -
- * Placed at the end of each user page. Takes a `user` param
- * which is the user object the page is displayed for.
- * @property {React.ComponentType} RolesPage -
- * Placed at the end of the roles list page.
- * @property {React.ComponentType} RoleViewPage -
- * Placed at the end of each role page. Takes a `role` param
- * which is the role object the page is displayed for.
- */
- this.componentExtra = {};
+ public info: PluginInfo,
+ logger: Logger,
+ ) {
+ this.package = packageData;
+ this.logger = logger.child({ plugin: this.info.name }) as unknown as Logger;
}
/**
@@ -770,27 +799,27 @@ class BaseWebPlugin {
* send any messages that goes to or via the controller after the
* connection has been closed and before a new one is established.
*
- * @param {string} event - one of connect, drop, resume and close
+ * @param event - one of connect, drop, resume and close
*/
- onControllerConnectionEvent(event) { }
+ onControllerConnectionEvent(event: "connect" | "drop" | "resume" | "close") { }
}
/**
* Invokes the given hook on all plugins
*
- * @param {Map} plugins -
+ * @param plugins -
* Mapping of plugin names to plugins to invoke the hook on.
- * @param {string} hook - Name of hook to invoke.
- * @param {...*} args - Arguments to pass on to the hook.
- * @returns {Promise} Non-empty return values from the hooks.
+ * @param hook - Name of hook to invoke.
+ * @param args - Arguments to pass on to the hook.
+ * @returns Non-undefined return values from the hooks.
*/
-async function invokeHook(plugins, hook, ...args) {
- let results = [];
+export async function invokeHook(plugins: Map, hook: string, ...args: any[]) {
+ let results: unknown[] = [];
for (let [name, plugin] of plugins) {
try {
// Use an object to detect if the hook failed on timeout or tried to return a value looking like an error
const timeout = {};
- let result = await helpers.timeout(
+ let result = await libHelpers.timeout(
plugin[hook](...args),
15000,
timeout
@@ -806,13 +835,3 @@ async function invokeHook(plugins, hook, ...args) {
}
return results;
}
-
-
-module.exports = {
- BaseInstancePlugin,
- BaseControllerPlugin,
- BaseControlPlugin,
- BaseWebPlugin,
-
- invokeHook,
-};
diff --git a/packages/lib/src/plugin_loader.js b/packages/lib/src/plugin_loader.ts
similarity index 66%
rename from packages/lib/src/plugin_loader.js
rename to packages/lib/src/plugin_loader.ts
index e97d7541..6771dd7b 100644
--- a/packages/lib/src/plugin_loader.js
+++ b/packages/lib/src/plugin_loader.ts
@@ -3,12 +3,10 @@
* Helpers for loading plugins in Node.js
* @module lib/plugin_loader
*/
-"use strict";
-
-const libErrors = require("./errors");
-const libPlugin = require("./plugin");
-const { logger } = require("./logging");
-const path = require("path");
+import path from "path";
+import * as libErrors from "./errors";
+import * as libPlugin from "./plugin";
+import { logger } from "./logging";
/**
@@ -18,16 +16,15 @@ const path = require("path");
* loaded the info modules will not be reloaded should this function be
* called again.
*
- * @param {Map} pluginList -
+ * @param pluginList -
* Mapping of plugin name to require path for the plugins to load.
- * @returns {Promise>} Array of plugin info modules.
- * @static
+ * @returns Array of plugin info modules.
*/
-async function loadPluginInfos(pluginList) {
- let plugins = [];
+export async function loadPluginInfos(pluginList: Map) {
+ let plugins: libPlugin.PluginInfo[] = [];
for (let [pluginName, pluginPath] of pluginList) {
- let pluginInfo;
- let pluginPackage;
+ let pluginInfo: libPlugin.PluginInfo;
+ let pluginPackage: { version: string };
try {
pluginInfo = require(path.posix.join(pluginPath, "info"));
@@ -56,7 +53,12 @@ async function loadPluginInfos(pluginList) {
return plugins;
}
-function loadPluginClass(entrypointName, className, pluginClass, pluginInfo) {
+function loadPluginClass(
+ entrypointName: string,
+ className: string,
+ pluginClass: any,
+ pluginInfo: libPlugin.PluginInfo
+) {
let resolvedPath = path.posix.join(pluginInfo.requirePath, pluginInfo[entrypointName]);
let entrypoint = require(resolvedPath);
if (!entrypoint[className]) {
@@ -75,45 +77,41 @@ function loadPluginClass(entrypointName, className, pluginClass, pluginInfo) {
/**
* Load controller plugin class of a plugin
*
- * @param {Object} pluginInfo -
+ * @param pluginInfo -
* Plugin info object returned from {@link
- * module:lib.loadPluginInfos} to load class from.
- * @returns {Promise} plugin class
- * @static
+ * loadPluginInfos} to load class from.
+ * @returns plugin class
*/
-async function loadControllerPluginClass(pluginInfo) {
+export async function loadControllerPluginClass(
+ pluginInfo: libPlugin.PluginInfo
+): Promise {
return loadPluginClass("controllerEntrypoint", "ControllerPlugin", libPlugin.BaseControllerPlugin, pluginInfo);
}
/**
* Load instance plugin class of a plugin
- *
- * @param {Object} pluginInfo -
+
+ * @param pluginInfo -
* Plugin info object returned from {@link
- * module:lib.loadPluginInfos} to load class from.
- * @returns {Promise} plugin class
- * @static
+ * loadPluginInfos} to load class from.
+ * @returns plugin class
*/
-async function loadInstancePluginClass(pluginInfo) {
+export async function loadInstancePluginClass(
+ pluginInfo: libPlugin.PluginInfo
+): Promise {
return loadPluginClass("instanceEntrypoint", "InstancePlugin", libPlugin.BaseInstancePlugin, pluginInfo);
}
/**
* Load control plugin class of a plugin
*
- * @param {Object} pluginInfo -
+ * @param pluginInfo -
* Plugin info object returned from {@link
- * module:lib.loadPluginInfos} to load class from.
- * @returns {Promise} plugin class
- * @static
+ * loadPluginInfos} to load class from.
+ * @returns plugin class
*/
-async function loadControlPluginClass(pluginInfo) {
+export async function loadControlPluginClass(
+ pluginInfo: libPlugin.PluginInfo
+): Promise {
return loadPluginClass("controlEntrypoint", "ControlPlugin", libPlugin.BaseControlPlugin, pluginInfo);
}
-
-module.exports = {
- loadPluginInfos,
- loadControllerPluginClass,
- loadInstancePluginClass,
- loadControlPluginClass,
-};
diff --git a/packages/lib/src/prometheus.js b/packages/lib/src/prometheus.ts
similarity index 76%
rename from packages/lib/src/prometheus.js
rename to packages/lib/src/prometheus.ts
index 7a69f391..93540e8c 100644
--- a/packages/lib/src/prometheus.js
+++ b/packages/lib/src/prometheus.ts
@@ -7,9 +7,8 @@
* metric collections in prom-client.
*
* The ordinary use case of instrumenting a code base should be covered by
- * {@link module:lib.Counter}, {@link
- * module:lib.Gauge}, {@link module:lib.Histogram} and
- * {@link module:lib.exposition}. See documentation for each of
+ * {@link Counter}, {@link Gauge}, {@link Histogram} and
+ * {@link exposition}. See documentation for each of
* the listed interfaces for more information.
*
* @example
@@ -39,21 +38,13 @@
* app.listen(9100);
* @module lib/prometheus
*/
-"use strict";
/**
- * Result from collecting a {@link module:lib.Collector}
- * @typedef {Object} CollectorResult
- * @property {module:lib.Metric} metric -
- * Metric collected.
- * @property {Map>} samples -
- * Mapping of metric suffix to mapping of label keys to values
- * collected. For normal metrics the first level contains a single
- * entry under the empty string as key. If the metric does not have
- * labels the second level also contains a single entry under the empty
- * string as a key.
+ * Result from collecting a {@link Collector}
*
- * @example Simple Metric
+ * @example
+ * Simple Metric
+ * ```ts
* let result = {
* metric: new Metric("count", "simple_total_count", "A simple counter"),
* samples: new Map([
@@ -62,8 +53,11 @@
* ])],
* ]),
* }
+ * ```
*
- * @example Labeled Metric
+ * @example
+ * Labeled Metric
+ * ```ts
* let result = {
* metric: new Metric(
* "count", "labeled_total_count", "A labeled counter", ["a", "b"]
@@ -76,8 +70,11 @@
* ])],
* ]),
* }
+ * ```
*
- * @example Histogram Metric
+ * @example
+ * Histogram Metric
+ * ```ts
* let result = {
* metric: new Metric(
* "histogram", "histogram_size", "A histogram of sizes"
@@ -97,25 +94,42 @@
* ]),
* }
*/
+export interface CollectorResult {
+ /** Metric collected. */
+ metric: Metric;
+ /**
+ * Mapping of metric suffix to mapping of label keys to values
+ * collected. For normal metrics the first level contains a single
+ * entry under the empty string as key. If the metric does not have
+ * labels the second level also contains a single entry under the empty
+ * string as a key.
+ */
+ samples: Map>;
+}
+
+export type MetricType = "counter" | "gauge" | "histogram" | "summary" | "untyped";
/**
* Represents a collectable metric
*
* Used in implementing collectors in order to validate the name, help text
* and labels attached to the collector.
- * @static
*/
-class Metric {
- /**
- * @param {string} type -
- * Metric type, should be one of `counter`, `gauge`, `histogram`,
- * `summary` or `untyped`,
- * @param {string} name - Name of the metric.
- * @param {string} help - Help text for the metric.
- * @param {Array} labels -
- * @param {Array} _reserved_labels -
- */
- constructor(type, name, help, labels = [], _reserved_labels = []) {
+export class Metric {
+ constructor(
+ /**
+ * Metric type, should be one of `counter`, `gauge`, `histogram`,
+ * `summary` or `untyped`.
+ */
+ public type: MetricType,
+ /** Name of the metric */
+ public name: string,
+ /** Help text for the metric */
+ public help: string,
+ /** Labels for this metric. */
+ public labels: string[] = [],
+ _reserved_labels: string[] = []
+ ) {
if (typeof name !== "string") {
throw new Error("Expected name to be a string");
}
@@ -135,40 +149,13 @@ class Metric {
throw new Error(`Reserved label '${label}'`);
}
}
-
- /**
- * Name of the metric
- * @type {string}
- */
- this.name = name;
-
- /**
- * Help text for the metric
- * @type {string}
- */
- this.help = help;
-
- /**
- * Metric type, should be one of `counter`, `gauge`, `histogram`,
- * `summary` or `untyped`.
- * @type {string}
- */
- this.type = type;
-
- /**
- * Labels for this metric.
- * @type {Array}
- */
- this.labels = labels;
}
}
/**
* The default registry which collectors are regististered to.
- * @type {module:lib.CollectorRegistry}
- * @static
*/
-let defaultRegistry;
+export let defaultRegistry: CollectorRegistry;
/**
* Base class for all collectors
@@ -176,16 +163,14 @@ let defaultRegistry;
* This servers mostly as a conceptual base for all collectors, the only
* feature implemented here is registring to the default registry on
* construction. If you want to implement a custom collector you most
- * likely want to base it on {@link module:lib.LabeledCollector}
+ * likely want to base it on {@link LabeledCollector}
* instead.
- *
- * @static
*/
-class Collector {
+export class Collector {
/**
* Create collector
*
- * @param {boolean=} register -
+ * @param register -
* Whether to register this collector to the default registry.
*/
constructor(register = true) {
@@ -197,23 +182,21 @@ class Collector {
/**
* Retrieve metric data from this collctor.
*
- * Called by {@link module:lib.CollectorRegistry} to gather
+ * Called by {@link CollectorRegistry} to gather
* the metric data this Collector exports.
- *
- * @returns {*} Async iterator of {@link CollectorResult}.
*/
- async* collect() { }
+ async* collect(): AsyncIterable { }
}
const labelEscapesChars = /[\\\n\"]/;
/**
* Escapes a label value in accordance with the text exposition format
*
- * @param {string} value - Value to escape.
- * @returns {string} escaped value with \, ", and newline escaped.
+ * @param value - Value to escape.
+ * @returns escaped value with \, ", and newline escaped.
* @private
*/
-function escapeLabelValue(value) {
+function escapeLabelValue(value: string) {
if (!labelEscapesChars.test(value)) {
return value;
}
@@ -224,32 +207,34 @@ function escapeLabelValue(value) {
;
}
+export type LabelValues = string[] | [Record];
+
/**
* Convert label expression to unique string
*
- * @param {Array<(string|Object)>} labels -
+ * @param labels -
* label values to compute key for.
- * @param {Array} metricLabels - labels defined for the metric.
- * @returns {string} computed key
+ * @param metricLabels - labels defined for the metric.
+ * @returns computed key
* @private
*/
-function labelsToKey(labels, metricLabels) {
+function labelsToKey(labels: LabelValues, metricLabels: string[]) {
let items = [];
if (labels.length === 1 && typeof labels[0] === "object") {
- labels = labels[0];
+ let labelObj = labels[0];
for (let name of metricLabels) {
- if (!Object.hasOwnProperty.call(labels, name)) {
+ if (!Object.hasOwnProperty.call(labelObj, name)) {
throw new Error(`Missing label '${name}'`);
}
- if (typeof labels[name] !== "string") {
+ if (typeof labelObj[name] !== "string") {
throw new Error(
`Expected value for label '${name}' to be a string`
);
}
- items.push(`${name}="${escapeLabelValue(labels[name])}"`);
+ items.push(`${name}="${escapeLabelValue(labelObj[name])}"`);
}
- for (let name of Object.keys(labels)) {
+ for (let name of Object.keys(labelObj)) {
if (!metricLabels.includes(name)) {
throw new Error(`Extra label '${name}'`);
}
@@ -265,12 +250,13 @@ function labelsToKey(labels, metricLabels) {
}
for (let i=0; i < metricLabels.length; ++i) {
- if (typeof labels[i] !== "string") {
+ let label = labels[i];
+ if (typeof label !== "string") {
throw new Error(
`Expected value for label '${metricLabels[i]}' to be a string`
);
}
- items.push(`${metricLabels[i]}="${escapeLabelValue(labels[i])}"`);
+ items.push(`${metricLabels[i]}="${escapeLabelValue(label)}"`);
}
}
@@ -281,8 +267,8 @@ function labelsToKey(labels, metricLabels) {
return items.join(",");
}
-function keyToLabels(key) {
- let labels = new Map();
+function keyToLabels(key: string) {
+ let labels = new Map();
if (key !== "") {
for (let pair of key.split(",")) {
@@ -299,7 +285,7 @@ function keyToLabels(key) {
return labels;
}
-function removeMatchingLabels(mapping, labels) {
+function removeMatchingLabels(mapping: Map, labels: Record) {
for (let key of mapping.keys()) {
let candidate = keyToLabels(key);
let hasLabels = Object.entries(labels).every(([name, value]) => (
@@ -318,55 +304,57 @@ function removeMatchingLabels(mapping, labels) {
* set is a child to the collector that can be retrieved with the .labels()
* method.
*
- * @extends module:lib.Collector
- * @static
+ * @extends Collector
*/
-class LabeledCollector extends Collector {
+export class LabeledCollector extends Collector {
+ callback: (collector: LabeledCollector) => void | Promise;
+ metric: Metric;
+
+ private _children: Map;
+ private _childClass: { new(collector: LabeledCollector, key: string): Child };
+
/**
* Create optionally labeled collector
*
- * @param {string} type - Type for metric.
- * @param {string} name - Name of the metric.
- * @param {string} help - Help text for metric.
- * @param {Object} options - options for collector.
- * @param {Array=} options.labels -
+ * @param type - Type for metric.
+ * @param name - Name of the metric.
+ * @param help - Help text for metric.
+ * @param options - options for collector.
+ * @param options.labels -
* Labels for this metric, defaults to no labels.
- * @param {Array=} options._reservedLabels -
+ * @param options._reservedLabels -
* Labels which may not be used. Is passed to Metric constructor.
- * @param {boolean=} options.register -
+ * @param options.register -
* If true registers this collector with the default registry.
* Defaults to true.
- * @param {function()=} options.callback -
+ * @param options.callback -
* Possibly async function that is called before the metric is
* collected. The collector being collected is passed as the
* argument.
- * @param {
- * function(module:lib.LabeledCollector,string)
- * } childClass -
+ * @param childClass -
* Constructor taking instance of collector and label key as
* arguments and returns a child instance.
*/
- constructor(type, name, help, options, childClass) {
- let labels = [];
- let reservedLabels = [];
- let register = true;
- let callback = null;
- for (let [key, value] of Object.entries(options)) {
- if (key === "labels") {
- labels = value;
- } else if (key === "_reservedLabels") {
- reservedLabels = value;
- } else if (key === "register") {
- register = value;
- } else if (key === "callback") {
- callback = value;
- } else {
- throw new Error(`Unrecognized option '${key}'`);
- }
+ constructor(
+ type: MetricType,
+ name: string,
+ help: string,
+ options: {
+ labels?: string[],
+ _reservedLabels?: string[],
+ register?: boolean,
+ callback?: (collector: LabeledCollector) => void | Promise,
+ },
+ childClass: { new(collector: LabeledCollector, key: string): Child }
+ ) {
+ const { labels = [], _reservedLabels = [], register = true, callback, ...rest } = options;
+
+ for (let key of Object.keys(rest)) {
+ throw new Error(`Unrecognized option '${key}'`);
}
// Make sure we don't register to the default registry if metric throws.
- let metric = new Metric(type, name, help, labels, reservedLabels);
+ let metric = new Metric(type, name, help, labels, _reservedLabels);
super(register);
@@ -387,13 +375,13 @@ class LabeledCollector extends Collector {
* exported from the collector until it's removed explicitly with
* the .remove() or .removeAll() methods.
*
- * @param {...string|Object} labels -
+ * @param labels -
* A string value passed for each label defined on the metric in the
* same order as the labels option given to the collector, or an
* object mapping label name to label value.
- * @returns {*} Child collector for the given labels.
+ * @returns Child collector for the given labels.
*/
- labels(...labels) {
+ labels(...labels: LabelValues): Child {
let key = labelsToKey(labels, this.metric.labels);
let child = this._children.get(key);
if (child === undefined) {
@@ -409,13 +397,13 @@ class LabeledCollector extends Collector {
* Remove the child collector and the value it stores from the collector
* itself. This will remove the entry exported for the given labels.
*
- * @param {...string|Object} labels -
+ * @param labels -
* A string value passed for each label defined on the metric in the
* same order as the labels option given to the collector, or an
* object mapping label name to label value.
- * @returns {string} key for labels to remove (for use in subclasses).
+ * @returns key for labels to remove (for use in subclasses).
*/
- remove(...labels) {
+ remove(...labels: LabelValues) {
let key = labelsToKey(labels, this.metric.labels);
if (key === "") {
throw new Error("labels cannot be empty");
@@ -436,7 +424,7 @@ class LabeledCollector extends Collector {
* Object mapping with label name to label values that should be
* matches.
*/
- removeAll(labels) {
+ removeAll(labels: Record) {
if (!Object.keys(labels).length) {
throw new Error("labels cannot be empty");
}
@@ -461,39 +449,46 @@ class LabeledCollector extends Collector {
/**
* Base class for implementing single value per label collectors
*
- * @extends module:lib.LabeledCollector
- * @static
+ * @extends LabeledCollector
*/
-class ValueCollector extends LabeledCollector {
+export class ValueCollector extends LabeledCollector {
+ _values: Map;
+ protected _defaultChild: Child | null;
+
/**
* Create optionally labeled value collector
*
- * @param {string} type - Type for metric.
- * @param {string} name - Name of the metric.
- * @param {string} help - Help text for metric.
- * @param {Object} options - options for collector.
- * @param {Array=} options.labels -
+ * @param type - Type for metric.
+ * @param name - Name of the metric.
+ * @param help - Help text for metric.
+ * @param options - options for collector.
+ * @param options.labels -
* Labels for this metric, defaults to no labels.
- * @param {Array=} options._reservedLabels -
+ * @param options._reservedLabels -
* Labels which may not be used. Is passed to Metric constructor.
- * @param {boolean=} options.register -
+ * @param options.register -
* If true registers this collector with the default registry.
* Defaults to true.
- * @param {function()=} options.callback -
+ * @param options.callback -
* Possibly async function that is called before the collector value
* is collected. The collector being collected is passed as the
* argument.
- * @param {
- * function(
- * new:module:lib.ValueCollectorChild,
- * module:lib.LabeledCollector,
- * string
- * )
- * } childClass -
+ * @param childClass -
* Constructor taking instance of collector and label key as
* arguments and returns a child instance.
*/
- constructor(type, name, help, options, childClass) {
+ constructor(
+ type: MetricType,
+ name: string,
+ help: string,
+ options: {
+ labels?: string[],
+ _reservedLabels?: string[],
+ register?: boolean,
+ callback?: (collector: ValueCollector) => void | Promise,
+ },
+ childClass: { new(collector: ValueCollector, key: string): Child }
+ ) {
super(type, name, help, options, childClass);
this._values = new Map();
@@ -509,18 +504,18 @@ class ValueCollector extends LabeledCollector {
* Get the current value for this collector.
*
* Note: Only works if this is an unlabeled collector.
- * @returns {number} value stored.
+ * @returns value stored.
*/
get() {
return this._defaultChild.get();
}
- remove(...labels) {
+ remove(...labels: LabelValues) {
let key = super.remove(...labels);
this._values.delete(key);
}
- removeAll(labels) {
+ removeAll(labels: Record) {
super.removeAll(labels);
removeMatchingLabels(this._values, labels);
}
@@ -535,7 +530,10 @@ class ValueCollector extends LabeledCollector {
* Child collector representing the value of a single label set
*/
class ValueCollectorChild {
- constructor(collector, key) {
+ protected _values: Map;
+ protected _key: string;
+
+ constructor(collector: ValueCollector, key: string) {
this._values = collector._values;
this._key = key;
@@ -545,8 +543,7 @@ class ValueCollectorChild {
/**
* Returns the current value of label set
*
- * Note: Only works if this is an unlabeled collector.
- * @returns {number} value stored.
+ * @returns value stored.
*/
get() {
return this._values.get(this._key);
@@ -555,12 +552,12 @@ class ValueCollectorChild {
/**
* Child counter holding the value for a single label set.
- * @extends module:lib~ValueCollectorChild
+ * @extends ValueCollectorChild
*/
class CounterChild extends ValueCollectorChild {
/**
* Increment counter for label set
- * @param {number} value - Positive number to increment by.
+ * @param value - Positive number to increment by.
*/
inc(value = 1) {
// Note: Inverted to also catch NaN
@@ -583,7 +580,7 @@ class CounterChild extends ValueCollectorChild {
* Counters should be created at module load time and referenced in the
* functions that increment them, for example:
*
- * ```js
+ * ```ts
* const totalRequests = new Counter(
* "app_request_count_total",
* "Total requests handled.",
@@ -596,10 +593,9 @@ class CounterChild extends ValueCollectorChild {
* ```
*
* The `totalRequests` counter will register with the default registry and
- * provided exposition is set up (see {@link
- * module:lib.exposition}) the counter will be exported to
- * Prometheus starting out with a value of 0. And that is all there is to
- * it.
+ * provided exposition is set up (see {@link exposition}) the counter will
+ * be exported to Prometheus starting out with a value of 0. And that is
+ * all there is to it.
*
* It is sometimes useful however to divide a metric up into diffrent
* sections, for example to have a different count for each endpoint handled
@@ -607,7 +603,7 @@ class CounterChild extends ValueCollectorChild {
* an error. For this Prometheus provides labels, and to use them pass the
* labels option as the third argument to Counter:
*
- * ```js
+ * ```ts
* const totalRequests = new Counter(
* "app_request_count_total",
* "Total requests handled.",
@@ -639,7 +635,7 @@ class CounterChild extends ValueCollectorChild {
* that will be used. This is done by calling `.labels()` for each
* combination, for example:
*
- * ```js
+ * ```ts
* for (let endpoint of allEndpoints) {
* for (let status of ["ok", "err"]) {
* totalRequests.labels(endpoint, status);
@@ -652,16 +648,15 @@ class CounterChild extends ValueCollectorChild {
* labels you actually need as resource usage for a metric increases
* exponentially with the number of labels used.
*
- * @extends module:lib.ValueCollector
- * @static
+ * @extends ValueCollector
*/
-class Counter extends ValueCollector {
+export class Counter extends ValueCollector {
/**
* Create optionally labeled counter
*
- * @param {string} name - Name of the metric.
- * @param {string} help - Help text for metric.
- * @param {Object=} options - options for collector.
+ * @param name - Name of the metric.
+ * @param help - Help text for metric.
+ * @param options - options for collector.
* @param {Array=} options.labels -
* Labels for this metric, defaults to no labels.
* @param {boolean=} options.register -
@@ -672,7 +667,15 @@ class Counter extends ValueCollector {
* collected. The collector being collected is passed as the
* argument.
*/
- constructor(name, help, options = {}) {
+ constructor(
+ name: string,
+ help: string,
+ options: {
+ labels?: string[],
+ register?: boolean,
+ callback?: (collector: Counter) => void | Promise,
+ } = {},
+ ) {
super("counter", name, help, options, CounterChild);
}
@@ -680,7 +683,7 @@ class Counter extends ValueCollector {
* Increment counter value
*
* Note: Only works if this is an unlabeled collector.
- * @param {number} value - Positive number to increment by.
+ * @param value - Positive number to increment by.
*/
inc(value = 1) {
this._defaultChild.inc(value);
@@ -689,13 +692,13 @@ class Counter extends ValueCollector {
/**
* Child gauge holding the value for a single label set.
- * @extends module:lib~ValueCollectorChild
+ * @extends ValueCollectorChild
*/
class GaugeChild extends ValueCollectorChild {
/**
* Increment gague for label set
*
- * @param {number} value - number to increment by.
+ * @param value - number to increment by.
*/
inc(value = 1) {
this._values.set(this._key, this._values.get(this._key) + value);
@@ -704,7 +707,7 @@ class GaugeChild extends ValueCollectorChild {
/**
* Decrement gague for label set
*
- * @param {number} value - number to decrease by.
+ * @param value - number to decrease by.
*/
dec(value = 1) {
this._values.set(this._key, this._values.get(this._key) - value);
@@ -713,9 +716,9 @@ class GaugeChild extends ValueCollectorChild {
/**
* Set gague for label set
*
- * @param {number} value - number to set gauge to.
+ * @param value - number to set gauge to.
*/
- set(value) {
+ set(value: number) {
this._values.set(this._key, value);
}
@@ -729,7 +732,7 @@ class GaugeChild extends ValueCollectorChild {
/**
* Start a timer for setting a duration for label set
*
- * @returns {function()}
+ * @returns
* function that when called will set the guage to the duration in
* seconds from when the timer was started
*/
@@ -753,7 +756,7 @@ class GaugeChild extends ValueCollectorChild {
* Gauges should be created at module load time and referenced in the
* functions that modify them, for example:
*
- * ```js
+ * ```ts
* const activeRequests = new Gauge(
* "app_active_request_count",
* "Number of requests in-flight.",
@@ -770,16 +773,15 @@ class GaugeChild extends ValueCollectorChild {
* ```
*
* The `activeRequests` gauge will register with the default registry and
- * provided exposition is set up (see {@link
- * module:lib.exposition}) the gauge will be exported to
- * Prometheus starting out with a value of 0.
+ * provided exposition is set up (see {@link exposition}) the gauge will be
+ * exported to Prometheus starting out with a value of 0.
*
* Sometimes keeping track of the value measured is impractical or
* prohibitly expensive. In those cases you can update the value
* of the collector as it's being collected for export with a callback
* function passed as one of the options.
*
- * ```js
+ * ```ts
* const userCount = new Gauge(
* "app_user_count",
* "Number of users in the app.",
@@ -805,7 +807,7 @@ class GaugeChild extends ValueCollectorChild {
* directly on the counter itself, instead a child counter with label values
* set has to be retrieved with the `.labels()` method. For example:
*
- * ```js
+ * ```ts
* const userCount = new Gauge(
* "app_user_count",
* "Number of users in the app",
@@ -831,7 +833,7 @@ class GaugeChild extends ValueCollectorChild {
* label values that will be used. This is done by calling `.labels()` for
* each combination, for example:
*
- * ```js
+ * ```ts
* for (let role of ["system", "admin", "normal") {
* userCount.labels(role);
* }
@@ -842,27 +844,34 @@ class GaugeChild extends ValueCollectorChild {
* labels you actually need as resource usage for a metric increases
* exponentially with the number of labels used.
*
- * @extends module:lib.ValueCollector
- * @static
+ * @extends ValueCollector
*/
-class Gauge extends ValueCollector {
+export class Gauge extends ValueCollector {
/**
* Create optionally labeled gauge
*
- * @param {string} name - Name of the metric.
- * @param {string} help - Help text for metric.
- * @param {Object=} options - options for collector.
- * @param {Array=} options.labels -
+ * @param name - Name of the metric.
+ * @param help - Help text for metric.
+ * @param options - options for collector.
+ * @param options.labels -
* Labels for this metric, defaults to no labels.
- * @param {boolean=} options.register -
+ * @param options.register -
* If true registers this collector with the default registry.
* Defaults to true.
- * @param {function()=} options.callback -
+ * @param options.callback -
* Possibly async function that is called when the metric is
* collected. The collector being collected is passed as the
* argument.
*/
- constructor(name, help, options = {}) {
+ constructor(
+ name: string,
+ help: string,
+ options: {
+ labels?: string[],
+ register?: boolean,
+ callback?: (collector: Gauge) => void | Promise,
+ } = {},
+ ) {
super("gauge", name, help, options, GaugeChild);
}
@@ -870,7 +879,7 @@ class Gauge extends ValueCollector {
* Increment gague value
*
* Note: Only works if this is an unlabeled collector.
- * @param {number} value - number to increment by.
+ * @param value - number to increment by.
*/
inc(value = 1) {
this._defaultChild.inc(value);
@@ -880,7 +889,7 @@ class Gauge extends ValueCollector {
* Decrement gague value
*
* Note: Only works if this is an unlabeled collector.
- * @param {number} value - number to decrease by.
+ * @param value - number to decrease by.
*/
dec(value = 1) {
this._defaultChild.dec(value);
@@ -890,9 +899,9 @@ class Gauge extends ValueCollector {
* Set gague value
*
* Note: Only works if this is an unlabeled collector.
- * @param {number} value - number to set gauge to.
+ * @param value - number to set gauge to.
*/
- set(value) {
+ set(value: number) {
this._defaultChild.set(value);
}
@@ -909,7 +918,7 @@ class Gauge extends ValueCollector {
* Start a timer for setting a duration
*
* Note: Only works if this is an unlabeled collector.
- * @returns {function()}
+ * @returns
* function that when called will set the guage to the duration in
* seconds from when the timer was started
*/
@@ -922,7 +931,11 @@ class Gauge extends ValueCollector {
* Child Summary holding the sum and count for a single label set.
*/
class SummaryChild {
- constructor(collector, key) {
+ protected _sumValues: Map;
+ protected _countValues: Map;
+ protected _key: string;
+
+ constructor(collector: Summary, key: string) {
this._sumValues = collector._sumValues;
this._countValues = collector._countValues;
this._key = key;
@@ -933,7 +946,6 @@ class SummaryChild {
/**
* Sum of all observations for label set
- * @type {number}
*/
get sum() {
return this._sumValues.get(this._key);
@@ -941,7 +953,6 @@ class SummaryChild {
/**
* Count of observations for label set
- * @type {number}
*/
get count() {
return this._countValues.get(this._key);
@@ -950,9 +961,9 @@ class SummaryChild {
/**
* Observe a given value and increment matching buckets for label set
*
- * @param {number} value - number to count into summary.
+ * @param value - number to count into summary.
*/
- observe(value) {
+ observe(value: number) {
this._sumValues.set(this._key, this._sumValues.get(this._key) + value);
this._countValues.set(this._key, this._countValues.get(this._key) + 1);
}
@@ -960,7 +971,7 @@ class SummaryChild {
/**
* Start a timer for observing a duration for label set
*
- * @returns {function()}
+ * @returns
* function that when called will store the duration in seconds from
* when the timer was started into the metric.
*/
@@ -981,7 +992,7 @@ class SummaryChild {
* and average size of values for. A common case for this is request
* duration, for example:
*
- * ```js
+ * ```ts
* const requestDuration = new Summary(
* "app_request_duration_seconds",
* "Time to process app requests",
@@ -1010,7 +1021,7 @@ class SummaryChild {
* child summary with label values set has to be retrieved with the
* `.labels()` method. For example:
*
- * ```js
+ * ```ts
* const requestDuration = new Summary(
* "app_request_duration_seconds",
* "Time to process app requests",
@@ -1034,7 +1045,7 @@ class SummaryChild {
* that will be used. This is done by calling `.labels()` for each
* combination, for example:
*
- * ```js
+ * ```ts
* for (let endpoint of ["/status", "/api", ...) {
* requestDuration.labels(endpoint);
* }
@@ -1044,27 +1055,37 @@ class SummaryChild {
* series that need to be stored and processed. You should carefully
* evaluate which labels you actually need as resource usage for a metric
* increases exponentially with the number of labels used.
- *
- * @static
*/
-class Summary extends LabeledCollector {
+export class Summary extends LabeledCollector {
+ _sumValues: Map;
+ _countValues: Map;
+ protected _defaultChild: SummaryChild | null;
+
/**
* Create optionally labeled summary
*
- * @param {string} name - Name of the metric.
- * @param {string} help - Help text for metric.
- * @param {Object=} options - options for collector.
- * @param {Array=} options.labels -
+ * @param name - Name of the metric.
+ * @param help - Help text for metric.
+ * @param options - options for collector.
+ * @param options.labels -
* Labels for this metric, defaults to no labels.
- * @param {boolean=} options.register -
+ * @param options.register -
* If true registers this collector with the default registry.
* Defaults to true.
- * @param {function()=} options.callback -
+ * @param options.callback -
* Possibly async function that is called when the metric is
* collected. The collector being collected is passed as the
* argument.
*/
- constructor(name, help, options = {}) {
+ constructor(
+ name: string,
+ help: string,
+ options: {
+ labels?: string[],
+ register?: boolean,
+ callback?: (collector: Summary) => void | Promise,
+ } = {},
+ ) {
super("summary", name, help, options, SummaryChild);
this._sumValues = new Map();
@@ -1107,9 +1128,9 @@ class Summary extends LabeledCollector {
* Observe a given value and increment matching buckets
*
* Note: Only works if this is an unlabeled collector.
- * @param {number} value - number to count into histogram buckets.
+ * @param value - number to count into histogram buckets.
*/
- observe(value) {
+ observe(value: number) {
this._defaultChild.observe(value);
}
@@ -1117,7 +1138,7 @@ class Summary extends LabeledCollector {
* Start a timer for observing a duration
*
* Note: Only works if this is an unlabeled collector.
- * @returns {function()}
+ * @returns
* function that when called will store the duration in seconds from
* when the timer was started into the metric.
*/
@@ -1125,13 +1146,13 @@ class Summary extends LabeledCollector {
return this._defaultChild.startTimer();
}
- remove(...labels) {
+ remove(...labels: LabelValues) {
let key = super.remove(...labels);
this._sumValues.delete(key);
this._countValues.delete(key);
}
- removeAll(labels) {
+ removeAll(labels: Record) {
super.removeAll(labels);
removeMatchingLabels(this._sumValues, labels);
removeMatchingLabels(this._countValues, labels);
@@ -1144,7 +1165,7 @@ class Summary extends LabeledCollector {
}
}
-function formatValue(value) {
+function formatValue(value: number) {
if (value === Infinity) {
return "+Inf";
}
@@ -1154,7 +1175,7 @@ function formatValue(value) {
return value.toString();
}
-function formatBucketKey(bucket, key) {
+function formatBucketKey(bucket: number, key: string) {
return `${key === "" ? "" : `${key},`}le="${formatValue(bucket)}"`;
}
@@ -1162,7 +1183,13 @@ function formatBucketKey(bucket, key) {
* Child histogram holding the buckets for a single label set.
*/
class HistogramChild {
- constructor(collector, key) {
+ protected _bucketValues: Map;
+ protected _sumValues: Map;
+ protected _countValues: Map;
+ protected _key: string
+ protected _bucketKeys: Map;
+
+ constructor(collector: Histogram, key: string) {
this._bucketValues = collector._bucketValues;
this._sumValues = collector._sumValues;
this._countValues = collector._countValues;
@@ -1182,7 +1209,6 @@ class HistogramChild {
/**
* Mapping of bucket upper bounds to count of observations for label set
- * @type {Map}
*/
get buckets() {
return new Map(
@@ -1194,7 +1220,6 @@ class HistogramChild {
/**
* Sum of all observations for label set
- * @type {number}
*/
get sum() {
return this._sumValues.get(this._key);
@@ -1202,7 +1227,6 @@ class HistogramChild {
/**
* Count of observations for label set
- * @type {number}
*/
get count() {
return this._countValues.get(this._key);
@@ -1211,9 +1235,9 @@ class HistogramChild {
/**
* Observe a given value and increment matching buckets for label set
*
- * @param {number} value - number to count into histogram buckets.
+ * @param value - number to count into histogram buckets.
*/
- observe(value) {
+ observe(value: number) {
for (let [bound, key] of this._bucketKeys) {
if (value <= bound) {
this._bucketValues.set(key, this._bucketValues.get(key) + 1);
@@ -1227,7 +1251,7 @@ class HistogramChild {
/**
* Start a timer for observing a duration for label set
*
- * @returns {function()}
+ * @returns
* function that when called will store the duration in seconds from
* when the timer was started into the metric.
*/
@@ -1248,7 +1272,7 @@ class HistogramChild {
* reports a metric that you want insight into the distribution of values
* for. A common case for this is request duration, for example:
*
- * ```js
+ * ```ts
* const requestDuration = new Histogram(
* "app_request_duration_seconds",
* "Time to process app requests",
@@ -1276,7 +1300,7 @@ class HistogramChild {
* child histogram with label values set has to be retrieved with the
* `.labels()` method. For example:
*
- * ```js
+ * ```ts
* const requestDuration = new Histogram(
* "app_request_duration_seconds",
* "Time to process app requests",
@@ -1300,7 +1324,7 @@ class HistogramChild {
* will be used. This is done by calling `.labels()` for each combination,
* for example:
*
- * ```js
+ * ```ts
* for (let endpoint of ["/status", "/api", ...) {
* requestDuration.labels(endpoint);
* }
@@ -1312,44 +1336,51 @@ class HistogramChild {
* time series being made.) You should carefully evaluate which labels you
* actually need as resource usage for a metric increases exponentially with
* the number of labels used.
- *
- * @static
*/
-class Histogram extends LabeledCollector {
+export class Histogram extends LabeledCollector {
+ _bucketValues: Map;
+ _sumValues: Map;
+ _countValues: Map;
+ _buckets: number[];
+ protected _defaultChild: HistogramChild | null;
+
/**
* Create optionally labeled histogram
*
- * @param {string} name - Name of the metric.
- * @param {string} help - Help text for metric.
- * @param {Object=} options - options for collector.
- * @param {Array=} options.buckets -
+ * @param name - Name of the metric.
+ * @param help - Help text for metric.
+ * @param options - options for collector.
+ * @param options.buckets -
* Buckets to use for the histogram. This is an array of inclusive
* upper bounds. Values observed will increment a bucket if it is
* less than or equal to the upper bound.
- * @param {Array=} options.labels -
+ * @param options.labels -
* Labels for this metric, defaults to no labels.
- * @param {boolean=} options.register -
+ * @param options.register -
* If true registers this collector with the default registry.
* Defaults to true.
- * @param {function()=} options.callback -
+ * @param options.callback -
* Possibly async function that is called when the metric is
* collected. The collector being collected is passed as the
* argument.
*/
- constructor(name, help, options = {}) {
+ constructor(
+ name: string,
+ help: string,
+ options: {
+ buckets?: number[],
+ labels?: string[],
+ register?: boolean,
+ callback?: (collector: Histogram) => void | Promise,
+ } = {},
+ ) {
// These defaults are taken from the Python Prometheus client
- let buckets = [0.005, 0.01, 0.025, 0.05, 0.075, 0.1, 0.25, 0.5, 0.75, 1, 2.5, 5, 7.5, 10, Infinity];
+ let {
+ buckets = [0.005, 0.01, 0.025, 0.05, 0.075, 0.1, 0.25, 0.5, 0.75, 1, 2.5, 5, 7.5, 10, Infinity],
+ ...rest
+ } = options;
- let parentOptions = { _reservedLabels: ["le"] };
- for (let [key, value] of Object.entries(options)) {
- if (key === "buckets") {
- buckets = value;
- } else {
- parentOptions[key] = value;
- }
- }
-
- super("histogram", name, help, parentOptions, HistogramChild);
+ super("histogram", name, help, { ...rest, _reservedLabels: ["le"] }, HistogramChild);
if (buckets.slice(-1)[0] !== Infinity) {
buckets = [...buckets, Infinity];
@@ -1378,7 +1409,6 @@ class Histogram extends LabeledCollector {
* Mapping of bucket upper bounds to count of observations for label set
*
* Note: Only available if this is an unlabeled collector.
- * @type {Map}
*/
get buckets() {
return this._defaultChild.buckets;
@@ -1388,7 +1418,6 @@ class Histogram extends LabeledCollector {
* Sum of all observations for label set
*
* Note: Only available if this is an unlabeled collector.
- * @type {number}
*/
get sum() {
return this._defaultChild.sum;
@@ -1398,7 +1427,6 @@ class Histogram extends LabeledCollector {
* Count of observations for label set
*
* Note: Only available if this is an unlabeled collector.
- * @type {number}
*/
get count() {
return this._defaultChild.count;
@@ -1408,9 +1436,8 @@ class Histogram extends LabeledCollector {
* Observe a given value and increment matching buckets
*
* Note: Only works if this is an unlabeled collector.
- * @param {number} value - number to count into histogram buckets.
*/
- observe(value) {
+ observe(value: number) {
this._defaultChild.observe(value);
}
@@ -1418,7 +1445,7 @@ class Histogram extends LabeledCollector {
* Start a timer for observing a duration
*
* Note: Only works if this is an unlabeled collector.
- * @returns {function()}
+ * @returns
* function that when called will store the duration in seconds from
* when the timer was started into the metric.
*/
@@ -1426,7 +1453,7 @@ class Histogram extends LabeledCollector {
return this._defaultChild.startTimer();
}
- remove(...labels) {
+ remove(...labels: LabelValues) {
let key = super.remove(...labels);
for (let bucket of this._buckets) {
let bucketKey = formatBucketKey(bucket, key);
@@ -1436,7 +1463,7 @@ class Histogram extends LabeledCollector {
this._countValues.delete(key);
}
- removeAll(labels) {
+ removeAll(labels: Record) {
super.removeAll(labels);
for (let bucket of this._buckets) {
removeMatchingLabels(
@@ -1462,13 +1489,13 @@ class Histogram extends LabeledCollector {
* `start` and with each subsequent number being equal to the previous
* one plus `width`.
*
- * @param {number} start - Number to start buckets at.
- * @param {number} width - Distance between ecah bucket.
- * @param {number} count - Number of buckets.
- * @returns {Array} array of buckets.
+ * @param start - Number to start buckets at.
+ * @param width - Distance between ecah bucket.
+ * @param count - Number of buckets.
+ * @returns array of buckets.
*/
- static linear(start, width, count) {
- let buckets = [];
+ static linear(start: number, width: number, count: number) {
+ let buckets: number[] = [];
for (let i = 0; i < count; i++) {
buckets.push(start);
start += width;
@@ -1484,13 +1511,13 @@ class Histogram extends LabeledCollector {
* `start` and with each subsequent number being equal to the previous
* multiplied with `factor`.
*
- * @param {number} start - Number to start buckets at.
- * @param {number} factor - Ratio between each bucket.
- * @param {number} count - Number of buckets.
- * @returns {Array} array of buckets.
+ * @param start - Number to start buckets at.
+ * @param factor - Ratio between each bucket.
+ * @param count - Number of buckets.
+ * @returns array of buckets.
*/
- static exponential(start, factor, count) {
- let buckets = [];
+ static exponential(start: number, factor: number, count: number) {
+ let buckets: number[] = [];
for (let i = 0; i < count; i++) {
buckets.push(start);
start *= factor;
@@ -1511,7 +1538,7 @@ class Histogram extends LabeledCollector {
* registring them to the default registry and then adding them to your own
* registry, for example:
*
- * ```js
+ * ```ts
* const myRegistry = new CollectorRegistry();
* const myCounter = new Counter( "a_counter", "A counter.", { register: false });
* myRegistry.register(myCounter);
@@ -1523,20 +1550,14 @@ class Histogram extends LabeledCollector {
* The same collector can be registered to multiple registries. This may be
* used to implement responding with different sets of metrics depending on
* what is requested.
- *
- * @static
*/
-class CollectorRegistry {
- constructor() {
- this.collectors = [];
- }
+export class CollectorRegistry {
+ collectors: Collector[] = [];
/**
* Collect metrics from all registered collectors.
- *
- * @returns {*} Async iterator of {@link CollectorResult}.
*/
- async* collect() {
+ async* collect(): AsyncIterable {
for (let collector of this.collectors) {
for await (let result of collector.collect()) {
yield result;
@@ -1547,9 +1568,9 @@ class CollectorRegistry {
/**
* Add collector to the registry.
*
- * @param {lib/prometheus.Collector} collector - Collector to add.
+ * @param collector - Collector to add.
*/
- register(collector) {
+ register(collector: Collector) {
let index = this.collectors.lastIndexOf(collector);
if (index !== -1) {
throw new Error(
@@ -1563,9 +1584,9 @@ class CollectorRegistry {
/**
* Remove collector from the registry.
*
- * @param {lib/prometheus.Collector} collector - Collector to remove.
+ * @param collector - Collector to remove.
*/
- unregister(collector) {
+ unregister(collector: Collector) {
let index = this.collectors.lastIndexOf(collector);
if (index === -1) {
throw new Error(
@@ -1579,14 +1600,14 @@ class CollectorRegistry {
defaultRegistry = new CollectorRegistry();
-function escapeHelp(help) {
+function escapeHelp(help: string) {
return help
.replace(/\\/g, "\\\\")
.replace(/\n/g, "\\n")
;
}
-async function* expositionLines(resultsIterator) {
+async function* expositionLines(resultsIterator: AsyncIterable) {
let first = true;
for await (let result of resultsIterator) {
if (first) {
@@ -1634,46 +1655,49 @@ async function* expositionLines(resultsIterator) {
* app.get("/metrics", (req, res, next) => getMetrics(req, res).catch(next));
* app.listen(9100);
*
- * @param {*} resultsIterator -
+ * @param resultsIterator -
* Asynchronously itreable of {@link CollectorResult} results to create
* exposition for. Defaults to collecting results from {@link
- * module:lib.defaultRegistry}.
- * @returns {string} Prometheus exposition.
+ * defaultRegistry}.
+ * @returns Prometheus exposition.
*
- * @property {string} exposition.contentType
+ * @property exposition.contentType
* HTTP Content-Type for the exposition format that's implemented
- *
- * @static
*/
-async function exposition(resultsIterator = defaultRegistry.collect()) {
+export async function exposition(resultsIterator = defaultRegistry.collect()) {
let lines = "";
for await (let line of expositionLines(resultsIterator)) {
lines += line;
}
return lines;
}
-
exposition.contentType = "text/plain; version=0.0.4";
/**
* Serialize CollectorResult into a plain object
*
- * Converts a {@link module:lib~CollectorResult} into a plain
+ * Converts a {@link CollectorResult} into a plain
* object form that can be stringified to JSON.
*
- * @param {module:lib~CollectorResult} result -
+ * @param result -
* Result to serialize into plain object form.
- * @param {Object} options - Options for controlling the serialization.
- * @param {string} options.addLabels -
+ * @param options - Options for controlling the serialization.
+ * @param options.addLabels -
* Additional labels to append to each value. This may be used if
* multiple sources are combined have the same metric and a qualifier is
* needed to make sure the label sets are unique.
- * @param {string} options.metricName - Override metric name of the result.
- * @param {string} options.metricHelp - Override metric help of the result.
- * @returns {Object} plain object form of the result.
- * @static
+ * @param options.metricName - Override metric name of the result.
+ * @param options.metricHelp - Override metric help of the result.
+ * @returns plain object form of the result.
*/
-function serializeResult(result, options = {}) {
+export function serializeResult(
+ result: CollectorResult,
+ options: {
+ addLabels?: string,
+ metricName?: string,
+ metricHelp?: string,
+ } = {}
+) {
let addLabels = null;
let metricName = result.metric.name;
let metricHelp = result.metric.help;
@@ -1689,7 +1713,7 @@ function serializeResult(result, options = {}) {
}
}
- let samples;
+ let samples: Map>;
if (addLabels === null) {
samples = result.samples;
addLabels = {};
@@ -1717,20 +1741,23 @@ function serializeResult(result, options = {}) {
help: metricHelp,
labels: [...result.metric.labels, ...Object.keys(addLabels)],
},
- samples: [...samples].map(([name, metricSamples]) => [name, [...metricSamples]]),
+ samples: [...samples].map(
+ ([name, metricSamples]) => [name, [...metricSamples]] as [string, [string, number][]]
+ ),
};
}
/**
* Deserialize CollectorResult from plain object
*
- * Reverse counterpart to {@link module:lib.serializeResult}.
+ * Reverse counterpart to {@link serializeResult}.
*
- * @param {Object} serializedResult - Previously serialized result object.
- * @returns {module:lib~CollectorResult} deserialized result.
- * @static
+ * @param serializedResult - Previously serialized result object.
+ * @returns deserialized result.
*/
-function deserializeResult(serializedResult) {
+export function deserializeResult(
+ serializedResult: ReturnType
+): CollectorResult {
return {
metric: new Metric(
serializedResult.metric.type,
@@ -1748,61 +1775,43 @@ function deserializeResult(serializedResult) {
/**
* Default collectors provided by this library
- * @type {Object}
*/
-let defaultCollectors = {};
-defaultCollectors.processCpuSecondsTotal = new Gauge(
- "process_cpu_seconds_total",
- "Total user and system CPU time spent in seconds.",
- {
- callback: async function(collector) {
- let usage = process.cpuUsage();
- collector.set((usage.user + usage.system) / 1e6);
+export const defaultCollectors = {
+ processCpuSecondsTotal: new Gauge(
+ "process_cpu_seconds_total",
+ "Total user and system CPU time spent in seconds.",
+ {
+ callback: function(collector) {
+ let usage = process.cpuUsage();
+ collector.set((usage.user + usage.system) / 1e6);
+ },
},
- },
-);
+ ),
-defaultCollectors.processResidentMemoryBytes = new Gauge(
- "process_resident_memory_bytes",
- "Resident memory size in bytes.",
- {
- callback: function(collector) {
- collector.set(process.memoryUsage().rss);
+ processResidentMemoryBytes: new Gauge(
+ "process_resident_memory_bytes",
+ "Resident memory size in bytes.",
+ {
+ callback: function(collector) {
+ collector.set(process.memoryUsage().rss);
+ },
},
- },
-);
+ ),
-defaultCollectors.processHeapBytes = new Gauge(
- "process_heap_bytes",
- "Process heap size in bytes.",
- {
- callback: function(collector) {
- collector.set(process.memoryUsage().heapUsed);
+ processHeapBytes: new Gauge(
+ "process_heap_bytes",
+ "Process heap size in bytes.",
+ {
+ callback: function(collector) {
+ collector.set(process.memoryUsage().heapUsed);
+ },
},
- },
-);
+ ),
+
+ processStartTimeSeconds: new Gauge(
+ "process_start_time_seconds",
+ "Start time of the process since unix epoch in seconds."
+ ),
+} as const;
-defaultCollectors.processStartTimeSeconds = new Gauge(
- "process_start_time_seconds",
- "Start time of the process since unix epoch in seconds."
-);
defaultCollectors.processStartTimeSeconds.setToCurrentTime();
-
-
-module.exports = {
- Counter,
- Gauge,
- Summary,
- Histogram,
- CollectorRegistry,
- Collector,
- LabeledCollector,
- ValueCollector,
- Metric,
-
- exposition,
- defaultRegistry,
- defaultCollectors,
- serializeResult,
- deserializeResult,
-};
diff --git a/packages/lib/src/schema.js b/packages/lib/src/schema.ts
similarity index 59%
rename from packages/lib/src/schema.js
rename to packages/lib/src/schema.ts
index 63c2f5c9..d06fdfc2 100644
--- a/packages/lib/src/schema.js
+++ b/packages/lib/src/schema.ts
@@ -2,8 +2,7 @@
* JSON schemas used for validating link messages
* @module lib/schema
*/
-"use strict";
-const Ajv = require("ajv");
+import Ajv, { JSONSchemaType, ValidateFunction } from "ajv";
const ajv = new Ajv({
// These are duplicated in scripts/compile_validator
allowUnionTypes: true,
@@ -14,17 +13,16 @@ const ajv = new Ajv({
/**
* Compile JSON schema into validator
- * @function
- * @param {Object} schema - JSON schema to create validator for.
- * @returns {Function} Validator for the schema.
+ * @param schema - JSON schema to create validator for.
+ * @returns Validator for the schema.
*/
-function compile(schema) {
+export function compile(schema: JSONSchemaType) {
if (typeof global !== "object" || !global.lazySchemaCompilation) {
- return ajv.compile(schema);
+ return ajv.compile(schema);
}
- let doValidate;
- function validate(data) {
+ let doValidate: ValidateFunction;
+ const validate = >function (data) {
if (!doValidate) {
doValidate = ajv.compile(schema);
}
@@ -32,9 +30,5 @@ function compile(schema) {
validate.errors = doValidate.errors;
return result;
}
- return validate;
+ return validate
}
-
-module.exports = {
- compile,
-};
diff --git a/packages/lib/src/shared_commands.js b/packages/lib/src/shared_commands.ts
similarity index 72%
rename from packages/lib/src/shared_commands.js
rename to packages/lib/src/shared_commands.ts
index c641d1df..ada6ac4a 100644
--- a/packages/lib/src/shared_commands.js
+++ b/packages/lib/src/shared_commands.ts
@@ -3,15 +3,15 @@
* @module lib/shared_commands
*/
"use strict";
-const path = require("path");
+import path from "path";
-const libConfig = require("./config");
-const libFileOps = require("./file_ops");
-const { logger } = require("./logging");
-const libHelpers = require("./helpers");
+import * as libConfig from "./config";
+import * as libFileOps from "./file_ops";
+import { logger } from "./logging";
+import * as libHelpers from "./helpers";
-function print(...content) {
+function print(...content: any[]) {
// eslint-disable-next-line no-console
console.log(...content);
}
@@ -22,9 +22,9 @@ function print(...content) {
* Can be passed to yargs.command to implement a plugin list management
* command. Use handlePluginCommand to do the requested action.
*
- * @param {Object} yargs - yargs command builder.
+ * @param yargs - yargs command builder.
*/
-function pluginCommand(yargs) {
+export function pluginCommand(yargs: any) {
yargs
.command("add ", "Add plugin by require path")
.command("remove ", "Remove plugin by name")
@@ -40,20 +40,24 @@ function pluginCommand(yargs) {
*
* Handle the actions that are made available by pluginCommand.
*
- * @param {Object} args - yargs args object.
- * @param {Map} pluginList - Current list of plugins.
- * @param {string} pluginListPath - Path to plugin list config file.
+ * @param args - yargs args object.
+ * @param pluginList - Current list of plugins.
+ * @param pluginListPath - Path to plugin list config file.
*/
-async function handlePluginCommand(args, pluginList, pluginListPath) {
+export async function handlePluginCommand(
+ args: Record,
+ pluginList: Map,
+ pluginListPath: string
+) {
let command = args._[1];
if (command === "add") {
- let pluginPath = args.path;
+ let pluginPath = args.path as string;
if (/^\.\.?[\/\\]/.test(pluginPath)) {
pluginPath = path.resolve(pluginPath);
}
- let pluginInfo;
+ let pluginInfo: { name: string };
try {
// eslint-disable-next-line node/global-require
pluginInfo = require(path.posix.join(pluginPath, "info"));
@@ -74,7 +78,7 @@ async function handlePluginCommand(args, pluginList, pluginListPath) {
print(`Added ${pluginInfo.name}`);
} else if (command === "remove") {
- if (!pluginList.delete(args.name)) {
+ if (!pluginList.delete(args.name as string)) {
logger.error(`Plugin with name ${args.name} does not exist`);
process.exitCode = 1;
return;
@@ -97,11 +101,11 @@ async function handlePluginCommand(args, pluginList, pluginListPath) {
* Can be passed to yargs.command to implement a config command. Use
* handleConfigCommand to do the requested action.
*
- * @param {Object} yargs - yargs command builder.
+ * @param yargs - yargs command builder.
*/
-function configCommand(yargs) {
+export function configCommand(yargs: any) {
yargs
- .command("set [value]", "Set config field", yargs => {
+ .command("set [value]", "Set config field", (yargs: any) => {
yargs.options({
"stdin": { describe: "read value from stdin", nargs: 0, type: "boolean" },
});
@@ -119,15 +123,19 @@ function configCommand(yargs) {
*
* Handle the actions that are made available by configCommand.
*
- * @param {Object} args - yargs args object.
- * @param {module:lib.Config} instance - Config instance.
- * @param {string} configPath - Path to configuration file.
+ * @param args - yargs args object.
+ * @param instance - Config instance.
+ * @param configPath - Path to configuration file.
*/
-async function handleConfigCommand(args, instance, configPath) {
+export async function handleConfigCommand(
+ args: Record,
+ instance: libConfig.Config,
+ configPath: string
+) {
let command = args._[1];
if (command === "list") {
- for (let GroupClass of instance.constructor.groups.values()) {
+ for (let GroupClass of (instance as any).constructor.groups.values()) {
for (let def of GroupClass.definitions.values()) {
let value = instance.get(def.fullName);
print(`${def.fullName} ${JSON.stringify(value)}`);
@@ -136,7 +144,7 @@ async function handleConfigCommand(args, instance, configPath) {
} else if (command === "show") {
try {
- print(instance.get(args.field));
+ print(instance.get(args.field as string));
} catch (err) {
if (err instanceof libConfig.InvalidField) {
logger.error(err.message);
@@ -154,7 +162,7 @@ async function handleConfigCommand(args, instance, configPath) {
}
try {
- instance.set(args.field, args.value);
+ instance.set(args.field as string, args.value);
await libFileOps.safeOutputFile(configPath, JSON.stringify(instance.serialize(), null, 4));
} catch (err) {
if (err instanceof libConfig.InvalidField || err instanceof libConfig.InvalidValue) {
@@ -165,12 +173,3 @@ async function handleConfigCommand(args, instance, configPath) {
}
}
}
-
-
-module.exports = {
- pluginCommand,
- handlePluginCommand,
-
- configCommand,
- handleConfigCommand,
-};
diff --git a/packages/lib/src/stream.ts b/packages/lib/src/stream.ts
index a35185b2..5e2ab36c 100644
--- a/packages/lib/src/stream.ts
+++ b/packages/lib/src/stream.ts
@@ -138,7 +138,7 @@ type ReadStreamOptions = Parameters[1];
* Options to pass to fs.createReadStream. Values passed for `fd` and
* `fs` will be ignored.
*/
-export async function createReverseReadStream(path: string, options: ReadStreamOptions) {
+export async function createReverseReadStream(path: string, options?: ReadStreamOptions) {
if (typeof options === "string") {
options = { encoding: options };
}
diff --git a/packages/lib/src/users.js b/packages/lib/src/users.ts
similarity index 83%
rename from packages/lib/src/users.js
rename to packages/lib/src/users.ts
index 5e505fc4..40d93b10 100644
--- a/packages/lib/src/users.js
+++ b/packages/lib/src/users.ts
@@ -7,24 +7,23 @@
* @module lib/users
*/
"use strict";
-const libErrors = require("./errors");
+import * as libErrors from "./errors";
-const PlayerStats = require("./PlayerStats");
+import PlayerStats from "./PlayerStats";
/**
* Represents a permission that can be granted
- * @static
*/
-class Permission {
- constructor(name, title, description, grantByDefault) {
- this.name = name;
- this.title = title;
- this.description = description;
- this.grantByDefault = grantByDefault;
- }
+export class Permission {
+ constructor(
+ public name: string,
+ public title: string,
+ public description: string,
+ public grantByDefault: boolean,
+ ) { }
}
-const permissions = new Map();
+export const permissions = new Map();
/**
* Define a new user permission for Clusterio
@@ -33,15 +32,25 @@ const permissions = new Map();
* granted to roles, and checked on users. Name should be `lower_case`
* style and start with the plugin name followed by a dot.
*
- * @param {Object} def - The definition for the permission.
- * @param {string} def.name - The internal name for the permission.
- * @param {string} def.title - User facing name for permission.
- * @param {string} def.description - User facing description.
- * @param {boolean} def.grantByDefault -
+ * @param def - The definition for the permission.
+ * @param def.name - The internal name for the permission.
+ * @param def.title - User facing name for permission.
+ * @param def.description - User facing description.
+ * @param def.grantByDefault -
* If true this role is granted by the generated Player role by default.
*
*/
-function definePermission({ name, title, description, grantByDefault=false }) {
+export function definePermission({
+ name,
+ title,
+ description,
+ grantByDefault = false,
+}: {
+ name: string,
+ title: string,
+ description: string,
+ grantByDefault?: boolean,
+}) {
if (typeof name !== "string" || !name.length) {
throw new Error("Expected name to be a non-empty string");
}
@@ -64,10 +73,14 @@ function definePermission({ name, title, description, grantByDefault=false }) {
/**
* Represents a collection of granted permissions
- * @static
*/
-class Role {
- constructor(serializedRole) {
+export class Role {
+ id: number;
+ name: string;
+ description: string;
+ permissions: Set;
+
+ constructor(serializedRole: ReturnType) {
this.id = serializedRole.id;
this.name = serializedRole.name;
this.description = serializedRole.description;
@@ -98,14 +111,18 @@ class Role {
*
* Ensures the role with id 0 exisits and has the core.admin permission.
*
- * @param {Map} roles - role storage to modify.
- * @returns {module:lib.Role} the default admin role
- * @static
+ * @param roles - role storage to modify.
+ * @returns the default admin role
*/
-function ensureDefaultAdminRole(roles) {
+export function ensureDefaultAdminRole(roles: Map) {
let admin = roles.get(0);
if (!admin) {
- admin = new Role({ id: 0, name: "Cluster Admin", description: "Cluster wide administrator." });
+ admin = new Role({
+ id: 0,
+ name: "Cluster Admin",
+ description: "Cluster wide administrator.",
+ permissions: [],
+ });
roles.set(0, admin);
}
admin.permissions.add("core.admin");
@@ -119,13 +136,17 @@ function ensureDefaultAdminRole(roles) {
* assigned by default. Note that this may not be the same role as the one
* that is assigned to new users by default.
*
- * @param {Map} roles - role storage to modify.
- * @static
+ * @param roles - role storage to modify.
*/
-function ensureDefaultPlayerRole(roles) {
+export function ensureDefaultPlayerRole(roles: Map) {
let player = roles.get(1);
if (!player) {
- player = new Role({ id: 1, name: "Player", description: "Default player role." });
+ player = new Role({
+ id: 1,
+ name: "Player",
+ description: "Default player role.",
+ permissions: [],
+ });
roles.set(1, player);
}
player.grantDefaultPermissions();
@@ -136,56 +157,42 @@ function ensureDefaultPlayerRole(roles) {
* Represeents a user in the cluster
*
* Holds data about a Factorio user in the cluster.
- * @static
*/
-class User {
- constructor(serializedUser, loadedRoles) {
- /**
- * Factorio user name.
- * @type {string}
- */
+export class User {
+ /** Factorio user name. */
+ name: string;
+ /** Instances this user is online on. */
+ instances: Set;
+ /** Unix time in seconds the user token must be issued after to be valid. */
+ tokenValidAfter: number;
+ /** True if the user is promoted to admin on the Factorio instances. */
+ isAdmin: boolean;
+ /** True if the user is whitelisted on the Factorio instances. */
+ isWhitelisted: boolean;
+ /** True if the user is banned from Factorio instances. */
+ isBanned: boolean;
+ /** Reason for being banned. Ignored if isBanned is false. */
+ banReason: string;
+ /** Roles this user has */
+ roles: Set;
+ /** Per instance statistics for the player this user account is tied to. */
+ instanceStats: Map;
+ /** Combined statistics for the player this user account is tied to. */
+ playerStats: PlayerStats;
+ /** True if this user object has been removed from the cluster. */
+ isDeleted: boolean;
+
+ constructor(
+ serializedUser: ReturnType,
+ loadedRoles: Map
+ ) {
this.name = serializedUser.name;
-
- /**
- * Instances this user is online on.
- * @type {Set}
- */
this.instances = new Set();
-
- /**
- * Unix time in seconds the user token must be issued after to be valid.
- * @type {number}
- */
this.tokenValidAfter = serializedUser.token_valid_after || 0;
-
- /**
- * True if the user is promoted to admin on the Factorio instances.
- * @type {boolean}
- */
this.isAdmin = Boolean(serializedUser.is_admin);
-
- /**
- * True if the user is whitelisted on the Factorio instances.
- * @type {boolean}
- */
this.isWhitelisted = Boolean(serializedUser.is_whitelisted);
-
- /**
- * True if the user is banned from Factorio instances.
- * @type {boolean}
- */
this.isBanned = Boolean(serializedUser.is_banned);
-
- /**
- * Reason for being banned. Ignored if isBanned is false.
- * @type {string}
- */
this.banReason = serializedUser.ban_reason || "";
-
- /**
- * Roles this user has
- * @type {Set}
- */
this.roles = new Set();
if (serializedUser.roles) {
for (let roleId of serializedUser.roles) {
@@ -195,32 +202,26 @@ class User {
}
}
}
-
- /**
- * Per instance statistics for the player this user account is tied to.
- * @type {Map}
- */
this.instanceStats = new Map(
(serializedUser.instance_stats ? serializedUser.instance_stats : []).map(
([id, stats]) => [id, new PlayerStats(stats)]
)
);
-
- /**
- * Combined statistics for the player this user account is tied to.
- * @type {module:lib.PlayerStats}
- */
this.playerStats = this._calculatePlayerStats();
-
- /**
- * True if this user object has been removed from the cluster.
- * @type {boolean}
- */
this.isDeleted = false;
}
serialize() {
- let serialized = {
+ let serialized: {
+ name: string,
+ roles?: number[],
+ token_valid_after?: number,
+ is_admin?: boolean,
+ is_whitelisted?: boolean,
+ is_banned?: boolean,
+ ban_reason?: string,
+ instance_stats?: [number, object][],
+ } = {
name: this.name,
};
@@ -272,11 +273,11 @@ class User {
* permission. If the permission is not granted for the user a
* "Permission denied" error is thrown.
*
- * @param {string} permission - The permission to check for.
+ * @param permission - The permission to check for.
* @throws {Error} If the given permission does not exist.
- * @throws {module:lib.PermissionError} if the user does noh have the given permission.
+ * @throws {libErrors.PermissionError} if the user does noh have the given permission.
*/
- checkPermission(permission) {
+ checkPermission(permission: string) {
if (!permissions.has(permission)) {
throw new Error(`permission ${permission} does not exist`);
}
@@ -290,12 +291,12 @@ class User {
throw new libErrors.PermissionError("Permission denied");
}
- notifyJoin(instance_id) {
+ notifyJoin(instance_id: number) {
this.instances.add(instance_id);
User.onlineUsers.add(this);
}
- notifyLeave(instance_id) {
+ notifyLeave(instance_id: number) {
this.instances.delete(instance_id);
if (!this.instances.size) {
User.onlineUsers.delete(this);
@@ -321,12 +322,11 @@ class User {
}
return playerStats;
}
+ /**
+ * Set of users currently online in the cluster.
+ */
+ static onlineUsers = new Set();
}
-/**
- * Set of users currently online in the cluster.
- * @type {module:lib.User}
- */
-User.onlineUsers = new Set();
// Definitions for the built in permissions used in Clusterio.
// description should answer "this permission allows you to ___"
@@ -677,14 +677,3 @@ definePermission({
title: "Dump WebSocket",
description: "Dump all WebSocket communicatation from the controller.",
});
-
-module.exports = {
- Permission,
- Role,
- User,
-
- permissions,
- definePermission,
- ensureDefaultAdminRole,
- ensureDefaultPlayerRole,
-};
diff --git a/packages/lib/src/zip_ops.js b/packages/lib/src/zip_ops.ts
similarity index 75%
rename from packages/lib/src/zip_ops.js
rename to packages/lib/src/zip_ops.ts
index 39cb1bb0..f69f63e8 100644
--- a/packages/lib/src/zip_ops.js
+++ b/packages/lib/src/zip_ops.ts
@@ -1,4 +1,4 @@
-"use strict";
+import type JSZip from "jszip";
/**
* Returns the root folder in the zip file
@@ -6,13 +6,10 @@
* Returns the name of the folder that all files in the zip file is
* contained in. Throws an error if there are multiple such folders.
*
- * @param {JSZip} zip - Zip to search through.
- * @returns {string} name of the root folder.
- * @memberof module:lib
- * @private
- * @inner
+ * @param zip - Zip to search through.
+ * @returns name of the root folder.
*/
-function findRoot(zip) {
+export function findRoot(zip: JSZip) {
let root = null;
zip.forEach((relativePath, file) => {
let index = relativePath.indexOf("/");
@@ -34,7 +31,3 @@ function findRoot(zip) {
return root;
}
-
-module.exports = {
- findRoot,
-};