@ayonli/jsext
Version:
A JavaScript extension package for building strong and modern applications.
1,575 lines (1,549 loc) • 62.5 kB
JavaScript
var _a$2;
const id = Symbol.for("id");
typeof ServiceWorkerGlobalScope === "function"
&& globalThis instanceof ServiceWorkerGlobalScope;
typeof SharedWorkerGlobalScope === "function"
&& globalThis instanceof SharedWorkerGlobalScope;
typeof DedicatedWorkerGlobalScope === "function"
&& globalThis instanceof DedicatedWorkerGlobalScope;
const isDeno = typeof Deno === "object" && typeof Deno.version === "object";
const isBun = typeof Bun === "object" && typeof Bun.version === "string";
const isNodeLike = typeof process === "object" && !!((_a$2 = process.versions) === null || _a$2 === void 0 ? void 0 : _a$2.node) && !isDeno;
const isNode = isNodeLike && !isDeno && !isBun;
isNode && parseInt(process.version.slice(1)) < 14;
isNode && parseInt(process.version.slice(1)) < 16;
isNode && parseInt(process.version.slice(1)) < 20;
const isNodeWorkerThread = isNode
&& (process.abort.disabled === true || process.argv.includes("--worker-thread"));
const isMainThread = !isNodeWorkerThread
&& (isBun ? Bun.isMainThread : typeof WorkerGlobalScope === "undefined");
/**
* Functions for dealing with numbers.
* @module
*/
/** Returns `true` if the given value is a float number, `false` otherwise. */
/**
* Creates a generator that produces sequential numbers from `1` to
* `Number.MAX_SAFE_INTEGER`, useful for generating unique IDs.
*
* @param loop Repeat the sequence when the end is reached.
*
* @example
* ```ts
* import { serial } from "@ayonli/jsext/number";
*
* const idGenerator = serial();
*
* console.log(idGenerator.next().value); // 1
* console.log(idGenerator.next().value); // 2
* console.log(idGenerator.next().value); // 3
* ```
*/
function serial(loop = false) {
return sequence(1, Number.MAX_SAFE_INTEGER, 1, loop);
}
/**
* Creates a generator that produces sequential numbers from `min` to `max` (inclusive).
* @inner
*/
function* sequence(min, max, step = 1, loop = false) {
let id = min;
while (true) {
yield id;
if ((id += step) > max) {
if (loop) {
id = min;
}
else {
break;
}
}
}
}
/**
* A channel implementation that transfers data across routines, even across
* multiple threads, inspired by Golang.
* @module
*/
var _a$1;
if (typeof Symbol.dispose === "undefined") {
Object.defineProperty(Symbol, "dispose", { value: Symbol("Symbol.dispose") });
}
const idGenerator = serial(true);
/**
* A channel implementation that transfers data across routines, even across
* multiple threads, inspired by Golang.
*/
class Channel {
constructor(capacity = 0) {
this[_a$1] = idGenerator.next().value;
this.buffer = [];
this.producers = [];
this.consumers = [];
this.error = null;
this.state = 1;
if (capacity < 0) {
throw new RangeError("the capacity of a channel must not be negative");
}
this.capacity = capacity;
}
/**
* Pushes data to the channel.
*
* If there is a receiver, the data will be consumed immediately. Otherwise:
*
* - If this is an non-buffered channel, this function will block until a
* receiver is available and the data is consumed.
*
* - If this is a buffered channel, then:
* - If the buffer size is within the capacity, the data will be pushed
* to the buffer.
* - Otherwise, this function will block until there is new space for
* the data in the buffer.
*/
send(data) {
if (this.state !== 1) {
throw new TypeError("the channel is closed");
}
else if (this.consumers.length) {
const consume = this.consumers.shift();
return Promise.resolve(consume(null, data));
}
else if (this.capacity && this.buffer.length < this.capacity) {
this.buffer.push(data);
return Promise.resolve(undefined);
}
else {
return new Promise(resolve => {
this.producers.push(() => {
if (this.capacity) {
const _data = this.buffer.shift();
this.buffer.push(data);
resolve();
return _data;
}
else {
resolve();
return data;
}
});
});
}
}
/**
* Retrieves data from the channel.
*
* If there isn't data available at the moment, this function will block
* until new data is available.
*
* If the channel is closed, then:
*
* - If there is error set in the channel, this function throws that error
* immediately.
* - Otherwise, this function returns `undefined` immediately.
*/
recv() {
if (this.buffer.length) {
const data = this.buffer.shift();
if (this.state === 2 && !this.buffer.length) {
this.state = 0;
}
return Promise.resolve(data);
}
else if (this.producers.length) {
const produce = this.producers.shift();
if (this.state === 2 && !this.producers.length) {
this.state = 0;
}
return Promise.resolve(produce());
}
else if (this.state === 0) {
return Promise.resolve(undefined);
}
else if (this.error) {
// Error can only be consumed once, after that, that closure will
// be complete.
const { error } = this;
this.state = 0;
this.error = null;
return Promise.reject(error);
}
else if (this.state === 2) {
this.state = 0;
return Promise.resolve(undefined);
}
else {
return new Promise((resolve, reject) => {
this.consumers.push((err, data) => {
if (this.state === 2 && !this.consumers.length) {
this.state = 0;
}
err ? reject(err) : resolve(data);
});
});
}
}
/**
* Closes the channel. If `err` is supplied, it will be captured by the
* receiver.
*
* No more data shall be sent once the channel is closed.
*
* Explicitly closing the channel is not required, if the channel is no
* longer used, it will be automatically released by the GC. However, if
* the channel is used in a `for await...of...` loop, closing the channel
* will allow the loop to break automatically.
*
* Moreover, if the channel is used between parallel threads, it will no
* longer be able to release automatically, must explicitly call this
* function in order to release for GC.
*/
close(err = null) {
if (this.state !== 1) {
// prevent duplicated call
return;
}
this.state = 2;
this.error = err;
let consume;
while (consume = this.consumers.shift()) {
consume(err, undefined);
}
}
[(_a$1 = id, Symbol.asyncIterator)]() {
const channel = this;
return {
async next() {
const bufSize = channel.buffer.length;
const queueSize = channel.producers.length;
const value = await channel.recv();
return {
value: value,
done: channel.state === 0 && !bufSize && !queueSize,
};
}
};
}
[Symbol.dispose]() {
this.close();
}
}
const channelStore = new Map();
function isChannelMessage(msg) {
return msg
&& typeof msg === "object"
&& ["send", "close"].includes(msg.type)
&& typeof msg.channelId === "number";
}
async function handleChannelMessage(msg) {
const record = channelStore.get(msg.channelId);
if (!record)
return;
if (msg.type === "send") {
await record.raw.send(msg.value);
}
else if (msg.type === "close") {
const { value: err, channelId } = msg;
record.raw.close(err);
channelStore.delete(channelId);
if (isMainThread && record.writers.length > 1) {
// distribute the channel close event to all threads
record.writers.forEach(write => {
write("close", err, channelId);
});
}
}
}
function wireChannel(channel, channelWrite) {
const channelId = channel[id];
if (!channelStore.has(channelId)) {
const send = channel.send.bind(channel);
const close = channel.close.bind(channel);
channelStore.set(channelId, {
channel,
raw: { send, close },
writers: [channelWrite],
counter: 0,
});
Object.defineProperties(channel, {
send: {
configurable: true,
writable: true,
value: async (data) => {
const record = channelStore.get(channelId);
if (record) {
const channel = record.channel;
if (channel["state"] !== 1) {
throw new TypeError("the channel is closed");
}
const write = record.writers[record.counter++ % record.writers.length];
await Promise.resolve(write("send", data, channelId));
}
},
},
close: {
configurable: true,
writable: true,
value: (err = null) => {
const record = channelStore.get(channelId);
if (record) {
channelStore.delete(channelId);
const channel = record.channel;
record.writers.forEach(write => {
write("close", err, channelId);
});
// recover to the original methods
Object.defineProperties(channel, {
send: {
configurable: true,
writable: true,
value: record.raw.send,
},
close: {
configurable: true,
writable: true,
value: record.raw.close,
},
});
channel.close(err);
}
},
},
});
}
else {
const record = channelStore.get(channelId);
record.writers.push(channelWrite);
}
}
function unwrapChannel(obj, channelWrite) {
var _a, _b;
const channelId = obj["@@id"];
let channel = (_a = channelStore.get(channelId)) === null || _a === void 0 ? void 0 : _a.channel;
if (!channel) {
channel = Object.assign(Object.create(Channel.prototype), {
[id]: channelId,
capacity: (_b = obj.capacity) !== null && _b !== void 0 ? _b : 0,
buffer: [],
producers: [],
consumers: [],
error: null,
state: 1,
});
}
wireChannel(channel, channelWrite);
return channel;
}
if (!Symbol.asyncIterator) {
// @ts-ignore
Symbol.asyncIterator = Symbol("Symbol.asyncIterator");
}
/**
* Checks if the given object is an IteratorLike (implemented `next`).
* @param {any} obj
* @returns {obj is { [x: string | symbol]: any; next: Function }}
*/
function isIteratorLike(obj) {
// An iterable object has a 'next' method, however including a 'next' method
// doesn't ensure the object is an iterator, it is only iterator-like.
return typeof obj === "object"
&& obj !== null
&& typeof obj.next === "function";
}
/**
* Checks if the given object is an IterableIterator (implemented both
* `@@iterator` and `next`).
* @param {any} obj
*/
function isIterableIterator(obj) {
return isIteratorLike(obj)
&& typeof obj[Symbol.iterator] === "function";
}
/**
* Checks if the given object is an AsyncIterableIterator (implemented
* both `@@asyncIterator` and `next`).
* @param {any} obj
* @returns {obj is AsyncIterableIterator<any>}
*/
function isAsyncIterableIterator(obj) {
return isIteratorLike(obj)
&& typeof obj[Symbol.asyncIterator] === "function";
}
/**
* Checks if the given object is a Generator.
* @param {any} obj
* @returns {obj is Generator}
*/
function isGenerator(obj) {
return isIterableIterator(obj)
&& hasGeneratorSpecials(obj);
}
/**
* Checks if the given object is an AsyncGenerator.
* @param {any} obj
* @returns {obj is AsyncGenerator}
*/
function isAsyncGenerator(obj) {
return isAsyncIterableIterator(obj)
&& hasGeneratorSpecials(obj);
}
/**
* @param {any} obj
*/
function hasGeneratorSpecials(obj) {
return typeof obj.return === "function"
&& typeof obj.throw === "function";
}
/**
* Utilities for encoding and decoding binary representations like hex and
* base64 strings.
* @module
*/
new TextEncoder();
/**
* Functions for dealing with byte arrays (`Uint8Array`).
* @module
*/
new TextEncoder();
new TextDecoder();
/**
* This module includes functions for dealing with classes.
* @module
*/
/**
* Checks if a value is a class/constructor.
*
* @example
* ```ts
* import { isClass } from "@ayonli/jsext/class";
*
* console.assert(isClass(class Foo { }));
* console.assert(!isClass(function foo() { }));
* ```
*/
/**
* Checks if a class is a subclass of another class.
*
* @example
* ```ts
* import { isSubclassOf } from "@ayonli/jsext/class";
*
* class Moment extends Date {}
*
* console.assert(isSubclassOf(Moment, Date));
* console.assert(isSubclassOf(Moment, Object)); // all classes are subclasses of Object
* ```
*/
function isSubclassOf(ctor1, ctor2) {
return typeof ctor1 === "function"
&& typeof ctor2 === "function"
&& ctor1.prototype instanceof ctor2;
}
/**
* Functions for dealing with objects.
* @module
*/
/**
* Returns `true` if the specified object has the indicated property as its own property.
* If the property is inherited, or does not exist, the function returns `false`.
*
* @example
* ```ts
* import { hasOwn } from "@ayonli/jsext/object";
*
* const obj = { foo: "hello" };
*
* console.log(hasOwn(obj, "foo")); // true
* console.log(hasOwn(obj, "toString")); // false
* ```
*/
function hasOwn(obj, key) {
return Object.prototype.hasOwnProperty.call(obj, key);
}
function pick(obj, keys) {
return keys.reduce((result, key) => {
if (key in obj && obj[key] !== undefined) {
result[key] = obj[key];
}
return result;
}, {});
}
function omit(obj, keys) {
const allKeys = Reflect.ownKeys(obj);
const keptKeys = allKeys.filter(key => !keys.includes(key));
const result = pick(obj, keptKeys);
// special treatment for Error types
if (obj instanceof Error) {
["name", "message", "stack", "cause"].forEach(key => {
if (!keys.includes(key) &&
obj[key] !== undefined &&
!hasOwn(result, key)) {
result[key] = obj[key];
}
});
}
return result;
}
/**
* Returns `true` is the given value is a plain object, that is, an object created by
* the `Object` constructor or one with a `[[Prototype]]` of `null`.
*
* @example
* ```ts
* import { isPlainObject } from "@ayonli/jsext/object";
*
* console.log(isPlainObject({ foo: "bar" })); // true
* console.log(isPlainObject(Object.create(null))); // true
* console.log(isPlainObject(new Map([["foo", "bar"]]))); // false
* ```
*/
function isPlainObject(value) {
if (typeof value !== "object" || value === null)
return false;
const proto = Object.getPrototypeOf(value);
return proto === null || proto.constructor === Object;
}
/**
* A generic exception class, which can be used to represent any kind of error.
* It's similar to the `DOMException`, but for any JavaScript environment.
*
* @example
* ```ts
* // throw an exception with a name
* import { Exception } from "@ayonli/jsext/error";
*
* throw new Exception("Operation timeout after 5 seconds", "TimeoutError");
* ```
*
* @example
* ```ts
* // throw an exception with a code (not recommended, always use a name or both)
* import { Exception } from "@ayonli/jsext/error";
*
* throw new Exception("Operation timeout after 5 seconds", 408);
* ```
*
* @example
* ```ts
* // throw an exception with a cause
* import { Exception } from "@ayonli/jsext/error";
*
* try {
* throw new Error("Something went wrong");
* } catch (error) {
* throw new Exception("An error occurred", { cause: error });
* }
* ```
*/
class Exception extends Error {
constructor(message, options = 0) {
super(message);
this.code = 0;
if (typeof options === "number") {
this.code = options;
}
else if (typeof options === "string") {
Object.defineProperty(this, "name", {
configurable: true,
enumerable: false,
writable: true,
value: options,
});
}
else {
if (options.name) {
Object.defineProperty(this, "name", {
configurable: true,
enumerable: false,
writable: true,
value: options.name,
});
}
if (options.cause) {
Object.defineProperty(this, "cause", {
configurable: true,
enumerable: false,
writable: true,
value: options.cause,
});
}
if (options.code) {
this.code = options.code;
}
}
}
}
Object.defineProperty(Exception.prototype, "name", {
configurable: true,
enumerable: false,
get() {
return this.constructor.name;
},
});
/**
* This module includes some common errors that can be used in the application.
* @module
*/
/**
* This error indicates that the requested operation, such as modifying a file,
* is not allowed by the current user.
*
* NOTE: This error has an HTTP-compatible code of `403`.
*/
class NotAllowedError extends Exception {
constructor(message, options = {}) {
super(message, { ...options, name: "NotAllowedError", code: 403 });
}
}
/**
* This error indicates that the requested resource, such as a file, is not
* found.
*
* NOTE: This error has an HTTP-compatible code of `404`.
*/
class NotFoundError extends Exception {
constructor(message, options = {}) {
super(message, { ...options, name: "NotFoundError", code: 404 });
}
}
/**
* This error indicates that the target resource path, such as a file, already
* exists.
*
* NOTE: This error has an HTTP-compatible code of `409`.
*/
class AlreadyExistsError extends Exception {
constructor(message, options = {}) {
super(message, { ...options, name: "AlreadyExistsError", code: 409 });
}
}
/**
* This error indicates that the requested function or feature is not supported
* by the current environment.
*
* NOTE: This error has an HTTP-compatible code of `405`.
*/
class NotSupportedError extends Exception {
constructor(message, options = {}) {
super(message, { ...options, name: "NotSupportedError", code: 405 });
}
}
/**
* This error indicates that the requested operation, such as a function, is not
* implemented.
*
* NOTE: This error has an HTTP-compatible code of `501`.
*
* NOTE: `NotImplementedError` should only be used for stubs or placeholders,
* it should not be used to indicate the lack of support for a feature, in such
* cases, use {@link NotSupportedError} instead.
*/
class NotImplementedError extends Exception {
constructor(message, options = {}) {
super(message, { ...options, name: "NotImplementedError", code: 501 });
}
}
/**
* This error indicates that the requested operation, such as a network request,
* is timed out.
*
* NOTE: This error has an HTTP-compatible code of `408`.
*/
class TimeoutError extends Exception {
constructor(message, options = {}) {
super(message, { ...options, name: "TimeoutError", code: 408 });
}
}
/**
* This error indicates that the connection between the client and the server
* cannot be established.
*/
class NetworkError extends Exception {
constructor(message, options = {}) {
super(message, { ...options, name: "NetworkError" });
}
}
var common = /*#__PURE__*/Object.freeze({
__proto__: null,
AlreadyExistsError: AlreadyExistsError,
NetworkError: NetworkError,
NotAllowedError: NotAllowedError,
NotFoundError: NotFoundError,
NotImplementedError: NotImplementedError,
NotSupportedError: NotSupportedError,
TimeoutError: TimeoutError
});
var _a;
if (typeof globalThis.Event !== "function") {
// @ts-ignore
globalThis.Event = (_a = class Event {
constructor(type, eventInitDict = {}) {
this.type = type;
this.eventInitDict = eventInitDict;
this.bubbles = false;
this.cancelable = false;
this.cancelBubble = false;
this.composed = false;
this.currentTarget = null;
this.defaultPrevented = false;
this.eventPhase = _a.NONE;
this.isTrusted = false;
this.returnValue = true;
this.target = null;
this.timeStamp = Date.now();
this.srcElement = null;
this.AT_TARGET = 2;
this.BUBBLING_PHASE = 3;
this.CAPTURING_PHASE = 1;
this.NONE = 0;
if (eventInitDict.bubbles !== undefined) {
this.bubbles = eventInitDict.bubbles;
}
if (eventInitDict.cancelable !== undefined) {
this.cancelable = eventInitDict.cancelable;
}
if (eventInitDict.composed !== undefined) {
this.composed = eventInitDict.composed;
}
}
composedPath() {
return [];
}
preventDefault() {
if (this.cancelable) {
this.defaultPrevented = true;
}
}
stopImmediatePropagation() {
// Do nothing
}
stopPropagation() {
this.cancelBubble = true;
}
initEvent(type, bubbles = undefined, cancelable = undefined) {
this.type = type;
this.bubbles = bubbles !== null && bubbles !== void 0 ? bubbles : false;
this.cancelable = cancelable !== null && cancelable !== void 0 ? cancelable : false;
}
},
_a.AT_TARGET = 2,
_a.BUBBLING_PHASE = 3,
_a.CAPTURING_PHASE = 1,
_a.NONE = 0,
_a);
}
if (typeof globalThis.EventTarget !== "function") {
// @ts-ignore
globalThis.EventTarget = class EventTarget {
constructor() {
this.listeners = {};
}
addEventListener(type, callback, options = {}) {
var _b;
if (!(type in this.listeners)) {
this.listeners[type] = [];
}
// @ts-ignore
this.listeners[type].push({ callback, once: (_b = options === null || options === void 0 ? void 0 : options.once) !== null && _b !== void 0 ? _b : false });
}
removeEventListener(type, callback) {
if (!(type in this.listeners)) {
return;
}
const stack = this.listeners[type];
for (let i = 0, l = stack.length; i < l; i++) {
if (stack[i].callback === callback) {
stack.splice(i, 1);
return;
}
}
if (stack.length === 0) {
delete this.listeners[type];
}
}
dispatchEvent(event) {
if (!(event.type in this.listeners)) {
return true;
}
Object.defineProperties(event, {
currentTarget: { configurable: true, value: this },
target: { configurable: true, value: this },
});
const stack = this.listeners[event.type].slice();
for (let i = 0, l = stack.length; i < l; i++) {
const listener = stack[i];
try {
listener.callback.call(this, event);
}
catch (err) {
setTimeout(() => {
throw err;
});
}
if (listener.once) {
this.removeEventListener(event.type, listener.callback);
}
}
return !event.defaultPrevented;
}
};
}
/**
* Functions for converting errors to/from other types of objects.
* @module
*/
const errorTypeRegistry = new Map([
["Exception", Exception]
]);
/**
* Registers an error constructor that can be used by {@link fromObject} to
* reverse a plain object which is previously transformed by {@link toObject}
* back to an error instance.
* */
function registerErrorType(ctor) {
errorTypeRegistry.set(ctor.name, ctor);
}
/**
* Returns the error constructor by the `name`.
* @inner
*/
function getErrorConstructor(name) {
let type = errorTypeRegistry.get(name);
if (!type && name in globalThis) {
const value = globalThis[name];
if (value === Error || isSubclassOf(value, Error) ||
(typeof DOMException === "function" && value === DOMException)) {
type = value;
}
}
return type ? type : null;
}
const commonErrors = Object.values(common)
.filter(value => isSubclassOf(value, Error));
commonErrors.forEach(ctor => registerErrorType(ctor));
/**
* Transforms the error to a plain object so that it can be serialized to JSON
* and later reversed back to an error instance using {@link fromObject}.
*
* @example
* ```ts
* import { toObject } from "@ayonli/jsext/error";
*
* const err = new Error("Something went wrong.");
*
* const obj = toObject(err);
* console.log(obj);
* // {
* // "@@type": "Error",
* // name: "Error",
* // message: "Something went wrong.",
* // stack: "Error: Something went wrong.\n at <anonymous>:1:13"
* // }
* ```
*/
function toObject(err) {
if (!(err instanceof Error) && err["name"] && err["message"]) { // Error-like
err = fromObject(err, Error);
}
const obj = {
"@@type": err.constructor.name,
...omit(err, ["toString", "toJSON", "__callSiteEvals"]),
};
if ("cause" in obj) {
obj["cause"] = obj["cause"] instanceof Error ? toObject(obj["cause"]) : obj["cause"];
}
if (obj["@@type"] === "AggregateError" && Array.isArray(obj["errors"])) {
obj["errors"] = obj["errors"].map(item => {
return item instanceof Error ? toObject(item) : item;
});
}
return obj;
}
function fromObject(obj, ctor = null, strict = false) {
var _a, _b, _c, _d;
// @ts-ignore
if (!(obj === null || obj === void 0 ? void 0 : obj.name) || (strict && !obj["@@type"])) {
return null;
}
// @ts-ignore
const typeName = obj["@@type"] || obj.name;
// @ts-ignore
ctor !== null && ctor !== void 0 ? ctor : (ctor = ((_a = getErrorConstructor(typeName)) !== null && _a !== void 0 ? _a : Error));
let err;
if (ctor.name === "DOMException" && typeof DOMException === "function") {
err = new ctor((_b = obj["message"]) !== null && _b !== void 0 ? _b : "", obj["name"]);
}
else {
err = Object.create(ctor.prototype, {
message: {
configurable: true,
enumerable: false,
writable: true,
value: (_c = obj["message"]) !== null && _c !== void 0 ? _c : "",
},
});
Object.defineProperty(err, "name", {
configurable: true,
enumerable: false,
writable: true,
value: obj["name"],
});
}
if (obj["stack"] !== undefined) {
Object.defineProperty(err, "stack", {
configurable: true,
enumerable: false,
writable: true,
value: obj["stack"],
});
}
if (obj["cause"] != undefined) {
Object.defineProperty(err, "cause", {
configurable: true,
enumerable: false,
writable: true,
value: isPlainObject(obj["cause"])
? ((_d = fromObject(obj["cause"], undefined, true)) !== null && _d !== void 0 ? _d : obj["cause"])
: obj["cause"],
});
}
const otherKeys = Reflect.ownKeys(obj).filter(key => ![
"@@type",
"name",
"message",
"stack",
"cause"
].includes(key));
otherKeys.forEach(key => {
var _a;
// @ts-ignore
(_a = err[key]) !== null && _a !== void 0 ? _a : (err[key] = obj[key]);
});
// @ts-ignore
if (isAggregateError(err) && Array.isArray(err["errors"])) {
err["errors"] = err["errors"].map(item => {
return isPlainObject(item) ? fromObject(item) : item;
});
}
return err;
}
/** @inner */
function isDOMException(value) {
return ((typeof DOMException === "function") && (value instanceof DOMException))
|| (value instanceof Error && value.constructor.name === "DOMException"); // Node.js v16-
}
/** @inner */
function isAggregateError(value) {
// @ts-ignore
return (typeof AggregateError === "function" && value instanceof AggregateError)
|| (value instanceof Error && value.constructor.name === "AggregateError");
}
(() => {
try {
return new RegExp("^\(?:\\p{Emoji_Modifier_Base}\\p{Emoji_Modifier}?|\\p{Emoji_Presentation}|\\p{Emoji}\\uFE0F)(?:\\u200d(?:\\p{Emoji_Modifier_Base}\\p{Emoji_Modifier}?|\\p{Emoji_Presentation}|\\p{Emoji}\\uFE0F))*$", "u");
}
catch (_a) {
return new RegExp("^(\\u00a9|\\u00ae|[\\u25a0-\\u27bf]|\\ud83c[\\ud000-\\udfff]|\\ud83d[\\ud000-\\udfff]|\\ud83e[\\ud000-\\udfff])$");
}
})();
/**
* Functions for dealing with strings.
* @module
*/
const _trim = String.prototype.trim;
const _trimEnd = String.prototype.trimEnd;
const _trimStart = String.prototype.trimStart;
/**
* Removes leading and trailing spaces or custom characters of the string.
*
* @example
* ```ts
* import { trim } from "@ayonli/jsext/string";
*
* console.log(trim(" hello world ")); // "hello world"
* console.log(trim(" hello world! ", " !")); // "hello world"
* ```
*/
function trim(str, chars = "") {
if (!chars) {
return _trim.call(str);
}
else {
return trimEnd(trimStart(str, chars), chars);
}
}
/**
* Removes trailing spaces or custom characters of the string.
*
* @example
* ```ts
* import { trimEnd } from "@ayonli/jsext/string";
*
* console.log(trimEnd(" hello world ")); // " hello world"
* console.log(trimEnd(" hello world! ", " !")); // " hello world"
* ```
*/
function trimEnd(str, chars = "") {
if (!chars) {
return _trimEnd.call(str);
}
else {
let i = str.length;
while (i-- && chars.indexOf(str[i]) !== -1) { }
return str.substring(0, i + 1);
}
}
/**
* Removes leading spaces or custom characters of the string.
*
* @example
* ```ts
* import { trimStart } from "@ayonli/jsext/string";
*
* console.log(trimStart(" hello world ")); // "hello world "
* console.log(trimStart(" !hello world! ", " !")); // "hello world! "
* ```
*/
function trimStart(str, chars = "") {
if (!chars) {
return _trimStart.call(str);
}
else {
let i = 0;
do { } while (chars.indexOf(str[i]) !== -1 && ++i);
return str.substring(i);
}
}
function isVolume(path, strict = false) {
return strict ? /^[a-zA-Z]:$/.test(path) : /^[a-zA-Z]:(\\)?$/.test(path);
}
/**
* Checks if the given `path` is a Windows specific path.
*
* @example
* ```ts
* import { isWindowsPath } from "@ayonli/jsext/path";
*
* console.assert(isWindowsPath("C:\\Windows\\System32"));
* console.assert(isWindowsPath("c:\\Windows\\System32")); // case-insensitive on volume
* console.assert(isWindowsPath("D:/Program Files")); // forward slash is also valid
* console.assert(isWindowsPath("E:")); // volume without path is also valid
* ```
*/
function isWindowsPath(path) {
return /^[a-zA-Z]:/.test(path) && path.slice(1, 4) !== "://";
}
/**
* Checks if the given `path` is a Posix specific path.
*
* @example
* ```ts
* import { isPosixPath } from "@ayonli/jsext/path";
*
* console.assert(isPosixPath("/usr/bin"));
* ```
*/
function isPosixPath(path) {
return /^\//.test(path);
}
/**
* Checks if the given `path` is a file system path.
*
* @example
* ```ts
* import { isFsPath } from "@ayonli/jsext/path";
*
* console.assert(isFsPath("/usr/bin"));
* console.assert(isFsPath("C:\\Windows\\System32"));
* console.assert(isFsPath("./foo/bar"));
* console.assert(isFsPath("../foo/bar"));
* ```
*/
function isFsPath(path) {
return /^(\.[\/\\]|\.\.[\/\\]|[a-zA-Z]:|\/)/.test(path);
}
/**
* Checks if the given string is a URL, whether standard or non-standard.
*
* @example
* ```ts
* import { isUrl } from "@ayonli/jsext/path";
*
* console.assert(isUrl("http://example.com"));
* console.assert(isUrl("https://example.com?foo=bar#baz"));
* console.assert(isUrl("ftp://example.com")); // ftp url
* console.assert(isUrl("file:///C:/Windows/System32")); // file url
* console.assert(isUrl("file://localhost/C:/Windows/System32")); // file url with hostname
* console.assert(isUrl("file:///usr/bin"));
* ```
*/
function isUrl(str) {
return /^[a-z](([a-z\-]+)?:\/\/\S+|[a-z\-]+:\/\/$)/i.test(str) || isFileUrl(str);
}
/**
* Checks if the given string or {@link URL} instance is a file URL, whether
* with or without `//`.
*
* @example
* ```ts
* import { isFileUrl } from "@ayonli/jsext/path";
*
* console.assert(isFileUrl("file:///C:/Windows/System32"));
* console.assert(isFileUrl("file://localhost/C:/Windows/System32"));
* console.assert(isFileUrl("file:///usr/bin"));
* console.assert(isFileUrl("file:/usr/bin"));
* console.assert(isFileUrl("file:///usr/bin?foo=bar"));
* console.assert(isFileUrl(new URL("file:///usr/bin?foo=bar")));
* ```
*/
function isFileUrl(path) {
return typeof path === "string"
? /^file:((\/\/|\/)\S+|\/?$)/i.test(path)
: path.protocol === "file:";
}
/**
* Checks if the given `path` is an absolute path.
*
* @example
* ```ts
* import { isAbsolute } from "@ayonli/jsext/path";
*
* console.assert(isAbsolute("/usr/bin"));
* console.assert(isAbsolute("C:\\Windows\\System32"));
* console.assert(isAbsolute("http://example.com"));
* console.assert(isAbsolute("file:///C:/Windows/System32"));
* console.assert(isAbsolute("file://localhost/C:/Windows/System32?foo=bar#baz"));
* ```
*/
function isAbsolute(path) {
return isPosixPath(path) || isWindowsPath(path) || isUrl(path);
}
/**
* Splits the `path` into well-formed segments.
*
* @example
* ```ts
* import { split } from "@ayonli/jsext/path";
*
* console.log(split("/usr/bin")); // ["/", "usr", "bin"]
* console.log(split("C:\\Windows\\System32")); // ["C:\\", "Windows", "System32"]
* console.log(split("file:///user/bin")); // ["file:///", "usr", "bin"]
*
* console.log(split("http://example.com/foo/bar?foo=bar#baz"));
* // ["http://example.com", "foo", "bar", "?foo=bar", "#baz"]
* ```
*/
function split(path) {
if (!path) {
return [];
}
else if (isUrl(path)) {
const { protocol, host, pathname, search, hash } = new URL(path);
let origin = protocol + "//" + host;
if (protocol === "file:" && !host) {
origin += "/";
}
if (pathname === "/") {
if (search && hash) {
return [origin, search, hash];
}
else if (search) {
return [origin, search];
}
else if (hash) {
return [origin, hash];
}
else {
return [origin];
}
}
else {
const segments = trim(decodeURI(pathname), "/").split(/[/\\]+/);
if (search && hash) {
return [origin, ...segments, search, hash];
}
else if (search) {
return [origin, ...segments, search];
}
else if (hash) {
return [origin, ...segments, hash];
}
else {
return [origin, ...segments];
}
}
}
else if (isWindowsPath(path)) {
const [_, volume, ...segments] = split("file:///" + path.replace(/[/\\]+/g, "/"));
return [volume + "\\", ...segments];
}
else if (isPosixPath(path)) {
const [_, ...segments] = split("file://" + path.replace(/[/\\]+/g, "/"));
return ["/", ...segments];
}
else { // relative path
path = path.replace(/[/\\]+/g, "/");
const [_path, query] = path.split("?");
if (query) {
const segments = _path ? trimEnd(_path, "/").split("/") : [];
const [search, hash] = query.split("#");
if (hash) {
return [...segments, "?" + search, "#" + hash];
}
else {
return [...segments, "?" + search];
}
}
else {
const [pathname, hash] = path.split("#");
const segments = pathname ? trimEnd(pathname, "/").split("/") : [];
if (hash) {
return [...segments, "#" + hash];
}
else {
return segments;
}
}
}
}
/**
* Platform-independent utility functions for dealing with file system paths and
* URLs.
*
* The functions in this module are designed to be generic and work in any
* runtime, whether server-side or browsers. They can be used for both system
* paths and URLs.
* @module
*/
/**
* Platform-specific path segment separator. The value is `\` in Windows
* server-side environments, and `/` elsewhere.
*/
const sep = (() => {
if (isDeno) {
if (Deno.build.os === "windows") {
return "\\";
}
}
else if (isNodeLike) {
if (process.platform === "win32") {
return "\\";
}
}
return "/";
})();
/**
* Returns the current working directory.
*
* **NOTE:** In the browser, this function returns the current origin and pathname.
*
* This function may fail in unsupported environments or being rejected by the
* permission system of the runtime.
*/
function cwd() {
if (isDeno) {
return Deno.cwd();
}
else if (isNodeLike) {
return process.cwd();
}
else if (typeof location === "object" && location.origin) {
return location.origin + (location.pathname === "/" ? "" : location.pathname);
}
else {
throw new NotSupportedError("Unable to determine the current working directory.");
}
}
/**
* Concatenates all given `segments` into a well-formed path.
*
* @example
* ```ts
* import { join } from "@ayonli/jsext/path";
*
* console.log(join("foo", "bar")); // "foo/bar" or "foo\\bar" on Windows
* console.log(join("/", "foo", "bar")); // "/foo/bar"
* console.log(join("C:\\", "foo", "bar")); // "C:\\foo\\bar"
* console.log(join("file:///foo", "bar", "..")) // "file:///foo"
*
* console.log(join("http://example.com", "foo", "bar", "?query"));
* // "http://example.com/foo/bar?query"
* ```
*/
function join(...segments) {
let _paths = [];
for (let i = 0; i < segments.length; i++) {
const path = segments[i];
if (path) {
if (isAbsolute(path)) {
_paths = [];
}
_paths.push(path);
}
}
const paths = [];
for (let i = 0; i < _paths.length; i++) {
let segment = _paths[i];
for (const _segment of split(segment)) {
if (_segment === "..") {
if (!paths.length || paths.every(p => p === "..")) {
paths.push("..");
}
else if (paths.length > 2
|| (paths.length === 2 && !isAbsolute(paths[1]))
|| (paths.length === 1 && !isAbsolute(paths[0]))) {
paths.pop();
}
}
else if (_segment && _segment !== ".") {
paths.push(_segment);
}
}
}
if (!paths.length) {
return ".";
}
const start = paths[0];
const _sep = isUrl(start) || isPosixPath(start) ? "/" : isWindowsPath(start) ? "\\" : sep;
let path = "";
for (let i = 0; i < paths.length; i++) {
const segment = paths[i];
if (!path || segment[0] === "?" || segment[0] === "#") {
path += segment;
}
else if (isVolume(segment)) {
if (path) {
path += segment + "/";
}
else {
path = segment;
}
}
else {
path += (path.endsWith(_sep) ? "" : _sep) + trim(segment, "/\\");
}
}
if (/^file:\/\/\/[a-z]:$/i.test(path)) {
return path + "/";
}
else {
return path;
}
}
/**
* Resolves path `segments` into a well-formed path.
*
* This function is similar to {@link join}, except it always returns an
* absolute path based on the current working directory if the input segments
* are not absolute by themselves.
*/
function resolve(...segments) {
segments = segments.filter(s => s !== "");
const _cwd = cwd();
if (!segments.length) {
return _cwd;
}
segments = isAbsolute(segments[0]) ? segments : [_cwd, ...segments];
return join(...segments);
}
/**
* Converts the given URL to a file system path if it's not one already.
*
* @example
* ```ts
* import { toFsPath } from "@ayonli/jsext/path";
*
* console.log(toFsPath("file:///foo/bar")); // "/foo/bar"
* console.log(toFsPath("file:///c:/foo/bar")); // "c:\\foo\\bar"
* ```
*/
function toFsPath(url) {
if (typeof url === "object") {
if (url.protocol === "file:") {
return join(fileUrlToFsPath(url.toString()));
}
else {
throwNonFileUrlConversionError();
}
}
if (isFsPath(url)) {
return url;
}
else if (isFileUrl(url)) {
return join(fileUrlToFsPath(url));
}
else if (!isUrl(url)) {
return resolve(url);
}
else {
throwNonFileUrlConversionError();
}
}
function fileUrlToFsPath(url) {
return url.replace(/^file:(\/\/)?/i, "").replace(/^\/([a-z]):/i, "$1:");
}
function throwNonFileUrlConversionError() {
throw new NotSupportedError("Cannot convert a non-file URL to a file system path.");
}
/**
* This error indicates that the operation is invalid, such as trying to copy a
* directory without the `recursive` option.
*
* NOTE: This error has an HTTP-compatible code of `400`.
*/
class InvalidOperationError extends Exception {
constructor(message, options = {}) {
super(message, { ...options, name: "InvalidOperationError", code: 400 });
}
}
registerErrorType(InvalidOperationError);
/**
* This error indicates that an operation cannot be performed because the target
* path is a directory while a file is expected.
*
* NOTE: This error has an HTTP-compatible code of `400`.
*/
class IsDirectoryError extends Exception {
constructor(message, options = {}) {
super(message, { ...options, name: "IsDirectoryError", code: 400 });
}
}
registerErrorType(IsDirectoryError);
/**
* This error indicates that an operation cannot be performed because the target
* path is a file while a directory is expected.
*
* NOTE: This error has an HTTP-compatible code of `400`.
*/
class NotDirectoryError extends Exception {
constructor(message, options = {}) {
super(message, { ...options, name: "NotDirectoryError", code: 400 });
}
}
registerErrorType(NotDirectoryError);
/**
* This error indicates that the file is too large, or the file system doesn't
* have enough space to store the new content.
*
* NOTE: This error has an HTTP-compatible code of `413`.
*/
class FileTooLargeError extends Exception {
constructor(message, options = {}) {
super(message, { ...options, name: "FileTooLargeError", code: 413 });
}
}
registerErrorType(FileTooLargeError);
/**
* This error indicates that too many symbolic links were encountered when
* resolving the filename.
*
* NOTE: This error has an HTTP-compatible code of `508`.
*/
class FilesystemLoopError extends Exception {
constructor(message, options = {}) {
super(message, { ...options, name: "FilesystemLoopError", code: 508 });
}
}
registerErrorType(FilesystemLoopError);
/**
* This error indicates that the file is busy at the moment, such as being
* locked by another program.
*
* NOTE: This error has an HTTP-compatible code of `423`.
*/
class BusyError extends Exception {
constructor(message, options = {}) {
super(message, { ...options, name: "BusyError", code: 423 });
}
}
registerErrorType(BusyError);
/**
* This error indicates that the operation is interrupted by the underlying file
* system.
*
* NOTE: This error has an HTTP-compatible code of `500`.
*/
class InterruptedError extends Exception {
constructor(message, options = {}) {
super(message, { ...options, name: "InterruptedError", code: 500 });
}
}
registerErrorType(InterruptedError);
/**
* Universal file system APIs for both server and browser applications.
*
* This module is guaranteed to work in the following environments:
*
* - Node.js
* - Deno
* - Bun
* - Modern browsers
* - Cloudflare Workers (limited support and experimental)
*
* We can also use the {@link runtime} function to check whether the runtime
* has file system support. When `runtime().fsSupport` is `true`, this module
* should work properly.
*
* In most browsers, this module uses the
* [Origin Private File System](https://developer.mozilla.org/en-US/docs/Web/API/File_System_API/Origin_private_file_system).
* In Chromium browsers, this module can also access the device's local file
* system via `window.showOpenFilePicker()` and `window.showDirectoryPicker()`.
*
* This module also provides limited support for Cloudflare Workers, however it
* requires setting the `[site].bucket` option in the `wrangler.toml` file. Only
* the reading functions are supported, such as {@link readFile} and
* {@link readDir}, these functions allow us reading static files in the workers,
* writing functions is not implemented at the moment. More details about
* serving static assets in Cloudflare Workers can be found here:
* [Add static assets to an existing Workers project](https://developers.cloudflare.com/workers/configuration/sites/start-from-worker/).
*
* **Errors:**
*
* When a file system operation fails, this module throws one of following
* derived {@link Exception} instances:
*
* - `NotAllowedError`: The operation is not allowed, such as being blocked by
* the permission system.
* - `NotFoundError`: The file or directory does not exist.
* - `AlreadyExistsError`: The file or directory already exists.
* - `InvalidOperationError`: The operation is invalid, such as trying to copy a
* directory without the `recursive` option.
* - `IsDirectoryError`: The path is a directory, not a file.
* - `NotDirectoryError`: The path is a file, not a directory.
* - `FileTooLargeError`: The file is too large, or the file system doesn't have
* enough space to store the new content.
* - `FilesystemLoopError`: Too many symbolic links were encountered when
* resolving the filename.
* - `BusyError`: The file is busy at the moment, such as being locked by
* another program.
* - `InterruptedError`: The operation is interrupted by the underlying file
* system.
* - `NotSupportedError`: The operation is not supported by the current
* environment.
*
* Other errors may also be thrown by the runtime, such as `TypeError`.
* @module
*/
/**
* Platform-specific end-of-line marker. The value is `\r\n` in Windows
* server-side environments, and `\n` elsewhere.
*/
(() => {
if (typeof Deno === "object" && typeof Deno.build === "object") {
return Deno.build.os === "windows" ? "\r\n" : "\n";
}
else if (typeof process === "object" && typeof process.platform === "string") {
return process.platform === "win32" ? "\r\n" : "\n";
}
else {
return "\n";
}
})();
const urlCache = new Map();
/**
* This function is primarily used to bypass the same-origin policy for Web
* Workers in the browser, it downloads the script from the given URL and
* converts it to an object URL which can be used by the `Worker` constructor.
*
* This function can also be used in other scenarios as it also corrects the
* content-type of the response to ensure the script can be loaded p