wikibase-edit
Version:
Edit Wikibase from NodeJS
95 lines (79 loc) • 3.67 kB
text/typescript
import type { PostData, PostQuery } from './request/post.js'
import type { AbsoluteUrl } from './types/common.js'
import type { ObjectEntries } from 'type-fest/source/entries.js'
const stringNumberPattern = /^(-|\+)?\d+(\.\d+)?$/
const signedStringNumberPattern = /^(-|\+)\d+(\.\d+)?$/
export type Query = Record<string, string | number>
export function stringifyQuery (query: Query | PostQuery | PostData) {
// @ts-expect-error
return new URLSearchParams(query).toString()
}
export type NonEmptyString = Exclude<string, ''>
export function isNonEmptyString (str: unknown): str is NonEmptyString {
return typeof str === 'string' && str.length > 0
}
export function buildUrl (base: AbsoluteUrl, query: Query | PostQuery) {
return `${base}?${stringifyQuery(query)}` as AbsoluteUrl
}
// helpers to simplify polymorphisms
export function forceArray <T> (obj: T | T[]): T[] {
if (obj == null) return []
if (!(obj instanceof Array)) return [ obj ]
return obj
}
export const isString = (str: unknown) => typeof str === 'string'
export const isNumber = (num: unknown) => typeof num === 'number'
export function isStringNumber (str: string): str is `${number}` {
return stringNumberPattern.test(str)
}
type Sign = '-' | '+'
type SignNumber = `${Sign}${number}`
export function isSignedStringNumber (str: unknown): str is SignNumber {
return typeof str === 'string' && signedStringNumberPattern.test(str)
}
export const isArray = (array: unknown) => array instanceof Array
type PlainObject = Exclude<object, null | unknown[]>
export function isPlainObject (obj: unknown): obj is PlainObject {
if (obj instanceof Array) return false
if (obj === null) return false
return typeof obj === 'object'
}
export function isntEmpty (value: unknown): value is Exclude<unknown, null | undefined> {
return value != null
}
export function mapValues (obj, fn) {
function aggregator (index, key) {
index[key] = fn(key, obj[key])
return index
}
return Object.keys(obj).reduce(aggregator, {})
}
export const wait = (ms: number) => new Promise(resolve => setTimeout(resolve, ms))
// Work around the TS2345 error when using Array include method
// https://stackoverflow.com/questions/55906553/typescript-unexpected-error-when-using-includes-with-a-typed-array/70532727#70532727
// Implementation inspired by https://8hob.io/posts/elegant-safe-solution-for-typing-array-includes/#elegant-and-safe-solution
export function arrayIncludes <T extends (string | number)> (array: readonly T[], value: string | number): value is T {
const arrayT: readonly (string | number)[] = array
return arrayT.includes(value)
}
// Same as `arrayIncludes` but for sets
export function setHas <T extends (string | number)> (set: Set<T>, value: string | number): value is T {
const setT: Set<string | number> = set
return setT.has(value)
}
export function objectEntries <Obj extends object> (obj: Obj) {
return Object.entries(obj) as ObjectEntries<Obj>
}
export function objectFromEntries <K extends string, V> (entries: [ K, V ][]) {
return Object.fromEntries(entries) as Record<K, V>
}
export function objectValues <Obj extends object> (obj: Obj) {
return Object.values(obj) as Obj[keyof Obj][]
}
// Source: https://www.totaltypescript.com/tips/create-your-own-objectkeys-function-using-generics-and-the-keyof-operator
export function objectKeys <Obj extends object> (obj: Obj): (keyof Obj)[] {
return Object.keys(obj) as (keyof Obj)[]
}
export function hasTruthy (params: object, attribute: string): params is Exclude<{ [attribute]: unknown }, { [attribute]: false | undefined | null | 0 | '' }> {
return attribute in params && params[attribute]
}