From 4301db72c3b23e609a15eab5f8b25c8ba8feaf84 Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Thu, 11 Jul 2024 16:38:58 -0400 Subject: [PATCH] get types mostly working --- packages/svelte/src/internal/shared/clone.js | 25 ++++--- .../svelte/src/internal/shared/clone.test.ts | 1 + .../svelte/src/internal/shared/types.d.ts | 69 +++++++++++++++++++ 3 files changed, 82 insertions(+), 13 deletions(-) diff --git a/packages/svelte/src/internal/shared/clone.js b/packages/svelte/src/internal/shared/clone.js index 4c0a96fe75..715ec3d855 100644 --- a/packages/svelte/src/internal/shared/clone.js +++ b/packages/svelte/src/internal/shared/clone.js @@ -1,21 +1,20 @@ +/** @import { Snapshot } from './types' */ import { get_prototype_of, is_array, object_prototype } from './utils.js'; /** * @template T * @param {T} value - * @returns {T} + * @returns {Snapshot} */ export function snapshot(value) { - return /** @type {T} */ ( - clone(/** @type {import('#client').ProxyStateObject} */ (value), new Map()) - ); + return clone(value, new Map()); } /** - * @template {import('#client').ProxyStateObject} T + * @template T * @param {T} value - * @param {Map>} cloned - * @returns {Record} + * @param {Map>} cloned + * @returns {Snapshot} */ function clone(value, cloned) { if (typeof value === 'object' && value !== null) { @@ -23,8 +22,7 @@ function clone(value, cloned) { if (unwrapped !== undefined) return unwrapped; if (is_array(value)) { - /** @type {Record} */ - const copy = []; + const copy = /** @type {Snapshot} */ ([]); cloned.set(value, copy); for (const element of value) { @@ -35,21 +33,22 @@ function clone(value, cloned) { } if (get_prototype_of(value) === object_prototype) { - /** @type {Record} */ + /** @type {Snapshot} */ const copy = {}; cloned.set(value, copy); for (var key in value) { + // @ts-expect-error copy[key] = clone(value[key], cloned); } return copy; } - if (typeof value.toJSON === 'function') { - return clone(value.toJSON(), cloned); + if (typeof (/** @type {T & { toJSON?: any } } */ (value).toJSON) === 'function') { + return clone(/** @type {T & { toJSON(): any } } */ (value).toJSON(), cloned); } } - return structuredClone(value); + return /** @type {Snapshot} */ (structuredClone(value)); } diff --git a/packages/svelte/src/internal/shared/clone.test.ts b/packages/svelte/src/internal/shared/clone.test.ts index a89637f567..55fe87e350 100644 --- a/packages/svelte/src/internal/shared/clone.test.ts +++ b/packages/svelte/src/internal/shared/clone.test.ts @@ -60,6 +60,7 @@ test('class with state field', () => { const copy = snapshot(new Foo()); + // @ts-expect-error I can't figure out a way to exclude prototype properties assert.deepEqual(copy, { x: 1 }); }); diff --git a/packages/svelte/src/internal/shared/types.d.ts b/packages/svelte/src/internal/shared/types.d.ts index 426d928ce3..b9d852df78 100644 --- a/packages/svelte/src/internal/shared/types.d.ts +++ b/packages/svelte/src/internal/shared/types.d.ts @@ -6,3 +6,72 @@ export type Store = { export type SourceLocation = | [line: number, column: number] | [line: number, column: number, SourceLocation[]]; + +type Primitive = string | number | boolean | null | undefined; + +type TypedArray = + | Int8Array + | Uint8Array + | Uint8ClampedArray + | Int16Array + | Uint16Array + | Int32Array + | Uint32Array + | Float32Array + | Float64Array + | BigInt64Array + | BigUint64Array; + +/** The things that `structuredClone` can handle — https://developer.mozilla.org/en-US/docs/Web/API/Web_Workers_API/Structured_clone_algorithm */ +type Cloneable = + | ArrayBuffer + | DataView + | Date + | Error + | Map + | RegExp + | Set + | TypedArray + // web APIs + | Blob + | CryptoKey + | DOMException + | DOMMatrix + | DOMMatrixReadOnly + | DOMPoint + | DOMPointReadOnly + | DOMQuad + | DOMRect + | DOMRectReadOnly + | File + | FileList + | FileSystemDirectoryHandle + | FileSystemFileHandle + | FileSystemHandle + | ImageBitmap + | ImageData + | RTCCertificate + | VideoFrame; + +/** Turn `SvelteDate`, `SvelteMap` and `SvelteSet` into their non-reactive counterparts. (`URL` is uncloneable.) */ +type NonReactive = T extends Date + ? Date + : T extends Map + ? Map + : T extends Set + ? Set + : T; + +export type Snapshot = T extends Primitive + ? T + : T extends Cloneable + ? NonReactive + : T extends { toJSON(): infer R } + ? R + : T extends Array + ? Array> + : T extends object + ? T extends { [key: string]: any } + ? { [K in keyof T]: Snapshot } + : never + : never;