UNPKG

seroval

Version:
439 lines (416 loc) 12.9 kB
import { createAggregateErrorNode, createArrayBufferNode, createArrayNode, createAsyncIteratorFactoryInstanceNode, createBigIntNode, createBigIntTypedArrayNode, createBoxedNode, createDataViewNode, createDateNode, createErrorNode, createIteratorFactoryInstanceNode, createNumberNode, createPluginNode, createRegExpNode, createSetNode, createStreamConstructorNode, createStringNode, createTypedArrayNode, } from '../../base-primitives'; import { Feature } from '../../compat'; import { NIL, SerovalNodeType } from '../../constants'; import { SerovalParserError, SerovalUnsupportedTypeError } from '../../errors'; import { FALSE_NODE, NULL_NODE, TRUE_NODE, UNDEFINED_NODE, } from '../../literals'; import { createSerovalNode } from '../../node'; import { OpaqueReference } from '../../opaque-reference'; import { SpecialReference } from '../../special-reference'; import type { Stream } from '../../stream'; import { createStream, isStream } from '../../stream'; import { serializeString } from '../../string'; import type { SerovalAbortSignalConstructorNode, SerovalAbortSignalSyncNode, SerovalAggregateErrorNode, SerovalArrayNode, SerovalBigIntTypedArrayNode, SerovalBoxedNode, SerovalDataViewNode, SerovalErrorNode, SerovalMapNode, SerovalNode, SerovalNullConstructorNode, SerovalObjectNode, SerovalObjectRecordKey, SerovalObjectRecordNode, SerovalPluginNode, SerovalPromiseConstructorNode, SerovalSetNode, SerovalTypedArrayNode, } from '../../types'; import { getErrorOptions } from '../../utils/error'; import { iteratorToSequence } from '../../utils/iterator-to-sequence'; import type { BigIntTypedArrayValue, TypedArrayValue, } from '../../utils/typed-array'; import type { BaseParserContextOptions } from '../parser'; import { BaseParserContext, ParserNodeType } from '../parser'; type ObjectLikeNode = SerovalObjectNode | SerovalNullConstructorNode; export type BaseSyncParserContextOptions = BaseParserContextOptions; export default abstract class BaseSyncParserContext extends BaseParserContext { protected parseItems(current: unknown[]): SerovalNode[] { const nodes = []; for (let i = 0, len = current.length; i < len; i++) { if (i in current) { nodes[i] = this.parse(current[i]); } } return nodes; } protected parseArray(id: number, current: unknown[]): SerovalArrayNode { return createArrayNode(id, current, this.parseItems(current)); } protected parseProperties( properties: Record<string | symbol, unknown>, ): SerovalObjectRecordNode { const entries = Object.entries(properties); const keyNodes: SerovalObjectRecordKey[] = []; const valueNodes: SerovalNode[] = []; for (let i = 0, len = entries.length; i < len; i++) { keyNodes.push(serializeString(entries[i][0])); valueNodes.push(this.parse(entries[i][1])); } // Check special properties, symbols in this case let symbol = Symbol.iterator; if (symbol in properties) { keyNodes.push(this.parseWellKnownSymbol(symbol)); valueNodes.push( createIteratorFactoryInstanceNode( this.parseIteratorFactory(), this.parse( iteratorToSequence(properties as unknown as Iterable<unknown>), ), ), ); } symbol = Symbol.asyncIterator; if (symbol in properties) { keyNodes.push(this.parseWellKnownSymbol(symbol)); valueNodes.push( createAsyncIteratorFactoryInstanceNode( this.parseAsyncIteratorFactory(), this.parse(createStream()), ), ); } symbol = Symbol.toStringTag; if (symbol in properties) { keyNodes.push(this.parseWellKnownSymbol(symbol)); valueNodes.push(createStringNode(properties[symbol] as string)); } symbol = Symbol.isConcatSpreadable; if (symbol in properties) { keyNodes.push(this.parseWellKnownSymbol(symbol)); valueNodes.push(properties[symbol] ? TRUE_NODE : FALSE_NODE); } return { k: keyNodes, v: valueNodes, s: keyNodes.length, }; } protected parsePlainObject( id: number, current: Record<string, unknown>, empty: boolean, ): ObjectLikeNode { return this.createObjectNode( id, current, empty, this.parseProperties(current), ); } protected parseBoxed(id: number, current: object): SerovalBoxedNode { return createBoxedNode(id, this.parse(current.valueOf())); } protected parseTypedArray( id: number, current: TypedArrayValue, ): SerovalTypedArrayNode { return createTypedArrayNode(id, current, this.parse(current.buffer)); } protected parseBigIntTypedArray( id: number, current: BigIntTypedArrayValue, ): SerovalBigIntTypedArrayNode { return createBigIntTypedArrayNode(id, current, this.parse(current.buffer)); } protected parseDataView(id: number, current: DataView): SerovalDataViewNode { return createDataViewNode(id, current, this.parse(current.buffer)); } protected parseError(id: number, current: Error): SerovalErrorNode { const options = getErrorOptions(current, this.features); return createErrorNode( id, current, options ? this.parseProperties(options) : NIL, ); } protected parseAggregateError( id: number, current: AggregateError, ): SerovalAggregateErrorNode { const options = getErrorOptions(current, this.features); return createAggregateErrorNode( id, current, options ? this.parseProperties(options) : NIL, ); } protected parseMap( id: number, current: Map<unknown, unknown>, ): SerovalMapNode { const keyNodes: SerovalNode[] = []; const valueNodes: SerovalNode[] = []; for (const [key, value] of current.entries()) { keyNodes.push(this.parse(key)); valueNodes.push(this.parse(value)); } return this.createMapNode(id, keyNodes, valueNodes, current.size); } protected parseSet(id: number, current: Set<unknown>): SerovalSetNode { const items: SerovalNode[] = []; for (const item of current.keys()) { items.push(this.parse(item)); } return createSetNode(id, current.size, items); } protected parsePlugin( id: number, current: unknown, ): SerovalPluginNode | undefined { const currentPlugins = this.plugins; if (currentPlugins) { for (let i = 0, len = currentPlugins.length; i < len; i++) { const plugin = currentPlugins[i]; if (plugin.parse.sync && plugin.test(current)) { return createPluginNode( id, plugin.tag, plugin.parse.sync(current, this, { id, }), ); } } } return undefined; } protected parseStream(id: number, _current: Stream<unknown>): SerovalNode { return createStreamConstructorNode( id, this.parseSpecialReference(SpecialReference.StreamConstructor), [], ); } protected parsePromise( id: number, _current: Promise<unknown>, ): SerovalPromiseConstructorNode { return this.createPromiseConstructorNode(id); } protected parseAbortSignalSync( id: number, current: AbortSignal, ): SerovalAbortSignalSyncNode { return createSerovalNode( SerovalNodeType.AbortSignalSync, id, NIL, NIL, NIL, NIL, NIL, NIL, NIL, this.parse(current.reason), NIL, NIL, ); } protected parseAbortSignal( id: number, current: AbortSignal, ): SerovalAbortSignalConstructorNode | SerovalAbortSignalSyncNode { if (current.aborted) { return this.parseAbortSignalSync(id, current); } return this.createAbortSignalConstructorNode(id); } // biome-ignore lint/complexity/noExcessiveCognitiveComplexity: ehh protected parseObject(id: number, current: object): SerovalNode { if (Array.isArray(current)) { return this.parseArray(id, current); } if (isStream(current)) { return this.parseStream(id, current); } const currentClass = current.constructor; if (currentClass === OpaqueReference) { return this.parse( (current as OpaqueReference<unknown, unknown>).replacement, ); } const parsed = this.parsePlugin(id, current); if (parsed) { return parsed; } switch (currentClass) { case Object: return this.parsePlainObject( id, current as Record<string, unknown>, false, ); case undefined: return this.parsePlainObject( id, current as Record<string, unknown>, true, ); case Date: return createDateNode(id, current as unknown as Date); case RegExp: return createRegExpNode(id, current as unknown as RegExp); case Error: case EvalError: case RangeError: case ReferenceError: case SyntaxError: case TypeError: case URIError: return this.parseError(id, current as unknown as Error); case Number: case Boolean: case String: case BigInt: return this.parseBoxed(id, current); case ArrayBuffer: return createArrayBufferNode(id, current as unknown as ArrayBuffer); case Int8Array: case Int16Array: case Int32Array: case Uint8Array: case Uint16Array: case Uint32Array: case Uint8ClampedArray: case Float32Array: case Float64Array: return this.parseTypedArray(id, current as unknown as TypedArrayValue); case DataView: return this.parseDataView(id, current as unknown as DataView); case Map: return this.parseMap(id, current as unknown as Map<unknown, unknown>); case Set: return this.parseSet(id, current as unknown as Set<unknown>); default: break; } // Promises if (currentClass === Promise || current instanceof Promise) { return this.parsePromise(id, current as unknown as Promise<unknown>); } const currentFeatures = this.features; if ( currentFeatures & Feature.AbortSignal && typeof AbortSignal !== 'undefined' && currentClass === AbortSignal ) { return this.parseAbortSignal(id, current as unknown as AbortSignal); } // BigInt Typed Arrays if (currentFeatures & Feature.BigIntTypedArray) { switch (currentClass) { case BigInt64Array: case BigUint64Array: return this.parseBigIntTypedArray( id, current as unknown as BigIntTypedArrayValue, ); default: break; } } if ( currentFeatures & Feature.AggregateError && typeof AggregateError !== 'undefined' && (currentClass === AggregateError || current instanceof AggregateError) ) { return this.parseAggregateError(id, current as unknown as AggregateError); } // Slow path. We only need to handle Errors and Iterators // since they have very broad implementations. if (current instanceof Error) { return this.parseError(id, current); } // Generator functions don't have a global constructor // despite existing if (Symbol.iterator in current || Symbol.asyncIterator in current) { return this.parsePlainObject(id, current, !!currentClass); } throw new SerovalUnsupportedTypeError(current); } protected parseFunction(current: unknown): SerovalNode { const ref = this.getReference(current); if (ref.type !== ParserNodeType.Fresh) { return ref.value; } const plugin = this.parsePlugin(ref.value, current); if (plugin) { return plugin; } throw new SerovalUnsupportedTypeError(current); } parse<T>(current: T): SerovalNode { try { switch (typeof current) { case 'boolean': return current ? TRUE_NODE : FALSE_NODE; case 'undefined': return UNDEFINED_NODE; case 'string': return createStringNode(current as string); case 'number': return createNumberNode(current as number); case 'bigint': return createBigIntNode(current as bigint); case 'object': { if (current) { const ref = this.getReference(current); return ref.type === ParserNodeType.Fresh ? this.parseObject(ref.value, current as object) : ref.value; } return NULL_NODE; } case 'symbol': return this.parseWellKnownSymbol(current); case 'function': { return this.parseFunction(current); } default: throw new SerovalUnsupportedTypeError(current); } } catch (error) { throw new SerovalParserError(error); } } }