SundryJS
A collection of useful functions, types, and objects.
Installation
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";
Reference
EventEmitter
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.
Assert
assert/assertNever
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);
}
assert/doNotThrowWithDetails
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,
},
);
}
assert/invariant
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
assert/nullThrows
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"
assert/throwWithDetails
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,
},
);
}
assert/undefinedThrows
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"
Collection
collection/firstX
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
collection/flatten
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]
collection/lastX
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
collection/nthX
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
collection/onlyX
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
Str
str/camelCase
Converts a string to camel-case.
Examples: foo-bar -> fooBar FooBar -> fooBar fooBar -> fooBar foo_bar -> fooBar
camelCase(str: string): string
str/kebabCase
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
str/pascalCase
Converts a string to pascal-case.
Examples: foo-bar -> FooBar FooBar -> FooBar fooBar -> FooBar foo_bar -> FooBar
pascalCase(str: string): string
str/snakeCase
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
Time
time/time
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
Types
types/isType
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");
types/ObjectPattern
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,
// }
types/Opaque
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);
types/StringToType
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>
types/TypeToString
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.
Verify
verify/hasOwnProperty
Safer wrapper around hasOwnProperty.
See https://eslint.org/docs/rules/no-prototype-builtins
hasOwnProperty(
obj: Record<string, unknown>,
property: string,
): boolean
verify/hasSameProperties
Determines if two object have the same properties.
hasSameProperties(
a: Record<string | number | symbol, any>,
b: Record<string | number | symbol, any>,
): boolean
verify/objectPatternVerify
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