UNPKG

iopa

Version:

API-first, Internet of Things (IoT) stack for Typescript, official implementation of the Internet Open Protocols Alliance (IOPA) reference pattern

231 lines (208 loc) 5.71 kB
/* eslint-disable no-restricted-syntax */ import type { IMap, IRef } from '@iopa/types' interface IIopaMap<T> { get<K extends keyof T>(key: K): T[K] has<K extends keyof T>(key: K): boolean set<K extends keyof T>(key: K, value: T[K]) set(value: IMapInit<any>) default<K extends keyof T>(key: K, valueFn: T[K] | (() => T[K])): T[K] delete<K extends keyof T>(key: K): boolean entries(): [any, any][] toJSON(): any } export type IMapInit<T> = Partial<T> | [keyof T, T[keyof T]][] | IopaMap<T> export default class IopaMap<T> implements IMap<T> { private static readonly _BLACK_LIST_STRINGIFY: string[] = [ 'app', 'body', 'bodyUsed', 'capability', 'create', 'createChild', 'delete', 'dispose', 'get', 'headers', 'iopa.Body', 'iopa.Events', 'iopa.Headers', 'iopa.IsFinalized', 'iopa.LogStream', 'iopa.RawRequest', 'iopa.RawRequestClone', 'iopa.RawResponse', 'iopa.RawResponseClone', 'iopa.Url', 'log', 'method', 'ok', 'redirected', 'response', 'server.AbortController', 'server.AbortSignal', 'server.Capabilities', 'server.CurrentMiddleware', 'server.Environment', 'server.Events', 'server.PassThroughOnException', 'server.Trace', 'server.WaitUntil', 'set', 'setCapability', 'signal', 'toJSON', 'url' ] public constructor(data?: IMapInit<T>, prevData?: IIopaMap<T>) { if (prevData) { this._loadEntries(prevData.entries()) } if (data) { if (Array.isArray(data)) { this._loadEntries(data) } else if ('entries' in data) { this._loadEntries((data.entries as Function)()) } else { this._loadEntries( Object.entries(data as unknown as Record<keyof T, T[keyof T]>) as [ keyof T, T[keyof T] ][] ) } } } public get<K extends keyof T>(key: K): T[K] { return this[key as unknown as string] } public set(value: IMapInit<T>): void public set<K extends keyof T>(key: K, value: T[K]): void public set<K extends keyof T>(data: IMapInit<T> | K, value?: T[K]): void { if (value || typeof data !== 'object') { this[data as unknown as string] = value return } if (Array.isArray(data)) { this._loadEntries(data) } else if ('entries' in data) { this._loadEntries((data.entries as Function)()) } else { this._loadEntries( Object.entries(data as unknown as Record<keyof T, T[keyof T]>) as [ keyof T, T[keyof T] ][] ) } } private _loadEntries(entries: [keyof T, T[keyof T]][]): void { for (const entry of entries) { this.set(entry[0], entry[1]) } } public getRef(iRef: IRef<T>): T | undefined { return this[iRef.id] } public has<K extends keyof T>(key: K): boolean { return key in this } public addRef<I extends T>(iRef: IRef<T>, value: I): I { this[iRef.id] = value return value } public delete<K extends keyof T>(key: K): boolean { if (key in this) { delete this[key as unknown as string] return true } return false } public default<K extends keyof T>( key: K, valueFn: T[K] | (() => T[K]) ): T[K] { if (key in this) { /** noop */ } else if (typeof valueFn === 'function') { this.set(key, (valueFn as Function)()) } else { this.set(key, valueFn) } return this.get(key) } public entries(): [any, any][] { return Object.entries(this) as any } public toString(): string { return jsonSerialize(this.toJSON()) } public toJSON(): T { const jsonObj: any = {} for (const key of Object.getOwnPropertyNames(this).filter( (key) => !key.startsWith('_') && !IopaMap._BLACK_LIST_STRINGIFY.includes(key) && // eslint-disable-next-line eqeqeq this[key] != null )) { if ( typeof this[key] === 'object' && this[key].constructor.name.toString() === 'URL' ) { jsonObj[key] = (this[key] as URL).href break } jsonObj[key] = this[key] } const proto1 = Object.getPrototypeOf(this) const proto2 = Object.getPrototypeOf(proto1) ;[proto1, proto2].forEach((proto) => { for (const key of Object.getOwnPropertyNames(proto).filter( (key) => !(key in jsonObj) && !key.startsWith('_') && !IopaMap._BLACK_LIST_STRINGIFY.includes(key) && // eslint-disable-next-line eqeqeq this[key] != null )) { const desc = Object.getOwnPropertyDescriptor(proto, key) const hasGetter = desc && typeof desc.get === 'function' if (hasGetter) { const value = desc.get.call(this) if ( typeof value === 'object' && value.constructor.name.toString() === 'Headers' ) { jsonObj[key] = Object.fromEntries((value as any).entries()) break } jsonObj[key] = value } } }) if (this['iopa.Headers']) { jsonObj['iopa.Headers'] = Object.fromEntries( this['iopa.Headers'].entries() ) } return jsonObj } } function getCircularReplacer(): (key: any, value: any) => any { const seen = new WeakSet() return (key, value) => { if (typeof value === 'object' && value !== null) { if (seen.has(value)) { return undefined } seen.add(value) if ('toJSON' in value) { return value.toJSON() } } return value } } function jsonSerialize(data: any): string { return JSON.stringify(data, getCircularReplacer(), 2) }