pocketbase / js-sdk

PocketBase JavaScript SDK
https://www.npmjs.com/package/pocketbase
MIT License
2.17k stars 127 forks source link

Difficulties using explicit typing. #289

Closed VentGrey closed 6 months ago

VentGrey commented 6 months ago

I don't know if this is a TypeScript only-issue (it seems to be).

The type system does weird mental gymnastics when trying to get some props typed on Sveltekit + TypeScript.

Take this as an example:

  1. Declare a function to get the PocketBase Health using the JS-SDK:
// If I add the HealthCheckResponse type here, error happens earlier in code. I'll leave this example function untyped.

export async function GetPocketBaseHealth(pb: Client) {
    return await pb.health.check().then(
        (result) => {
            return result;
        }
    ).catch(
        (err: ClientResponseError) => {
            return null;
        }
    );
}

Typescript reports the function declaration as function GetPocketBaseHealth(pb: Client): Promise<HealthCheckResponse | null>

  1. Use that function to get the PocketBase Health Object in the server:
// locals.pb is a PocketBase Client type.
const pb_health = await GetPocketBaseHealth(locals.pb);

There, TS types (at least in codium), indicate that pb_health type is HealthCheckResponse | null. Makes total sense so far.

  1. On +page.svelte get the PocketBase health variable:
<script lang="ts">
    export let data;    
    $: PBHealth = data.pb_health;
</script>

Here, PBHealth is still detected as HealthCheckResponse | null.

  1. Declare a component that takes a PBHealth prop. When you try to use it, Sveltekit will whine about it being an implicit any type. Fortunately we know the types we need:
<script lang="ts">
    import type HealthCheckResponse from "pocketbase";
    // import HealthCheckResponse from "pocketbase"; also works.

    export let PBHealth: HealthCheckResponse | null;
</script>

Importing HealthCheckResponse in any other way throws a module not found error. However this makes sense as it is a class AFAIK ((alias) class HealthCheckResponse import HealthCheckResponse). This also works without type.

  1. Go back to +page.svelte to see this error in the component prop:
<ExampleComponent PBHealth={PBHealth} />
Type 'HealthCheckResponse | null' is not assignable to type 'Client | null'.
  Type 'HealthCheckResponse' is missing the following properties from type 'Client': baseUrl, lang, authStore, settings, and 24 more.ts

I'm failing to see where did I declare a Client type variable (other than locals.pb). Or how to tell TypeScript that I don't care about that and "forcefully" type such variables.

Variations

You can encounter this error earlier if you modify the example function in step 1:

export async function GetPocketBaseHealth(pb: Client): Promise<HealthCheckResponse | null> {
    return await pb.health.check().then(
        (result) => {
            return result;
        }
    ).catch(
        (err: ClientResponseError) => {
            return null;
        }
    );
}

Throws the same error.

export async function GetPocketBaseHealth(pb: Client): Promise<HealthCheckResponse | null> {
    const result: HealthCheckResponse | null = await pb.health.check().then(
        (result) => {
            return result;
        }
    ).catch(
        (err: ClientResponseError) => {
            return null;
        }
    );

    return result;
}

Also returns the same error.

Solutions

One solution I found was to declare the PBHealth component prop as any which totally defies TypeScript's purpose and turns it into blue JS.

Another approach is to create an "copy" Object from the original variable and try to cast it using Zod or a similar library. But that's plain memory wasting.

I'm aware of #152 progress and don't really know if this is related or not.

ganigeorgiev commented 6 months ago

I'm confused and not sure that I understand what you are trying to do, but I don't think your issue in SDK related.

Please understand that the issues tracker is not a general dev support forum.

In any case better TS support is planned in the future but for now other tasks are with higher priority.

If you still think that there is an issue with the JS SDK, then please elaborate more clearly what is the problem that you have and what you are trying to do.

ganigeorgiev commented 6 months ago

As a side-note, keep in mind that in step 4 you are importing the type of the default export, which would explain why the error is not assignable to 'Client | null'. Try to add curly brackets to the import type declaration, aka. import type { HealthCheckResponse } from "pocketbase".

VentGrey commented 6 months ago

I would say my goal is to have all PocketBase server side interactions fully-typed, I'm trying to follow the generic types and other conventions presented in the README.md here.

I've read through other SDK issues in here, as well as a closed PR. It seems that type support is a big task on itself. If such a goal is not currently possible, I understand.

Doing the curly braces import errors out on Module ""pocketbase"" has no exported member "HealthCheckResponse". Did you mean to use "import HealthCheckResponse from "pocketbase"" instead?ts(2614)

I found a kind of dirty workaround casting types to unknown and then properly forming them with Zod. Thanks for your response.

ganigeorgiev commented 6 months ago

The curly brackets is the correct notation when not targeting the default import but the issue is that the HealthCheckResponse interface is not exported.

I've submitted a fix in v0.21.2 release.

I've tested it locally and it is working correctly for me. If you are still not able to resolve your issue, then please create a minimal reproducible repo and I'll try to investigate it further.

VentGrey commented 6 months ago

Latest patch fixes it. VSCodium now reports interface instead of class. Thanks a bunch!