From 737550b787ab0723e2dd331c8aec7a9267dcad55 Mon Sep 17 00:00:00 2001 From: thecodewarrior Date: Wed, 26 Jun 2024 05:39:58 -0700 Subject: [PATCH] Add `StructuredCloneable` type (#897) --- index.d.ts | 1 + readme.md | 4 + source/structured-cloneable.d.ts | 89 +++++++++++++++++++ test-d/structured-cloneable.ts | 144 +++++++++++++++++++++++++++++++ 4 files changed, 238 insertions(+) create mode 100644 source/structured-cloneable.d.ts create mode 100644 test-d/structured-cloneable.ts diff --git a/index.d.ts b/index.d.ts index 0d06f8339..7d3ec213d 100644 --- a/index.d.ts +++ b/index.d.ts @@ -64,6 +64,7 @@ export type {Simplify} from './source/simplify'; export type {SimplifyDeep} from './source/simplify-deep'; export type {Jsonify} from './source/jsonify'; export type {Jsonifiable} from './source/jsonifiable'; +export type {StructuredCloneable} from './source/structured-cloneable'; export type {Schema} from './source/schema'; export type {LiteralToPrimitive} from './source/literal-to-primitive'; export type {LiteralToPrimitiveDeep} from './source/literal-to-primitive-deep'; diff --git a/readme.md b/readme.md index e094442a6..e2b4be1bc 100644 --- a/readme.md +++ b/readme.md @@ -249,6 +249,10 @@ type ShouldBeNever = IfAny<'not any', 'not never', 'never'>; - [`JsonArray`](source/basic.d.ts) - Matches a JSON array. - [`JsonValue`](source/basic.d.ts) - Matches any valid JSON value. +### Structured clone + +- [`StructuredCloneable`](source/structured-cloneable.d.ts) - Matches a value that can be losslessly cloned using `structuredClone`. + ### Async - [`Promisable`](source/promisable.d.ts) - Create a type that represents either the value or the value wrapped in `PromiseLike`. diff --git a/source/structured-cloneable.d.ts b/source/structured-cloneable.d.ts new file mode 100644 index 000000000..e633c7264 --- /dev/null +++ b/source/structured-cloneable.d.ts @@ -0,0 +1,89 @@ +import type {TypedArray} from './typed-array'; + +type StructuredCloneablePrimitive = + | string + | number + | bigint + | boolean + | null + | undefined + | Boolean + | Number + | String; + +type StructuredCloneableData = + | ArrayBuffer + | DataView + | Date + | Error + | RegExp + | TypedArray + | Blob + | File; +// DOM exclusive types +// | AudioData +// | CropTarget +// | CryptoKey +// | DOMException +// | DOMMatrix +// | DOMMatrixReadOnly +// | DOMPoint +// | DOMPointReadOnly +// | DOMQuad +// | DOMRect +// | DOMRectReadOnly +// | FileList +// | FileSystemDirectoryHandle +// | FileSystemFileHandle +// | FileSystemHandle +// | GPUCompilationInfo +// | GPUCompilationMessage +// | ImageBitmap +// | ImageData +// | RTCCertificate +// | VideoFrame + +type StructuredCloneableCollection = + | readonly StructuredCloneable[] + | {readonly [key: string]: StructuredCloneable; readonly [key: number]: StructuredCloneable} + | ReadonlyMap + | ReadonlySet; + +/** +Matches a value that can be losslessly cloned using `structuredClone`. + +Note: +- Custom error types will be cloned as the base `Error` type +- This type doesn't include types exclusive to the TypeScript DOM library (e.g. `DOMRect` and `VideoFrame`) + +@see https://developer.mozilla.org/en-US/docs/Web/API/Web_Workers_API/Structured_clone_algorithm + +@example +``` +import type {StructuredCloneable} from 'type-fest'; + +class CustomClass {} + +// @ts-expect-error +const error: StructuredCloneable = { + custom: new CustomClass(), +}; + +structuredClone(error); +//=> {custom: {}} + +const good: StructuredCloneable = { + number: 3, + date: new Date(), + map: new Map(), +} + +good.map.set('key', 1); + +structuredClone(good); +//=> {number: 3, date: Date(2022-10-17 22:22:35.920), map: Map {'key' -> 1}} +``` + +@category Structured clone +*/ +export type StructuredCloneable = StructuredCloneablePrimitive | StructuredCloneableData | StructuredCloneableCollection; diff --git a/test-d/structured-cloneable.ts b/test-d/structured-cloneable.ts new file mode 100644 index 000000000..5295b4548 --- /dev/null +++ b/test-d/structured-cloneable.ts @@ -0,0 +1,144 @@ +import {expectAssignable, expectNotAssignable} from 'tsd'; +import type {StructuredCloneable} from '..'; + +/* +Source: https://developer.mozilla.org/en-US/docs/Web/API/Web_Workers_API/Structured_clone_algorithm + +# Supported types +## JavaScript types +- Array +- ArrayBuffer +- Boolean +- DataView +- Date +- Error types (but see Error types below). +- Map +- Number +- Object: but only plain objects (e.g. from object literals). +- Primitive types, except symbol. +- RegExp: but note that lastIndex is not preserved. +- Set +- String +- TypedArray + +## Error types +For Error types, the error name must be one of: Error, EvalError, RangeError, ReferenceError, SyntaxError, TypeError, URIError (or will be set to "Error"). + +## Web/API types + - Blob + - File + +## DOM exclusive types (not included) + - AudioData + - CropTarget + - CryptoKey + - DOMException: browsers must serialize the properties name and message. Other attributes may also be serialized/cloned. + - DOMMatrix + - DOMMatrixReadOnly + - DOMPoint + - DOMPointReadOnly + - DOMQuad + - DOMRect + - DOMRectReadOnly + - FileList + - FileSystemDirectoryHandle + - FileSystemFileHandle + - FileSystemHandle + - GPUCompilationInfo + - GPUCompilationMessage + - ImageBitmap + - ImageData + - RTCCertificate + - VideoFrame +*/ + +// Date, Boolean, Number, String +expectAssignable(new Date()); +declare const booleanWrapperObject: Boolean; +expectAssignable(booleanWrapperObject); +declare const numberWrapperObject: Number; +expectAssignable(numberWrapperObject); +declare const stringWrapperObject: String; +expectAssignable(stringWrapperObject); + +// Primitive types, except symbol. +expectAssignable(undefined); +expectAssignable(null); +expectAssignable(true); +expectAssignable(1); +expectAssignable(1n); +expectAssignable(''); +declare const symbolValue: symbol; +expectNotAssignable(symbolValue); + +// RegExp: but note that lastIndex is not preserved. +expectAssignable(/foo/); + +// ArrayBuffer, DataView +expectAssignable(new ArrayBuffer(10)); +expectAssignable(new DataView(new ArrayBuffer(10))); + +// TypedArray +expectAssignable(new Int8Array(10)); +expectAssignable(new Uint8Array(10)); +expectAssignable(new Uint8ClampedArray(10)); +expectAssignable(new Int16Array(10)); +expectAssignable(new Uint16Array(10)); +expectAssignable(new Int32Array(10)); +expectAssignable(new Uint32Array(10)); +expectAssignable(new Float32Array(10)); +expectAssignable(new Float64Array(10)); +expectAssignable(new BigInt64Array(10)); +expectAssignable(new BigUint64Array(10)); + +// Error types +declare const error: Error; +expectAssignable(error); +declare const evalError: EvalError; +expectAssignable(evalError); +declare const rangeError: RangeError; +expectAssignable(rangeError); +declare const referenceError: ReferenceError; +expectAssignable(referenceError); +declare const syntaxError: SyntaxError; +expectAssignable(syntaxError); +declare const typeError: TypeError; +expectAssignable(typeError); +declare const uriError: URIError; +expectAssignable(uriError); + +// Object: but only plain objects (e.g. from object literals). +expectAssignable({}); +expectAssignable({x: 10}); +expectAssignable({x: {y: 10}}); +expectAssignable({x: 10} as const); +class CustomType {} +expectNotAssignable(new CustomType()); +class CustomTypeWithProperties { + foo = 'wow'; + bar = 1; +} +expectNotAssignable(new CustomTypeWithProperties()); + +// Array +expectAssignable([]); +expectAssignable([1, 2, 3]); +expectAssignable([1, 2, 3] as const); +expectAssignable([[1, 2], [3, 4]]); +expectAssignable([{x: 1}, {x: 2}]); + +// Map +expectAssignable(new Map()); +expectAssignable(new Map()); +expectAssignable(new Map>()); + +// Set +expectAssignable(new Set()); +expectAssignable(new Set()); +expectAssignable(new Set>()); + +// Web/API types +declare const blob: Blob; +expectAssignable(blob); +declare const file: File; +expectAssignable(file);