New Case Study:See how Anthropic automated 95% of dependency reviews with Socket.Learn More
Socket
Sign inDemoInstall
Socket

@kaciras/utilities

Package Overview
Dependencies
Maintainers
1
Versions
40
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@kaciras/utilities - npm Package Compare versions

Comparing version 0.7.0 to 0.8.0

370

lib/browser.js

@@ -124,3 +124,3 @@ const htmlEscapes = {

* b: [2],
* c: [3, 4, 5]
* c: new Set([3, 4, 5]),
* });

@@ -155,2 +155,6 @@ * // Returns an iterable of:

*
* For Iterable inputs, just convert it to an array. The recursive function will be
* called multiple times at each index, we still need an array to hold the elements.
* e.g. `cartesianProductArray(Array.from(iterable))`.
*
* @example

@@ -160,3 +164,3 @@ * cartesianProductArray([

* [2],
* [3, 4, 5]
* new Set([3, 4, 5]),
* ]);

@@ -170,6 +174,6 @@ * // Returns an iterable of:

* [1, 2, 5]
*/ function cartesianProductArray(entries) {
const temp = new Array(entries.length);
*/ function cartesianProductArray(src) {
const temp = new Array(src.length);
function* recursive(index) {
if (index === entries.length) {
if (index === src.length) {
yield [

@@ -179,3 +183,3 @@ ...temp

} else {
for (const value of entries[index]){
for (const value of src[index]){
temp[index] = value;

@@ -216,2 +220,45 @@ yield* recursive(index + 1);

/**
* An AbortSignal that never aborts.
*/ const NeverAbort = {
aborted: false,
reason: undefined,
get onabort () {
return null;
},
set onabort (_){},
throwIfAborted () {},
dispatchEvent () {
throw new Error("Not supported");
},
addEventListener () {},
removeEventListener () {}
};
let uniqueIdCounter = 0;
/**
* Generate a unique positive number, each call returns a different value.
*
* This function more efficient than `Math.random()`.
*/ function uniqueId() {
return uniqueIdCounter += 1;
}
class AbortError extends Error {
constructor(...args){
super(...args);
this.name = "AbortError";
}
}
/**
* Get a Promise that will be fulfilled after specified time.
* When canceled, the returned Promise will be rejected with an 'AbortError'.
*
* @param ms Time to sleep in millisecond.
* @param signal An optional AbortSignal that can be used to cancel the scheduled sleep.
*/ function sleep(ms, signal = NeverAbort) {
return new Promise((resolve, reject)=>{
setTimeout(resolve, ms);
signal.addEventListener("abort", ()=>reject(new AbortError()));
});
}
/**
* Event dispatcher for only one type of event.

@@ -296,2 +343,65 @@ *

}
/**
* Wrap publish-subscribe functions to request-response model.
* The remote service must attach the id in response message.
*
* # NOTE
* If you disable timeout, there will be a memory leak when response
* message can't be received. WeakMap doesn't help in this scenario,
* since the key is deserialized from the message.
*
* @example
* const { request, subscribe } = pubSub2ReqRes("DEMO", window.postMessage);
* window.addEventListener("message", e => subscribe(e.data));
* const response = await request({ text: "Hello" });
*
* @param id
* @param publish The publish message function
* @param timeout The number of milliseconds to wait for response,
* set to zero or negative value to disable timeout.
*/ function pubSub2ReqRes(id, publish, timeout = 10e3) {
const txMap = new Map();
function dispatch(message) {
if (typeof message !== "object") {
return;
}
if (message.i !== id) {
return;
}
const session = txMap.get(message.s);
if (session) {
session.resolve(message);
txMap.delete(message.s);
clearTimeout(session.timer);
}
}
function request(message, transfers = []) {
const s = message.s = uniqueId();
message.i = id;
publish(message, transfers);
let timer;
if (timeout > 0) {
timer = setTimeout(expire, timeout, s);
}
return new Promise((resolve, reject)=>{
txMap.set(s, {
resolve,
reject,
timer
});
});
}
function expire(sessionId) {
const tx = txMap.get(sessionId);
if (tx) {
txMap.delete(sessionId);
tx.reject(new AbortError("Timed out"));
}
}
return {
txMap,
request,
dispatch
};
}

@@ -358,4 +468,3 @@ /*

// @formatter:on
const divRE = /^([-+0-9.]+)\s*(\w+)$/;
const groupRE = /\d+([a-z]+)\s*/gi;
const groupRE = /[0-9.]+\s?([a-z]+)\s*/gi;
class UnitConvertor {

@@ -399,3 +508,3 @@ name;

* @param precision The number of digits to appear after the decimal point.
*/ n2sDivision(value, unit, precision = 2) {
*/ formatDiv(value, unit, precision = 2) {
if (!Number.isFinite(value)) {

@@ -411,21 +520,2 @@ throw new TypeError(`${value} is not a finite number`);

/**
* Convert string to number in specified unit.
*
* @example
* durationConvertor.s2nModulo("10000d", "d"); // 10000
* durationConvertor.s2nModulo("0h", "s"); // 0
* durationConvertor.s2nModulo("0.5m", "s"); // 30
* durationConvertor.s2nModulo("1d 3h 0m 15s", "s"); // 97215
*
* @param value The string to parse.
* @param unit Target unit to converted to.
*/ s2nDivision(value, unit) {
const match = divRE.exec(value);
if (!match) {
throw new Error(`Can not convert "${value}" to ${this.name}`);
}
const [, v, u] = match;
return Number(v) * this.getFraction(u) / this.getFraction(unit);
}
/**
* Formats a value and unit.

@@ -447,3 +537,3 @@ *

* @param parts Maximum number of groups in result.
*/ n2sModulo(value, unit, parts = 2) {
*/ formatMod(value, unit, parts = 2) {
if (!Number.isFinite(value)) {

@@ -481,3 +571,14 @@ throw new TypeError(`${value} is not a finite number`);

* @param unit Target unit to converted to.
*/ s2nModulo(value, unit) {
*/ /**
* Convert string to number in specified unit.
*
* @example
* durationConvertor.s2nModulo("10000d", "d"); // 10000
* durationConvertor.s2nModulo("0h", "s"); // 0
* durationConvertor.s2nModulo("0.5m", "s"); // 30
* durationConvertor.s2nModulo("1d 3h 0m 15s", "s"); // 97215
*
* @param value The string to parse.
* @param unit Target unit to converted to.
*/ parse(value, unit) {
const { name , units , fractions } = this;

@@ -496,2 +597,9 @@ let k = Infinity;

}
switch(value.charCodeAt(0)){
case 45:
/* - */ result = -result;
// eslint-disable-next-line no-fallthrough
case 43:
/* + */ seen += 1;
}
if (seen === value.length && seen > 0) {

@@ -756,45 +864,2 @@ return result / this.getFraction(unit);

/**
* An AbortSignal that never aborts.
*/ const NeverAbort = {
aborted: false,
reason: undefined,
get onabort () {
return null;
},
set onabort (_){},
throwIfAborted () {},
dispatchEvent () {
throw new Error("Not supported");
},
addEventListener () {},
removeEventListener () {}
};
let uniqueIdCounter = 1;
/**
* Generate a unique number, each call returns a different value.
*
* This function more efficient than `Math.random()`.
*/ function uniqueId() {
return uniqueIdCounter += 1;
}
class AbortError extends Error {
constructor(...args){
super(...args);
this.name = "AbortError";
}
}
/**
* Get a Promise that will be fulfilled after specified time.
* When canceled, the returned Promise will be rejected with an 'AbortError'.
*
* @param ms Time to sleep in millisecond.
* @param signal An optional AbortSignal that can be used to cancel the scheduled sleep.
*/ function sleep(ms, signal = NeverAbort) {
return new Promise((resolve, reject)=>{
setTimeout(resolve, ms);
signal.addEventListener("abort", ()=>reject(new AbortError()));
});
}
/**
* Detect if the pointer is inside the element.

@@ -966,3 +1031,3 @@ *

const transfers = [];
for (const arg of message.args){
for (const arg of message.a){
const ts = transferCache.get(arg);

@@ -974,38 +1039,72 @@ if (ts) {

const response = await send(message, transfers);
if (response.isError) {
throw response.value;
if ("e" in response) {
throw response.e;
} else {
return response.value;
return response.v;
}
}
/**
* Handle an RPC request, call specific method in the target, and send the response.
* Handle an RPC request, call specific method in the target.
*
* This function can be used for request-response model.
*
* @example
* // Create RPC server on http://localhost:9789
* import consumers from "stream/consumers";
* import { RPC } from "@kaciras/utilities/browser";
*
* const functions = {
* hello: (name: string) => `Hello ${name}!`,
* };
*
* const server = http.createServer((req, res) => {
* consumers.json(req)
* .then(msg => RPC.serve(functions, msg))
* .then(d => res.end(JSON.stringify(d[0])));
* });
*
* server.listen(9789);
*
* @param target The service object contains methods that client can use.
* @param message RPC request message
* @param respond The function to send the response message.
*/ async function serve(target, message, respond) {
const { id , path , args } = message;
* @param message RPC request message.
*/ async function serve(target, message) {
const { s , p , a , i } = message;
try {
for(let i = path.length - 1; i > 0; i--){
target = target[path[i]];
for(let k = p.length - 1; k > 0; k--){
target = target[p[k]];
}
const value = await target[path[0]](...args);
const transfers = transferCache.get(value);
respond({
id,
value,
isError: false
}, transfers);
const v = await target[p[0]](...a);
return [
{
i,
s,
v
},
transferCache.get(v) ?? []
];
} catch (e) {
respond({
id,
value: e,
isError: true
});
return [
{
i,
s,
e
},
[]
];
}
}
function createServer(id, target, respond) {
return async (message)=>{
if (typeof message !== "object") {
return; // Not an RPC message.
}
const { p , i } = message;
if (i === id && Array.isArray(p)) {
respond(...await serve(target, message));
}
};
}
class RPCHandler {
/**
* Keys for current property in reversed order, e.g.
* Keys for current property in reversed order.
*

@@ -1020,9 +1119,9 @@ * @example

return callRemote(send, {
path: this.path,
args
p: this.path,
a: args
});
}
get(send, prop) {
get(send, key) {
return new Proxy(send, new RPCHandler([
prop,
key,
...this.path

@@ -1032,64 +1131,10 @@ ]));

}
/**
* Wrap publish-subscribe functions to request-response model.
* The remote service must attach request message id in response message.
*
* # NOTE
* If you disable timeout, there will be a memory leak when response
* message can't be received.
*
* WeakMap doesn't help in this scenario, since the key is deserialized from the message.
*
* @example
* const { request, subscribe } = pubSub2ReqRes(window.postMessage);
* window.addEventListener("message", e => subscribe(e.data));
* const response = await request({ text: "Hello" });
*
* @param publish The publish message function
* @param timeout The number of milliseconds to wait for response,
* set to zero & negative value to disable timeout.
*/ function pubSub2ReqRes(publish, timeout = 10e3) {
const txMap = new Map();
function expire(id) {
const session = txMap.get(id);
if (session) {
txMap.delete(id);
session.reject(new AbortError("Timed out"));
}
function createClient(send, id, addListener) {
if (id) {
const { request , dispatch } = pubSub2ReqRes(id, send);
send = request;
addListener(dispatch);
}
function request(message) {
const id = message.id = uniqueId();
publish(message);
let timer;
if (timeout > 0) {
timer = setTimeout(expire, timeout, id);
}
return new Promise((resolve, reject)=>{
txMap.set(id, {
resolve,
reject,
timer
});
});
}
function dispatch(message) {
const session = txMap.get(message.id);
if (session) {
clearTimeout(session.timer);
txMap.delete(message.id);
session.resolve(message);
}
}
return {
txMap,
request,
dispatch
};
return new Proxy(send, new RPCHandler([]));
}
function createClient(connection) {
return new Proxy(connection, new RPCHandler([]));
}
function createServer(controller) {
return (message, respond)=>serve(controller, message, respond);
}

@@ -1100,3 +1145,2 @@ var rpc = /*#__PURE__*/Object.freeze({

createServer: createServer,
pubSub2ReqRes: pubSub2ReqRes,
serve: serve,

@@ -1225,2 +1269,2 @@ transfer: transfer

export { AbortError, Composite, FetchClient, FetchClientError, LRUCache, MultiEventEmitter, MultiMap, NeverAbort, rpc as RPC, ResponseFacade, SingleEventEmitter, UnitConvertor, base64url, blobToBase64URL, cartesianProductArray, cartesianProductObj, compositor, createInstance, dataSizeIEC, dataSizeSI, dragSortContext, durationConvertor, escapeHTML, fetchFile, identity, isPointerInside, noop, nthInChildren, saveFile, selectFile, sha256, silencePromise, silentCall, sleep, svgToUrl, swapElements, uniqueId };
export { AbortError, Composite, FetchClient, FetchClientError, LRUCache, MultiEventEmitter, MultiMap, NeverAbort, rpc as RPC, ResponseFacade, SingleEventEmitter, UnitConvertor, base64url, blobToBase64URL, cartesianProductArray, cartesianProductObj, compositor, createInstance, dataSizeIEC, dataSizeSI, dragSortContext, durationConvertor, escapeHTML, fetchFile, identity, isPointerInside, noop, nthInChildren, pubSub2ReqRes, saveFile, selectFile, sha256, silencePromise, silentCall, sleep, svgToUrl, swapElements, uniqueId };

@@ -45,2 +45,33 @@ type Handler<T extends any[]> = (...args: T) => any;

}
export type PostMessage = (message: any, transfers: Transferable[]) => void;
export interface PromiseController {
timer?: ReturnType<typeof setTimeout>;
resolve(value: unknown): void;
reject(reason: unknown): void;
}
export interface RequestResponseWrapper {
request(message: object): Promise<any>;
dispatch(message: object): void;
txMap: Map<number, PromiseController>;
}
/**
* Wrap publish-subscribe functions to request-response model.
* The remote service must attach the id in response message.
*
* # NOTE
* If you disable timeout, there will be a memory leak when response
* message can't be received. WeakMap doesn't help in this scenario,
* since the key is deserialized from the message.
*
* @example
* const { request, subscribe } = pubSub2ReqRes("DEMO", window.postMessage);
* window.addEventListener("message", e => subscribe(e.data));
* const response = await request({ text: "Hello" });
*
* @param id
* @param publish The publish message function
* @param timeout The number of milliseconds to wait for response,
* set to zero or negative value to disable timeout.
*/
export declare function pubSub2ReqRes(id: string, publish: PostMessage, timeout?: number): RequestResponseWrapper;
export {};

@@ -21,17 +21,4 @@ export declare class UnitConvertor<T extends readonly string[]> {

*/
n2sDivision(value: number, unit?: T[number], precision?: number): string;
formatDiv(value: number, unit?: T[number], precision?: number): string;
/**
* Convert string to number in specified unit.
*
* @example
* durationConvertor.s2nModulo("10000d", "d"); // 10000
* durationConvertor.s2nModulo("0h", "s"); // 0
* durationConvertor.s2nModulo("0.5m", "s"); // 30
* durationConvertor.s2nModulo("1d 3h 0m 15s", "s"); // 97215
*
* @param value The string to parse.
* @param unit Target unit to converted to.
*/
s2nDivision(value: string, unit?: T[number]): number;
/**
* Formats a value and unit.

@@ -54,3 +41,3 @@ *

*/
n2sModulo(value: number, unit?: T[number], parts?: number): string;
formatMod(value: number, unit?: T[number], parts?: number): string;
/**

@@ -68,3 +55,15 @@ * Convert string to number in specified unit.

*/
s2nModulo(value: string, unit?: T[number]): number;
/**
* Convert string to number in specified unit.
*
* @example
* durationConvertor.s2nModulo("10000d", "d"); // 10000
* durationConvertor.s2nModulo("0h", "s"); // 0
* durationConvertor.s2nModulo("0.5m", "s"); // 30
* durationConvertor.s2nModulo("1d 3h 0m 15s", "s"); // 97215
*
* @param value The string to parse.
* @param unit Target unit to converted to.
*/
parse(value: string, unit?: T[number]): number;
}

@@ -71,0 +70,0 @@ /**

@@ -0,1 +1,2 @@

export type ItemOfIterable<T> = T extends Iterable<infer E> ? E : never;
export type Awaitable<T> = T | Promise<T>;

@@ -28,2 +29,6 @@ export declare const noop: () => void;

}
type ObjectSrc = Record<string, Iterable<unknown>>;
type CartesianProductObject<T extends ObjectSrc> = {
-readonly [K in keyof T]: ItemOfIterable<T[K]>;
};
/**

@@ -36,3 +41,3 @@ * Get the cartesian product generator of objects.

* b: [2],
* c: [3, 4, 5]
* c: new Set([3, 4, 5]),
* });

@@ -47,6 +52,13 @@ * // Returns an iterable of:

*/
export declare function cartesianProductObj<K extends string, V>(src: Record<K, V[]>): Iterable<Record<K, V>>;
export declare function cartesianProductObj<const T extends ObjectSrc>(src: T): Iterable<CartesianProductObject<T>>;
type ArraySrc = ReadonlyArray<Iterable<unknown>>;
type CastArray<T extends ArraySrc> = T extends readonly [infer E, ...infer REST] ? REST extends ArraySrc ? [ItemOfIterable<E>, ...CastArray<REST>] : never : T;
type CartesianProductArray<T extends ArraySrc> = T extends readonly [any, ...any[]] ? CastArray<T> : T[number];
/**
* Get the cartesian product generator of multiple arrays.
*
* For Iterable inputs, just convert it to an array. The recursive function will be
* called multiple times at each index, we still need an array to hold the elements.
* e.g. `cartesianProductArray(Array.from(iterable))`.
*
* @example

@@ -56,3 +68,3 @@ * cartesianProductArray([

* [2],
* [3, 4, 5]
* new Set([3, 4, 5]),
* ]);

@@ -67,3 +79,3 @@ * // Returns an iterable of:

*/
export declare function cartesianProductArray<T>(entries: T[][]): Iterable<T[]>;
export declare function cartesianProductArray<const T extends ArraySrc>(src: T): Iterable<CartesianProductArray<T>>;
/**

@@ -70,0 +82,0 @@ * Create a new instance with the `parent` as prototype and the `value` as child.

@@ -6,3 +6,3 @@ /**

/**
* Generate a unique number, each call returns a different value.
* Generate a unique positive number, each call returns a different value.
*

@@ -9,0 +9,0 @@ * This function more efficient than `Math.random()`.

@@ -127,3 +127,3 @@ import process from 'process';

* b: [2],
* c: [3, 4, 5]
* c: new Set([3, 4, 5]),
* });

@@ -158,2 +158,6 @@ * // Returns an iterable of:

*
* For Iterable inputs, just convert it to an array. The recursive function will be
* called multiple times at each index, we still need an array to hold the elements.
* e.g. `cartesianProductArray(Array.from(iterable))`.
*
* @example

@@ -163,3 +167,3 @@ * cartesianProductArray([

* [2],
* [3, 4, 5]
* new Set([3, 4, 5]),
* ]);

@@ -173,6 +177,6 @@ * // Returns an iterable of:

* [1, 2, 5]
*/ function cartesianProductArray(entries) {
const temp = new Array(entries.length);
*/ function cartesianProductArray(src) {
const temp = new Array(src.length);
function* recursive(index) {
if (index === entries.length) {
if (index === src.length) {
yield [

@@ -182,3 +186,3 @@ ...temp

} else {
for (const value of entries[index]){
for (const value of src[index]){
temp[index] = value;

@@ -221,2 +225,46 @@ yield* recursive(index + 1);

/**
* An AbortSignal that never aborts.
*/ const NeverAbort = {
aborted: false,
reason: undefined,
get onabort () {
return null;
},
set onabort (_){},
throwIfAborted () {},
dispatchEvent () {
throw new Error("Not supported");
},
addEventListener () {},
removeEventListener () {}
};
let uniqueIdCounter = 0;
/**
* Generate a unique positive number, each call returns a different value.
*
* This function more efficient than `Math.random()`.
*/ function uniqueId() {
return uniqueIdCounter += 1;
}
class AbortError extends Error {
constructor(...args){
super(...args);
this.name = "AbortError";
}
}
/**
* Get a Promise that will be fulfilled after specified time.
* When canceled, the returned Promise will be rejected with an 'AbortError'.
*
* @param ms Time to sleep in millisecond.
* @param signal An optional AbortSignal that can be used to cancel the scheduled sleep.
*/ function sleep(ms, signal = NeverAbort) {
{
return tp.setTimeout(ms, undefined, {
signal
});
}
}
/**
* Event dispatcher for only one type of event.

@@ -301,2 +349,68 @@ *

}
/**
* Wrap publish-subscribe functions to request-response model.
* The remote service must attach the id in response message.
*
* # NOTE
* If you disable timeout, there will be a memory leak when response
* message can't be received. WeakMap doesn't help in this scenario,
* since the key is deserialized from the message.
*
* @example
* const { request, subscribe } = pubSub2ReqRes("DEMO", window.postMessage);
* window.addEventListener("message", e => subscribe(e.data));
* const response = await request({ text: "Hello" });
*
* @param id
* @param publish The publish message function
* @param timeout The number of milliseconds to wait for response,
* set to zero or negative value to disable timeout.
*/ function pubSub2ReqRes(id, publish, timeout = 10e3) {
const txMap = new Map();
function dispatch(message) {
if (typeof message !== "object") {
return;
}
if (message.i !== id) {
return;
}
const session = txMap.get(message.s);
if (session) {
session.resolve(message);
txMap.delete(message.s);
clearTimeout(session.timer);
}
}
function request(message, transfers = []) {
const s = message.s = uniqueId();
message.i = id;
publish(message, transfers);
let timer;
if (timeout > 0) {
timer = setTimeout(expire, timeout, s);
{
timer.unref();
}
}
return new Promise((resolve, reject)=>{
txMap.set(s, {
resolve,
reject,
timer
});
});
}
function expire(sessionId) {
const tx = txMap.get(sessionId);
if (tx) {
txMap.delete(sessionId);
tx.reject(new AbortError("Timed out"));
}
}
return {
txMap,
request,
dispatch
};
}

@@ -521,4 +635,3 @@ /**

// @formatter:on
const divRE = /^([-+0-9.]+)\s*(\w+)$/;
const groupRE = /\d+([a-z]+)\s*/gi;
const groupRE = /[0-9.]+\s?([a-z]+)\s*/gi;
class UnitConvertor {

@@ -562,3 +675,3 @@ name;

* @param precision The number of digits to appear after the decimal point.
*/ n2sDivision(value, unit, precision = 2) {
*/ formatDiv(value, unit, precision = 2) {
if (!Number.isFinite(value)) {

@@ -574,21 +687,2 @@ throw new TypeError(`${value} is not a finite number`);

/**
* Convert string to number in specified unit.
*
* @example
* durationConvertor.s2nModulo("10000d", "d"); // 10000
* durationConvertor.s2nModulo("0h", "s"); // 0
* durationConvertor.s2nModulo("0.5m", "s"); // 30
* durationConvertor.s2nModulo("1d 3h 0m 15s", "s"); // 97215
*
* @param value The string to parse.
* @param unit Target unit to converted to.
*/ s2nDivision(value, unit) {
const match = divRE.exec(value);
if (!match) {
throw new Error(`Can not convert "${value}" to ${this.name}`);
}
const [, v, u] = match;
return Number(v) * this.getFraction(u) / this.getFraction(unit);
}
/**
* Formats a value and unit.

@@ -610,3 +704,3 @@ *

* @param parts Maximum number of groups in result.
*/ n2sModulo(value, unit, parts = 2) {
*/ formatMod(value, unit, parts = 2) {
if (!Number.isFinite(value)) {

@@ -644,3 +738,14 @@ throw new TypeError(`${value} is not a finite number`);

* @param unit Target unit to converted to.
*/ s2nModulo(value, unit) {
*/ /**
* Convert string to number in specified unit.
*
* @example
* durationConvertor.s2nModulo("10000d", "d"); // 10000
* durationConvertor.s2nModulo("0h", "s"); // 0
* durationConvertor.s2nModulo("0.5m", "s"); // 30
* durationConvertor.s2nModulo("1d 3h 0m 15s", "s"); // 97215
*
* @param value The string to parse.
* @param unit Target unit to converted to.
*/ parse(value, unit) {
const { name , units , fractions } = this;

@@ -659,2 +764,9 @@ let k = Infinity;

}
switch(value.charCodeAt(0)){
case 45:
/* - */ result = -result;
// eslint-disable-next-line no-fallthrough
case 43:
/* + */ seen += 1;
}
if (seen === value.length && seen > 0) {

@@ -761,46 +873,2 @@ return result / this.getFraction(unit);

/**
* An AbortSignal that never aborts.
*/ const NeverAbort = {
aborted: false,
reason: undefined,
get onabort () {
return null;
},
set onabort (_){},
throwIfAborted () {},
dispatchEvent () {
throw new Error("Not supported");
},
addEventListener () {},
removeEventListener () {}
};
let uniqueIdCounter = 1;
/**
* Generate a unique number, each call returns a different value.
*
* This function more efficient than `Math.random()`.
*/ function uniqueId() {
return uniqueIdCounter += 1;
}
class AbortError extends Error {
constructor(...args){
super(...args);
this.name = "AbortError";
}
}
/**
* Get a Promise that will be fulfilled after specified time.
* When canceled, the returned Promise will be rejected with an 'AbortError'.
*
* @param ms Time to sleep in millisecond.
* @param signal An optional AbortSignal that can be used to cancel the scheduled sleep.
*/ function sleep(ms, signal = NeverAbort) {
{
return tp.setTimeout(ms, undefined, {
signal
});
}
}
/**
* Because RPC should keep the signature of the remote function, we cannot add a parameter

@@ -833,3 +901,3 @@ * for transfers to remote function.

const transfers = [];
for (const arg of message.args){
for (const arg of message.a){
const ts = transferCache.get(arg);

@@ -841,38 +909,72 @@ if (ts) {

const response = await send(message, transfers);
if (response.isError) {
throw response.value;
if ("e" in response) {
throw response.e;
} else {
return response.value;
return response.v;
}
}
/**
* Handle an RPC request, call specific method in the target, and send the response.
* Handle an RPC request, call specific method in the target.
*
* This function can be used for request-response model.
*
* @example
* // Create RPC server on http://localhost:9789
* import consumers from "stream/consumers";
* import { RPC } from "@kaciras/utilities/browser";
*
* const functions = {
* hello: (name: string) => `Hello ${name}!`,
* };
*
* const server = http.createServer((req, res) => {
* consumers.json(req)
* .then(msg => RPC.serve(functions, msg))
* .then(d => res.end(JSON.stringify(d[0])));
* });
*
* server.listen(9789);
*
* @param target The service object contains methods that client can use.
* @param message RPC request message
* @param respond The function to send the response message.
*/ async function serve(target, message, respond) {
const { id , path , args } = message;
* @param message RPC request message.
*/ async function serve(target, message) {
const { s , p , a , i } = message;
try {
for(let i = path.length - 1; i > 0; i--){
target = target[path[i]];
for(let k = p.length - 1; k > 0; k--){
target = target[p[k]];
}
const value = await target[path[0]](...args);
const transfers = transferCache.get(value);
respond({
id,
value,
isError: false
}, transfers);
const v = await target[p[0]](...a);
return [
{
i,
s,
v
},
transferCache.get(v) ?? []
];
} catch (e) {
respond({
id,
value: e,
isError: true
});
return [
{
i,
s,
e
},
[]
];
}
}
function createServer(id, target, respond) {
return async (message)=>{
if (typeof message !== "object") {
return; // Not an RPC message.
}
const { p , i } = message;
if (i === id && Array.isArray(p)) {
respond(...await serve(target, message));
}
};
}
class RPCHandler {
/**
* Keys for current property in reversed order, e.g.
* Keys for current property in reversed order.
*

@@ -887,9 +989,9 @@ * @example

return callRemote(send, {
path: this.path,
args
p: this.path,
a: args
});
}
get(send, prop) {
get(send, key) {
return new Proxy(send, new RPCHandler([
prop,
key,
...this.path

@@ -899,65 +1001,10 @@ ]));

}
/**
* Wrap publish-subscribe functions to request-response model.
* The remote service must attach request message id in response message.
*
* # NOTE
* If you disable timeout, there will be a memory leak when response
* message can't be received.
*
* WeakMap doesn't help in this scenario, since the key is deserialized from the message.
*
* @example
* const { request, subscribe } = pubSub2ReqRes(window.postMessage);
* window.addEventListener("message", e => subscribe(e.data));
* const response = await request({ text: "Hello" });
*
* @param publish The publish message function
* @param timeout The number of milliseconds to wait for response,
* set to zero & negative value to disable timeout.
*/ function pubSub2ReqRes(publish, timeout = 10e3) {
const txMap = new Map();
function expire(id) {
const session = txMap.get(id);
if (session) {
txMap.delete(id);
session.reject(new AbortError("Timed out"));
}
function createClient(send, id, addListener) {
if (id) {
const { request , dispatch } = pubSub2ReqRes(id, send);
send = request;
addListener(dispatch);
}
function request(message) {
const id = message.id = uniqueId();
publish(message);
let timer;
if (timeout > 0) {
timer = setTimeout(expire, timeout, id);
timer.unref();
}
return new Promise((resolve, reject)=>{
txMap.set(id, {
resolve,
reject,
timer
});
});
}
function dispatch(message) {
const session = txMap.get(message.id);
if (session) {
clearTimeout(session.timer);
txMap.delete(message.id);
session.resolve(message);
}
}
return {
txMap,
request,
dispatch
};
return new Proxy(send, new RPCHandler([]));
}
function createClient(connection) {
return new Proxy(connection, new RPCHandler([]));
}
function createServer(controller) {
return (message, respond)=>serve(controller, message, respond);
}

@@ -968,3 +1015,2 @@ var rpc = /*#__PURE__*/Object.freeze({

createServer: createServer,
pubSub2ReqRes: pubSub2ReqRes,
serve: serve,

@@ -1123,2 +1169,2 @@ transfer: transfer

export { AbortError, Composite, FetchClient, FetchClientError, LRUCache, MultiEventEmitter, MultiMap, NeverAbort, rpc as RPC, ResponseFacade, SingleEventEmitter, UnitConvertor, base64url, blobToBase64URL, cartesianProductArray, cartesianProductObj, compositor, createInstance, dataSizeIEC, dataSizeSI, durationConvertor, escapeHTML, fetchFile, identity, noop, onExit, sha256, silencePromise, silentCall, sleep, svgToUrl, uniqueId };
export { AbortError, Composite, FetchClient, FetchClientError, LRUCache, MultiEventEmitter, MultiMap, NeverAbort, rpc as RPC, ResponseFacade, SingleEventEmitter, UnitConvertor, base64url, blobToBase64URL, cartesianProductArray, cartesianProductObj, compositor, createInstance, dataSizeIEC, dataSizeSI, durationConvertor, escapeHTML, fetchFile, identity, noop, onExit, pubSub2ReqRes, sha256, silencePromise, silentCall, sleep, svgToUrl, uniqueId };

@@ -1,14 +0,16 @@

export type Respond = (resp: ResponseMessage, transfer?: Transferable[]) => void;
export type RPCSend = (message: RequestMessage, transfer?: Transferable[]) => Promise<ResponseMessage>;
export type RPCReceive = (message: RequestMessage, respond: Respond) => void;
import { PostMessage } from "./event.js";
export interface RequestMessage {
id?: number;
args: any[];
path: PropertyKey[];
a: any[];
p: PropertyKey[];
i?: unknown;
s?: number;
}
export interface ResponseMessage {
id?: number;
value: any;
isError: boolean;
}
export type ResponseMessage = ({
v: unknown;
} | {
e: unknown;
}) & {
i?: unknown;
s?: number;
};
/**

@@ -29,10 +31,32 @@ * By default, every function parameter, return value and object property value is copied,

export declare function transfer<T>(obj: T, transfers: Transferable[]): T;
export type RPCSend = (message: RequestMessage, transfer: Transferable[]) => Promise<ResponseMessage>;
type ServeResultTuple = [ResponseMessage, Transferable[]];
/**
* Handle an RPC request, call specific method in the target, and send the response.
* Handle an RPC request, call specific method in the target.
*
* This function can be used for request-response model.
*
* @example
* // Create RPC server on http://localhost:9789
* import consumers from "stream/consumers";
* import { RPC } from "@kaciras/utilities/browser";
*
* const functions = {
* hello: (name: string) => `Hello ${name}!`,
* };
*
* const server = http.createServer((req, res) => {
* consumers.json(req)
* .then(msg => RPC.serve(functions, msg))
* .then(d => res.end(JSON.stringify(d[0])));
* });
*
* server.listen(9789);
*
* @param target The service object contains methods that client can use.
* @param message RPC request message
* @param respond The function to send the response message.
* @param message RPC request message.
*/
export declare function serve(target: any, message: RequestMessage, respond: Respond): Promise<void>;
export declare function serve(target: any, message: RequestMessage): Promise<ServeResultTuple>;
export type Respond = (resp: ResponseMessage, transfer: Transferable[]) => void;
export declare function createServer(id: string, target: any, respond: Respond): (message: RequestMessage) => Promise<void>;
/**

@@ -45,43 +69,40 @@ * Takes a type and wraps it in a Promise, if it not already is one.

type Promisify<T> = T extends Promise<unknown> ? T : Promise<T>;
type RemoteProperty<T> = T extends Function ? Remote<T> : T;
export type RemoteObject<T> = {
type RemoteProperty<T> = T extends Function ? RemoteCallable<T> : T extends object ? Remote<T> : T;
export type Remote<T> = {
[P in keyof T]: RemoteProperty<T[P]>;
};
type RemoteCallable<T> = T extends (...args: infer Args) => infer R ? (...args: {
[I in keyof Args]: Args[I];
}) => Promisify<Awaited<R>> : unknown;
export type Remote<T> = RemoteObject<T> & RemoteCallable<T>;
export type PostMessage = (message: object) => void;
export interface PromiseController {
timer?: ReturnType<typeof setTimeout>;
resolve(value: unknown): void;
reject(reason: unknown): void;
}
export interface ReqResWrapper {
request(message: object): Promise<any>;
dispatch(message: object): void;
txMap: Map<number, PromiseController>;
}
type RemoteCallable<T> = T extends (...args: infer A) => infer R ? (...args: A) => Promisify<Awaited<R>> : unknown;
type Listen = (callback: (message: ResponseMessage) => void) => void;
/**
* Wrap publish-subscribe functions to request-response model.
* The remote service must attach request message id in response message.
* Create an RPC client with publish-subscribe channel.
*
* # NOTE
* If you disable timeout, there will be a memory leak when response
* message can't be received.
* @param post Function to post request message.
* @param id
* @param listen Listener to receive response message.
*/
export declare function createClient<T = any>(post: PostMessage, id: string, listen: Listen): Remote<T>;
/**
* Create an RPC client with request-response channel.
*
* WeakMap doesn't help in this scenario, since the key is deserialized from the message.
*
* @example
* const { request, subscribe } = pubSub2ReqRes(window.postMessage);
* window.addEventListener("message", e => subscribe(e.data));
* const response = await request({ text: "Hello" });
* // Call remote function `hello` with HTTP protocol.
* import { RPC } from "@kaciras/utilities/browser";
*
* @param publish The publish message function
* @param timeout The number of milliseconds to wait for response,
* set to zero & negative value to disable timeout.
* const client = RPC.createClient(async message => {
* const response = await fetch("http://localhost:9789", {
* method: "POST",
* body: JSON.stringify(message),
* });
* if (response.ok) {
* return response.json();
* } else {
* throw new Error("fetch failed: " + response.status);
* }
* });
*
* expect(await client.hello("world")).toBe("Hello world!");
*
* @param send Function to post request message and receive response message.
*/
export declare function pubSub2ReqRes(publish: PostMessage, timeout?: number): ReqResWrapper;
export declare function createClient<T = any>(connection: RPCSend): Remote<T>;
export declare function createServer(controller: object): RPCReceive;
export declare function createClient<T = any>(send: RPCSend): Remote<T>;
export {};
{
"name": "@kaciras/utilities",
"version": "0.7.0",
"version": "0.8.0",
"license": "MIT",

@@ -21,2 +21,5 @@ "description": "A set of common JS functions for node and browser.",

],
"tsd": {
"directory": "tsst"
},
"devDependencies": {

@@ -28,12 +31,15 @@ "@jest/globals": "^29.5.0",

"@rollup/plugin-replace": "^5.0.2",
"@stryker-mutator/core": "^6.4.1",
"@stryker-mutator/jest-runner": "^6.4.1",
"@swc/core": "^1.3.40",
"@stryker-mutator/core": "^6.4.2",
"@stryker-mutator/jest-runner": "^6.4.2",
"@swc/core": "^1.3.42",
"@swc/jest": "^0.2.24",
"@types/node": "^18.15.3",
"@tsd/typescript": "^5.0.2",
"@types/node": "^18.15.8",
"eslint": "^8.36.0",
"is-builtin-module": "^3.2.1",
"jest": "^29.5.0",
"jest-runner-tsd": "^5.0.0",
"mockttp": "^3.7.0",
"rollup": "^3.19.1",
"rollup": "^3.20.2",
"tsd-lite": "^0.7.0",
"typescript": "^5.0.2"

@@ -43,6 +49,7 @@ },

"lint": "eslint --fix .",
"test": "jest",
"test:types": "jest -c jest.config.tsd.js",
"test:unit": "jest",
"test:mutation": "stryker run",
"build": "tsc --build tsconfig-build.json && node build-lib.js"
"build": "node build-lib.js"
}
}

@@ -25,10 +25,13 @@ # Utilities

The package has 2 entry points, "./node" for NodeJS and "./browser" for browsers. Most functions work for both, but there are still a few functions that only work in one runtime.
The package has 2 entry points. Most functions work for both, but there are still some differences:
* `@kaciras/utilities/browser` can be imported from any environment, also have functions work with DOM.
* `@kaciras/utilities/node` have no browser-specific functions, but add utilities for Node, it can only be used in NodeJS.
```javascript
// Import for Node.
// Use in Node.
import { /* ... */ } from "@kaciras/utilities/node";
// Import for browser
// Use in other environment.
import { /* ... */ } from "@kaciras/utilities/browser";
```
SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc