Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Typescript: cannot compose schema from multiple files #289

Open
okomarov opened this issue Dec 25, 2022 · 1 comment
Open

Typescript: cannot compose schema from multiple files #289

okomarov opened this issue Dec 25, 2022 · 1 comment

Comments

@okomarov
Copy link

okomarov commented Dec 25, 2022

This simplified example can be found on Replit.

I want to organise my file structure by store:

index.ts
  user.ts
  portfolio.ts
  ...

In index.ts I have:

import { openDB } from 'idb';

import { PortfolioSchema, createPortfolio } from './portfolio';
import { UserSchema, createUser } from './user';

interface Schema extends PortfolioSchema, UserSchema {}

const main = async () => {
  const client = await openDB<Schema>('myDb', 1, {
    upgrade(db) {
      createPortfolio(db);
      createUser(db);
    },
  });
};

where the first issue with e.g. createPortfolio(db):

Argument of type 'IDBPDatabase' is not assignable to parameter of type 'IDBPDatabase'.
Types of property 'objectStoreNames' are incompatible.
Type 'TypedDOMStringList<"portfolio" | "user">' is not assignable to type 'TypedDOMStringList<"portfolio">'.
Type '"portfolio" | "user"' is not assignable to type '"portfolio"'.
Type '"user"' is not assignable to type '"portfolio"'.

which is of the type A | B is not assignable to A. So, I want to narrow down db, where the definition of the sub-schema in portfolio.ts and the type guard are (the user.ts is very similar):

import { DBSchema, IDBPDatabase } from 'idb';

interface Portfolio {
  userId: string;
  portfolioId: string;
}

export interface PortfolioSchema extends DBSchema {
  portfolio: {
    key: string[];
    value: Portfolio;
  };
}

export const createPortfolio = (db: IDBPDatabase<PortfolioSchema>) => {
  db.createObjectStore('portfolio', { keyPath: ['userId', 'teamId', 'portfolioId'] });
};

export const isPortfolio = <T extends PortfolioSchema>(db: IDBPDatabase<T>): db is IDBPDatabase<PortfolioSchema> =>
  'portfolio' in db.objectStoreNames;

The issue with the type guard is:

A type predicate's type must be assignable to its parameter's type.
Type 'IDBPDatabase' is not assignable to type 'IDBPDatabase'.
Types of property 'objectStoreNames' are incompatible.
Type 'TypedDOMStringList<"portfolio">' is not assignable to type 'TypedDOMStringList<StoreNames>'.
Type '"portfolio"' is not assignable to type 'StoreNames'.

How can I achieve sub-schema composition where interfaces, sub-schemas and methods are all defined in separate files?

@keitwb
Copy link

keitwb commented Dec 7, 2023

I had the same issue. As lame as this is, I ended up just using the as operator to cast the DB when calling the create* methods in the root db upgrade function like so:

const main = async () => {
  const client = await openDB<Schema>('myDb', 1, {
    upgrade(db) {
      createPortfolio(db as unknown as PortfolioSchema);
      createUser(db as unknown as UserSchema);
    },
  });
};

Very much a hacky workaround but I fiddled for a while with typings to make it work and couldn't find any trick to it. Given that the DBSchema uses index types I suspect it isn't possible, but would be interested if any Typescript experts know of any way.

I'm not sure about the type guard issue.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants