A Collection of Type Definitions

One of the harder parts of software development is defining things, and in particular data models and interfaces. Here I provide a number of data models that you might find useful for your own project. These use TypeScript definitions to describe the models, but can be used for vanilla JS too.

By separating our data model design from implementation we can more effecticely decouple different parts of our code and create software that is much more reusable.

The Collection

The collection is like an async version of Map. It represents a number of records, each with an ID which is locally unique to the collection. Like a map, you can get an item by its ID, and iterate through entries.

Unlike a map, the collection has a page funciton, which returns a subset of collection's entries as an array, along with the total number of items in the collection. The number parameter is a zero-indexed number for specifying which page to fetch, and the size parameter dictates how many items should be on that page. Paging through a collection like this is a common paradigm on the web, and this function should make that easier.

Apart from that, there are very few constraints on its behaviour. The collection should be ordered but the sort criteria is not specified. It may contain the same object multiple times under different IDs, the object may or may not have an existance outside of the collection, the ID may or may not be a property of the object, etc etc.

type Page<T> = Array<T> & {
	total: number;
}

interface Collection<T, Key extends string = string> {
	[Symbol.asyncIterator](): AsyncIterator<[Key, T]>

	/**
	 * Add a new item to the collection and generate a key for it
	 * @param item The item to add
	 * @returns The generated key
	 */
	add(item: T): Promise<Key>
	/**
	 * Return the number of items in the collection
	 */
	count(): Promise<number>
	/**
	 * Remove an item from the collection
	 * @param key The key of the item to remove
	 * @returns True if the item existed, false if it didn't
	 */
	delete(key: Key): Promise<boolean>
	/**
	 * Get a specific item from the collection
	 * @param key The key of the item to get
	 * @returns The item if it exists, or undefined
	 */
	get(key: Key): Promise<T|undefined>
	/**
	 * Returns true if the collection contains an item for the given key
	 * @param key The key of the item to check
	 */
	has(key: Key): Promise<boolean>
	/**
	 * Get a page of items from the collection, which is an array
	 * of specific size along with a `total` property which is
	 * the total number of items in the entire collection.
	 * A page's first item will be the item at position `number * size`
	 * of the whole collection, its last item will be
	 * the item at `((number + 1) * size) - 1`.
	 * The final page will have fewer than `size` items if `page.total` is
	 * not perfectly divisible by `size`.
	 * The total number of pages can be calculated by
	 * `Math.ceil(page.total/size)`
	 * @param number The page number to fetch, starting from 0
	 * @param size The number of items on the page
	 */
	page(number: number, size: number): Promise<Page<[Key, T]>>
	/**
	 * Insert an item into the collection under the given key.
	 * The returned inserted item may not be the same object as the
	 * original item, and may have had properties modified.
	 * @param key The key to assign to the item
	 * @param item The item to insert
	 * @returns The inserted item
	 */
	set(key: Key, item: T): Promise<T>
}

The DateLike

The DateLike is a type for representing a date or datetime. It's based on the upcoming Temporal spec, but doesn't specify exactly which Temporal type you need to use. Its only functionality is to return the date as machine-readable (ISO 8601) and human-readable (locale) strings. It also exposes properties that allow it to be converted to a full Temporal type instance.

Note that this is not compatible with the Date object, but it's fairly trivial to create a wrapper around Date that exposes the right properties/methods.

interface DateLike {
	toString(): string
	toLocaleString(locale?: string | string[], options?: object): string
	year: number
	month: number
	day?: number
	hour?: number
	minute?: number
	timeZone?: string
}