fix: only allow each key to be set once in a server `Warp`

elliott/warp-core
Elliott Johnson 10 hours ago
parent 39fe99f992
commit c96b1e68cd
No known key found for this signature in database

@ -45,7 +45,7 @@ const warp = new Warp<string, number>('my-app');
const rand = warp.getOrInsertComputed('random', () => Math.random());
```
On the client, a `Warp` behaves like any other `Map` — values you add to it stay there until you remove them with `delete` or `clear`. On the server, values can't be removed, since they may already be in use or on their way to the client.
On the client, a `Warp` behaves like any other `Map` — values you add to it stay there until you remove them with `delete` or `clear`. On the server, each key can only be set once, and values can't be removed, since they may already be in use or on their way to the client. For the same reason, treat values as immutable once they've been added to a `Warp`.
Keys can be strings, numbers, booleans or bigints. If you're a library author, use your package name as the `Warp`'s id, so that it doesn't conflict with other libraries:

@ -94,6 +94,14 @@ Each `withWarp` call can only contain one `render`, since all the values added t
`withWarp(...)` cannot be called inside another `withWarp(...)` or `render(...)` call
```
### warp_key_exists
```
Key `%key%` has already been set in `Warp` `%id%`%stack%
```
On the server, each key in a `Warp` can only be set once. Something may already have read the first value during the render, so replacing it would mean the client hydrates with different data than the server rendered with. Use `getOrInsert` or `getOrInsertComputed` if the value might already exist.
### warp_method_unsupported
```

@ -70,6 +70,12 @@ Each `withWarp` call can only contain one `render`, since all the values added t
> `withWarp(...)` cannot be called inside another `withWarp(...)` or `render(...)` call
## warp_key_exists
> Key `%key%` has already been set in `Warp` `%id%`%stack%
On the server, each key in a `Warp` can only be set once. Something may already have read the first value during the render, so replacing it would mean the client hydrates with different data than the server rendered with. Use `getOrInsert` or `getOrInsertComputed` if the value might already exist.
## warp_method_unsupported
> `warp.%method%(...)` is not supported on the server

@ -142,6 +142,21 @@ export function warp_context_nested() {
throw error;
}
/**
* Key `%key%` has already been set in `Warp` `%id%`%stack%
* @param {string} key
* @param {string} id
* @param {string} stack
* @returns {never}
*/
export function warp_key_exists(key, id, stack) {
const error = new Error(`warp_key_exists\nKey \`${key}\` has already been set in \`Warp\` \`${id}\`${stack}\nhttps://svelte.dev/e/warp_key_exists`);
error.name = 'Svelte error';
throw error;
}
/**
* `warp.%method%(...)` is not supported on the server
* @param {string} method

@ -58,7 +58,17 @@ export class Warp {
* @returns {this}
*/
set(key, value) {
set(get_store(), this.#id, key, value);
const store = get_store();
if (get_values(store, this.#id).has(key)) {
e.warp_key_exists(
String(key),
this.#id,
DEV ? `. It was first set in:\n${get_stack(store, this.#id, key)}` : ''
);
}
set(store, this.#id, key, value);
return this;
}

@ -276,6 +276,25 @@ describe('withWarp', () => {
expect(values?.get('a')).toBe('render');
expect(values?.get('b')).toBe('withWarp');
});
test('throws when a key is set twice', async () => {
await expect(
render(() => {
warp.set('a', 1);
warp.set('a', 2);
})
).rejects.toThrow('warp_key_exists');
});
test('getOrInsert does not throw when the key exists', async () => {
const { head } = await render(() => {
warp.set('a', 1);
expect(warp.getOrInsert('a', 2)).toBe(1);
expect(warp.getOrInsertComputed('a', () => 3)).toBe(1);
});
expect(revive(head)?.get('test')?.get('a')).toBe(1);
});
});
describe('hydratable', () => {

Loading…
Cancel
Save