Mirror of https://github.com/roostorg/coop github.com/roostorg/coop
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

at 557ff54b2b435e5f1e789c6a8a4e1bebf2d7deb6 127 lines 4.1 kB view raw
1import stringify from 'safe-stable-stringify'; 2import { type Opaque } from 'type-fest'; 3 4import { JSON } from './json-schema-types.js'; 5 6/** 7 * This function accepts any JS string and encodes it in base64, using a UTF8 8 * representation of the string to derive the underlying binary data. 9 */ 10export function b64Encode<T extends string>(it: T) { 11 return Buffer.from(it, 'utf-8').toString('base64') as B64Of<T>; 12} 13 14/** 15 * This function accepts any b64 encoded string, as generated by 16 * {@link b64Encode}, and returns the original JS string that was encoded. 17 */ 18export function b64Decode<T extends B64Of<string>>(it: T) { 19 return Buffer.from(it, 'base64').toString('utf-8') as T[typeof meta]; 20} 21 22export function b64UrlEncode<T extends string>(it: T) { 23 return b64Encode(it).replace('+', '-').replace('/', '_') as B64UrlOf<T>; 24} 25 26export function b64UrlDecode<T extends B64UrlOf<string>>(it: T) { 27 const b64String = it.replace('-', '+').replace('_', '/') as B64Of< 28 (typeof it)[typeof meta] 29 >; 30 31 return b64Decode(b64String); 32} 33 34export function b64EncodeArrayBuffer<T extends ArrayBuffer>(it: T) { 35 return Buffer.from(it).toString('base64') as B64Of<T>; 36} 37 38/** 39 * Converts a value to JSON, while preserving its type for future inspection. 40 * 41 * Sometimes, we need to stringify a value (e.g., to use the string as a key), 42 * but we'd still like to Typescript to track the original type that we 43 * stringified, so that we can have type checking on the data we'll get back 44 * if/when we JSON.parse the string later. That's what this `jsonStringify` 45 * helper function does. See {@link jsonParse}. 46 * 47 * NB: technically, this should return a JsonOf<Jsonify<T>>, but we don't do 48 * that for now because using Jsonify almost always runs up against TS stack 49 * limits. 50 * 51 * @param it The value to stringify. 52 */ 53export function jsonStringify<T>(it: T) { 54 // eslint-disable-next-line no-restricted-syntax 55 return stringify(it) as JsonOf<T>; 56} 57 58/** 59 * Identical to {@link jsonStringify}, except that it does not normalize the 60 * order of object keys in the final, returned string. Therefore, e.g., 61 * `{ a: 0, b: 0 }` and `{ b: 0, a: 0 }` will produce different strings. This is 62 * usually not what you want -- it prevents the resulting string from being used 63 * reliably as a cache key, e.g. -- but may give slightly better performance. 64 */ 65export function jsonStringifyUnstable<T>(it: T) { 66 // eslint-disable-next-line no-restricted-syntax 67 return JSON.stringify(it) as JsonOf<T>; 68} 69 70/** 71 * Parses the JSON, and returns its original type, for JSON generated by 72 * {@link jsonStringify}. 73 */ 74export function jsonParse<T extends JsonOf<unknown>>(it: T) { 75 // eslint-disable-next-line no-restricted-syntax 76 return JSON.parse(it) as (typeof it)[typeof meta]; 77} 78 79/** 80 * Returns the parsed value if JSON parsing succeeds; else undefined. 81 */ 82export function tryJsonParse(it: string): JSON | undefined { 83 try { 84 // eslint-disable-next-line no-restricted-syntax 85 return JSON.parse(it); 86 } catch (e) { 87 return undefined; 88 } 89} 90 91/** 92 * Constructs an array whose elements are tuples of the characters 93 * and the number of times they appear in order of the string. 94 * IE: teeest = [['t', 1], ['e', 3], ['s', 1], ['t', 1]] 95 */ 96export function runEncode(text: string): [string, number][] { 97 const ret: [string, number][] = []; 98 let lastChar = null; 99 let charCount = 0; 100 for (const c of text) { 101 if (c === lastChar) { 102 charCount++; 103 } else { 104 if (lastChar !== null) { 105 ret.push([lastChar, charCount]); 106 } 107 lastChar = c; 108 charCount = 1; 109 } 110 } 111 if (lastChar) { 112 ret.push([lastChar, charCount]); 113 } 114 return ret; 115} 116 117/** 118 * Escape any characters that have a special meaning in regex syntax. 119 */ 120export function regexEscape(text: string) { 121 return text.replace(/[-\/\\^$*+?.()|[\]{}]/g, '\\$&'); 122} 123 124declare const meta: unique symbol; 125export type JsonOf<T> = Opaque<string, 'JSON'> & { readonly [meta]: T }; 126export type B64Of<T> = Opaque<string, 'B64'> & { readonly [meta]: T }; 127export type B64UrlOf<T> = Opaque<string, 'B64Url'> & { readonly [meta]: T };