Skip to main content
Module

x/oak/structured_clone.ts

A middleware framework for handling HTTP with Deno 🐿️ 🦕
Extremely Popular
Go to Latest
File
// Copyright 2018-2022 the oak authors. All rights reserved. MIT license.
export type StructuredClonable = | { [key: string]: StructuredClonable } | Array<StructuredClonable> | ArrayBuffer | ArrayBufferView | BigInt | bigint | Blob // deno-lint-ignore ban-types | Boolean | boolean | Date | Error | EvalError | Map<StructuredClonable, StructuredClonable> // deno-lint-ignore ban-types | Number | number | RangeError | ReferenceError | RegExp | Set<StructuredClonable> // deno-lint-ignore ban-types | String | string | SyntaxError | TypeError | URIError;
/** Internal functions on the `Deno.core` namespace */interface DenoCore { deserialize(value: unknown): StructuredClonable; serialize(value: StructuredClonable): unknown;}
const objectCloneMemo = new WeakMap();
function cloneArrayBuffer( srcBuffer: ArrayBuffer, srcByteOffset: number, srcLength: number, // deno-lint-ignore no-explicit-any _cloneConstructor: any,) { // this function fudges the return type but SharedArrayBuffer is disabled for a while anyway return srcBuffer.slice( srcByteOffset, srcByteOffset + srcLength, );}
/** A loose approximation for structured cloning, used when the `Deno.core` * APIs are not available. */// deno-lint-ignore no-explicit-anyfunction cloneValue(value: any): any { switch (typeof value) { case "number": case "string": case "boolean": case "undefined": case "bigint": return value; case "object": { if (objectCloneMemo.has(value)) { return objectCloneMemo.get(value); } if (value === null) { return value; } if (value instanceof Date) { return new Date(value.valueOf()); } if (value instanceof RegExp) { return new RegExp(value); } if (value instanceof SharedArrayBuffer) { return value; } if (value instanceof ArrayBuffer) { const cloned = cloneArrayBuffer( value, 0, value.byteLength, ArrayBuffer, ); objectCloneMemo.set(value, cloned); return cloned; } if (ArrayBuffer.isView(value)) { const clonedBuffer = cloneValue(value.buffer); // Use DataViewConstructor type purely for type-checking, can be a // DataView or TypedArray. They use the same constructor signature, // only DataView has a length in bytes and TypedArrays use a length in // terms of elements, so we adjust for that. let length; if (value instanceof DataView) { length = value.byteLength; } else { // deno-lint-ignore no-explicit-any length = (value as any).length; } // deno-lint-ignore no-explicit-any return new (value.constructor as any)( clonedBuffer, value.byteOffset, length, ); } if (value instanceof Map) { const clonedMap = new Map(); objectCloneMemo.set(value, clonedMap); value.forEach((v, k) => { clonedMap.set(cloneValue(k), cloneValue(v)); }); return clonedMap; } if (value instanceof Set) { // assumes that cloneValue still takes only one argument const clonedSet = new Set([...value].map(cloneValue)); objectCloneMemo.set(value, clonedSet); return clonedSet; }
// default for objects // deno-lint-ignore no-explicit-any const clonedObj: Record<any, any> = {}; objectCloneMemo.set(value, clonedObj); const sourceKeys = Object.getOwnPropertyNames(value); for (const key of sourceKeys) { clonedObj[key] = cloneValue(value[key]); } Reflect.setPrototypeOf(clonedObj, Reflect.getPrototypeOf(value)); return clonedObj; } case "symbol": case "function": default: throw new DOMException("Uncloneable value in stream", "DataCloneError"); }}
// deno-lint-ignore no-explicit-anyconst core = (Deno as any)?.core as DenoCore | undefined;const structuredClone: ((value: unknown) => unknown) | undefined = // deno-lint-ignore no-explicit-any (globalThis as any).structuredClone;
/** * Provides structured cloning * @param value * @returns */function sc<T extends StructuredClonable>(value: T): T { return structuredClone ? structuredClone(value) : core ? core.deserialize(core.serialize(value)) : cloneValue(value);}
/** Clones a state object, skipping any values that cannot be cloned. */// deno-lint-ignore no-explicit-anyexport function cloneState<S extends Record<string, any>>(state: S): S { const clone = {} as S; for (const [key, value] of Object.entries(state)) { try { const clonedValue = sc(value); clone[key as keyof S] = clonedValue; } catch { // we just no-op values that cannot be cloned } } return clone;}