TypeScript: special case for auto-incrementing primary keys?
See original GitHub issueFor object stores with auto incrementing primary keys, we know the primary key will be available on all objects read from the database, but not necessarily on all objects written to the database. Currently idb does not supply TypeScript with that information, though. Seems to me that it could be improved to do so, if DBSchemaValue also included the keyPath
and autoIncrement
values passed to createObjectStore
.
Example:
type Foo = {
id?: number;
bar: string;
}
export interface FooDB extends DBSchema {
foos: {
key: number;
value: Foo;
};
}
const db = await openDB<FooDB>("foo", 1, {
upgrade(db, oldVersion) {
if (oldVersion === 0) {
upgradeDB.createObjectStore("leagues", {
keyPath: "id",
autoIncrement: true,
});
}
},
});
// This works, only because we defined Foo with an optional id property
const foo: Foo = {
bar: "baz",
};
await db.add("foo", foo);
// foo2 is guaranteed to have a numeric id property, but TypeScript doens't know this because it has type Foo
const foo2 = await db.get("foo", 1);
// Ideally, foo2's type would instead be:
type Foo2 = Foo & { id: number }
// ...but that would require encoding in FooDB that id is an autoincrementing primary key.
I guess a caveat would be that if keyPath
is nested like a.b.c
I’m not sure if TypeScript will be able to handle that. But based on pure conjecture and absolutely no data, I’m going to say that it’s very rare to use a key path that is both nested and auto incrementing, but it’s pretty common to use a non-nested key path that is autoincrementing.
Issue Analytics
- State:
- Created 4 years ago
- Reactions:1
- Comments:12 (3 by maintainers)
@dumbmatter Thanks for the suggestion. I’m solving for now by isolating obj construction in a function with an output type that omits the id and passing the result with casts
Its a little messy but mostly keeps type checking in place.
After further consideration, I think I have a better idea for how to solve this issue.
Recapping the old ideas:
Idea 1: My original idea was problematic because it will not work for nested keys.
Idea 2: Your idea from this comment in my PR IMHO is not ideal because it requires users to think about idb when writing types for their objects. In general, I think people don’t write these types specifically for idb. They write them because these objects are used throughout their application. It seems too invasive to suggest that we need to use these special types from idb.
But there are other options too!
Idea 3: Tweak idea 1 to make it work for nested keys. So instead of:
do this:
Then similar to my original idea, use
value
for any object written to IndexedDB, and use the intersection ofvalue
andautoIncrementKeyPath
for any value returned from IndexedDB. That will work for an arbitrarily nested key path.Idea 4: Same as idea 3, but have users specify the whole object.
This seems like a clunkier version of idea 3, since it forces you to be redundant, right?
My concern is the same as I wrote above about idea 1 - I assume people don’t write these types specifically for idb, they write them because they use them in general in their application. So it would actually look like:
Functions which don’t care about
id
would useValue
, and functions that do care aboutid
would useValueWithKey
. And I think most non-trivial applications would have both types of functions. If users are going to create these types anyway, we might as well use them directly in idb.From my selfish perspective as a user of idb, ideas 1, 3, and 4 would be perfectly acceptable for my purposes. Idea 2 might be a little annoying, but workable.
What are your thoughts? And if you’d like a PR for idea 3 or 4, let me know, I’d be happy to do it.