From 4c117f3abfa88a7bcfaa319fc50cd2b76bb8acbf Mon Sep 17 00:00:00 2001 From: Kate Corcoran Date: Fri, 21 Jun 2024 18:03:59 -0700 Subject: [PATCH] Add `StructuredCloneable`/`StructuredCloneableWeb` types --- index.d.ts | 2 + readme.md | 5 + source/structured-cloneable-web.d.ts | 92 ++++++++++++++ source/structured-cloneable.d.ts | 66 ++++++++++ test-d/structured-cloneable-web.ts | 184 +++++++++++++++++++++++++++ test-d/structured-cloneable.ts | 106 +++++++++++++++ 6 files changed, 455 insertions(+) create mode 100644 source/structured-cloneable-web.d.ts create mode 100644 source/structured-cloneable.d.ts create mode 100644 test-d/structured-cloneable-web.ts create mode 100644 test-d/structured-cloneable.ts diff --git a/index.d.ts b/index.d.ts index 0d06f8339..fc18554e2 100644 --- a/index.d.ts +++ b/index.d.ts @@ -64,6 +64,8 @@ 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 {StructuredCloneableWeb} from './source/structured-cloneable-web'; 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 0227dd669..662af5a4e 100644 --- a/readme.md +++ b/readme.md @@ -249,6 +249,11 @@ 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`. +- [`StructuredCloneableWeb`](source/structured-cloneable-web.d.ts) - Matches a value that can be losslessly cloned using `structuredClone` (includes web types like `Blob` and `DOMRect`). + ### 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-web.d.ts b/source/structured-cloneable-web.d.ts new file mode 100644 index 000000000..093a9fffa --- /dev/null +++ b/source/structured-cloneable-web.d.ts @@ -0,0 +1,92 @@ +import type {TypedArray} from './typed-array'; + +type StructuredCloneableWebValue = + | Blob + | CryptoKey + | DOMException + | DOMMatrix + | DOMMatrixReadOnly + | DOMPoint + | DOMPointReadOnly + | DOMQuad + | DOMRect + | DOMRectReadOnly + | File + | FileList + | FileSystemDirectoryHandle + | FileSystemFileHandle + | FileSystemHandle + | ImageBitmap + | ImageData + | RTCCertificate + | VideoFrame; +// Missing types: +// | AudioData +// | CropTarget +// | GPUCompilationInfo +// | GPUCompilationMessage + +type StructuredCloneablePrimitive = + | string + | number + | bigint + | boolean + | null + | undefined + | Boolean + | Number + | String; + +type StructuredCloneableData = + | ArrayBuffer + | DataView + | Date + | Error + | RegExp + | TypedArray; + +type StructuredCloneableCollection = + | readonly StructuredCloneableWeb[] + | {readonly [k: string]: StructuredCloneableWeb; readonly [k: number]: StructuredCloneableWeb} + | ReadonlyMap + | ReadonlySet; + +/** +Matches a value that can be losslessly cloned using `structuredClone` (includes web types like `Blob` and `DOMRect`). + +Can be used to type values that you expect to pass to `structuredClone`. + +Note: +- Custom error types will be cloned as the base `Error` type +- This variant includes web-specific cloneable types (e.g. `Blob` and `DOMRect`) + +@see `StructuredCloneable` for a version that doesn't include web types + +@example +``` +import type {StructuredCloneableWeb} from 'type-fest'; + +class CustomClass {} + +// @ts-expect-error +const error: StructuredCloneableWeb = { + custom: new CustomClass(), +}; + +structuredClone(error); +//=> {custom: {}} + +const good: StructuredCloneableWeb = { + blob: new Blob(["
"], { type: "text/html" }), + date: new Date(), + map: new Map(), +} +good.map.set("key", 1) + +structuredClone(good); +//=> {blob: Blob {size: 11, type: "text/html"}, date: Date(2022-10-17 22:22:35.920), map: Map {"key" -> 1}} +``` + +@category Structured clone + */ +export type StructuredCloneableWeb = StructuredCloneableWebValue | StructuredCloneablePrimitive | StructuredCloneableData | StructuredCloneableCollection; diff --git a/source/structured-cloneable.d.ts b/source/structured-cloneable.d.ts new file mode 100644 index 000000000..460385680 --- /dev/null +++ b/source/structured-cloneable.d.ts @@ -0,0 +1,66 @@ +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; + +type StructuredCloneableCollection = + | readonly StructuredCloneable[] + | {readonly [k: string]: StructuredCloneable; readonly [k: number]: StructuredCloneable} + | ReadonlyMap + | ReadonlySet; + +/** +Matches a value that can be losslessly cloned using `structuredClone`. + +Can be used to type values that you expect to pass to `structuredClone`. + +Note: +- Custom error types will be cloned as the base `Error` type +- This type doesn't include the web-specific cloneable types (e.g. `Blob` and `DOMRect`) + +@see `StructuredCloneableWeb` for a version that includes the web-specific types + +@example +``` +import type {StructuredCloneable} from 'type-fest'; + +class CustomClass {} + +// @ts-expect-error +const error: StructuredCloneable = { + custom: new CustomClass(), +}; + +structuredClone(error); +//=> {custom: {}} + +const good: StructuredCloneable = { + num: 3, + date: new Date(), + map: new Map(), +} +good.map.set("key", 1) + +structuredClone(good); +//=> {num: 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-web.ts b/test-d/structured-cloneable-web.ts new file mode 100644 index 000000000..d286dcc65 --- /dev/null +++ b/test-d/structured-cloneable-web.ts @@ -0,0 +1,184 @@ +import {expectAssignable, expectNotAssignable} from 'tsd'; +import type {StructuredCloneableWeb} 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 objects: 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 + - AudioData + - Blob + - 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 + - File + - 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 objects: 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()); + +// 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 cryptoKey: CryptoKey; +expectAssignable(cryptoKey); +declare const domException: DOMException; +expectAssignable(domException); +declare const domMatrix: DOMMatrix; +expectAssignable(domMatrix); +declare const domMatrixReadOnly: DOMMatrixReadOnly; +expectAssignable(domMatrixReadOnly); +declare const domPoint: DOMPoint; +expectAssignable(domPoint); +declare const domPointReadOnly: DOMPointReadOnly; +expectAssignable(domPointReadOnly); +declare const domQuad: DOMQuad; +expectAssignable(domQuad); +declare const domRect: DOMRect; +expectAssignable(domRect); +declare const domRectReadOnly: DOMRectReadOnly; +expectAssignable(domRectReadOnly); +declare const file: File; +expectAssignable(file); +declare const fileList: FileList; +expectAssignable(fileList); +declare const fileSystemDirectoryHandle: FileSystemDirectoryHandle; +expectAssignable(fileSystemDirectoryHandle); +declare const fileSystemFileHandle: FileSystemFileHandle; +expectAssignable(fileSystemFileHandle); +declare const fileSystemHandle: FileSystemHandle; +expectAssignable(fileSystemHandle); +declare const imageBitmap: ImageBitmap; +expectAssignable(imageBitmap); +declare const imageData: ImageData; +expectAssignable(imageData); +declare const rtcCertificate: RTCCertificate; +expectAssignable(rtcCertificate); +declare const videoFrame: VideoFrame; +expectAssignable(videoFrame); + +// Missing types: + +/* +declare const audioData: AudioData; +expectAssignable(audioData); +declare const cropTarget: CropTarget; +expectAssignable(cropTarget); +declare const gpuCompilationInfo: GPUCompilationInfo; +expectAssignable(gpuCompilationInfo); +declare const gpuCompilationMessage: GPUCompilationMessage; +expectAssignable(gpuCompilationMessage); +*/ diff --git a/test-d/structured-cloneable.ts b/test-d/structured-cloneable.ts new file mode 100644 index 000000000..6b60aa69d --- /dev/null +++ b/test-d/structured-cloneable.ts @@ -0,0 +1,106 @@ +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"). +*/ + +// 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()); + +// 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>());