Things I've written or seen that are good, but eventually didn't use.
Last active
October 6, 2021 14:47
-
-
Save renoirb/49e4dc04259a79cc023aa97f6dd3790a to your computer and use it in GitHub Desktop.
JavaScript utility functions bag
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
function daysAgo (n = 1) { | |
const d = parseInt(n) || 0 | |
const dateObj = new Date() | |
if (isNaN(d)) { | |
return dateObj | |
} | |
return new Date(dateObj.setDate(dateObj.getDate() - d)) | |
} | |
/** | |
* Convert numbers to words. | |
* | |
* Refactored to make it less obscure. | |
* | |
* Original Copyright, 25th July 2006, by Stephen Chapman | |
* | |
* @param {number} s | |
* @return {string} word representation of a given number | |
*/ | |
function numberToWords(s) { | |
// American Numbering System | |
var thousies = ['', 'thousand', 'million', 'billion', 'trillion'] | |
// Or English | |
// var thousies = ['','thousand','million', 'milliard','billion']; | |
var unit = [ | |
'zero', | |
'one', | |
'two', | |
'three', | |
'four', | |
'five', | |
'six', | |
'seven', | |
'eight', | |
'nine', | |
] | |
var teenies = [ | |
'ten', | |
'eleven', | |
'twelve', | |
'thirteen', | |
'fourteen', | |
'fifteen', | |
'sixteen', | |
'seventeen', | |
'eighteen', | |
'nineteen', | |
] | |
var tweenies = [ | |
'twenty', | |
'thirty', | |
'forty', | |
'fifty', | |
'sixty', | |
'seventy', | |
'eighty', | |
'ninety', | |
] | |
s = s.toString() | |
s = s.replace(/[\, ]/g, '') | |
if (s != parseFloat(s)) { | |
return 'not a number' | |
} | |
var x = s.indexOf('.') | |
if (x == -1) { | |
x = s.length | |
} | |
if (x > 15) { | |
return 'too big' | |
} | |
var n = s.split('') | |
var str = '' | |
var sk = 0 | |
for (var i = 0; i < x; i++) { | |
if ((x - i) % 3 == 2) { | |
if (n[i] == '1') { | |
str += teenies[Number(n[i + 1])] + ' ' | |
i++ | |
sk = 1 | |
} else if (n[i] != 0) { | |
str += tweenies[n[i] - 2] + ' ' | |
sk = 1 | |
} | |
} else if (n[i] != 0) { | |
str += unit[n[i]] + ' ' | |
if ((x - i) % 3 == 0) { | |
str += 'hundred ' | |
} | |
sk = 1 | |
} | |
if ((x - i) % 3 == 1) { | |
if (sk) { | |
str += thousies[(x - i - 1) / 3] + ' ' | |
} | |
sk = 0 | |
} | |
} | |
return str.trim() | |
} | |
Common Map mutations
Implementation
/**
* Get the count of memberes in a given Map
*/
export const getMapLength = (m: ReadonlyMap<unknown, unknown>): number => [...m.keys()].length
/**
* To either increase or decrease a value
*/
export type IncreaseDecrease = -1 | 1
/**
* Check whether we want to increase or decrease operation
*/
export const isIncreaseOrDecrease = (sign: unknown): sign is IncreaseDecrease => sign === -1 || sign === 1
/**
* Ensure a passed arguments is only about Increase/Decrease a property's value
*/
export const assertsIncreaseOrDecrease: (sign: unknown) => asserts sign is IncreaseDecrease = sign => {
if (isIncreaseOrDecrease(sign) === false) {
throw new RangeError(`We cannot use "${sign}" for increase/decrease, it can exclusively be 1 or -1`)
}
}
/**
* Attempt at increasing/decreasing a value.
*
* Should only return a number when the second argument provided could be increased/decreased to a valid number.
*
* @throws RangeError if decrease went under 0
* @throws TypeError if input increase/decrease mutation did not compute to a number
*/
export const increaseOrDecrease: (sign: IncreaseDecrease, something: unknown) => number = (sign, something): number => {
const propertyValue = typeof something === 'string' && Number.isInteger(something) ? +something : void 0
const value = propertyValue + sign
if (Number.isNaN(value)) {
throw new TypeError(`Value ${propertyValue} cannot ${increaseOrDecrease}, it becomes a non number`)
}
if (value < 0) {
throw new RangeError(
`Value ${propertyValue} cannot be ${increaseOrDecrease} any further, it becomes lower than zero`,
)
}
return value
}
/* eslint-disable functional/prefer-readonly-type */
/**
* Inside a Map, mutate all members, to a new value.
*/
export const mutateMapSetAllInnerItemsTo = <T, K extends keyof T>(
m: Map<unknown, T>,
propertyKey: K,
value: number | undefined | string,
): void => {
m.forEach(i => {
Object.assign(i, { [propertyKey]: value })
})
}
/**
* Inside a Map, at entry matching mapKey, mutate inner object at propertyKey to either increase or decrease.
*
* @throws ReferenceError if no entry for mapKey found in map
* @throws ReferenceError if entry at mapKey decreased lower than zero {@see increaseOrDecrease}
* @throws TypeError if entry at mapKey for inner object at propertyKey did not return a number
*/
export const mutateMapSetInnerItemIncreaseOrDecrease = <T extends Record<string, unknown>, K extends keyof T>(
m: Map<string, T>,
propertyKey: K,
mapKey: string,
sign: IncreaseDecrease,
): void => {
assertsIncreaseOrDecrease(sign)
if (m.has(mapKey)) {
const match = m.get(mapKey)
const propertyValue = Reflect.has(match, propertyKey) ? Reflect.get(match, propertyKey) : void 0
if (propertyValue) {
const value = increaseOrDecrease(sign, propertyValue)
// What might have thrown here are:
// - If value is lower than zero
// - propertyValue could not return a number
// Anything else?
Object.assign(match, { [propertyKey]: value })
} else {
throw new ReferenceError(`In the map, for member ${mapKey}, there was no value at "${propertyKey}"`)
}
} else {
// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/ReferenceError
// The ReferenceError object represents an error when a non-existent variable is referenced.
// Missing key qualifies as Reference?
throw new ReferenceError(`There is no member ${mapKey} in this map`)
}
}
/* eslint-enable functional/prefer-readonly-type */
Managing a bucket of sets
interface CalendarEvent {
start: string
}
type AssertApplicableBucketItem<TData> = (input: unknown) => asserts input is TData
type AssertApplicableBucketItemKey<TKey> = (input: unknown) => asserts input is TKey
type BucketKeyNormalizer<TData, TKey> = (input: TData) => TKey
type Bucket<TData, TKey> = Map<TKey, Set<Readonly<TData>>>
/**
* @throws TypeError When TData key resolved is not in valid format
*/
type BucketMarshaller<TData = unknown> = (input: TData) => void
const normalizeEventKey: BucketKeyNormalizer<CalendarEvent, string> = i => (('start' in i || {}) ? /\d{4}-\d{2}-\d{2}/.exec(i.start ?? '') ?? [''] : [''])[0]
const addToBucket: BucketMarshaller<CalendarEvent> = input => {
// TODO AssertApplicableBucketItem on input ^
const key = normalizeEventKey(input)
// TODO AssertApplicableBucketItem on key ^
if(!bucket.has(key)) {
bucket.set(key, new Set<Readonly<CalendarEvent>>())
}
// Voir https://gist.github.com/renoirb/49e4dc04259a79cc023aa97f6dd3790a#gistcomment-3812526
(bucket.get(key) as Set<Readonly<CalendarEvent>>).add(JSON.parse(JSON.stringify(input)))
}
const bucket: Bucket<CalendarEvent, string> = new Map()
// Implementation
const events = [{ start: '2021-07-16T12:00:00-07:00', /* ... */ }]
for (const event of events) {
addToBucket(event)
}
Keep Set of <things>
in LocalStorage
/**
* Sort function predicate.
*
* To help memory, here is the gist of how it should work;
*
* - If compareFunction(left, right) returns a value > than 0, sort right before left.
* - If compareFunction(left, right) returns a value < than 0, sort left before b.
* - If compareFunction(left, right) returns 0, left and right are considered equal.
*
* Source:
* - https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/sort
*/
export type ISortFnPredicate<T> = (left: T, right: T) => -1 | 1 | 0
/**
* Bucket in which to keep the decisions taken.
*
* One LocalStorage key per grouping.
*/
export interface ISetLocalStorageAdapter<T = string | number> {
readonly storageKey: string
readonly items: T[]
add(item: T): void
remove(item: T): void
}
export const createSetLocalStorageAdapter = <T = unknown>(
storageKey: string,
w: WindowProxy,
): ISetLocalStorageAdapter<T> => {
const initially = w.localStorage.getItem(storageKey)
const initiallyEmpty = initially === null
const duplicates = (items: T[]): T[] => {
const unique = new Set(items)
const collected = items.filter((k: T) => {
if (unique.has(k)) {
unique.delete(k)
return false
} else {
return true
}
})
return collected
}
const getItems = (k: string): T[] => {
const stringified = w.localStorage.getItem(k) ?? '[]'
const items: T[] = []
if (initiallyEmpty === false) {
try {
const parsed = JSON.parse(stringified) as T[]
items.push(...parsed)
} catch (e) {
console.log(
`Something went wrong on LocalStorage.getItem("${storageKey}")`,
e,
)
}
}
return items
}
// initial state at call time
const _initialStateItems: T[] = getItems(storageKey)
if (initiallyEmpty) {
w.localStorage.setItem(storageKey, JSON.stringify(_initialStateItems))
}
const out: ISetLocalStorageAdapter<T> = {
storageKey,
get items(): T[] {
const items = getItems(storageKey)
return items
},
add(item: T) {
const before = getItems(storageKey)
const items = before.filter((k: T) => k !== item).sort()
const foundDuplicates = duplicates(items)
if (foundDuplicates.includes(item)) {
console.log(`${storageKey} add(${item}), found duplicate, not adding`, {
foundDuplicates,
})
} else {
items.push(item)
w.localStorage.setItem(storageKey, JSON.stringify(items))
}
},
remove(item: T) {
const before = getItems(storageKey)
const items = before.filter((k: T) => k !== item).sort()
const foundDuplicates = duplicates(items)
if (foundDuplicates.includes(item)) {
console.log(`${storageKey} remove(${item}), found duplicate`, {
foundDuplicates,
})
}
w.localStorage.setItem(storageKey, JSON.stringify(items))
},
}
Object.freeze(out.storageKey)
return out
}
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Calculate perf stats for Node.js