Skip to content

Instantly share code, notes, and snippets.

@jRimbault
Last active May 9, 2022 19:23
Show Gist options
  • Save jRimbault/c69aecdb0e2e7dd6a554b034ea18a6ed to your computer and use it in GitHub Desktop.
Save jRimbault/c69aecdb0e2e7dd6a554b034ea18a6ed to your computer and use it in GitHub Desktop.
/**
* Every asserts stays true.
*
* This means Storage<{ key: number } & { foo: string }> and Storage<{}> can coexist.
*
* The first is an extension of the second.
* This is trivially true, but "wrong" for this use case.
*
* Is there a better way to achieve the same thing ?
*/
interface Storage<Map extends Record<string, unknown> = {}> {
set<Key extends string, Value>(
key: Exclude<Key, keyof Map>,
value: Value
): asserts this is Storage<Map & { [k in Key]: Value }>;
get<Key extends keyof Map>(key: Key): Map[Key];
// doesn't work
remove<Key extends string>(
key: Key
): asserts this is Storage<{ [K in Exclude<keyof Map, Key>]: Map[K] }>;
// doesn't work
clear(): asserts this is Storage<{}>;
}
declare const store: Storage;
{
store.set("key", 1);
store.set("foo", "");
store.set("bar", { hello: "world" });
const a = store.get("key");
const b = store.get("foo");
store.remove("foo"); // doesn't work
store.clear(); // doesn't work
store.get("foo");
const c = store.get("bar");
}
/**
* This could be solvable with an immutable design.
*
* But JS lacks the move semantics which would enforce correct usage.
* (and/or variable shadowing which would _help_ but not _enforce_)
*/
interface ImmutableStorage<Map extends Record<string, unknown> = {}> {
set<Key extends string, Value>(key: Exclude<Key, keyof Map>, value: Value):
ImmutableStorage<Map & { [k in Key]: Value }>;
get<Key extends keyof Map>(key: Key): Map[Key];
remove<Key extends string>(key: Key):
ImmutableStorage<{ [K in Exclude<keyof Map, Key>]: Map[K] }>;
clear(): ImmutableStorage<{}>;
}
declare const empty: ImmutableStorage;
{
const one = empty.set("key", 1);
const two = one.set("foo", "");
const three = two.set("bar", { hello: "world" });
const a = three.get("key");
const b = three.get("foo");
const four = three.remove("foo");
const five = four.clear();
five.get("foo");
const c = five.get("bar");
}
@chriskrycho
Copy link

One other note: you might want to pick a different name! Storage here is getting interface-merged with the global Storage interface for the web storage API! (playground) That's not the cause of the issue you're seeing here, but it is likely to troll you in other ways.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment