Skip to content

Commit 79fef6a

Browse files
feat: structuredClone typing
Co-Authored-By: Kisaragi <48310258+KisaragiEffective@users.noreply.github.com>
1 parent bc8ab11 commit 79fef6a

File tree

2 files changed

+193
-1
lines changed

2 files changed

+193
-1
lines changed

lib/lib.dom.d.ts

Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,3 +66,89 @@ interface FontFaceSet extends EventTarget {
6666
thisArg?: This
6767
): void;
6868
}
69+
70+
declare namespace BetterTypeScriptLibInternals {
71+
export namespace StructuredClone {
72+
type Basics = [EvalError, RangeError, ReferenceError, TypeError, SyntaxError, URIError, Error, Boolean, String, Date, RegExp]
73+
type DOMSpecifics = [
74+
DOMException,
75+
DOMMatrix,
76+
DOMMatrixReadOnly,
77+
DOMPoint,
78+
DOMPointReadOnly,
79+
DOMQuad,
80+
DOMRect,
81+
DOMRectReadOnly,
82+
]
83+
type FileSystemTypeFamily = [
84+
FileSystemDirectoryHandle,
85+
FileSystemFileHandle,
86+
FileSystemHandle,
87+
]
88+
type WebGPURelatedTypeFamily = [
89+
// GPUCompilationInfo,
90+
// GPUCompilationMessage,
91+
]
92+
type TypedArrayFamily = [
93+
Int8Array, Int16Array, Int32Array, BigInt64Array, Uint8Array, Uint16Array, Uint32Array, BigUint64Array, Uint8ClampedArray,
94+
]
95+
type Weaken = [
96+
...Basics,
97+
// AudioData,
98+
Blob,
99+
// CropTarget,
100+
// CryptoTarget,
101+
...DOMSpecifics,
102+
...FileSystemTypeFamily,
103+
...WebGPURelatedTypeFamily,
104+
File, FileList,
105+
...TypedArrayFamily,
106+
DataView, ImageBitmap, ImageData,
107+
RTCCertificate,
108+
VideoFrame,
109+
];
110+
111+
type MapSubtype<R> = {[k in keyof Weaken]: R extends Weaken[k] ? true : false};
112+
type SelectNumericLiteral<H> = number extends H ? never : H;
113+
type FilterByNumericLiteralKey<R extends Record<string | number, any>> = {[
114+
k in keyof R as `${R[k] extends true ? Exclude<SelectNumericLiteral<k>, symbol> : never}`
115+
]: []};
116+
type HitWeakenEntry<E> = keyof FilterByNumericLiteralKey<MapSubtype<E>>;
117+
118+
type NonCloneablePrimitive = Function | { new(...args: any[]): any } | ((...args: any[]) => any) | symbol;
119+
120+
type Writeable<T> = T extends readonly [] ? [] : T extends readonly [infer X, ...infer XS] ? [X, ...XS] : T extends { [x: PropertyKey]: any } ? {
121+
-readonly [P in keyof T]: Writeable<T[P]>;
122+
} : T;
123+
124+
type StructuredCloneOutput<T> =
125+
T extends NonCloneablePrimitive
126+
? never
127+
: T extends ReadonlyArray<any>
128+
? number extends T["length"]
129+
? Array<StructuredCloneOutput<T[number]>>
130+
: T extends [infer X, ...infer XS]
131+
? [StructuredCloneOutput<X>, ...StructuredCloneOutput<XS>]
132+
: Writeable<T>
133+
: T extends Map<infer K, infer V>
134+
? Map<StructuredCloneOutput<K>, StructuredCloneOutput<V>>
135+
: T extends Set<infer E>
136+
? Set<StructuredCloneOutput<E>>
137+
: T extends Record<any, any>
138+
? HitWeakenEntry<T> extends never
139+
? Writeable<{-readonly [k in Exclude<keyof T, symbol> as `${[StructuredCloneOutput<T[k]>] extends [never] ? never : k}`]: StructuredCloneOutput<T[k]>}>
140+
// hit
141+
: Weaken[HitWeakenEntry<T>]
142+
: T
143+
144+
type AvoidCyclicConstraint<T> = [T] extends [infer R] ? R : never;
145+
146+
// 上限が不正にきつくなっているのを無視する
147+
type NeverOrUnknown<T> = [T] extends [never] ? never : unknown;
148+
}
149+
}
150+
151+
/** [MDN Reference](https://developer.mozilla.org/docs/Web/API/structuredClone) */
152+
declare function structuredClone<const T extends BetterTypeScriptLibInternals.StructuredClone.NeverOrUnknown<BetterTypeScriptLibInternals.StructuredClone.StructuredCloneOutput<BetterTypeScriptLibInternals.StructuredClone.AvoidCyclicConstraint<T>>>>(
153+
value: T, options?: StructuredSerializeOptions,
154+
): BetterTypeScriptLibInternals.StructuredClone.StructuredCloneOutput<T>

tests/src/dom.ts

Lines changed: 107 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,113 @@
1-
import { expectType } from "tsd";
1+
import { expectNotType, expectType } from "tsd";
22

33
const test = async (url: string) => {
44
const response = await fetch(url);
55
const json = await response.json();
66
expectType<JSONValue>(json);
77
};
8+
9+
// structuredClone
10+
{
11+
// primitives
12+
expectType<5>(structuredClone(5));
13+
expectType<"hello">(structuredClone("hello"));
14+
expectType<true>(structuredClone(true));
15+
expectType<undefined>(structuredClone(undefined));
16+
expectType<null>(structuredClone(null));
17+
// plain objects
18+
expectType<{ a: 5 }>(structuredClone({ a: 5 }));
19+
expectType<{
20+
a: 5;
21+
nested: {
22+
b: "hello";
23+
};
24+
}>(structuredClone({ a: 5, nested: { b: "hello" } }));
25+
const obj = { a: 5 };
26+
expectType<{ a: number }>(structuredClone(obj));
27+
// arrays
28+
expectType<[1, 2, 3]>(structuredClone([1, 2, 3]));
29+
expectType<["a", "b", "c"]>(structuredClone(["a", "b", "c"]));
30+
const arr = [1, 2, 3];
31+
expectType<number[]>(structuredClone(arr));
32+
// read-onlyness is removed
33+
{
34+
const a: readonly number[] = [1, 2, 3];
35+
const b = structuredClone(a);
36+
expectType<number[]>(b);
37+
b.push(4);
38+
}
39+
// TypedArrays
40+
expectType<Int16Array>(structuredClone(new Int16Array()));
41+
{
42+
// class instances are converted to base built-in types
43+
class Weirdo extends Int16Array {
44+
public weirdo: undefined = undefined;
45+
}
46+
47+
class Weirdo2 extends Int32Array {
48+
public weirdo2: undefined = undefined;
49+
}
50+
51+
expectType<Int16Array>(structuredClone(new Weirdo()));
52+
53+
// @ts-expect-error property does not exist
54+
structuredClone(new Weirdo()).weirdo;
55+
const f: readonly [Weirdo] = [new Weirdo()] as const;
56+
expectType<[Int16Array]>(structuredClone(f));
57+
// @ts-expect-error property does not exist
58+
structuredClone(f)[0].weirdo;
59+
60+
const g = { a: new Weirdo() };
61+
const g2 = structuredClone(g);
62+
expectType<{ a: Int16Array }>(g2);
63+
// @ts-expect-error property does not exist
64+
g2.a.weirdo;
65+
66+
const h = new Map([[new Weirdo(), new Weirdo2()]]);
67+
const i = structuredClone(h);
68+
expectType<Map<Int16Array, Int32Array>>(i);
69+
expectNotType<Map<Weirdo, Weirdo2>>(i);
70+
71+
const j = new Set([new Weirdo()]);
72+
const k: Set<Int16Array> = structuredClone(j);
73+
expectNotType<Set<Weirdo>>(k);
74+
75+
class Empty {}
76+
expectType<{}>(structuredClone(new Empty()));
77+
class SingleProp {
78+
hello: number = 3;
79+
}
80+
expectType<{ hello: number }>(structuredClone(new SingleProp()));
81+
82+
class WithConstructor {
83+
hi: number;
84+
constructor(hi: number) {
85+
this.hi = hi;
86+
}
87+
}
88+
89+
expectType<{ hi: number }>(structuredClone(new WithConstructor(1)));
90+
91+
class WithFunction {
92+
hello(): "hi" {
93+
return "hi";
94+
}
95+
}
96+
97+
expectType<{}>(structuredClone(new WithFunction()));
98+
// @ts-expect-error
99+
structuredClone(new WithFunction()).hello();
100+
// ^?
101+
const x = structuredClone({ s: () => 1 });
102+
// ^?
103+
}
104+
// non-clonable objects
105+
{
106+
// @ts-expect-error
107+
const m = structuredClone(class {});
108+
// @ts-expect-error
109+
const n = structuredClone(Symbol.iterator);
110+
// @ts-expect-error
111+
const p = structuredClone(() => 1);
112+
}
113+
}

0 commit comments

Comments
 (0)