first commit

This commit is contained in:
Stefan Hacker
2026-04-03 09:38:48 +02:00
commit 37ad745546
47450 changed files with 3120798 additions and 0 deletions
+26
View File
@@ -0,0 +1,26 @@
export interface CacheEntry<T> {
t: number;
value: T;
}
export declare class Cache<T> {
method: (key: string) => Promise<T>;
ttl: number;
evictionTime: number;
gcPeriod: number;
maxEntries: number;
private entries;
map: Map<string, CacheEntry<T>>;
private timer;
constructor(method?: (key: string) => Promise<T>);
put(key: string, value: T): void;
getFromSource(key: string): Promise<T>;
get(key: string): Promise<T>;
getSync(key: string): T | null;
exists(key: string): boolean;
scheduleGC(): void;
startGC(): void;
runGC: () => void;
stopGC: () => void;
retire(key: string, newTime?: number): boolean;
remove(key: string): boolean;
}
+119
View File
@@ -0,0 +1,119 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.Cache = void 0;
const noop = () => { };
class Cache {
constructor(method = noop) {
this.method = method;
this.ttl = 10000; // Time how long item is kept in cache without refreshing.
this.evictionTime = 20000; // After this time item is evicted from cache.
this.gcPeriod = 30000; // How often to run GC.
this.maxEntries = 100000;
this.entries = 0; // Number of values in cache.
this.map = new Map();
this.runGC = () => {
const now = Date.now();
for (const key of this.map.keys()) {
const entry = this.map.get(key);
if (entry && now - entry.t >= this.evictionTime) {
this.map.delete(key);
this.entries--;
}
}
this.scheduleGC();
};
this.stopGC = () => {
clearTimeout(this.timer);
};
}
put(key, value) {
const entry = {
t: Date.now(),
value,
};
if (this.map.get(key)) {
this.map.set(key, entry);
}
else {
this.map.set(key, entry);
this.entries++;
}
if (this.entries > this.maxEntries) {
for (const iterationKey of this.map.keys()) {
if (key !== iterationKey) {
this.map.delete(iterationKey);
this.entries--;
break;
}
}
}
}
async getFromSource(key) {
const value = await this.method(key);
this.put(key, value);
return value;
}
async get(key) {
const entry = this.map.get(key);
if (entry) {
const now = Date.now();
if (now - entry.t <= this.ttl) {
return entry.value;
}
else if (now - entry.t <= this.evictionTime) {
this.getFromSource(key).catch(noop);
return entry.value;
}
else {
this.map.delete(key);
this.entries--;
return await this.getFromSource(key);
}
}
else {
return await this.getFromSource(key);
}
}
getSync(key) {
const entry = this.map.get(key);
if (!entry)
return null;
const now = Date.now();
if (now - entry.t <= this.ttl) {
return entry.value;
}
else if (now - entry.t <= this.evictionTime) {
this.getFromSource(key).catch(noop);
return entry.value;
}
return null;
}
exists(key) {
const entry = this.map.get(key);
if (!entry)
return false;
const now = Date.now();
return now - entry.t <= this.evictionTime;
}
scheduleGC() {
this.timer = setTimeout(this.runGC, this.gcPeriod);
this.timer.unref();
}
startGC() {
this.scheduleGC();
}
retire(key, newTime = 0) {
const entry = this.map.get(key);
if (!entry)
return false;
entry.t = newTime;
return true;
}
remove(key) {
const success = this.map.delete(key);
if (success)
this.entries--;
return success;
}
}
exports.Cache = Cache;
+17
View File
@@ -0,0 +1,17 @@
/**
* An externally resolvable/rejectable "promise". Use it to resolve/reject
* promise at any time.
*
* ```ts
* const future = new Defer();
*
* future.promise.then(value => console.log(value));
*
* future.resolve(123);
* ```
*/
export declare class Defer<T> {
readonly resolve: (data: T) => void;
readonly reject: (error: any) => void;
readonly promise: Promise<T>;
}
+24
View File
@@ -0,0 +1,24 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.Defer = void 0;
/**
* An externally resolvable/rejectable "promise". Use it to resolve/reject
* promise at any time.
*
* ```ts
* const future = new Defer();
*
* future.promise.then(value => console.log(value));
*
* future.resolve(123);
* ```
*/
class Defer {
constructor() {
this.promise = new Promise((resolve, reject) => {
this.resolve = resolve;
this.reject = reject;
});
}
}
exports.Defer = Defer;
+25
View File
@@ -0,0 +1,25 @@
/**
* Creates a lock manager, which can create exclusive locks across browser tabs.
* Uses `window.localStorage` by default to lock across tabs.
*
* Below example, will wait for 5 seconds to acquire a lock, and then execute
* the function once lock is acquired and release the lock after function
* execution. It will fail with `LOCK_TIMEOUT` error if lock is not acquired
* within the 5 seconds. The lock will acquired for 2 seconds (default 1000ms).
*
* ```ts
* Locks.get().lock('my-lock', 2000, 5000)(async () => {
* console.log('Lock acquired');
* });
* ```
*/
export declare class Locks {
protected readonly store: Record<string, string>;
protected readonly now: () => number;
protected readonly pfx: string;
static get: () => Locks;
constructor(store?: Record<string, string>, now?: () => number, pfx?: string);
acquire(id: string, ms?: number): (() => void) | undefined;
isLocked(id: string): boolean;
lock(id: string, ms?: number, timeoutMs?: number, checkMs?: number): <T>(fn: () => Promise<T>) => Promise<T>;
}
+79
View File
@@ -0,0 +1,79 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.Locks = void 0;
const defaultStore = typeof window === 'object' && window && typeof window.localStorage === 'object' ? window.localStorage : null;
let _locks;
/**
* Creates a lock manager, which can create exclusive locks across browser tabs.
* Uses `window.localStorage` by default to lock across tabs.
*
* Below example, will wait for 5 seconds to acquire a lock, and then execute
* the function once lock is acquired and release the lock after function
* execution. It will fail with `LOCK_TIMEOUT` error if lock is not acquired
* within the 5 seconds. The lock will acquired for 2 seconds (default 1000ms).
*
* ```ts
* Locks.get().lock('my-lock', 2000, 5000)(async () => {
* console.log('Lock acquired');
* });
* ```
*/
class Locks {
constructor(store = defaultStore || {}, now = Date.now, pfx = 'lock-') {
this.store = store;
this.now = now;
this.pfx = pfx;
}
acquire(id, ms = 1000) {
if (ms <= 0)
return;
const key = this.pfx + id;
const lockUntil = this.store[key];
const now = this.now();
const isLocked = lockUntil !== undefined && parseInt(lockUntil, 36) > now;
if (isLocked)
return;
const lockUntilNex = (now + ms).toString(36);
this.store[key] = lockUntilNex;
const unlock = () => {
if (this.store[key] === lockUntilNex)
delete this.store[key];
};
return unlock;
}
isLocked(id) {
const key = this.pfx + id;
const lockUntil = this.store[key];
if (lockUntil === undefined)
return false;
const now = this.now();
const lockUntilNum = parseInt(lockUntil, 36);
return lockUntilNum > now;
}
lock(id, ms, timeoutMs = 2 * 1000, checkMs = 10) {
return async (fn) => {
const timeout = this.now() + timeoutMs;
let unlock;
while (!unlock) {
unlock = this.acquire(id, ms);
if (unlock)
break;
await new Promise((r) => setTimeout(r, checkMs));
if (this.now() > timeout)
throw new Error('LOCK_TIMEOUT');
}
try {
return await fn();
}
finally {
unlock();
}
};
}
}
exports.Locks = Locks;
Locks.get = () => {
if (!_locks)
_locks = new Locks();
return _locks;
};
+26
View File
@@ -0,0 +1,26 @@
export declare class LruCache<V> {
protected readonly limit: number;
protected capacity: number;
protected head: LruNode<V> | undefined;
protected tail: LruNode<V> | undefined;
protected map: Record<string, LruNode<V>>;
constructor(limit?: number);
get size(): number;
set(key: string, value: V): void;
get(key: string): V | undefined;
peek(key: string): V | undefined;
has(key: string): boolean;
clear(): void;
keys(): string[];
del(key: string): boolean;
protected pop(node: LruNode<V>): void;
protected push(node: LruNode<V>): void;
}
declare class LruNode<V> {
readonly k: string;
v: V;
l: LruNode<V> | undefined;
r: LruNode<V> | undefined;
constructor(k: string, v: V);
}
export {};
+106
View File
@@ -0,0 +1,106 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.LruCache = void 0;
class LruCache {
constructor(limit = 1000) {
this.limit = limit;
this.head = undefined;
this.tail = undefined;
this.map = Object.create(null);
this.capacity = limit | 0;
}
get size() {
return this.limit - this.capacity;
}
set(key, value) {
const node = this.map[key];
if (node) {
this.pop(node);
node.v = value;
this.push(node);
}
else {
if (!this.capacity) {
const head = this.head;
if (head) {
this.pop(head);
delete this.map[head.k];
this.capacity++;
}
}
this.capacity--;
const node = new LruNode(key, value);
this.map[key] = node;
this.push(node);
}
}
get(key) {
const node = this.map[key];
if (!node)
return;
if (this.tail !== node) {
this.pop(node);
this.push(node);
}
return node.v;
}
peek(key) {
const node = this.map[key];
return node instanceof LruNode ? node.v : undefined;
}
has(key) {
return key in this.map;
}
clear() {
this.head = undefined;
this.tail = undefined;
this.map = Object.create(null);
this.capacity = this.limit;
}
keys() {
return Object.keys(this.map);
}
del(key) {
const node = this.map[key];
if (node instanceof LruNode) {
this.pop(node);
delete this.map[key];
++this.capacity;
return true;
}
return false;
}
pop(node) {
const l = node.l;
const r = node.r;
if (this.head === node)
this.head = r;
else
l.r = r;
if (this.tail === node)
this.tail = l;
else
r.l = l;
// node.l = undefined;
// node.r = undefined;
}
push(node) {
const tail = this.tail;
if (tail) {
tail.r = node;
node.l = tail;
}
else
this.head = node;
this.tail = node;
}
}
exports.LruCache = LruCache;
class LruNode {
constructor(k, v) {
this.k = k;
this.v = v;
this.l = undefined;
this.r = undefined;
}
}
+6
View File
@@ -0,0 +1,6 @@
export declare class LruMap<K, V> extends Map<K, V> {
readonly limit: number;
constructor(limit?: number);
set(key: K, value: V): this;
get(key: K): V | undefined;
}
+30
View File
@@ -0,0 +1,30 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.LruMap = void 0;
class LruMap extends Map {
constructor(
// 2^30 - 1 (a SMI in V8, for 32-bit platforms)
limit = 1073741823) {
super();
this.limit = limit;
}
set(key, value) {
super.delete(key);
super.set(key, value);
if (super.size > this.limit)
super.delete(super.keys().next().value);
return this;
}
get(key) {
const value = super.get(key);
if (value === void 0) {
if (super.delete(key))
super.set(key, value);
return value;
}
super.delete(key);
super.set(key, value);
return value;
}
}
exports.LruMap = LruMap;
+9
View File
@@ -0,0 +1,9 @@
import { LruMap } from './LruMap';
export declare class LruTtlMap<K, V> extends LruMap<K, V> {
private readonly expiry;
clear(): void;
delete(key: K): boolean;
has(key: K, now?: number): boolean;
get(key: K, now?: number): V | undefined;
set(key: K, value: V, expiry?: number): this;
}
+40
View File
@@ -0,0 +1,40 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.LruTtlMap = void 0;
const LruMap_1 = require("./LruMap");
class LruTtlMap extends LruMap_1.LruMap {
constructor() {
super(...arguments);
this.expiry = new Map();
}
clear() {
this.expiry.clear();
super.clear();
}
delete(key) {
this.expiry.delete(key);
return super.delete(key);
}
has(key, now = 0) {
if (!super.has(key))
return false;
const expiry = this.expiry.get(key) || 0;
const expired = now > expiry;
if (expired)
this.delete(key);
return !expired;
}
get(key, now) {
if (!this.has(key, now))
return undefined;
const value = super.get(key);
super.set(key, value);
return value;
}
set(key, value, expiry = Infinity) {
super.set(key, value);
this.expiry.set(key, expiry);
return this;
}
}
exports.LruTtlMap = LruTtlMap;
+22
View File
@@ -0,0 +1,22 @@
/**
* Queue that is flushed automatically when it reaches some item limit
* or when timeout is reached.
*/
export declare class TimedQueue<T> {
/**
* Queue will be flushed when it reaches this number of items.
*/
itemLimit: number;
/**
* Queue will be flushed after this many milliseconds.
*/
timeLimit: number;
/**
* Method that will be called when queue is flushed.
*/
onFlush: (list: T[]) => void;
private list;
private timer;
push(item: T): void;
flush(): T[];
}
+55
View File
@@ -0,0 +1,55 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.TimedQueue = void 0;
/**
* Queue that is flushed automatically when it reaches some item limit
* or when timeout is reached.
*/
class TimedQueue {
constructor() {
/**
* Queue will be flushed when it reaches this number of items.
*/
this.itemLimit = 100;
/**
* Queue will be flushed after this many milliseconds.
*/
this.timeLimit = 5000;
/**
* Method that will be called when queue is flushed.
*/
this.onFlush = (list) => { };
this.list = [];
this.timer = null;
}
push(item) {
this.list.push(item);
if (this.list.length >= this.itemLimit) {
this.flush();
return;
}
if (!this.timer) {
this.timer = setTimeout(() => {
this.flush();
}, this.timeLimit);
}
}
flush() {
const list = this.list;
this.list = [];
if (this.timer)
clearTimeout(this.timer);
this.timer = null;
if (list.length) {
try {
this.onFlush(list);
}
catch (error) {
// tslint:disable-next-line
console.error('TimedQueue', error);
}
}
return list;
}
}
exports.TimedQueue = TimedQueue;
+26
View File
@@ -0,0 +1,26 @@
/**
* TimedState works similar to TimedQueue, but instead of saving
* a list of all items pushed, it reduces the state on each push.
*/
export declare class TimedState<S, I> {
protected readonly initState: () => S;
protected readonly reducer: (state: S, item: I) => S;
/**
* State will be flushed when it reaches this number of items.
*/
itemLimit: number;
/**
* State will be flushed after this many milliseconds.
*/
timeLimit: number;
/**
* Method that will be called when state is flushed.
*/
onFlush: (state: S) => void;
constructor(initState: () => S, reducer: (state: S, item: I) => S);
protected length: number;
protected state: S;
private timer;
push(item: I): void;
flush(): S;
}
+60
View File
@@ -0,0 +1,60 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.TimedState = void 0;
/**
* TimedState works similar to TimedQueue, but instead of saving
* a list of all items pushed, it reduces the state on each push.
*/
class TimedState {
constructor(initState, reducer) {
this.initState = initState;
this.reducer = reducer;
/**
* State will be flushed when it reaches this number of items.
*/
this.itemLimit = 100;
/**
* State will be flushed after this many milliseconds.
*/
this.timeLimit = 5000;
/**
* Method that will be called when state is flushed.
*/
this.onFlush = () => { };
this.length = 0;
this.state = this.initState();
this.timer = null;
}
push(item) {
this.length++;
this.state = this.reducer(this.state, item);
if (this.length >= this.itemLimit) {
this.flush();
return;
}
if (!this.timer) {
this.timer = setTimeout(() => {
this.flush();
}, this.timeLimit);
}
}
flush() {
const { state, length } = this;
this.state = this.initState();
this.length = 0;
if (this.timer)
clearTimeout(this.timer);
if (length) {
this.timer = null;
try {
this.onFlush(state);
}
catch (error) {
// tslint:disable-next-line
console.error('TimedState', error);
}
}
return state;
}
}
exports.TimedState = TimedState;
+2
View File
@@ -0,0 +1,2 @@
export declare const encode64: (str: string) => string;
export declare const decode64: (str: string) => string;
+7
View File
@@ -0,0 +1,7 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.decode64 = exports.encode64 = void 0;
const encode64 = (str) => Buffer.from(str).toString('base64');
exports.encode64 = encode64;
const decode64 = (str) => Buffer.from(str, 'base64').toString();
exports.decode64 = decode64;
+6
View File
@@ -0,0 +1,6 @@
import type { Code } from './types';
/**
* Executes only one instance of give code at a time. If other calls come in in
* parallel, they get resolved to the result of the ongoing execution.
*/
export declare const codeMutex: <T>() => (code: Code<T>) => Promise<T>;
+21
View File
@@ -0,0 +1,21 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.codeMutex = void 0;
/**
* Executes only one instance of give code at a time. If other calls come in in
* parallel, they get resolved to the result of the ongoing execution.
*/
const codeMutex = () => {
let result;
return async (code) => {
if (result)
return result;
try {
return await (result = code());
}
finally {
result = undefined;
}
};
};
exports.codeMutex = codeMutex;
+3
View File
@@ -0,0 +1,3 @@
import type { Code } from './types';
/** Limits concurrency of async code. */
export declare const concurrency: (limit: number) => <T = unknown>(code: Code<T>) => Promise<T>;
+42
View File
@@ -0,0 +1,42 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.concurrency = void 0;
const go_1 = require("./go");
/* tslint:disable */
class Task {
constructor(code) {
this.code = code;
this.promise = new Promise((resolve, reject) => {
this.resolve = resolve;
this.reject = reject;
});
}
}
/** Limits concurrency of async code. */
const concurrency = (limit) => {
let workers = 0;
const queue = new Set();
const work = async () => {
const task = queue.values().next().value;
if (task)
queue.delete(task);
else
return;
workers++;
try {
task.resolve(await task.code());
}
catch (error) {
task.reject(error);
}
finally {
workers--, queue.size && (0, go_1.go)(work);
}
};
return async (code) => {
const task = new Task(code);
queue.add(task);
return workers < limit && (0, go_1.go)(work), task.promise;
};
};
exports.concurrency = concurrency;
+6
View File
@@ -0,0 +1,6 @@
/**
* A class method decorator that limits the concurrency of the method to the
* given number of parallel executions. All invocations are queued and executed
* in the order they were called.
*/
export declare function concurrency<This, Args extends any[], Return>(limit: number): (fn: (this: This, ...args: Args) => Promise<Return>, context?: ClassMethodDecoratorContext<This, (this: This, ...args: Args) => Promise<Return>>) => (this: This, ...args: Args) => Promise<Return>;
+23
View File
@@ -0,0 +1,23 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.concurrency = concurrency;
const concurrency_1 = require("./concurrency");
/* tslint:disable no-invalid-this */
const instances = new WeakMap();
/**
* A class method decorator that limits the concurrency of the method to the
* given number of parallel executions. All invocations are queued and executed
* in the order they were called.
*/
function concurrency(limit) {
return (fn, context) => {
return async function (...args) {
let map = instances.get(this);
if (!map)
instances.set(this, (map = new WeakMap()));
if (!map.has(fn))
map.set(fn, (0, concurrency_1.concurrency)(limit));
return map.get(fn)(async () => await fn.call(this, ...args));
};
};
}
+27
View File
@@ -0,0 +1,27 @@
/**
* Constructs a function that will only invoke the first function passed to it
* concurrently. Once the function has been executed, the racer will be reset
* and the next invocation will be allowed to execute.
*
* Example:
*
* ```ts
* import {createRace} from 'thingies/es2020/createRace';
*
* const race = createRace();
*
* race(() => {
* race(() => {
* console.log('This will not be executed');
* });
* console.log('This will be executed');
* });
*
* race(() => {
* console.log('This will be executed');
* });
* ```
*
* @returns A "race" function that will only invoke the first function passed to it.
*/
export declare const createRace: () => <T>(fn: () => T) => T | undefined;
+44
View File
@@ -0,0 +1,44 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.createRace = void 0;
/**
* Constructs a function that will only invoke the first function passed to it
* concurrently. Once the function has been executed, the racer will be reset
* and the next invocation will be allowed to execute.
*
* Example:
*
* ```ts
* import {createRace} from 'thingies/es2020/createRace';
*
* const race = createRace();
*
* race(() => {
* race(() => {
* console.log('This will not be executed');
* });
* console.log('This will be executed');
* });
*
* race(() => {
* console.log('This will be executed');
* });
* ```
*
* @returns A "race" function that will only invoke the first function passed to it.
*/
const createRace = () => {
let invoked = false;
return (fn) => {
if (invoked)
return;
invoked = true;
try {
return fn();
}
finally {
invoked = false;
}
};
};
exports.createRace = createRace;
+8
View File
@@ -0,0 +1,8 @@
/**
* Creates a data URI from a string of data.
*
* @param data The data to convert to a data URI.
* @param mime The MIME type of the data.
* @returns The data URI.
*/
export declare const dataUri: (data: string, mime: string) => string;
+12
View File
@@ -0,0 +1,12 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.dataUri = void 0;
/**
* Creates a data URI from a string of data.
*
* @param data The data to convert to a data URI.
* @param mime The MIME type of the data.
* @returns The data URI.
*/
const dataUri = (data, mime) => `data:${mime};utf8,${encodeURIComponent(data)}`;
exports.dataUri = dataUri;
+1
View File
@@ -0,0 +1 @@
export declare function debug<This, Args extends any[], Return>(name?: string): (fn: (this: This, ...args: Args) => Return, context?: ClassMethodDecoratorContext<This, (this: This, ...args: Args) => Return>) => (this: This, ...args: Args) => Return;
+31
View File
@@ -0,0 +1,31 @@
"use strict";
/* tslint:disable no-invalid-this no-console */
Object.defineProperty(exports, "__esModule", { value: true });
exports.debug = debug;
let id = 0;
function debug(name) {
return (fn, context) => {
if (process.env.NODE_ENV !== 'production') {
return function (...args) {
id++;
const idStr = id.toString(36);
const currentName = name ?? (this ? this.constructor?.name + '.' : '') + (String(context?.name) ?? fn.name ?? 'anonymous');
console.log('%cRUN', 'background:white;color:blue', idStr, currentName, ...args);
try {
const res = fn.apply(this, args);
if (res instanceof Promise) {
res.then((res) => console.log('%cSUC', 'background:green;color:white', idStr, currentName, res), (err) => console.log('%cERR', 'background:red;color:white', idStr, currentName, err));
return res;
}
console.log('%cSUC', 'background:green;color:white', idStr, currentName, res);
return res;
}
catch (err) {
console.log('%cERR', 'background:red;color:white', idStr, currentName, err);
throw err;
}
};
}
return fn;
};
}
+7
View File
@@ -0,0 +1,7 @@
export type FanOutUnsubscribe = () => void;
export type FanOutListener<D> = (data: D) => void;
export declare class FanOut<D> {
readonly listeners: Set<FanOutListener<D>>;
emit(data: D): void;
listen(listener: FanOutListener<D>): FanOutUnsubscribe;
}
+17
View File
@@ -0,0 +1,17 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.FanOut = void 0;
class FanOut {
constructor() {
this.listeners = new Set();
}
emit(data) {
this.listeners.forEach((listener) => listener(data));
}
listen(listener) {
const listeners = this.listeners;
listeners.add(listener);
return () => listeners.delete(listener);
}
}
exports.FanOut = FanOut;
+3
View File
@@ -0,0 +1,3 @@
import type { Code } from './types';
/** Executes code concurrently. */
export declare const go: <T>(code: Code<T>) => void;
+8
View File
@@ -0,0 +1,8 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.go = void 0;
/** Executes code concurrently. */
const go = (code) => {
code().catch(() => { });
};
exports.go = go;
+1
View File
@@ -0,0 +1 @@
export declare const hasKeys: <T extends object>(obj: T) => boolean;
+9
View File
@@ -0,0 +1,9 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.hasKeys = void 0;
const hasKeys = (obj) => {
for (const key in obj)
return true;
return false;
};
exports.hasKeys = hasKeys;
+1
View File
@@ -0,0 +1 @@
export declare const hash: (str: string) => number;
+11
View File
@@ -0,0 +1,11 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.hash = void 0;
const hash = (str) => {
let hash = 5381;
let i = str.length;
while (i)
hash = (hash * 33) ^ str.charCodeAt(--i);
return hash >>> 0;
};
exports.hash = hash;
+31
View File
@@ -0,0 +1,31 @@
export * from './base64';
export * from './Cache';
export * from './codeMutex';
export * from './concurrency';
export { once } from './once';
export { concurrency as concurrencyDecorator } from './concurrencyDecorator';
export * from './dataUri';
export * from './debug';
export * from './Defer';
export * from './fanout';
export * from './go';
export * from './hash';
export * from './loadCss';
export * from './Locks';
export * from './LruMap';
export * from './LruCache';
export * from './LruTtlMap';
export * from './mutex';
export * from './normalizeEmail';
export * from './of';
export * from './promiseMap';
export * from './randomStr';
export * from './tick';
export * from './timeout';
export * from './TimedQueue';
export * from './TimedState';
export * from './types';
export * from './until';
export * from './xorshift';
export * from './hasKeys';
export * from './sync';
+37
View File
@@ -0,0 +1,37 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.concurrencyDecorator = exports.once = void 0;
const tslib_1 = require("tslib");
tslib_1.__exportStar(require("./base64"), exports);
tslib_1.__exportStar(require("./Cache"), exports);
tslib_1.__exportStar(require("./codeMutex"), exports);
tslib_1.__exportStar(require("./concurrency"), exports);
var once_1 = require("./once");
Object.defineProperty(exports, "once", { enumerable: true, get: function () { return once_1.once; } });
var concurrencyDecorator_1 = require("./concurrencyDecorator");
Object.defineProperty(exports, "concurrencyDecorator", { enumerable: true, get: function () { return concurrencyDecorator_1.concurrency; } });
tslib_1.__exportStar(require("./dataUri"), exports);
tslib_1.__exportStar(require("./debug"), exports);
tslib_1.__exportStar(require("./Defer"), exports);
tslib_1.__exportStar(require("./fanout"), exports);
tslib_1.__exportStar(require("./go"), exports);
tslib_1.__exportStar(require("./hash"), exports);
tslib_1.__exportStar(require("./loadCss"), exports);
tslib_1.__exportStar(require("./Locks"), exports);
tslib_1.__exportStar(require("./LruMap"), exports);
tslib_1.__exportStar(require("./LruCache"), exports);
tslib_1.__exportStar(require("./LruTtlMap"), exports);
tslib_1.__exportStar(require("./mutex"), exports);
tslib_1.__exportStar(require("./normalizeEmail"), exports);
tslib_1.__exportStar(require("./of"), exports);
tslib_1.__exportStar(require("./promiseMap"), exports);
tslib_1.__exportStar(require("./randomStr"), exports);
tslib_1.__exportStar(require("./tick"), exports);
tslib_1.__exportStar(require("./timeout"), exports);
tslib_1.__exportStar(require("./TimedQueue"), exports);
tslib_1.__exportStar(require("./TimedState"), exports);
tslib_1.__exportStar(require("./types"), exports);
tslib_1.__exportStar(require("./until"), exports);
tslib_1.__exportStar(require("./xorshift"), exports);
tslib_1.__exportStar(require("./hasKeys"), exports);
tslib_1.__exportStar(require("./sync"), exports);
+3
View File
@@ -0,0 +1,3 @@
type LoadCss = (href: string, id?: string) => HTMLLinkElement | undefined;
export declare const loadCss: LoadCss;
export {};
+21
View File
@@ -0,0 +1,21 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.loadCss = void 0;
exports.loadCss = typeof window !== 'object'
? () => void 0
: (href, id) => {
if (id) {
const link = document.getElementById(id);
if (link)
return link;
}
const link = document.createElement('link');
if (id)
link.id = id;
link.rel = 'stylesheet';
link.type = 'text/css';
link.href = href;
link.media = 'all';
document.getElementsByTagName('head')[0].appendChild(link);
return link;
};
+5
View File
@@ -0,0 +1,5 @@
/**
* Executes only one instance of give code at a time. For parallel calls, it
* returns the result of the ongoing execution.
*/
export declare function mutex<This, Args extends any[], Return>(fn: (this: This, ...args: Args) => Promise<Return>, context?: ClassMethodDecoratorContext<This, (this: This, ...args: Args) => Promise<Return>>): (this: This, ...args: Args) => Promise<Return>;
+27
View File
@@ -0,0 +1,27 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.mutex = mutex;
const codeMutex_1 = require("./codeMutex");
/* tslint:disable no-invalid-this */
/**
* Executes only one instance of give code at a time. For parallel calls, it
* returns the result of the ongoing execution.
*/
function mutex(fn, context) {
const isDecorator = !!context;
if (!isDecorator) {
const mut = (0, codeMutex_1.codeMutex)();
return async function (...args) {
return await mut(async () => await fn.call(this, ...args));
};
}
const instances = new WeakMap();
return async function (...args) {
let map = instances.get(this);
if (!map)
instances.set(this, (map = new WeakMap()));
if (!map.has(fn))
map.set(fn, (0, codeMutex_1.codeMutex)());
return await map.get(fn)(async () => await fn.call(this, ...args));
};
}
+16
View File
@@ -0,0 +1,16 @@
/**
* 1. Lower-cases whole email.
* 2. Removes dots ".".
* 3. Remotes name part after "+".
* 4. Throws if cannot parse the email.
*
* For example, this email
*
* Michal.Loler+twitter@Gmail.com
*
* will be normalized to
*
* michalloler@gmail.com
*
*/
export declare const normalizeEmail: (email: string) => string;
+28
View File
@@ -0,0 +1,28 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.normalizeEmail = void 0;
const DOT_REG = /\./g;
/**
* 1. Lower-cases whole email.
* 2. Removes dots ".".
* 3. Remotes name part after "+".
* 4. Throws if cannot parse the email.
*
* For example, this email
*
* Michal.Loler+twitter@Gmail.com
*
* will be normalized to
*
* michalloler@gmail.com
*
*/
const normalizeEmail = (email) => {
const [name, host] = email.split('@');
let [beforePlus] = name.split('+');
beforePlus = beforePlus.replace(DOT_REG, '');
const result = beforePlus.toLowerCase() + '@' + host.toLowerCase();
Number(result);
return result;
};
exports.normalizeEmail = normalizeEmail;
+10
View File
@@ -0,0 +1,10 @@
/**
* Given a promise awaits it and returns a 3-tuple, with the following members:
*
* - First entry is either the resolved value of the promise or `undefined`.
* - Second entry is either the error thrown by promise or `undefined`.
* - Third entry is a boolean, truthy if promise was resolved and falsy if rejected.
*
* @param promise Promise to convert to 3-tuple.
*/
export declare const of: <T, E = unknown>(promise: Promise<T>) => Promise<[T | undefined, E | undefined, boolean]>;
+21
View File
@@ -0,0 +1,21 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.of = void 0;
/**
* Given a promise awaits it and returns a 3-tuple, with the following members:
*
* - First entry is either the resolved value of the promise or `undefined`.
* - Second entry is either the error thrown by promise or `undefined`.
* - Third entry is a boolean, truthy if promise was resolved and falsy if rejected.
*
* @param promise Promise to convert to 3-tuple.
*/
const of = async (promise) => {
try {
return [await promise, undefined, true];
}
catch (error) {
return [undefined, error, false];
}
};
exports.of = of;
+5
View File
@@ -0,0 +1,5 @@
/**
* A class method decorator that limits a method to be called only once. All
* subsequent calls will return the result of the first call.
*/
export declare function once<This, Args extends any[], Return>(fn: (this: This, ...args: Args) => Return, context?: ClassMethodDecoratorContext<This, (this: This, ...args: Args) => Return>): (this: This, ...args: Args) => Return;
+19
View File
@@ -0,0 +1,19 @@
"use strict";
/* tslint:disable no-invalid-this */
Object.defineProperty(exports, "__esModule", { value: true });
exports.once = once;
const instances = new WeakMap();
/**
* A class method decorator that limits a method to be called only once. All
* subsequent calls will return the result of the first call.
*/
function once(fn, context) {
return function (...args) {
let map = instances.get(this);
if (!map)
instances.set(this, (map = new WeakMap()));
if (!map.has(fn))
map.set(fn, fn.apply(this, args));
return map.get(fn);
};
}
+5
View File
@@ -0,0 +1,5 @@
/**
* Creates promises of a list of values. Resolves all promises and
* returns an array of resolved values.
*/
export declare const promiseMap: (values: any[], onValue: (value: unknown) => Promise<unknown>, onError?: (error?: unknown, value?: unknown, index?: number) => void) => Promise<any>;
+35
View File
@@ -0,0 +1,35 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.promiseMap = void 0;
const noop = () => { };
/**
* Creates promises of a list of values. Resolves all promises and
* returns an array of resolved values.
*/
const promiseMap = (values, onValue, onError = noop) => new Promise((resolve) => {
const length = values.length;
if (!length) {
return resolve([]);
}
const results = [];
let resolvedCount = 0;
for (let i = 0; i < length; i++) {
const value = values[i];
const promise = onValue(value);
promise.then((result) => {
results[i] = result;
resolvedCount++;
if (resolvedCount === length) {
resolve(results);
}
}, (error) => {
results[i] = null;
onError(error, value, i);
resolvedCount++;
if (resolvedCount === length) {
resolve(results);
}
});
}
});
exports.promiseMap = promiseMap;
+1
View File
@@ -0,0 +1 @@
export declare function randomStr(length: number, alphabet?: string): string;
+13
View File
@@ -0,0 +1,13 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.randomStr = randomStr;
// Default alphabet allows "-" hyphens, because UUIDs have them.
const defaultAlphabet = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-';
function randomStr(length, alphabet = defaultAlphabet) {
let str = '';
const alphabetLength = alphabet.length;
for (let i = 0; i < length; i++) {
str += alphabet.charAt(Math.floor(Math.random() * alphabetLength));
}
return str;
}
+44
View File
@@ -0,0 +1,44 @@
import { FanOut } from './fanout';
/** React.js synchronous state interface. */
export interface SyncStore<T> {
subscribe: SyncStoreSubscribe;
getSnapshot: () => T;
}
export type SyncStoreSubscribe = (callback: () => void) => SyncStoreUnsubscribe;
export type SyncStoreUnsubscribe = () => void;
export type SyncDep<T> = SyncValue<T> & SyncStore<T> & FanOut<void>;
export type WrapListInSyncDep<T extends unknown[]> = {
[K in keyof T]: SyncDep<T[K]>;
};
export interface Disposable {
dispose(): void;
}
export interface SyncValue<T> {
value: T;
}
export declare class Value<V> extends FanOut<void> implements SyncStore<V>, SyncValue<V> {
constructor(value: V);
next(value: V, force?: boolean): void;
/** ----------------------------------------------------- {@link SyncValue} */
value: V;
/** ----------------------------------------------------- {@link SyncStore} */
readonly subscribe: SyncStoreSubscribe;
readonly getSnapshot: () => V;
}
export declare class Computed<N, V extends unknown[] = any> extends FanOut<void> implements SyncValue<N>, SyncStore<N>, Disposable {
protected readonly deps: WrapListInSyncDep<V>;
protected readonly compute: (args: V) => N;
private cache;
private subs;
constructor(deps: WrapListInSyncDep<V>, compute: (args: V) => N);
private _comp;
/** ----------------------------------------------------- {@link SyncValue} */
get value(): N;
/** ----------------------------------------------------- {@link SyncStore} */
readonly subscribe: SyncStoreSubscribe;
readonly getSnapshot: () => N;
/** ---------------------------------------------------- {@link Disposable} */
dispose(): void;
}
export declare const val: <V>(initial: V) => Value<V>;
export declare const comp: <V extends unknown[], N>(deps: WrapListInSyncDep<V>, compute: (args: V) => N) => Computed<N, V>;
+62
View File
@@ -0,0 +1,62 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.comp = exports.val = exports.Computed = exports.Value = void 0;
const fanout_1 = require("./fanout");
const NO_CACHE = Symbol();
class Value extends fanout_1.FanOut {
constructor(value) {
super();
/** ----------------------------------------------------- {@link SyncStore} */
this.subscribe = (cb) => this.listen(cb);
this.getSnapshot = () => this.value;
this.value = value;
}
next(value, force = false) {
if (!force && this.value === value)
return;
this.value = value;
this.emit();
}
}
exports.Value = Value;
class Computed extends fanout_1.FanOut {
constructor(deps, compute) {
super();
this.deps = deps;
this.compute = compute;
this.cache = NO_CACHE;
/** ----------------------------------------------------- {@link SyncStore} */
this.subscribe = (cb) => this.listen(cb);
this.getSnapshot = () => this._comp();
const subs = (this.subs = []);
const length = deps.length;
for (let i = 0; i < length; i++) {
const dep = deps[i];
const sub = dep.listen(() => {
this.cache = NO_CACHE;
this.emit();
});
subs.push(sub);
}
}
_comp() {
const cached = this.cache;
if (cached !== NO_CACHE)
return cached;
return (this.cache = this.compute(this.deps.map((dep) => dep.getSnapshot())));
}
/** ----------------------------------------------------- {@link SyncValue} */
get value() {
return this._comp();
}
/** ---------------------------------------------------- {@link Disposable} */
dispose() {
for (const sub of this.subs)
sub();
}
}
exports.Computed = Computed;
const val = (initial) => new Value(initial);
exports.val = val;
const comp = (deps, compute) => new Computed(deps, compute);
exports.comp = comp;
+1
View File
@@ -0,0 +1 @@
export declare const tick: (ms?: number) => Promise<unknown>;
+5
View File
@@ -0,0 +1,5 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.tick = void 0;
const tick = (ms = 1) => new Promise((r) => setTimeout(r, ms));
exports.tick = tick;
+17
View File
@@ -0,0 +1,17 @@
import type { Code } from './types';
/**
* Waits for given number of milliseconds before timing out. If provided code
* block does not complete within the given time, the promise will be rejected
* with `new Error('TIMEOUT')` error.
*
* ```ts
* const result = await timeout(1000, async () => {
* return 123;
* });
* ```
*
* @param ms Number of milliseconds to wait before timing out.
* @param code Code block or promise to execute.
* @returns The result of the code block or promise.
*/
export declare const timeout: <T>(ms: number, code: Code<T> | Promise<T>) => Promise<T>;
+30
View File
@@ -0,0 +1,30 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.timeout = void 0;
/**
* Waits for given number of milliseconds before timing out. If provided code
* block does not complete within the given time, the promise will be rejected
* with `new Error('TIMEOUT')` error.
*
* ```ts
* const result = await timeout(1000, async () => {
* return 123;
* });
* ```
*
* @param ms Number of milliseconds to wait before timing out.
* @param code Code block or promise to execute.
* @returns The result of the code block or promise.
*/
const timeout = (ms, code) => new Promise((resolve, reject) => {
const timer = setTimeout(() => reject(new Error('TIMEOUT')), ms);
const promise = typeof code === 'function' ? code() : code;
promise.then((result) => {
clearTimeout(timer);
resolve(result);
}, (error) => {
clearTimeout(timer);
reject(error);
});
});
exports.timeout = timeout;
+1
View File
@@ -0,0 +1 @@
export type Code<T = unknown> = () => Promise<T>;
+2
View File
@@ -0,0 +1,2 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
+1
View File
@@ -0,0 +1 @@
export declare const until: (check: () => boolean | Promise<boolean>, pollInterval?: number) => Promise<void>;
+12
View File
@@ -0,0 +1,12 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.until = void 0;
const tick_1 = require("./tick");
const until = async (check, pollInterval = 1) => {
do {
if (await check())
return;
await (0, tick_1.tick)(pollInterval);
} while (true);
};
exports.until = until;
+2
View File
@@ -0,0 +1,2 @@
export declare const makeXorShift32: (seed?: number) => () => number;
export declare const xorShift32: () => number;
+14
View File
@@ -0,0 +1,14 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.xorShift32 = exports.makeXorShift32 = void 0;
const makeXorShift32 = (seed = 1 + Math.round(Math.random() * ((-1 >>> 0) - 1))) => {
let x = seed | 0;
return function xorShift32() {
x ^= x << 13;
x ^= x >> 17;
x ^= x << 5;
return x;
};
};
exports.makeXorShift32 = makeXorShift32;
exports.xorShift32 = (0, exports.makeXorShift32)();