dexie / Dexie.js

A Minimalistic Wrapper for IndexedDB
https://dexie.org
Apache License 2.0
11.65k stars 641 forks source link

Typescript returns errors regarding ID that is undefined #1983

Closed tobiasBora closed 6 months ago

tobiasBora commented 6 months ago

If I create a new table like:

// A single row containing information about the file
export interface FileInfo {
  id?: number;
  filename: string;
  created: Date;
  modified: Date;
}

export class MyDb extends Dexie {
  // 'friends' is added by dexie when declaring the stores()
  // We just tell the typing system this is the case
  fileInfo!: Table<FileInfo, number>; // number = type of the primkey 

  constructor(name : string) {
    super(name);
    this.version(1).stores({
      fileInfo: '++id, filename, created, modified', // Primary key and indexed props
    });
    this.fileInfo = this.table("fileInfo"); // Note sure if needed but recommended here https://github.com/dexie/Dexie.js/issues/1023
  }
}

then each time I use file.id (e.g. in {#each $files as file (file.id)} The id is <button on:click="{e => db.files.delete(file.id)}"/> {/if}) I get many errors like:

Error: Argument of type 'number | undefined' is not assignable to parameter of type 'number'.
  Type 'undefined' is not assignable to type 'number'. (ts)

on the other hand, if I write id: instead of id?: in the typescript spec, then I get errors when I try to create entry as id is not provided. Is this a bug or am I doing something stupid here?

dfahlander commented 6 months ago

I think the last line in the constructor is confusing typescript. It's not needed so you should skip it.

tobiasBora commented 6 months ago

Thanks for your answer, sadly removing this lines changes nothing. I get this error on db.decks.update(file.id, …), delete, update, any custom function waiting for a number

tobiasBora commented 6 months ago

Oh, funnily, if I do files!: Table<File>; instead of files!: Table<File, number>, all errors regarding update, delete and update vanish. But it still raises an error on functions I created like:

  function moveFileUp(id: number) {
    db.transaction("rw", db.files, async () => {
      const fileToMove = await db.files.get(id);
      if (fileToMove) {
        const pos = fileToMove.position;
        // We try to update: if no object is updated, there is no object above
        const nbObjectModified = await db.files.where({position: pos-1}).modify(item => {++item.position});
        if (nbObjectModified == 1) {
          await db.positions.where({id: id}).modify(item => {--item.position});        
        }
      }
    });
  }

…

<button on:click={() => moveFileUp(file.id)}><Icon.CaretUpOutline/></button>

as I get an error:

Error: Argument of type 'number | undefined' is not assignable to parameter of type 'number'.
  Type 'undefined' is not assignable to type 'number'. (ts)
        <button on:click={() => moveFileUp(file.id)}><Icon.CaretUpOutline/></button>
dfahlander commented 6 months ago

Auto-incremented keys on your entities are only optional when adding/putting them but is 'required' (certain to not be undefined) when retrieving things back via toArray(), get() etc. In typescript you can use the ! to remove undefined from your type. Since your id is declared optional but it's actually required (if retrieved from dexie), you may just add a ! like this: file.id!.

Dexie 4 has also a better solution for this, see release notes of dexie 4:

interface Friend {
  id: number;
  name: string;
  age: number;
}

class MyDB extends Dexie {
  friends!: EntityTable<Friend, 'id'>; // Automatically makes `id` optional when adding and picks its TKey type.
}

As you see here, id shall be declared without the question mark and will still be considerd optional in calls to add() and put() etc - so it mirrors the actual typings a bit better and you won't have to do the ! everywhere.

tobiasBora commented 6 months ago

Thanks… but I tried this (with dexiejs from Master), and I get many errors. Now, my custom functions seem to be better recognized, but update/delete broke:

Error: Argument of type 'number' is not assignable to parameter of type 'File | "id"'. (ts)

I also get a new kind of error on get:

Error: No overload matches this call.
  Overload 1 of 4, '(key: "id"): PromiseExtended<File | undefined>', gave the following error.
    Argument of type 'number' is not assignable to parameter of type '"id"'.
  Overload 2 of 4, '(equalityCriterias: { [key: string]: any; }): PromiseExtended<File | undefined>', gave the following error.
    Argument of type 'number' is not assignable to parameter of type '{ [key: string]: any; }'. (ts)
    db.transaction("rw", db.files, async () => {
      const fileToMove = await db.files.get(id);
      if (fileToMove) {

I also can't create a new entry:

Error: Argument of type '{ name: string; slug: string; description: string; position: number; }' is not assignable to parameter of type 'Deck'.
  Property 'id' is missing in type '{ name: string; slug: string; description: string; position: number; }' but required in type 'File'. (ts)
      db.transaction("rw", db.files, async () => {
        const id = await db.files.add({
          name: newFileName,
          slug: slug,
          description: "Click me to edit this description",
          position: $decks.length
        });

Here is my code:


// Each file is made of decks
export interface File {
  // Make sure to update the associated class below
  id: number;
  name: string; //
  slug: string;
  description: string;
  position: number;
}

export class MyDb extends Dexie {
  files!: Table<File, 'id'>; 

  constructor(name : string) {
    super(name);
    this.version(1).stores({
      files: '++id, name, slug, position', // Primary key and indexed props
    });
  }
}
dfahlander commented 6 months ago

EntityTable not Table.

dfahlander commented 6 months ago

I've updated the docs around this. See https://dexie.org/docs/Typescript

tobiasBora commented 6 months ago

Ohh, good point awesome, thanks a lot! Just a minor point, the doc has a typo, one must use import Dexie, { type EntityTable } from "dexie"; not import Dexie, { EntityTable } from "dexie";

dfahlander commented 6 months ago

Good point. But I think it's not mandatory to use the type specifier even though it's clearer to read - or do you get an error when using import Dexie, { EntityTable } from "dexie" ? Just curious.

tobiasBora commented 6 months ago

I did get an error

dfahlander commented 6 months ago

Ok thanks. I think babel complains while typescript doesn't then. I'll fix it!