question-mark
Stuck on an issue?

Lightrun Answers was designed to reduce the constant googling that comes with debugging 3rd party libraries. It collects links to all the places you might be looking at while hunting down a tough bug.

And, if you’re still stuck at the end, we’re happy to hop on a call to see how we can help out.

TypeScript: special case for auto-incrementing primary keys?

See original GitHub issue

For 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:open
  • Created 4 years ago
  • Reactions:1
  • Comments:12 (3 by maintainers)

github_iconTop GitHub Comments

1reaction
meticoeuscommented, Feb 9, 2021

@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

ts.store.add(buildDbObjWithoutKey(args) as unknown as DbObjWithKey);

...

function buildDbObjWithoutKey(args): Omit<DbObjWithKey, "id"> {
  return {...};
}

Its a little messy but mostly keeps type checking in place.

1reaction
dumbmattercommented, Jan 28, 2020

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:

autoIncrementingStore: {
  key: number;
  value: {
    id?: number;
    name: string;
  };
  autoIncremetKeyPath: 'id';
};

do this:

autoIncrementingStore: {
  key: number;
  value: {
    id?: number;
    name: string;
  };
  autoIncremetKeyPath: { id: number };
};

Then similar to my original idea, use value for any object written to IndexedDB, and use the intersection of value and autoIncrementKeyPath 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.

autoIncrementingStore: {
  key: number;
  value: {
    id?: number;
    name: string;
  };
  valueWithKey: {
    id: number;
    name: string;
  };
};

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:

autoIncrementingStore: {
  key: number;
  value: Value;
  valueWithKey: ValueWithKey;
};

Functions which don’t care about id would use Value, and functions that do care about id would use ValueWithKey. 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.

Read more comments on GitHub >

github_iconTop Results From Across the Web

An argument against defaulting to auto-increment primary keys
An argument against defaulting to auto-increment primary keys ... You store an object (POJO in my case) using a Long or String identifier....
Read more >
node.js - How does autoIncrement work in NodeJs's Sequelize?
I already worked with Sequelize and what I remember is that you don't need to specify an id as a primary key. Sequelize...
Read more >
Composite Primary Key / ORM - Documentation - Deepkit
Deepkit ORM does not support composite primary keys. ... There might be certainly a few edge-cases (and for a few very specific database...
Read more >
TypeScript - Sequelize
Sequelize provides its own TypeScript definitions. ... 'CreationOptional' is a special type that marks the field as optional ... autoIncrement: true,
Read more >
Migrate Primary Keys to UUIDs - Sequelize/Node
... the hellish task of migrating an existing Sequelize + TypeScript application to use UUIDs instead of auto-incrementing primary keys.
Read more >

github_iconTop Related Medium Post

No results found

github_iconTop Related StackOverflow Question

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Dev.to Post

No results found

github_iconTop Related Hashnode Post

No results found