Skip to content

Commit 2af7ba2

Browse files
gyzerokFedor Nezhivoi
andauthored
fix: lift unsafe_state_mutation constraints for SvelteSet and SvelteMap created inside the derived (#16221)
Co-authored-by: Fedor Nezhivoi <f.nezhivoi@corp.vk.com>
1 parent c4b32c2 commit 2af7ba2

File tree

22 files changed

+418
-19
lines changed

22 files changed

+418
-19
lines changed

.changeset/fair-bats-visit.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'svelte': patch
3+
---
4+
5+
lift unsafe_state_mutation constraints for SvelteSet, SvelteMap, SvelteDate, SvelteURL and SvelteURLSearchParams created inside the derived

packages/svelte/src/reactivity/date.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
/** @import { Source } from '#client' */
22
import { derived } from '../internal/client/index.js';
3-
import { source, set } from '../internal/client/reactivity/sources.js';
3+
import { set, state } from '../internal/client/reactivity/sources.js';
44
import { tag } from '../internal/client/dev/tracing.js';
55
import { active_reaction, get, set_active_reaction } from '../internal/client/runtime.js';
66
import { DEV } from 'esm-env';
@@ -40,7 +40,7 @@ var inited = false;
4040
* ```
4141
*/
4242
export class SvelteDate extends Date {
43-
#time = source(super.getTime());
43+
#time = state(super.getTime());
4444

4545
/** @type {Map<keyof Date, Source<unknown>>} */
4646
#deriveds = new Map();

packages/svelte/src/reactivity/map.js

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
/** @import { Source } from '#client' */
22
import { DEV } from 'esm-env';
3-
import { set, source } from '../internal/client/reactivity/sources.js';
3+
import { set, source, state } from '../internal/client/reactivity/sources.js';
44
import { label, tag } from '../internal/client/dev/tracing.js';
55
import { get } from '../internal/client/runtime.js';
66
import { increment } from './utils.js';
@@ -54,8 +54,8 @@ import { increment } from './utils.js';
5454
export class SvelteMap extends Map {
5555
/** @type {Map<K, Source<number>>} */
5656
#sources = new Map();
57-
#version = source(0);
58-
#size = source(0);
57+
#version = state(0);
58+
#size = state(0);
5959

6060
/**
6161
* @param {Iterable<readonly [K, V]> | null | undefined} [value]

packages/svelte/src/reactivity/set.js

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
/** @import { Source } from '#client' */
22
import { DEV } from 'esm-env';
3-
import { source, set } from '../internal/client/reactivity/sources.js';
3+
import { source, set, state } from '../internal/client/reactivity/sources.js';
44
import { label, tag } from '../internal/client/dev/tracing.js';
55
import { get } from '../internal/client/runtime.js';
66
import { increment } from './utils.js';
@@ -48,8 +48,8 @@ var inited = false;
4848
export class SvelteSet extends Set {
4949
/** @type {Map<T, Source<boolean>>} */
5050
#sources = new Map();
51-
#version = source(0);
52-
#size = source(0);
51+
#version = state(0);
52+
#size = state(0);
5353

5454
/**
5555
* @param {Iterable<T> | null | undefined} [value]

packages/svelte/src/reactivity/url-search-params.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { DEV } from 'esm-env';
2-
import { source } from '../internal/client/reactivity/sources.js';
2+
import { state } from '../internal/client/reactivity/sources.js';
33
import { tag } from '../internal/client/dev/tracing.js';
44
import { get } from '../internal/client/runtime.js';
55
import { get_current_url } from './url.js';
@@ -34,7 +34,7 @@ export const REPLACE = Symbol();
3434
* ```
3535
*/
3636
export class SvelteURLSearchParams extends URLSearchParams {
37-
#version = DEV ? tag(source(0), 'SvelteURLSearchParams version') : source(0);
37+
#version = DEV ? tag(state(0), 'SvelteURLSearchParams version') : state(0);
3838
#url = get_current_url();
3939

4040
#updating = false;

packages/svelte/src/reactivity/url.js

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { DEV } from 'esm-env';
2-
import { source, set } from '../internal/client/reactivity/sources.js';
2+
import { set, state } from '../internal/client/reactivity/sources.js';
33
import { tag } from '../internal/client/dev/tracing.js';
44
import { get } from '../internal/client/runtime.js';
55
import { REPLACE, SvelteURLSearchParams } from './url-search-params.js';
@@ -40,14 +40,14 @@ export function get_current_url() {
4040
* ```
4141
*/
4242
export class SvelteURL extends URL {
43-
#protocol = source(super.protocol);
44-
#username = source(super.username);
45-
#password = source(super.password);
46-
#hostname = source(super.hostname);
47-
#port = source(super.port);
48-
#pathname = source(super.pathname);
49-
#hash = source(super.hash);
50-
#search = source(super.search);
43+
#protocol = state(super.protocol);
44+
#username = state(super.username);
45+
#password = state(super.password);
46+
#hostname = state(super.hostname);
47+
#port = state(super.port);
48+
#pathname = state(super.pathname);
49+
#hash = state(super.hash);
50+
#search = state(super.search);
5151
#searchParams;
5252

5353
/**
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
import { flushSync } from 'svelte';
2+
import { test } from '../../test';
3+
4+
export default test({
5+
compileOptions: {
6+
dev: true,
7+
runes: true
8+
},
9+
10+
test({ assert, target }) {
11+
const [button1, button2] = target.querySelectorAll('button');
12+
13+
assert.throws(() => {
14+
button1?.click();
15+
flushSync();
16+
}, /state_unsafe_mutation/);
17+
18+
assert.doesNotThrow(() => {
19+
button2?.click();
20+
flushSync();
21+
});
22+
}
23+
});
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
<script>
2+
let visibleExternal = $state(false);
3+
let external = $state([]);
4+
const throws = $derived.by(() => {
5+
external.push(1);
6+
return external;
7+
});
8+
9+
let visibleInternal = $state(false)
10+
const works = $derived.by(() => {
11+
let internal = $state([]);
12+
internal.push(1);
13+
return internal;
14+
});
15+
</script>
16+
17+
<button onclick={() => (visibleExternal = true)}>external</button>
18+
{#if visibleExternal}
19+
{throws}
20+
{/if}
21+
<button onclick={() => (visibleInternal = true)}>internal</button>
22+
{#if visibleInternal}
23+
{works}
24+
{/if}
25+
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
import { flushSync } from 'svelte';
2+
import { test } from '../../test';
3+
4+
export default test({
5+
compileOptions: {
6+
dev: true,
7+
runes: true
8+
},
9+
10+
test({ assert, target }) {
11+
const [button1, button2] = target.querySelectorAll('button');
12+
13+
assert.throws(() => {
14+
button1?.click();
15+
flushSync();
16+
}, /state_unsafe_mutation/);
17+
18+
assert.doesNotThrow(() => {
19+
button2?.click();
20+
flushSync();
21+
});
22+
}
23+
});
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
<script>
2+
import { SvelteDate } from 'svelte/reactivity';
3+
4+
let visibleExternal = $state(false);
5+
let external = new SvelteDate();
6+
const throws = $derived.by(() => {
7+
external.setTime(12345);
8+
return external;
9+
})
10+
11+
let visibleInternal = $state(false);
12+
const works = $derived.by(() => {
13+
let internal = new SvelteDate();
14+
internal.setTime(12345);
15+
return internal;
16+
})
17+
</script>
18+
19+
<button onclick={() => (visibleExternal = true)}>external</button>
20+
{#if visibleExternal}
21+
{throws}
22+
{/if}
23+
<button onclick={() => (visibleInternal = true)}>internal</button>
24+
{#if visibleInternal}
25+
{works}
26+
{/if}
27+
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
import { flushSync } from 'svelte';
2+
import { test } from '../../test';
3+
4+
export default test({
5+
compileOptions: {
6+
dev: true,
7+
runes: true
8+
},
9+
10+
test({ assert, target }) {
11+
const [button1, button2] = target.querySelectorAll('button');
12+
13+
assert.throws(() => {
14+
button1?.click();
15+
flushSync();
16+
}, /state_unsafe_mutation/);
17+
18+
assert.doesNotThrow(() => {
19+
button2?.click();
20+
flushSync();
21+
});
22+
}
23+
});
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
<script>
2+
import { SvelteMap } from 'svelte/reactivity';
3+
4+
let visibleExternal = $state(false);
5+
let external = new SvelteMap();
6+
const throws = $derived.by(() => {
7+
external.set(1, 1);
8+
return external;
9+
});
10+
11+
let visibleInternal = $state(false);
12+
const works = $derived.by(() => {
13+
let internal = new SvelteMap();
14+
internal.set(1, 1);
15+
return internal;
16+
});
17+
</script>
18+
19+
<button onclick={() => (visibleExternal = true)}>external</button>
20+
{#if visibleExternal}
21+
{throws}
22+
{/if}
23+
<button onclick={() => (visibleInternal = true)}>internal</button>
24+
{#if visibleInternal}
25+
{works}
26+
{/if}
27+
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
import { flushSync } from 'svelte';
2+
import { test } from '../../test';
3+
4+
export default test({
5+
compileOptions: {
6+
dev: true,
7+
runes: true
8+
},
9+
10+
test({ assert, target }) {
11+
const [button1, button2] = target.querySelectorAll('button');
12+
13+
assert.throws(() => {
14+
button1?.click();
15+
flushSync();
16+
}, /state_unsafe_mutation/);
17+
18+
assert.doesNotThrow(() => {
19+
button2?.click();
20+
flushSync();
21+
});
22+
}
23+
});
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
<script>
2+
let visibleExternal = $state(false);
3+
let external = $state({ v: 1 });
4+
const throws = $derived.by(() => {
5+
external.v = 2;
6+
return external;
7+
});
8+
9+
let visibleInternal = $state(false)
10+
const works = $derived.by(() => {
11+
let internal = $state({ v: 1 });
12+
internal.v = 2;
13+
return internal;
14+
});
15+
</script>
16+
17+
<button onclick={() => (visibleExternal = true)}>external</button>
18+
{#if visibleExternal}
19+
{throws}
20+
{/if}
21+
<button onclick={() => (visibleInternal = true)}>internal</button>
22+
{#if visibleInternal}
23+
{works}
24+
{/if}
25+
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
import { flushSync } from 'svelte';
2+
import { test } from '../../test';
3+
4+
export default test({
5+
compileOptions: {
6+
dev: true,
7+
runes: true
8+
},
9+
10+
test({ assert, target }) {
11+
const [button1, button2] = target.querySelectorAll('button');
12+
13+
assert.throws(() => {
14+
button1?.click();
15+
flushSync();
16+
}, /state_unsafe_mutation/);
17+
18+
assert.doesNotThrow(() => {
19+
button2?.click();
20+
flushSync();
21+
});
22+
}
23+
});
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
<script>
2+
let visibleExternal = $state(false);
3+
let external = $state(1);
4+
const throws = $derived.by(() => {
5+
external = 2;
6+
return external;
7+
});
8+
9+
let visibleInternal = $state(false);
10+
const works = $derived.by(() => {
11+
let internal = $state(1);
12+
internal = 2;
13+
return internal;
14+
});
15+
</script>
16+
17+
<button onclick={() => (visibleExternal = true)}>external</button>
18+
{#if visibleExternal}
19+
{throws}
20+
{/if}
21+
<button onclick={() => (visibleInternal = true)}>internal</button>
22+
{#if visibleInternal}
23+
{works}
24+
{/if}
25+
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
import { flushSync } from 'svelte';
2+
import { test } from '../../test';
3+
4+
export default test({
5+
compileOptions: {
6+
dev: true,
7+
runes: true
8+
},
9+
10+
test({ assert, target }) {
11+
const [button1, button2] = target.querySelectorAll('button');
12+
13+
assert.throws(() => {
14+
button1?.click();
15+
flushSync();
16+
}, /state_unsafe_mutation/);
17+
18+
assert.doesNotThrow(() => {
19+
button2?.click();
20+
flushSync();
21+
});
22+
}
23+
});

0 commit comments

Comments
 (0)