signal-desktop/ts/util/mapEmplace.ts
Jamie Kyle e617981e59
Support endorsements for group 1:1 sends
Co-authored-by: trevor-signal <131492920+trevor-signal@users.noreply.github.com>
2024-10-10 10:57:22 -07:00

85 lines
2.2 KiB
TypeScript

// Copyright 2024 Signal Messenger, LLC
// SPDX-License-Identifier: AGPL-3.0-only
import type { RequireAtLeastOne } from 'type-fest';
export type MapKey<T extends Map<unknown, unknown>> =
T extends Map<infer Key, unknown> ? Key : never;
export type MapValue<T extends Map<unknown, unknown>> =
T extends Map<unknown, infer Value> ? Value : never;
export type MapEmplaceOptions<T extends Map<unknown, unknown>> =
RequireAtLeastOne<{
insert?: (key: MapKey<T>, map: T) => MapValue<T>;
update?: (existing: MapValue<T>, key: MapKey<T>, map: T) => MapValue<T>;
}>;
/**
* Lightweight polyfill of the `Map.prototype.emplace` TC39 Proposal:
* @see https://github.com/tc39/proposal-upsert
*
* @throws If no `insert()` is provided and key is not present
*
* @example Get or Insert:
* ```ts
* let pagesByBook = new Map<BookId, Map<PageId, Page>>()
* for (let page of pages) {
* let bookPages = mapEmplace(pagesByBook, page.bookId, {
* insert: () => new Map(),
* })
* bookPages.set(page.id, page)
* }
* ```
*
* @example Get+Update or Insert:
* ```ts
* let unreadPages = new Map<BookId, number>()
* for (let page of pages) {
* if (page.readAt == null) {
* mapEmplace(unreadPages, page.bookId, {
* insert: () => 1,
* update: (prev) => prev + 1,
* })
* }
* }
* ```
*
* @example Get+Update or Throw
* ```ts
* let PagesCache = new Map<PageId, Page>()
*
* function onPageReadEvent(pageId: PageId, readAt: number) {
* if (PagesCache.has(pageId)) {
* mapEmplace(PagesCache, pageId, {
* update(page) {
* return { ...page, readAt }
* },
* })
* } else {
* // save for later
* onEarlyPageReadEvent(pageId, readAt)
* }
* }
* ```
*/
export function mapEmplace<T extends Map<unknown, unknown>>(
map: T,
key: MapKey<T>,
options: MapEmplaceOptions<T>
): MapValue<T> {
if (map.has(key)) {
let value = map.get(key) as MapValue<T>;
if (options.update != null) {
value = options.update(value, key, map);
map.set(key, value);
}
return value;
}
if (options.insert != null) {
const value = options.insert(key, map);
map.set(key, value);
return value;
}
throw new Error('Key was not present in map, and insert() was not provided');
}