alistair
A collection of utility functions to share across projects.
Installation
yarn add alistair
Packages:
alistair/id
:
ID package for generating stable IDs at runtime. Cryptographically insecure and no collision protection so really made for things that are not meant to be unique or when you need a quick id (e.g. an application invite code).
Declarations:
declare function id(length?: number, alphabet?: string): string;
Example usage:
import {id} from 'alistair/id';
const randomId = id();
const randomHex = id(6, 'abcdefg01234567890');
const randomCode = id(10);
alistair/hooks
:
A collection of pure utility React hooks for common use cases.
Available Hooks:
useLocalStorage<T>
Store and sync state with localStorage.
function useLocalStorage<T>(
key: string,
init: () => T,
): [value: T, set: (action: React.SetStateAction<T>) => void];
useThrottle<T>
Throttle a value's updates.
function useThrottle<T>(value: T, ms?: number): T;
useToggle
Manage boolean toggle state with convenient controls.
function useToggle(initialState?: boolean): [
enabled: boolean,
control: {
on: () => void;
off: () => void;
toggle: () => void;
reset: () => void;
},
];
useIsTabFocused
Track if the current browser tab is focused.
function useIsTabFocused(): boolean;
useIsOnline
Monitor the browser's online/offline status.
function useIsOnline(): boolean;
useLazyRef<T>
Create a ref with lazy initialization.
function useLazyRef<T>(init: () => T): T;
useInterval
Set up an interval that's properly cleaned up.
function useInterval(fn: () => void, interval: number): void;
useEvent<A, R>
Create a stable event handler that never changes identity.
function useEvent<A extends unknown[], R>(fn: (...args: A) => R): (...args: A) => R;
useAsyncFunction<A, R>
Manage async function state with loading and error handling.
function useAsyncFunction<A extends unknown[], R>(
execute: (...args: A) => Promise<R>,
): [
state: {
loading: boolean;
result: {type: 'initial'} | {type: 'success'; data: R} | {type: 'error'; error: unknown};
},
run: (...args: A) => Promise<{type: 'success'; data: R} | {type: 'error'; error: unknown}>,
reset: () => void,
];
alistair/http
:
A powerful HTTP client with lifecycle hooks, retries, and type-safe responses.
import {createHTTPClient} from 'alistair/http';
const client = createHTTPClient({
base: 'https://api.example.com',
transform: res => res.json(), // Optional response transformer
lifecycle: {
// Optional lifecycle hooks
before: async request => {
// Modify request before sending
request.headers.set('Authorization', 'Bearer token');
return request;
},
failure: async (count, request, response) => {
// Handle failures, implement retries
if (count < 3 && response.status === 429) {
await new Promise(resolve => setTimeout(resolve, 1000));
return request; // Retry the request
}
},
},
});
// Available methods: get, post, put, patch, delete, head, options
const data = await client.get('/users');
const response = await client.post('/users', {
body: {name: 'John'}, // Assumes JSON by default, specify Content-Type header to change
});
alistair/structs
:
Type-safe data structures for JavaScript/TypeScript.
StrictMap<K, V>
A strict variant of Map that enforces explicit handling of missing keys.
import {StrictMap} from 'alistair/structs';
const map = new StrictMap<string, number>();
// Get with default value if key doesn't exist
const value = map.getOr('key', () => 42);
// Get with alternative return type if key doesn't exist
const optional = map.getElse('missing', () => null);
ImmutableMap<K, V>
An immutable Map implementation.
import {ImmutableMap} from 'alistair/structs';
const map = new ImmutableMap<string, number>([['key', 1]]);
const newMap = map.set('key2', 2); // Returns new instance
ImmutableSet<T>
An immutable Set implementation.
import {ImmutableSet} from 'alistair/structs';
const set = new ImmutableSet<string>(['a', 'b']);
const newSet = set.add('c'); // Returns new instance
alistair/log
:
A structured logging utility with indentation support and colorized output.
import {log} from 'alistair/log';
log.info('Started server');
log.listening('on 3000');
log.success('Request received');
log.error('Request failed', new Error('Failed to process'));
log.warn('Server is not responding');
log.debug('Debug information');
log.fatal(new Error('Critical error'));
// Indented logging
log.indent(() => {
log.info('Nested information');
log.success('Nested success');
});
alistair/atoms
:
A lightweight state management solution with React bindings.
import {atom, useAtom, useAtomValue} from 'alistair/atoms';
// Create an atom
const countAtom = atom(0);
// Use in React components
function Counter() {
const [count, setCount] = useAtom(countAtom);
// Or read-only: const count = useAtomValue(countAtom);
return <button onClick={() => setCount(c => c + 1)}>Count: {count}</button>;
}
alistair/react
:
React utilities including context creators and store management.
import {createStrictContext, createStoreContext} from 'alistair/react';
// Create a type-safe context
const UserContext = createStrictContext<{name: string}>();
// Use in components
function App() {
return (
<UserContext.Provider value={{name: 'John'}}>
<Child />
</UserContext.Provider>
);
}
function Child() {
const user = UserContext.useContext();
return <div>{user.name}</div>;
}
alistair/prettier
:
Opinionated prettier config. Use it by creating a .prettierrc
file with:
"alistair/prettier"
alistair/bus
:
A type-safe event bus implementation for handling pub/sub patterns.
import {EventBus} from 'alistair/bus';
// Define event types
type Events = {
userJoined: [userId: string, timestamp: number];
userLeft: [userId: string];
};
// Create a type-safe event bus
const bus = new EventBus<Events>();
// Subscribe to events
const unsubscribe = bus.on('userJoined', (userId, timestamp) => {
console.log(`User ${userId} joined at ${timestamp}`);
});
// Emit events
bus.emit('userJoined', 'user123', Date.now());
// Unsubscribe when done
unsubscribe();
// Or manually unsubscribe
bus.off('userJoined', listener);
alistair/tags
:
Template literal utilities for string manipulation.
stripIndent
Removes leading indentation from multi-line strings while preserving relative indentation.
import {stripIndent} from 'alistair/tags';
const sql = stripIndent`
SELECT *
FROM users
WHERE id = ${userId}
AND status = 'active'
`;
// Result:
// SELECT *
// FROM users
// WHERE id = 123
// AND status = 'active'