@ztimson/momentum
Version:
Client library for momentum
1,407 lines (1,406 loc) • 105 kB
JavaScript
(function(global, factory) {
typeof exports === "object" && typeof module !== "undefined" ? factory(exports) : typeof define === "function" && define.amd ? define(["exports"], factory) : (global = typeof globalThis !== "undefined" ? globalThis : global || self, factory(global.utils = {}));
})(this, function(exports2) {
"use strict";var __defProp = Object.defineProperty;
var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value);
var __defProp2 = Object.defineProperty;
var __defNormalProp2 = (obj, key, value2) => key in obj ? __defProp2(obj, key, { enumerable: true, configurable: true, writable: true, value: value2 }) : obj[key] = value2;
var __publicField2 = (obj, key, value2) => __defNormalProp2(obj, typeof key !== "symbol" ? key + "" : key, value2);
function clean(obj, undefinedOnly = false) {
if (obj == null) throw new Error("Cannot clean a NULL value");
if (Array.isArray(obj)) {
obj = obj.filter((o) => o != null);
} else {
Object.entries(obj).forEach(([key, value2]) => {
if (undefinedOnly && value2 === void 0 || !undefinedOnly && value2 == null) delete obj[key];
});
}
return obj;
}
function deepCopy(value2) {
try {
return structuredClone(value2);
} catch {
return JSON.parse(JSONSanitize(value2));
}
}
function isEqual(a2, b2) {
const ta = typeof a2, tb = typeof b2;
if (ta != "object" || a2 == null || (tb != "object" || b2 == null))
return ta == "function" && tb == "function" ? a2.toString() == b2.toString() : a2 === b2;
const keys = Object.keys(a2);
if (keys.length != Object.keys(b2).length) return false;
return Object.keys(a2).every((key) => isEqual(a2[key], b2[key]));
}
function JSONAttemptParse(json) {
try {
return JSON.parse(json);
} catch {
return json;
}
}
function JSONSanitize(obj, space) {
return JSON.stringify(obj, (key, value2) => {
return value2;
}, space);
}
function makeArray(value2) {
return Array.isArray(value2) ? value2 : [value2];
}
class ASet extends Array {
/** Number of elements in set */
get size() {
return this.length;
}
/**
* Array to create set from, duplicate values will be removed
* @param {T[]} elements Elements which will be added to set
*/
constructor(elements = []) {
super();
if (!!(elements == null ? void 0 : elements["forEach"]))
elements.forEach((el) => this.add(el));
}
/**
* Add elements to set if unique
* @param items
*/
add(...items) {
items.filter((el) => !this.has(el)).forEach((el) => this.push(el));
return this;
}
/**
* Delete elements from set
* @param items Elements that will be deleted
*/
delete(...items) {
items.forEach((el) => {
const index = this.indexOf(el);
if (index != -1) this.splice(index, 1);
});
return this;
}
/**
* Create list of elements this set has which the comparison set does not
* @param {ASet<T>} set Set to compare against
* @return {ASet<T>} Different elements
*/
difference(set) {
return new ASet(this.filter((el) => !set.has(el)));
}
/**
* Check if set includes element
* @param {T} el Element to look for
* @return {boolean} True if element was found, false otherwise
*/
has(el) {
return this.indexOf(el) != -1;
}
/**
* Find index number of element, or -1 if it doesn't exist. Matches by equality not reference
*
* @param {T} search Element to find
* @param {number} fromIndex Starting index position
* @return {number} Element index number or -1 if missing
*/
indexOf(search2, fromIndex) {
return super.findIndex((el) => isEqual(el, search2), fromIndex);
}
/**
* Create list of elements this set has in common with the comparison set
* @param {ASet<T>} set Set to compare against
* @return {boolean} Set of common elements
*/
intersection(set) {
return new ASet(this.filter((el) => set.has(el)));
}
/**
* Check if this set has no elements in common with the comparison set
* @param {ASet<T>} set Set to compare against
* @return {boolean} True if nothing in common, false otherwise
*/
isDisjointFrom(set) {
return this.intersection(set).size == 0;
}
/**
* Check if all elements in this set are included in the comparison set
* @param {ASet<T>} set Set to compare against
* @return {boolean} True if all elements are included, false otherwise
*/
isSubsetOf(set) {
return this.findIndex((el) => !set.has(el)) == -1;
}
/**
* Check if all elements from comparison set are included in this set
* @param {ASet<T>} set Set to compare against
* @return {boolean} True if all elements are included, false otherwise
*/
isSuperset(set) {
return set.findIndex((el) => !this.has(el)) == -1;
}
/**
* Create list of elements that are only in one set but not both (XOR)
* @param {ASet<T>} set Set to compare against
* @return {ASet<T>} New set of unique elements
*/
symmetricDifference(set) {
return new ASet([...this.difference(set), ...set.difference(this)]);
}
/**
* Create joined list of elements included in this & the comparison set
* @param {ASet<T>} set Set join
* @return {ASet<T>} New set of both previous sets combined
*/
union(set) {
return new ASet([...this, ...set]);
}
}
class Cache {
/**
* Create new cache
*
* @param {keyof T} key Default property to use as primary key
* @param options
*/
constructor(key, options = {}) {
__publicField2(this, "store", {});
__publicField2(this, "complete", false);
__publicField2(this, "values", this.all());
this.key = key;
this.options = options;
if (options.storageKey && !options.storage && typeof Storage !== "undefined")
options.storage = localStorage;
if (options.storageKey && options.storage) {
const stored = options.storage.getItem(options.storageKey);
if (stored) {
try {
Object.assign(this.store, JSON.parse(stored));
} catch {
}
}
}
return new Proxy(this, {
get: (target, prop2) => {
if (prop2 in target) return target[prop2];
return deepCopy(target.store[prop2]);
},
set: (target, prop2, value2) => {
if (prop2 in target) target[prop2] = value2;
else this.set(prop2, value2);
return true;
}
});
}
getKey(value2) {
if (!this.key) throw new Error("No key defined");
return value2[this.key];
}
/**
* Get all cached items
*
* @return {T[]} Array of items
*/
all() {
return deepCopy(Object.values(this.store));
}
/**
* Add a new item to the cache. Like set, but finds key automatically
*
* @param {T} value Item to add to cache
* @param {number | undefined} ttl Override default expiry
* @return {this}
*/
add(value2, ttl = this.ttl) {
const key = this.getKey(value2);
this.set(key, value2, ttl);
return this;
}
/**
* Add several rows to the cache
*
* @param {T[]} rows Several items that will be cached using the default key
* @param complete Mark cache as complete & reliable, defaults to true
* @return {this}
*/
addAll(rows2, complete = true) {
rows2.forEach((r2) => this.add(r2));
this.complete = complete;
return this;
}
/**
* Remove all keys from cache
*/
clear() {
this.store = {};
}
/**
* Delete an item from the cache
*
* @param {K} key Item's primary key
*/
delete(key) {
delete this.store[key];
if (this.options.storageKey && this.options.storage)
this.options.storage.setItem(this.options.storageKey, JSON.stringify(this.store));
}
/**
* Return cache as an array of key-value pairs
* @return {[K, T][]} Key-value pairs array
*/
entries() {
return Object.entries(this.store);
}
/**
* Get item from the cache
* @param {K} key Key to lookup
* @return {T} Cached item
*/
get(key) {
return deepCopy(this.store[key]);
}
/**
* Get a list of cached keys
*
* @return {K[]} Array of keys
*/
keys() {
return Object.keys(this.store);
}
/**
* Get map of cached items
*
* @return {Record<K, T>}
*/
map() {
return deepCopy(this.store);
}
/**
* Add an item to the cache manually specifying the key
*
* @param {K} key Key item will be cached under
* @param {T} value Item to cache
* @param {number | undefined} ttl Override default expiry in seconds
* @return {this}
*/
set(key, value2, ttl = this.options.ttl) {
this.store[key] = value2;
if (this.options.storageKey && this.options.storage)
this.options.storage.setItem(this.options.storageKey, JSON.stringify(this.store));
if (ttl) setTimeout(() => {
this.complete = false;
this.delete(key);
}, ttl * 1e3);
return this;
}
}
function blackOrWhite(background) {
const exploded = background == null ? void 0 : background.match(background.length >= 6 ? /\w\w/g : /\w/g);
if (!exploded) return "black";
const [r2, g, b2] = exploded.map((hex) => parseInt(hex, 16));
const luminance = (0.299 * r2 + 0.587 * g + 0.114 * b2) / 255;
return luminance > 0.5 ? "black" : "white";
}
class PromiseProgress extends Promise {
constructor(executor) {
super((resolve, reject) => executor(
(value2) => resolve(value2),
(reason) => reject(reason),
(progress) => this.progress = progress
));
__publicField2(this, "listeners", []);
__publicField2(this, "_progress", 0);
}
get progress() {
return this._progress;
}
set progress(p2) {
if (p2 == this._progress) return;
this._progress = p2;
this.listeners.forEach((l) => l(p2));
}
static from(promise) {
if (promise instanceof PromiseProgress) return promise;
return new PromiseProgress((res, rej) => promise.then((...args) => res(...args)).catch((...args) => rej(...args)));
}
from(promise) {
const newPromise = PromiseProgress.from(promise);
this.onProgress((p2) => newPromise.progress = p2);
return newPromise;
}
onProgress(callback) {
this.listeners.push(callback);
return this;
}
then(res, rej) {
const resp = super.then(res, rej);
return this.from(resp);
}
catch(rej) {
return this.from(super.catch(rej));
}
finally(res) {
return this.from(super.finally(res));
}
}
function formatDate(format = "YYYY-MM-DD H:mm", date = /* @__PURE__ */ new Date(), tz) {
const timezones = [
["IDLW", -12],
["SST", -11],
["HST", -10],
["AKST", -9],
["PST", -8],
["MST", -7],
["CST", -6],
["EST", -5],
["AST", -4],
["BRT", -3],
["MAT", -2],
["AZOT", -1],
["UTC", 0],
["CET", 1],
["EET", 2],
["MSK", 3],
["AST", 4],
["PKT", 5],
["IST", 5.5],
["BST", 6],
["ICT", 7],
["CST", 8],
["JST", 9],
["AEST", 10],
["SBT", 11],
["FJT", 12],
["TOT", 13],
["LINT", 14]
];
function adjustTz(date2, gmt) {
const currentOffset = date2.getTimezoneOffset();
const adjustedOffset = gmt * 60;
return new Date(date2.getTime() + (adjustedOffset + currentOffset) * 6e4);
}
function day(num) {
return ["Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"][num] || "Unknown";
}
function doy(date2) {
const start = /* @__PURE__ */ new Date(`${date2.getFullYear()}-01-01 0:00:00`);
return Math.ceil((date2.getTime() - start.getTime()) / (1e3 * 60 * 60 * 24));
}
function month(num) {
return ["January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December"][num] || "Unknown";
}
function suffix(num) {
if (num % 100 >= 11 && num % 100 <= 13) return `${num}th`;
switch (num % 10) {
case 1:
return `${num}st`;
case 2:
return `${num}nd`;
case 3:
return `${num}rd`;
default:
return `${num}th`;
}
}
function tzOffset(offset) {
const hours = ~~(offset / 60);
const minutes = offset % 60;
return (offset > 0 ? "-" : "") + `${hours}:${minutes.toString().padStart(2, "0")}`;
}
if (typeof date == "number" || typeof date == "string") date = new Date(date);
let t;
if (tz == null) tz = -(date.getTimezoneOffset() / 60);
t = timezones.find((t2) => isNaN(tz) ? t2[0] == tz : t2[1] == tz);
if (!t) throw new Error(`Unknown timezone: ${tz}`);
date = adjustTz(date, t[1]);
const tokens = {
"YYYY": date.getFullYear().toString(),
"YY": date.getFullYear().toString().slice(2),
"MMMM": month(date.getMonth()),
"MMM": month(date.getMonth()).slice(0, 3),
"MM": (date.getMonth() + 1).toString().padStart(2, "0"),
"M": (date.getMonth() + 1).toString(),
"DDD": doy(date).toString(),
"DD": date.getDate().toString().padStart(2, "0"),
"Do": suffix(date.getDate()),
"D": date.getDate().toString(),
"dddd": day(date.getDay()),
"ddd": day(date.getDay()).slice(0, 3),
"HH": date.getHours().toString().padStart(2, "0"),
"H": date.getHours().toString(),
"hh": (date.getHours() % 12 || 12).toString().padStart(2, "0"),
"h": (date.getHours() % 12 || 12).toString(),
"mm": date.getMinutes().toString().padStart(2, "0"),
"m": date.getMinutes().toString(),
"ss": date.getSeconds().toString().padStart(2, "0"),
"s": date.getSeconds().toString(),
"SSS": date.getMilliseconds().toString().padStart(3, "0"),
"A": date.getHours() >= 12 ? "PM" : "AM",
"a": date.getHours() >= 12 ? "pm" : "am",
"ZZ": tzOffset(t[1] * 60).replace(":", ""),
"Z": tzOffset(t[1] * 60),
"z": typeof tz == "string" ? tz : t[0]
};
return format.replace(/YYYY|YY|MMMM|MMM|MM|M|DDD|DD|Do|D|dddd|ddd|HH|H|hh|h|mm|m|ss|s|SSS|A|a|ZZ|Z|z/g, (token) => tokens[token]);
}
function downloadFile(blob, name) {
if (!(blob instanceof Blob)) blob = new Blob(makeArray(blob));
const url = URL.createObjectURL(blob);
downloadUrl(url, name);
URL.revokeObjectURL(url);
}
function downloadUrl(href, name) {
const a2 = document.createElement("a");
a2.href = href;
a2.download = name || href.split("/").pop();
document.body.appendChild(a2);
a2.click();
document.body.removeChild(a2);
}
function fileBrowser(options = {}) {
return new Promise((res) => {
const input = document.createElement("input");
input.type = "file";
input.accept = options.accept || "*";
input.style.display = "none";
input.multiple = !!options.multiple;
input.onblur = input.onchange = async () => {
res(Array.from(input.files));
input.remove();
};
document.body.appendChild(input);
input.click();
});
}
function timestampFilename(name, date = /* @__PURE__ */ new Date()) {
if (typeof date == "number" || typeof date == "string") date = new Date(date);
const timestamp = formatDate("YYYY-MM-DD_HH:mm:ss", date);
return timestamp;
}
function uploadWithProgress(options) {
return new PromiseProgress((res, rej, prog) => {
const xhr = new XMLHttpRequest();
const formData2 = new FormData();
options.files.forEach((f) => formData2.append("file", f));
xhr.withCredentials = !!options.withCredentials;
xhr.upload.addEventListener("progress", (event) => event.lengthComputable ? prog(event.loaded / event.total) : null);
xhr.addEventListener("loadend", () => res(JSONAttemptParse(xhr.responseText)));
xhr.addEventListener("error", () => rej(JSONAttemptParse(xhr.responseText)));
xhr.addEventListener("timeout", () => rej({ error: "Request timed out" }));
xhr.open("POST", options.url);
Object.entries(options.headers || {}).forEach(([key, value2]) => xhr.setRequestHeader(key, value2));
xhr.send(formData2);
});
}
class TypedEmitter {
constructor() {
__publicField2(this, "listeners", {});
}
static emit(event, ...args) {
(this.listeners["*"] || []).forEach((l) => l(event, ...args));
(this.listeners[event.toString()] || []).forEach((l) => l(...args));
}
static off(event, listener) {
const e = event.toString();
this.listeners[e] = (this.listeners[e] || []).filter((l) => l === listener);
}
static on(event, listener) {
var _a;
const e = event.toString();
if (!this.listeners[e]) this.listeners[e] = [];
(_a = this.listeners[e]) == null ? void 0 : _a.push(listener);
return () => this.off(event, listener);
}
static once(event, listener) {
return new Promise((res) => {
const unsubscribe = this.on(event, (...args) => {
res(args.length == 1 ? args[0] : args);
if (listener) listener(...args);
unsubscribe();
});
});
}
emit(event, ...args) {
(this.listeners["*"] || []).forEach((l) => l(event, ...args));
(this.listeners[event] || []).forEach((l) => l(...args));
}
off(event, listener) {
this.listeners[event] = (this.listeners[event] || []).filter((l) => l === listener);
}
on(event, listener) {
var _a;
if (!this.listeners[event]) this.listeners[event] = [];
(_a = this.listeners[event]) == null ? void 0 : _a.push(listener);
return () => this.off(event, listener);
}
once(event, listener) {
return new Promise((res) => {
const unsubscribe = this.on(event, (...args) => {
res(args.length == 1 ? args[0] : args);
if (listener) listener(...args);
unsubscribe();
});
});
}
}
__publicField2(TypedEmitter, "listeners", {});
class CustomError extends Error {
constructor(message, code) {
super(message);
__publicField2(this, "_code");
if (code != null) this._code = code;
}
get code() {
return this._code || this.constructor.code;
}
set code(c) {
this._code = c;
}
static from(err) {
const code = Number(err.statusCode) ?? Number(err.code);
const newErr = new this(err.message || err.toString());
return Object.assign(newErr, {
stack: err.stack,
...err,
code: code ?? void 0
});
}
static instanceof(err) {
return err.constructor.code != void 0;
}
toString() {
return this.message || super.toString();
}
}
__publicField2(CustomError, "code", 500);
class BadRequestError extends CustomError {
constructor(message = "Bad Request") {
super(message);
}
static instanceof(err) {
return err.constructor.code == this.code;
}
}
__publicField2(BadRequestError, "code", 400);
class UnauthorizedError extends CustomError {
constructor(message = "Unauthorized") {
super(message);
}
static instanceof(err) {
return err.constructor.code == this.code;
}
}
__publicField2(UnauthorizedError, "code", 401);
class PaymentRequiredError extends CustomError {
constructor(message = "Payment Required") {
super(message);
}
static instanceof(err) {
return err.constructor.code == this.code;
}
}
__publicField2(PaymentRequiredError, "code", 402);
class ForbiddenError extends CustomError {
constructor(message = "Forbidden") {
super(message);
}
static instanceof(err) {
return err.constructor.code == this.code;
}
}
__publicField2(ForbiddenError, "code", 403);
class NotFoundError extends CustomError {
constructor(message = "Not Found") {
super(message);
}
static instanceof(err) {
return err.constructor.code == this.code;
}
}
__publicField2(NotFoundError, "code", 404);
class MethodNotAllowedError extends CustomError {
constructor(message = "Method Not Allowed") {
super(message);
}
static instanceof(err) {
return err.constructor.code == this.code;
}
}
__publicField2(MethodNotAllowedError, "code", 405);
class NotAcceptableError extends CustomError {
constructor(message = "Not Acceptable") {
super(message);
}
static instanceof(err) {
return err.constructor.code == this.code;
}
}
__publicField2(NotAcceptableError, "code", 406);
class InternalServerError extends CustomError {
constructor(message = "Internal Server Error") {
super(message);
}
static instanceof(err) {
return err.constructor.code == this.code;
}
}
__publicField2(InternalServerError, "code", 500);
class NotImplementedError extends CustomError {
constructor(message = "Not Implemented") {
super(message);
}
static instanceof(err) {
return err.constructor.code == this.code;
}
}
__publicField2(NotImplementedError, "code", 501);
class BadGatewayError extends CustomError {
constructor(message = "Bad Gateway") {
super(message);
}
static instanceof(err) {
return err.constructor.code == this.code;
}
}
__publicField2(BadGatewayError, "code", 502);
class ServiceUnavailableError extends CustomError {
constructor(message = "Service Unavailable") {
super(message);
}
static instanceof(err) {
return err.constructor.code == this.code;
}
}
__publicField2(ServiceUnavailableError, "code", 503);
class GatewayTimeoutError extends CustomError {
constructor(message = "Gateway Timeout") {
super(message);
}
static instanceof(err) {
return err.constructor.code == this.code;
}
}
__publicField2(GatewayTimeoutError, "code", 504);
class HttpResponse extends Response {
constructor(resp, stream) {
super(stream, { headers: resp.headers, status: resp.status, statusText: resp.statusText });
__publicField2(this, "data");
__publicField2(this, "ok");
__publicField2(this, "redirected");
__publicField2(this, "type");
__publicField2(this, "url");
this.ok = resp.ok;
this.redirected = resp.redirected;
this.type = resp.type;
this.url = resp.url;
}
}
const _Http = class _Http2 {
constructor(defaults = {}) {
__publicField2(this, "interceptors", {});
__publicField2(this, "headers", {});
__publicField2(this, "url");
this.url = defaults.url ?? null;
this.headers = defaults.headers || {};
if (defaults.interceptors) {
defaults.interceptors.forEach((i) => _Http2.addInterceptor(i));
}
}
static addInterceptor(fn) {
const key = Object.keys(_Http2.interceptors).length.toString();
_Http2.interceptors[key] = fn;
return () => {
_Http2.interceptors[key] = null;
};
}
addInterceptor(fn) {
const key = Object.keys(this.interceptors).length.toString();
this.interceptors[key] = fn;
return () => {
this.interceptors[key] = null;
};
}
request(opts = {}) {
var _a;
if (!this.url && !opts.url) throw new Error("URL needs to be set");
let url = (((_a = opts.url) == null ? void 0 : _a.startsWith("http")) ? opts.url : (this.url || "") + (opts.url || "")).replace(/([^:]\/)\/+/g, "$1");
if (opts.fragment) url.includes("#") ? url.replace(/#.*(\?|\n)/g, (match, arg1) => `#${opts.fragment}${arg1}`) : url += "#" + opts.fragment;
if (opts.query) {
const q = Array.isArray(opts.query) ? opts.query : Object.keys(opts.query).map((k) => ({ key: k, value: opts.query[k] }));
url += (url.includes("?") ? "&" : "?") + q.map((q2) => `${q2.key}=${q2.value}`).join("&");
}
const headers = clean({
"Content-Type": !opts.body ? void 0 : opts.body instanceof FormData ? "multipart/form-data" : "application/json",
..._Http2.headers,
...this.headers,
...opts.headers
});
if (typeof opts.body == "object" && opts.body != null && headers["Content-Type"] == "application/json")
opts.body = JSON.stringify(opts.body);
return new PromiseProgress((res, rej, prog) => {
try {
fetch(url, {
headers,
method: opts.method || (opts.body ? "POST" : "GET"),
body: opts.body
}).then(async (resp) => {
var _a2, _b;
for (let fn of [...Object.values(_Http2.interceptors), ...Object.values(this.interceptors)]) {
await new Promise((res2) => fn(resp, () => res2()));
}
const contentLength = resp.headers.get("Content-Length");
const total = contentLength ? parseInt(contentLength, 10) : 0;
let loaded = 0;
const reader = (_a2 = resp.body) == null ? void 0 : _a2.getReader();
const stream = new ReadableStream({
start(controller) {
function push() {
reader == null ? void 0 : reader.read().then((event) => {
if (event.done) return controller.close();
loaded += event.value.byteLength;
prog(loaded / total);
controller.enqueue(event.value);
push();
}).catch((error) => controller.error(error));
}
push();
}
});
resp = new HttpResponse(resp, stream);
if (opts.decode !== false) {
const content = (_b = resp.headers.get("Content-Type")) == null ? void 0 : _b.toLowerCase();
if (content == null ? void 0 : content.includes("form")) resp.data = await resp.formData();
else if (content == null ? void 0 : content.includes("json")) resp.data = await resp.json();
else if (content == null ? void 0 : content.includes("text")) resp.data = await resp.text();
else if (content == null ? void 0 : content.includes("application")) resp.data = await resp.blob();
}
if (resp.ok) res(resp);
else rej(resp);
}).catch((err) => rej(err));
} catch (err) {
rej(err);
}
});
}
};
__publicField2(_Http, "interceptors", {});
__publicField2(_Http, "headers", {});
let Http = _Http;
const CliEffects = {
CLEAR: "\x1B[0m"
};
const CliForeground = {
RED: "\x1B[31m",
YELLOW: "\x1B[33m",
BLUE: "\x1B[34m",
LIGHT_GREY: "\x1B[37m"
};
var LOG_LEVEL = /* @__PURE__ */ ((LOG_LEVEL2) => {
LOG_LEVEL2[LOG_LEVEL2["ERROR"] = 0] = "ERROR";
LOG_LEVEL2[LOG_LEVEL2["WARN"] = 1] = "WARN";
LOG_LEVEL2[LOG_LEVEL2["INFO"] = 2] = "INFO";
LOG_LEVEL2[LOG_LEVEL2["LOG"] = 3] = "LOG";
LOG_LEVEL2[LOG_LEVEL2["DEBUG"] = 4] = "DEBUG";
return LOG_LEVEL2;
})(LOG_LEVEL || {});
const _Logger = class _Logger2 extends TypedEmitter {
constructor(namespace) {
super();
this.namespace = namespace;
}
format(...text) {
const now = /* @__PURE__ */ new Date();
const timestamp = `${now.getFullYear()}-${now.getMonth() + 1}-${now.getDate()} ${now.getHours().toString().padStart(2, "0")}:${now.getMinutes().toString().padStart(2, "0")}:${now.getSeconds().toString().padStart(2, "0")}.${now.getMilliseconds().toString().padEnd(3, "0")}`;
return `${timestamp}${this.namespace ? ` [${this.namespace}]` : ""} ${text.map((t) => typeof t == "string" ? t : JSONSanitize(t, 2)).join(" ")}`;
}
debug(...args) {
if (_Logger2.LOG_LEVEL < 4) return;
const str = this.format(...args);
_Logger2.emit(4, str);
console.debug(CliForeground.LIGHT_GREY + str + CliEffects.CLEAR);
}
log(...args) {
if (_Logger2.LOG_LEVEL < 3) return;
const str = this.format(...args);
_Logger2.emit(3, str);
console.log(CliEffects.CLEAR + str);
}
info(...args) {
if (_Logger2.LOG_LEVEL < 2) return;
const str = this.format(...args);
_Logger2.emit(2, str);
console.info(CliForeground.BLUE + str + CliEffects.CLEAR);
}
warn(...args) {
if (_Logger2.LOG_LEVEL < 1) return;
const str = this.format(...args);
_Logger2.emit(1, str);
console.warn(CliForeground.YELLOW + str + CliEffects.CLEAR);
}
error(...args) {
if (_Logger2.LOG_LEVEL < 0) return;
const str = this.format(...args);
_Logger2.emit(0, str);
console.error(CliForeground.RED + str + CliEffects.CLEAR);
}
};
__publicField2(_Logger, "LOG_LEVEL", 4);
function PE(str, ...args) {
const combined = [];
for (let i = 0; i < str.length || i < args.length; i++) {
if (str[i]) combined.push(str[i]);
if (args[i]) combined.push(args[i]);
}
return new PathEvent(combined.join(""));
}
function PES(str, ...args) {
let combined = [];
for (let i = 0; i < str.length || i < args.length; i++) {
if (str[i]) combined.push(str[i]);
if (args[i]) combined.push(args[i]);
}
const [paths, methods] = combined.join("").split(":");
return PathEvent.toString(paths, methods == null ? void 0 : methods.split(""));
}
class PathError extends Error {
}
class PathEvent {
constructor(Event) {
__publicField2(this, "module");
__publicField2(this, "fullPath");
__publicField2(this, "path");
__publicField2(this, "name");
__publicField2(this, "methods");
var _a;
if (typeof Event == "object") return Object.assign(this, Event);
let [p2, scope, method] = Event.replaceAll(/\/{2,}/g, "/").split(":");
if (!method) method = scope || "*";
if (p2 == "*" || !p2 && method == "*") {
p2 = "";
method = "*";
}
let temp = p2.split("/").filter((p22) => !!p22);
this.module = ((_a = temp.splice(0, 1)[0]) == null ? void 0 : _a.toLowerCase()) || "";
this.fullPath = p2;
this.path = temp.join("/");
this.name = temp.pop() || "";
this.methods = new ASet(method.split(""));
}
/** All/Wildcard specified */
get all() {
return this.methods.has("*");
}
set all(v2) {
v2 ? new ASet(["*"]) : this.methods.delete("*");
}
/** None specified */
get none() {
return this.methods.has("n");
}
set none(v2) {
v2 ? this.methods = new ASet(["n"]) : this.methods.delete("n");
}
/** Create method specified */
get create() {
return !this.methods.has("n") && (this.methods.has("*") || this.methods.has("c"));
}
set create(v2) {
v2 ? this.methods.delete("n").add("c") : this.methods.delete("c");
}
/** Read method specified */
get read() {
return !this.methods.has("n") && (this.methods.has("*") || this.methods.has("r"));
}
set read(v2) {
v2 ? this.methods.delete("n").add("r") : this.methods.delete("r");
}
/** Update method specified */
get update() {
return !this.methods.has("n") && (this.methods.has("*") || this.methods.has("u"));
}
set update(v2) {
v2 ? this.methods.delete("n").add("u") : this.methods.delete("u");
}
/** Delete method specified */
get delete() {
return !this.methods.has("n") && (this.methods.has("*") || this.methods.has("d"));
}
set delete(v2) {
v2 ? this.methods.delete("n").add("d") : this.methods.delete("d");
}
/**
* Combine multiple events into one parsed object. Longest path takes precedent, but all subsequent methods are
* combined until a "none" is reached
*
* @param {string | PathEvent} paths Events as strings or pre-parsed
* @return {PathEvent} Final combined permission
*/
static combine(...paths) {
let hitNone = false;
const combined = paths.map((p2) => new PathEvent(p2)).toSorted((p1, p2) => {
const l1 = p1.fullPath.length, l2 = p2.fullPath.length;
return l1 < l2 ? 1 : l1 > l2 ? -1 : 0;
}).reduce((acc, p2) => {
if (p2.none) hitNone = true;
if (!acc) return p2;
if (hitNone) return acc;
acc.methods = [...acc.methods, ...p2.methods];
return acc;
}, null);
combined.methods = new ASet(combined.methods);
return combined;
}
/**
* Filter a set of paths based on the target
*
* @param {string | PathEvent | (string | PathEvent)[]} target Array of events that will filtered
* @param filter {...PathEvent} Must container one of
* @return {boolean} Whether there is any overlap
*/
static filter(target, ...filter) {
const parsedTarget = makeArray(target).map((pe) => new PathEvent(pe));
const parsedFind = makeArray(filter).map((pe) => new PathEvent(pe));
return parsedTarget.filter((t) => {
if (!t.fullPath && t.all) return true;
return !!parsedFind.find((f) => (t.fullPath.startsWith(f.fullPath) || f.fullPath.startsWith(t.fullPath)) && (f.all || t.all || t.methods.intersection(f.methods).length));
});
}
/**
* Squash 2 sets of paths & return true if any overlap is found
*
* @param {string | PathEvent | (string | PathEvent)[]} target Array of Events as strings or pre-parsed
* @param has Target must have at least one of these path
* @return {boolean} Whether there is any overlap
*/
static has(target, ...has) {
const parsedRequired = makeArray(has).map((pe) => new PathEvent(pe));
const parsedTarget = makeArray(target).map((pe) => new PathEvent(pe));
return !!parsedRequired.find((r2) => {
if (!r2.fullPath && r2.all) return true;
const filtered = parsedTarget.filter((p2) => r2.fullPath.startsWith(p2.fullPath));
if (!filtered.length) return false;
const combined = PathEvent.combine(...filtered);
return !combined.none && (combined.all || r2.all) || combined.methods.intersection(r2.methods).length;
});
}
/**
* Squash 2 sets of paths & return true if the target has all paths
*
* @param {string | PathEvent | (string | PathEvent)[]} target Array of Events as strings or pre-parsed
* @param has Target must have all these paths
* @return {boolean} Whether there is any overlap
*/
static hasAll(target, ...has) {
return has.filter((h) => PathEvent.has(target, h)).length == has.length;
}
/**
* Same as `has` but raises an error if there is no overlap
*
* @param {string | string[]} target Array of Events as strings or pre-parsed
* @param has Target must have at least one of these path
*/
static hasFatal(target, ...has) {
if (!PathEvent.has(target, ...has)) throw new PathError(`Requires one of: ${makeArray(has).join(", ")}`);
}
/**
* Same as `hasAll` but raises an error if the target is missing any paths
*
* @param {string | string[]} target Array of Events as strings or pre-parsed
* @param has Target must have all these paths
*/
static hasAllFatal(target, ...has) {
if (!PathEvent.hasAll(target, ...has)) throw new PathError(`Requires all: ${makeArray(has).join(", ")}`);
}
/**
* Create event string from its components
*
* @param {string | string[]} path Event path
* @param {Method} methods Event method
* @return {string} String representation of Event
*/
static toString(path, methods) {
let p2 = makeArray(path).filter((p22) => p22 != null).join("/");
p2 = p2 == null ? void 0 : p2.trim().replaceAll(/\/{2,}/g, "/").replaceAll(/(^\/|\/$)/g, "");
if (methods == null ? void 0 : methods.length) p2 += `:${makeArray(methods).map((m) => m.toLowerCase()).join("")}`;
return p2;
}
/**
* Filter a set of paths based on this event
*
* @param {string | PathEvent | (string | PathEvent)[]} target Array of events that will filtered
* @return {boolean} Whether there is any overlap
*/
filter(target) {
return PathEvent.filter(target, this);
}
/**
* Create event string from its components
*
* @return {string} String representation of Event
*/
toString() {
return PathEvent.toString(this.fullPath, this.methods);
}
}
class PathEventEmitter {
constructor() {
__publicField2(this, "listeners", []);
}
emit(event, ...args) {
const parsed = new PathEvent(event);
this.listeners.filter((l) => PathEvent.has(l[0], event)).forEach(async (l) => l[1](parsed, ...args));
}
off(listener) {
this.listeners = this.listeners.filter((l) => l[1] != listener);
}
on(event, listener) {
makeArray(event).forEach((e) => this.listeners.push([new PathEvent(e), listener]));
return () => this.off(listener);
}
once(event, listener) {
return new Promise((res) => {
const unsubscribe = this.on(event, (event2, ...args) => {
res(args.length < 2 ? args[0] : args);
if (listener) listener(event2, ...args);
unsubscribe();
});
});
}
relayEvents(emitter) {
emitter.on("*", (event, ...args) => this.emit(event, ...args));
}
}
class Api extends Http {
constructor(url = location.origin, opts = {}) {
super({ ...(opts == null ? void 0 : opts.http) || {}, url });
__publicField(this, "emitter", new PathEventEmitter());
__publicField(this, "pending", {});
__publicField(this, "storageKey");
__publicField(this, "host");
__publicField(this, "_token", null);
__publicField(this, "emit", this.emitter.emit.bind(this.emitter));
__publicField(this, "off", this.emitter.off.bind(this.emitter));
__publicField(this, "on", this.emitter.on.bind(this.emitter));
__publicField(this, "once", this.emitter.once.bind(this.emitter));
__publicField(this, "relayEvents", this.emitter.relayEvents.bind(this.emitter));
this.url = url;
this.opts = opts;
this.host = new URL(url).host;
this.storageKey = `momentum:token:${this.host}`;
if (opts.persist && localStorage) {
const token = localStorage.getItem(this.storageKey);
if (token) this.token = token;
}
}
get sameOrigin() {
if (typeof window == "undefined" || !(window == null ? void 0 : window.location)) return false;
return window.location.host == this.host;
}
/** Current API token */
get token() {
return this._token;
}
set token(token) {
if (token == this._token) return;
this._token = token;
this.headers["Authorization"] = token ? `Bearer ${token}` : null;
if (this.opts.persist && localStorage) {
if (token) localStorage.setItem(this.storageKey, token);
else localStorage.removeItem(this.storageKey);
}
this.emit(PES`api/token:${token ? "u" : "d"}`, token);
}
/**
* Fetch current Momentum status
* @return {Promise<Health>}
*/
healthcheck() {
return this.request({ url: "/api/healthcheck" }).then((resp) => {
this.emit(PES`api/healthcheck:r`, resp);
return resp;
});
}
/**
* Create API request
* @param {HttpRequestOptions} options Request options
* @return {Promise} Response
*/
request(options) {
const key = JSONSanitize(options);
const method = options.method == "GET" ? "r" : options.method == "POST" ? "c" : options.method == "DELETE" ? "d" : "u";
if (this.pending[key] != null) return this.pending[key];
this.pending[key] = super.request({ ...options, credentials: "include" }).then((response) => {
this.emit(PES`api/response:${method}`, { request: options, response });
return options.decode === false ? response : response.data;
}).catch((err) => {
const e = (err == null ? void 0 : err.data) || err;
this.emit(PES`api/error:${method}`, { request: options, error: e });
throw e;
}).finally(() => delete this.pending[key]);
this.emit(PES`api/request:${method}`, { request: options, response: this.pending[key] });
return this.pending[key];
}
}
var ActionType = /* @__PURE__ */ ((ActionType2) => {
ActionType2[ActionType2["CRON"] = 0] = "CRON";
ActionType2[ActionType2["EVENT"] = 1] = "EVENT";
ActionType2[ActionType2["DELETE"] = 2] = "DELETE";
ActionType2[ActionType2["GET"] = 3] = "GET";
ActionType2[ActionType2["PATCH"] = 4] = "PATCH";
ActionType2[ActionType2["POST"] = 5] = "POST";
ActionType2[ActionType2["PUT"] = 6] = "PUT";
return ActionType2;
})(ActionType || {});
class Actions extends PathEventEmitter {
constructor(api) {
super();
__publicField(this, "api");
__publicField(this, "cache", new Cache("_id"));
this.api = typeof api == "string" ? new Api(api) : api;
}
/**
* All saved actions
* @param {boolean} reload Will use cached response if false
* @return {Promise<Action[]>} List of saved actions
*/
async all(reload) {
if (!reload && this.cache.complete) return this.cache.all();
return this.api.request({ url: `/api/` + PES`actions` }).then((resp) => {
this.cache.addAll(resp);
this.emit(PES`actions:r`, resp || []);
return resp;
});
}
/**
* Manually trigger an actions execution
* @param {string} id Action ID
* @param {HttpRequestOptions} opts Additional arguments
* @return {Promise<ActionResult>} All action output including console logs & return
*/
debug(id, opts = {}) {
return this.api.request({ url: "/api/" + PES`actions/debug/${id}`, method: "POST", ...opts });
}
/**
* Delete an existing action
* @param {string} id Action ID
* @return {Promise<void>} Delete complete
*/
delete(id) {
if (!id) throw new Error("Cannot delete action, missing ID");
return this.api.request({ url: `/api/` + PES`actions/${id}`, method: "DELETE" }).then(() => {
this.cache.delete(id);
this.emit(PES`actions/${id}:d`, id);
});
}
/**
* Fetch action info
* @param {string} id Action ID
* @param {boolean} reload Will use cached response if false
* @return {Promise<Action | null>} Requested action
*/
read(id, reload) {
if (!id) throw new Error("Cannot read action, missing ID");
const cached = this.cache.get(id);
if (!reload && cached) return Promise.resolve(cached);
return this.api.request({ url: `/api/` + PES`actions/${id}` }).then((action) => {
if (action) this.cache.add(action);
this.emit(PES`actions/${id}:r`, action);
return action;
});
}
/**
* Run an HTTP action
* @param {string} path HTTP path excluding `/api/actions/run`
* @param {HttpRequestOptions} opts HTTP options
* @return {Promise<T>} HTTP response
*/
run(path, opts = {}) {
return this.api.request({ ...opts, url: "/api/" + PES`actions/run/${path}` });
}
/**
* Update an action
* @param {Action} action The new action
* @return {Promise<Action>} Saved action
*/
update(action) {
return this.api.request({
url: `/api/` + PES`actions/${action._id}`,
method: "POST",
body: action
}).then((action2) => {
if (action2) this.cache.add(action2);
this.emit(PES`actions/${action2._id}:u`, action2);
return action2;
});
}
}
class Ai extends PathEventEmitter {
constructor(api) {
super();
__publicField(this, "api");
this.api = typeof api == "string" ? new Api(api) : api;
}
/**
* Ask the AI assistant a question
* @param {string} question Users question
* @param {any} context Additional data to aid response
* @return {Promise<string>} AI's response
*/
ask(question, context) {
if (!question) throw new Error("Cannot ask AI, missing question");
return this.api.request({ url: `/api/` + PES`ai`, method: "POST", body: {
question,
context
} }).then((response) => {
this.emit(PES`ai:c`, { question, context, response });
return response;
});
}
/**
* Clear AI assistant memory & context
* @return {Promise<void>} Resolves once complete
*/
clear() {
return this.api.request({ url: "/api/" + PES`ai`, method: "DELETE" }).then(() => this.emit(PES`ai:d`, this.api.token));
}
/**
* Current chat history
* @return {Promise<{role: string, content: string}[]>}
*/
history() {
return this.api.request({ url: "/api/" + PES`ai` }).then((resp) => {
this.emit(PES`ai:r`, resp);
return resp;
});
}
/**
* Get model info
* @return {Promise<{host: string, model: string}>} Model Info
*/
info() {
return this.api.request({ url: "/api/ai/info" });
}
}
class Analytics extends PathEventEmitter {
constructor(api) {
super();
__publicField(this, "api");
this.api = typeof api == "string" ? new Api(api) : api;
}
/**
* Perform IP trace
* @param {string} ip IP address to trace
* @return {Promise<IpTrace>} Trace results
*/
ipTrace(ip) {
if (!ip) throw new Error("Cannot trace, missing IP");
return this.api.request({ url: `/api/` + PES`analytics/trace/${ip}` }).then((resp) => {
this.emit(PES`analytics/trace/${ip}:r`, resp);
return resp;
});
}
}
class Token extends PathEventEmitter {
constructor(api) {
super();
this.api = api;
}
/**
* Fetch all tokens for user
* @param {string} username User to search
* @return {Promise<UserToken[]>} List of tokens
*/
all(username) {
return this.api.request({ url: "/" + PES`api/auth/tokens/${username}` }).then((resp) => {
this.emit(PES`token/${username}:r`, resp);
return resp;
});
}
/**
* Create a new user token
* @param {{name: string, owner: string, expire: null | Date}} token Token settings
* @return {Promise<UserToken>} Crated token
*/
create(token) {
return this.api.request({ url: "/" + PES`api/auth/tokens/${token.owner}`, body: token }).then((resp) => {
this.emit(PES`token/${token.owner}:c`, token);
return resp;
});
}
/**
* Delete an existing user token
* @param {string} id Token ID
* @return {Promise<void>} Resolves once complete
*/
delete(id) {
return this.api.request({ url: "/" + PES`api/auth/tokens/${id}`, method: "DELETE" }).then(() => this.emit(PES`token/${id}:d`, id));
}
}
class Totp {
constructor(api) {
/**
* Enable 2FA for user
* @param {string} username User to reset
* @return {Promise<void>} Resolves once complete
*/
__publicField(this, "enable", this.reset);
this.api = api;
}
/**
* Disable 2FA for user
* @param {string} username User to disable 2FA for
* @return {Promise<void>} Resolves once complete
*/
disable(username) {
return this.api.request({ url: "/" + PES`api/auth/totp/${username}`, method: "DELETE" });
}
/**
* Reset users 2FA
* @param {string} username User to reset
* @return {Promise<void>} Resolves once complete
*/
reset(username) {
return this.api.request({ url: "/" + PES`api/auth/totp/${username}`, method: "POST" });
}
/**
* Setup 2FA authentication method
* @param {string} username User to setup
* @param {string} method Authenticator type
* @param {string | null} totp null to being process, 2FA code to validate method
* @return {Promise<void>} Resolves once complete
*/
setup(username, method = "app", totp) {
return this.api.request({ url: "/" + PES`api/auth/totp/${username}`, body: clean({
method,
totp
}) });
}
}
class Auth extends PathEventEmitter {
constructor(api, opts = {}) {
super();
__publicField(this, "api");
/** Manage user tokens */
__publicField(this, "token");
/** Manage user 2FA */
__publicField(this, "totp");
__publicField(this, "_permissions", []);
__publicField(this, "_user");
// Permission helpers
__publicField(this, "filter", (...events) => PathEvent.filter(this.permissions, ...events));
__publicField(this, "has", (...events) => PathEvent.has(this.permissions, ...events));
__publicField(this, "hasAll", (...events) => PathEvent.hasAll(this.permissions, ...events));
__publicField(this, "hasFatal", (...events) => PathEvent.hasFatal(this.permissions, ...events));
__publicField(this, "hasAllFatal", (...events) => PathEvent.hasAllFatal(this.permissions, ...events));
this.opts = opts;
this.api = typeof api == "string" ? new Api(api) : api;
this.token = new Token(this.api);
this.totp = new Totp(this.api);
this.relayEvents(this.token);
this.opts = {
loginUrl: this.api.url + "/ui/login",
...this.opts
};
this.api.addInterceptor((resp, next) => {
const blacklist = [
"/api/auth/