A collection of useful functions, types, and objects.
Install this module with the following command:
npm install sundryjs
Add the module to your package.json dependencies:
npm install --save sundryjs
Add the module to your package.json dev-dependencies:
npm install --save-dev sundryjs
Import a module as follows:
import nullThrows from "sundryjs/assert/nullThrows";
For types, import it like this:
import type { Opaque } from "sundryjs/types/Opaque";
The EventEmitter
module implements a generic typed event emitter.
class EventEmitter<T>
T
defines an object keyed by the event name and the value being the event type.
The listen
method starts listening to specific events, executing the callback when the event is triggered.
listen<E extends string & keyof T>(
event: E,
cb: (data: T[E]) => void,
): EventSubscription
Starts listening to all events, using the callback when the event is triggered.
Note:
This method does not listen to the payload, but only the event name. The payload is not sent into the callback as with listen
. Use this only for information purposes. Also, use this sparingly as it can become quite expensive.
listenToAll<E extends string & keyof T>(
cb: (event: E) => void,
): EventSubscription
Triggers an event with the typed payload.
trigger<E extends string & keyof T>(event: E, payload: T[E]): void
Convenience function to remove a subscription. You can also use the EventSubscription
interface returned from the listen
methods.
remove(subscription: EventSubscription): void
Removes all subscribers from the event emitter.
removeAll(): void
The EventSubscription
interface is defined as:
interface EventSubscription {
remove: () => void;
}
Usage:
type Events = {
"auth:login:changed": {
user: string,
permissions: number,
};
// Any other event
};
class GlobalEventEmitter extends EventEmitter<Events> {}
const globalEvents = new GlobalEventEmitter();
// Listen to events fully typed
const subscription = globalEvents.listen("auth:login:changed", payload => {
console.log(`User ${payload.user} logged in with permission value ${payload.permissions}`);
});
// Trigger events requiring to supply the right payload fields
globalEvents.trigger("auth:login:changed", {
user: "Anthony",
permissions: 0755,
});
// Remove subscription
subscription.remove();
The remove
method removes the subscription from the event emitter.
A function that fails for all calls.
This is used for exhausive checking switches and similar use-cases.
assertNever(x: unknown): never
Usage:
The following code makes sure that value is either 1 or 2. Due to typing, TypeScript will be able to recognize this and statically suggests this error case.
switch (value) {
case 1:
// some code
break;
case 2:
// some other code
break;
default:
assertNever(value);
}
Handles the exception according to the environment.
Use this function in try/catch exceptions when you do not want to throw, but keep track of the issue in a development environment. The values will be written to the console when __DEV
is set, otherwise it will be ignored.
If no msg
was given, then it will try to derive it from the non-standard internalMessage
field and will fall back to the standard message
field. The data
values will be printed with the msg
in a collapsible console entry.
doNotThrowWithDetails(
err: Error,
msg?: string,
data?: Record<string, unknown>,
): void
Usage:
try {
// some code that might throw an exception
} catch(Error err) {
doNotThrowWithDetails(
err,
null, // use message from error
{ // Values which will be printed in the console log supporting debugging
foo: 23,
},
);
}
Use invariant()
to assert a state which your program assumes to be true.
Use template string to give more context on what went wrong.
The invariant message will be stripped in production, but the invariant will remain to ensure logic does not differ in production.
invariant(condition: unknown, msg?: string): asserts condition
Usage:
const value = someFunction(); // Might be any type
invariant(typeof value === "string", "Value is not a string.");
// Can be sure it is a string now
Util function to return the non-null (and non-undefined) type to keep everything strictly typed. Use template string to give more context on what went wrong.
nullThrows<T>(x: T | null | undefined, msg?: string): T
Usage:
const value = someFunction(); // Returns number | null | undefined
const numberValue = nullthrows(value, "Value is null.");
// numberValue is now of type "number"
Handles the exception according to the environment.
Use this function in try/catch exceptions when you still want to throw. This is very similar to doNotThrowWithDetails
, but it will throw an exception. However, it will also in addition write the context data to the console in a development environment when __DEV
is set.
No msg
will automatically be derived in this case. The data
values will be printed with the msg
, if available, in a collapsible console entry.
The return value gives TypeScript context that it will never return.
throwWithDetails(
err: Error,
msg?: string,
data?: Record<string, unknown>,
): never
Usage:
try {
// some code that might throw an exception
} catch(Error err) {
throwWithDetails(
err, // Will re-throw this error
null, // use message from error
{ // Values which will be printed in the console log supporting debugging
foo: 23,
},
);
}
Util function to return the non-undefined (only) type to keep everything strictly typed. Use template string to give more context on what went wrong.
undefinedThrows<T>(x: T | undefined, msg?: string): T
Usage:
const value = someFunction(); // Returns number | undefined
const numberValue = nullthrows(value, "Value is undefined.");
// numberValue is now of type "number"
Returns the first entry and will fail if none available.
firstX<T>(items: T[], msg?: string): T
Usage:
const firstEntry = firstX(list); // Fails when list is empty
Flattens a list within a list to only return a single-depth list.
flatten<T>(items: T[][]): T[]
Usage:
const complexList = [
[1, 2, 3],
[4, 5, 6],
];
const thinList = flatten(complexList);
// thinList = [1, 2, 3, 4, 5, 6]
Returns the first entry and will fail if not available.
lastX<T>(items: T[], msg?: string): T
Usage:
const lastEntry = lastX(list); // Fails when list is empty
Returns the first entry and will fail if not available.
Note: The index starts at zero.
nthX<T>(items: T[], idx: number, msg?: string): T
If msg
is not given, a useful message will be returned.
Should the xth entry be undefined
, then it will trigger an error.
Usage:
const fifthEntry = nthX(list, 4); // Fails when there is no 5th entry
Returns the first entry and will fail if there are none or more than one entry available.
onlyX<T>(items: T[], msg?: string): T
Usage:
const list = someFunction(); // Will return a list, but it is expected to be one entry
const entry = onlyX(list); // Fails when there is no or more than one entry
Converts a string to camel-case.
Examples: foo-bar -> fooBar FooBar -> fooBar fooBar -> fooBar foo_bar -> fooBar
camelCase(str: string): string
Converts a string to kebab-case.
Examples: foo-bar -> foo-bar FooBar -> foo-bar fooBar -> foo-bar foo_bar -> foo-bar
kebabCase(str: string): string
Converts a string to pascal-case.
Examples: foo-bar -> FooBar FooBar -> FooBar fooBar -> FooBar foo_bar -> FooBar
pascalCase(str: string): string
Converts a string to snake-case.
Examples: foo-bar -> foo_bar FooBar -> foo_bar fooBar -> foo_bar foo_bar -> foo_bar
snakeCase(str: string): string
Utility functions for time.
Following is a list of self-explanatory functions:
secToMs(seconds: number): number
msToSecs(ms: number): number
minsToSecs(minutes: number): number
secsToMins(secs: number): number
hrsToSecs(hours: number): number
secsToHrs(secs: number): number
daysToSecs(days: number): number
secsToDays(secs: number): number
weeksToSecs(weeks: number): number
secsToWeeks(secs: number): number
monthsToSecs(months: number): number
secsToMonths(secs: number): number
quartersToSecs(quarters: number): number
secsToQuarters(secs: number): number
halvesToSecs(halves: number): number
secsToHalves(secs: number): number
yearsToSec(years: number): number
secsToYears(secs: number): number
Type guard for runtime. It converts an input type to an output type according to some condition defined.
isType<T>(value: unknown, condition: boolean): value is T
Usage:
const value = isType(inputValue, typeof inputValue = "string");
Converts an object with string keys to a typed object.
Note: Make sure to mark the object as <const>
as this is required.
type ObjectPattern<T>
Usage:
const payload = <const>{
id: "number",
username: "string",
};
type Payload = ObjectPattern<typeof payload>;
// Payload is of type
// {
// id: number,
// username: string,
// }
Defines an opaque type for TypeScript.
Note: Make sure you give the K
a unique identifier across the system.
type Opaque<K, T>
Usage:
// A function which requires a specific type
function login(userID: UserID): void {
// Do the login
}
// Define Opaque type
type UserID = Opaque<"UserID", number>;
// Define a function to convert from a number value to the Opaque type
function makeUserID(userID: number): UserID {
return userID as UserID;
}
// Convert a validated input to the Opaque value
const user_id = makeUserID(validateUserID(input_user_id));
// Call function
login(user_id);
// Will FAIL
login(input_user_id);
Returns the type of a particular string literal
Converts a "string" literal into the string type, and many others.
type StringToType<T>
The following types are supported:
- "string" -> string
- "number" -> number
- "boolean" -> boolean
- "undefined" -> undefined
- "function" -> Function
- "object" -> Record<string, any>
Returns the type of a particular string literal
Converts a "string" literal into the string type, and many others.
type TypeToString<T>
The following types are supported:
- string -> "string"
- number -> "number"
- boolean -> "boolean"
- undefined -> "undefined"
- Function -> "function"
- Record<string, any> -> { [K in keyof T]: TypeToString<T[K]> }
- T -> any
- null -> "null"
Note: This type uses a recursive call and an object will be broken down into individual strings.
Safer wrapper around hasOwnProperty.
See https://eslint.org/docs/rules/no-prototype-builtins
hasOwnProperty(
obj: Record<string, unknown>,
property: string,
): boolean
Determines if two object have the same properties.
hasSameProperties(
a: Record<string | number | symbol, any>,
b: Record<string | number | symbol, any>,
): boolean
Verifies that a specific object has the structure as defined in an object pattern. It will throw when the type is not as expected.
objectPatternVerify<T>(
desc: string,
pattern: Record<string, ObjectPatternTypes>,
values: T,
options?: { fail?: boolean },
): T | null
Usage:
const pattern = {
name: "string",
age: "number",
};
const values = {
name: "John Doe",
age: 23,
};
const result = objectPatternVerify("<What is this?>", pattern, values);
// Will be null if it isn't of this type as defined above,
// but will return the value if it is