svelte
svelte copied to clipboard
Svelte 5: $state.snapshot() messes up the type of state with promises inside
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
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>;
}
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 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.