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.

Type Adapters: Best Practices

See original GitHub issue

I’m a bit unsure on the proper use and best practices of type adapters (and custom types in general).

I noticed that custom types can be supported by implementing transformValueFromDB and transformValueToDB directly in the DBConnection class. On the other hand, I can define my own type adapters. Which approach should be used in which situation?

Furthermore, I’m unsure how to deal with the type and next parameters when writing custom type adapters.

Let’s say I want to store RGB colors as 3-byte blobs in the (SQLite) database:

export class RgbColorTypeAdapter implements TypeAdapter {
    public transformValueFromDB(
        value: unknown,
        type: string,
        next: DefaultTypeAdapter,
    ): RgbColor {
        if (value instanceof Uint8Array && value.length == 3) {
            return {r: value[0], g: value[1], b: value[2]};
        }
        throw new Error(`Cannot decode database value ${value} (type ${typeof value}) as RgbColor`);
    }

    public transformValueToDB(
        value: RgbColor,
        type: string,
        next: DefaultTypeAdapter,
    ): Uint8Array {
        if (isRgbColor(value)) {
            return Uint8Array.of(value.r, value.g, value.b);
        }
        throw new Error(`Cannot encode value ${value} for database`);
    }
}

This works, but if I use it like this:

public color = this.optionalColumn('cachedColor', 'custom', 'RgbColor', new RgbColorTypeAdapter());

…then reading a null value fails. I assume this is because I need to call next before or after my custom code (probably depending on the from/to direction).

What are the best practices in this case?

Issue Analytics

  • State:closed
  • Created 2 years ago
  • Comments:5 (5 by maintainers)

github_iconTop GitHub Comments

1reaction
juanluispazcommented, Jun 11, 2021

Added a section in the documentation explaining it at https://ts-sql-query.readthedocs.io/en/latest/column-types/

1reaction
juanluispazcommented, Jun 10, 2021

Type adapters allow you to transform values when you send it to the database transformValueToDB and, when you receive it from the database transformValueFromDB.

The one defined at the DBConnection is the default one. If you create a custom type that applies to the whole database, that is the best place. Define the custom type probably is the most common strategy, and it looks like in your case is where you must place it.

The type adapter at column level is when you need to define a custom rule that only applies to that column. For example, the CustomBooleanTypeAdapter that allows you to create custom representations for a boolean (very useful on Oracle database, where there is not a boolean type).

The type adapter handles all the values sending or coming from the database, including null/undefined values; you must handle it.

The type param tells you what the expected type is; you must verify it and only process the value if it is the one you are applying the rule.

The next gives you access to the default implementation; you must call if you cannot handle the type.

In your case, your code will look like this:

interface RgbColor {r: number, g: number, b: number}

export class RgbColorTypeAdapter implements TypeAdapter {
    public transformValueFromDB(
        value: unknown,
        type: string,
        next: DefaultTypeAdapter,
    ): RgbColor {
        if (type === 'RgbColor' && value) {
            if (value instanceof Uint8Array && value.length == 3) {
                return {r: value[0], g: value[1], b: value[2]};
            }
            throw new Error(`Cannot decode database value ${value} (type ${typeof value}) as RgbColor`);
        }
        return next.transformValueFromDB(value, type);
    }

    public transformValueToDB(
        value: RgbColor,
        type: string,
        next: DefaultTypeAdapter,
    ): Uint8Array {
        if (type === 'RgbColor' && value) {
            if (isRgbColor(value)) {
                return Uint8Array.of(value.r, value.g, value.b);
            }
            throw new Error(`Cannot encode value ${value} for database`);
        }
        next.transformValueToDB(value, type)
    }
}

And then you will use it as:

public color = this.optionalColumn<RgbColor>('cachedColor', 'custom', 'RgbColor', new RgbColorTypeAdapter());

But, in your case is better to define at DBConnection level. The connection will look like this:

class DBConnection extends SqliteConnection<'DBConnection'> {
  transformValueFromDB(value: unknown, type: string) {
        if (type === 'RgbColor' && value) {
            if (value instanceof Uint8Array && value.length == 3) {
                return {r: value[0], g: value[1], b: value[2]};
            }
            throw new Error(`Cannot decode database value ${value} (type ${typeof value}) as RgbColor`);
        }
       return super.transformValueFromDB(value, type);
  }
  transformValueToDB(value: unknown, type: string) {
        if (type === 'RgbColor' && value) {
            if (isRgbColor(value)) {
                return Uint8Array.of(value.r, value.g, value.b);
            }
            throw new Error(`Cannot encode value ${value} for database`);
        }
       return super.transformValueToDB(value, type);
  }
}

And then you will use it as:

public color = this.optionalColumn<RgbColor>('cachedColor', 'custom', 'RgbColor');
Read more comments on GitHub >

github_iconTop Results From Across the Web

Writing Better Adapters - ProAndroidDev
Implementing adapters is one of the most frequent tasks for an Android developer. It's the base for every list. Looking at apps, lists...
Read more >
Networking Best Practices - VMware Docs
Consider these best practices when you configure your network. To ensure a stable connection between vCenter Server, ESXi, ...
Read more >
Adapter Development Methodology and Best Practices | InformIT
Integration and adapter software development require a different type of project management. Learn some methodologies to help lead you ...
Read more >
Resource Adapter Best Practices
This appendix describes some best practices for resource adapter developers using WebLogic Server 12.1.3. This appendix includes the following sections:.
Read more >
Ports & Adapters architecture on example - Wojciech Krzywiec
But above method requires to have adapters for two outgoing ports — database & eventPublisher. For a first one and its first method...
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