Deno has sessionStorage globally available, which is what I'm using as my in-memory database.
I'm working on a method that creates a new museum in sessionStorage. However, the problem is that my create method only works when my Museum type does not extend Record.
Here are my types:
// I want my Museum type to extend the Record type for the extra type safety
interface Museum extends Record<string, unknown> {
id: string
name: string
description: string
location: {
lat: number
lng: number
}
createdAt: Date
updatedAt?: Date
}
// I'm testing two different NewMuseum types
type NewMuseum = Omit<Museum, "id" | "createdAt" | "updatedAt">
// type NewMuseum = Partial<Museum>
Here is my class:
export class Repository {
async getList(): Promise<Museum[]> {
return JSON.parse(sessionStorage.getItem('museums') || '[]') as Museum[]
}
async create(museum: NewMuseum): Promise<Museum> {
const museumList = await this.getList()
const newMuseum = {
id: crypto.randomUUID(),
...museum,
createdAt: new Date(),
}
museumList.push(newMuseum)
sessionStorage.setItem('museums', JSON.stringify(museumList))
return newMuseum
}
}
When I go to spread the values of the new museum the linter catches an error on newMuseum
on the museumList.push(newMuseum)
line.
Argument of type '{ createdAt: Date; id: string; }' is not assignable to parameter of type 'Museum'.deno-ts(2345)
newMuseum isn't recognizing the values I'm spreading in it. Instead, newMuseum should look like this:
const newMuseum: {
createdAt: Date;
name: string;
description: string;
location: {
lat: number;
lng: number;
};
id: string;
}
How do I get newMuseum to recognize the spread values? Should I be attempting to fix the issue with the Partial utility type instead of Omit?
CodePudding user response:
Casting the newMuseum object as Museum is what did the trick. Here is the updated solution:
export class Repository {
async getList(): Promise<Museum[]> {
return JSON.parse(sessionStorage.getItem('museums') || '[]') as Museum[]
}
async create(museum: NewMuseum): Promise<Museum> {
const museumList = await this.getList()
const newMuseum = {
...museum,
id: crypto.randomUUID(),
createdAt: new Date(),
} as Museum
museumList.push(newMuseum)
sessionStorage.setItem('museums', JSON.stringify(museumList))
return newMuseum
}
}
Typescript doesn't really know the type of the spread operator so it assumes it's incompatible. as Museum tells the typescript typechecker "no worries, I'm sure this is right."
Shoutout to the Deno Discord for the help!
CodePudding user response:
The values you are expecting to be recognized in your spread operation are not there because the NewMuseum
doesn't contain them. Omit
is causing the set of keys to be changed:
type NewMuseum = Omit<Museum, "id" | "createdAt" | "updatedAt">;
resolves to
type NewMuseum = {
[x: string]: unknown;
[x: number]: unknown;
}
and not to
interface NewMuseum extends Record<string, unknown> {
name: string;
description: string;
location: {
lat: number;
lng: number;
};
}
As such, long before you attempt to spread a NewMuseum
the type information for those keys is already lost.
You might consider composing your types rather computing types via utilities like Omit
.
e.g.
type MuseumData = Record<string, unknown> & {
name: string;
description: string;
location: {
lat: number;
lng: number;
};
};
type MuseumMetadata = {
id: string;
createdAt: Date;
updatedAt?: Date;
};
type Museum = MuseumData & MuseumMetadata;
type NewMuseum = MuseumData;