Skip to content

Instantly share code, notes, and snippets.

@renoirb
Last active October 6, 2021 14:47
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save renoirb/49e4dc04259a79cc023aa97f6dd3790a to your computer and use it in GitHub Desktop.
Save renoirb/49e4dc04259a79cc023aa97f6dd3790a to your computer and use it in GitHub Desktop.
JavaScript utility functions bag

JavaScript utility functions in a bag

Things I've written or seen that are good, but eventually didn't use.

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()
}
@renoirb
Copy link
Author

renoirb commented May 4, 2021

Calculate perf stats for Node.js

const v8 = require('v8')
const stats = v8.getHeapStatistics();

/**
 * https://stackoverflow.com/a/62650303
 * --max-old-space-size=<heap limit (Mb)>
 */

const data = {
  ...stats,
  'heap limit (Mb)': stats.heap_size_limit / (1024 * 1024),
}

console.table(data)

@renoirb
Copy link
Author

renoirb commented Jul 14, 2021

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 */

@renoirb
Copy link
Author

renoirb commented Jul 16, 2021

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)
}

@renoirb
Copy link
Author

renoirb commented Oct 6, 2021

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