svelte icon indicating copy to clipboard operation
svelte copied to clipboard

Svelte 5: $state.snapshot() messes up the type of state with promises inside

Open xl0 opened this issue 1 year ago • 1 comments

Describe the bug

https://svelte.dev/playground/hello-world?version=5.0.5#H4sIAAAAAAAAA22QQWvDMAyF_4rm9eCU0HQ7pklgsMOOgw12mHdwMyU1deQQqetGyH8fSRYorAeD_en56Um9qpxHVul7r-SnRZVOQMWKbDO-Htp2w1_oZWR7y3iNl4EESVilKuOyc62At1TnRgkbVRgyZMSRYFfZEuFFrCD0IzTSpvDchcYxZiydo3qUyzAVyYhHgf8SyIHwvFCtwzGCvABGeXUNhpNoPYFw1Ea9hc5_3hgVxXC_3W6jaLcYc_qXJYcVjxfdt8NUJiMrrCosF6feSBmIg8eND7We5Rsm2_IhiOYo2sEQLamrE5XiAoHYI_LUQy_NomXyZA0xrJOLcbNk3t68McoOd9MyntD7AP2tPVsn0IIckKAZoB9PMtFh_DzLKdufRAJBoNK78pj38wQXUa6kH4rHABwalIOjOktmj8KQipXgt6hUuhMOH8MvMdbmKDICAAA=

Related to #13798

Code

<script lang="ts">

	interface State {
		p: Promise<string>
	}
	
	let p: Promise<string> = new Promise((ok) => setTimeout(() => ok("World!"), 2000));
	let s: State = $state({p});

	$effect(() => {	console.log($state.snapshot(s)); })
	
	function takesState(s: State) {
		/* , */
	}
	
</script>


<h1>
	Hello {#await p then m} {m} {/await}
</h1>

<button onclick={() => takesState($state.snapshot(s))}>Do something</button>

Typescript warning:

Argument of type '{ p: { then: {}; catch: {}; finally: {}; readonly [Symbol.toStringTag]: string; }; }' is not assignable to parameter of type 'State'.
  The types of 'p.then' are incompatible between these types.
    Type '{}' is not assignable to type '<TResult1 = string, TResult2 = never>(onfulfilled?: ((value: string) => TResult1 | PromiseLike<TResult1>) | null | undefined, onrejected?: ((reason: any) => TResult2 | PromiseLike<...>) | null | undefined) => Promise<...>'.
      Type '{}' provides no match for the signature '<TResult1 = string, TResult2 = never>(onfulfilled?: ((value: string) => TResult1 | PromiseLike<TResult1>) | null | undefined, onrejected?: ((reason: any) => TResult2 | PromiseLike<...>) | null | undefined): Promise<...>'.ts(2345)

Processed with Pretty TypeScript Errors: https://ts-error-translator.vercel.app/?error=IIJw5grgtgpgdgFwAQHsBmSEE8AOMkDkA3kjgFxIkIAW8FRAvgNxIDGAhgq9fc0mgEs47ADYisvFiBjsAJijjikAbQDKWKACMUIgHQIUqhCCFgAKuzABdCgGdjplnwYEkA20jgpk7W7YFgwpoi+Aak7CDssAgwIKgY2HiERpwwBLoAUEhIZrSYuDAe6IQ4+rRwrhH4QqwoUDicAsH4mjAIAO4w8Ji0tqEFtpnZ2WYFhIyu7p7eSL7+gezNmCj5SQQAPGYASoUQIggAjEgAvEj2JnBgADQ5O7Z7CABMJ54wAG6xAHwAFApoe4IxDBZAB+Cjfb5vUQQGB2ByXACUJ0+t12+yOAB8kAAFEB1dwwAAyAgA1jBNncHgdPkisXA9iIkFiIHBZDBBHBgTcFNIAFYwVgxUHg77SXwKCjsOBYJHHFHbNFPJk4vFQAnEsnrXTamnK+liZUstkc4GylG4-F9LU69JZYYjMbEFykPFvARsjxeJBQTjcfgoOI0fDzYQICDSQgUxVHU7nUw3BX3fbPU6cj4gH5-AECIHCpAQqEiGFwi5gM2opOHZUWtV9DXkxNU3V0hmG1nsoRc1BwPkCoVg-OimS2CWzaXlxvJ6uq9Wk8na3TNzyt5ntk2yBEUGsE62L9JAA

Reproduction

.

Logs

No response

System Info

5.0.5

Severity

Big annoyance

xl0 avatar Oct 23 '24 17:10 xl0

Temporary solution app.d.ts:

declare global {
	namespace $state {
		type MyPrimitive = string | number | boolean | null | undefined | Promise;

		type MySnapshot<T> = T extends MyPrimitive
			? T
			: T extends Cloneable
				? NonReactive<T>
				: T extends { toJSON(): infer R }
					? R
					: T extends Array<infer U>
						? Array<Snapshot<U>>
						: T extends object
							? T extends { [key: string]: unknown }
								? { [K in keyof T]: Snapshot<T[K]> }
								: never
							: never;

		export function snapshot<T>(state: T): MySnapshot<T>;
	}

xl0 avatar Oct 23 '24 20:10 xl0

What exactly is this doing @xl0? It seemed to have "magically" fixed my issue as well. $state.snapshot was messing up the type of state with an user-defined interface type.

pegvin avatar Feb 17 '25 19:02 pegvin

@pegvin It overrides the type definition for the .snapshot() function, treating promises as "primitive" types, like number and strings. Glad it works for you. Yeah, would be great if Svelte fixed it properly.

xl0 avatar Feb 19 '25 17:02 xl0