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 types problems #63

Closed
spech66 opened this issue Dec 17, 2024 · 8 comments · Fixed by #65
Closed

Typescript types problems #63

spech66 opened this issue Dec 17, 2024 · 8 comments · Fixed by #65
Assignees
Labels
bug Something isn't working documentation Improvements or additions to documentation
Milestone

Comments

@spech66
Copy link

spech66 commented Dec 17, 2024

We upgraded to the latest version (1.3.1). However there are still some issues trying to use the types.

Import won't use the right file

Could not find a declaration file for module 'scimmy'. 'xxx/node_modules/scimmy/dist/cjs/scimmy.cjs' implicitly has an 'any' type.   
Try `npm i --save-dev @types/scimmy` if it exists or add a new declaration (.d.ts) file containing `declare module 'scimmy';`ts(7016)

if we import using import { Resources } from "scimmy"; or import SCIMMY from "scimmy";

After copying the type file to a modules declarations file and making a few adjustments some types are working. However we a running in the following problems:

Resource as any

Resources.declare(Resources.User)
    .extend(SCIMMY.Schemas.EnterpriseUser, false)
    .ingress(async (resource, data, ctx: Context) => {})

or

User.extend(Schemas.EnterpriseUser, false)
    .ingress(async (resource, data, ctx: Context) => {})

Will show the ingress/egress/... resource/data as any type.

Enterprise Types

export type ScimUser = Omit<SUser, ShadowAttributes> & Omit<EnterpriseUser, ShadowAttributes> & {
    // eslint-disable-next-line @typescript-eslint/naming-convention
    "urn:ietf:params:scim:schemas:extension:enterprise:2.0:User": {
        department?: string;
    }
}

Getting a valid Enterprise User might need something like the above typing.

Group degress handler

The degress handler of Groups won't allow for promises as the method always require a void return instead of Promise.

@sleelin sleelin added bug Something isn't working documentation Improvements or additions to documentation labels Dec 26, 2024
@sleelin sleelin added this to the 1.3.2 milestone Dec 26, 2024
@sleelin
Copy link
Collaborator

sleelin commented Jan 6, 2025

Hi @spech66, apologies for the delay and thanks for the feedback!

I managed to reproduce the type resolution issue - it seems to only occur specifically when the moduleResolution compiler option in tsconfig.json is set to nodenext, and the type property in your package.json is set to anything other than module. The nodenext module resolution option means the compiler is seeking a nonexistent type declaration file from alongside the CommonJS file, instead of resolving the scimmy module type declaration file using the types field from package.json, which points to the singular dist/scimmy.d.ts file included in the package (note: since the type declarations for ESM and CJS are identical, only one version has been packaged). It appears that the solution is to explicitly specify the types property for each export in package.json, as you suggested in PR #52 🙂. As far as I can tell, this has no adverse side effects, so I've made this change and it will be included in the 1.3.2 release.

Regarding the actual Resource class type issues, unfortunately TypeScript still doesn't seem to understand the concept of inherited class static methods, and since the static extend method of the User and Group resource classes isn't explicitly declared, the chained calls to the static ingress/egress/degress methods fall back to the types from the parent Resource class (which in this case declare all handler arguments as any). I've updated the User and Group resource classes to explicitly declare the static extend method so that their chained types are correctly identified as the class they were called on, however due to the aforementioned TypeScript issue and the way SCIMMY has been implemented, there's no way to automatically handle instance types when a resource schema has been extended 😢.

As a workaround to the above, I have made the ingress/egress methods of the User and Group resource classes generics. These methods now accept a single optional type argument, which determines both the shape of the instance argument, and the shape of the return value of the handler passed to them. I have also included a new utility type, SCIMMY.Types.Schema.Extended, which can be used to automate type definition of schema-extended resources. Used in combination they would look something like the following:

import SCIMMY, {Resources} from "scimmy";
import type {Schema} from "scimmy/types";

type UserWithEnterpriseUser = Schema.Extended<SCIMMY.Schemas.User, typeof SCIMMY.Schemas.EnterpriseUser>;

Resources.declare(Resources.User)
    .extend(SCIMMY.Schemas.EnterpriseUser)
    .ingress<UserWithEnterpriseUser>(async (resource, data, ctx) => { ... })
    .egress<UserWithEnterpriseUser>(async (resource, ctx) => { ... });

/* or */

User.extend(SCIMMY.Schemas.EnterpriseUser)
    .ingress<UserWithEnterpriseUser>(async (resource, data, ctx) => { ... })
    .egress<UserWithEnterpriseUser>(async (resource, ctx) => { ... });

This workaround should hopefully address issues in TypeScript with handler argument/return value type inference for extended schemas, whilst also making it easier overall to customise the shape of the handler value types (e.g. marking attributes as required, or omitting them entirely).

Finally, I wasn't able to reproduce the issue with the Group degress handler return value (which also applied to the User degress handler), however I have updated the return value type of the degress handlers to explicitly be void | Promise<void> which should address this (frustrating that TypeScript still needs this to be explicit!).

As mentioned above, these changes will be included in the next release, v1.3.2, which should happen imminently!

Thanks,
Sam

@sleelin sleelin self-assigned this Jan 6, 2025
@spech66
Copy link
Author

spech66 commented Jan 6, 2025

@sleelin thank you very much for the very detailed explanation and your help with this issues! This looks really promising and I'm looking forward for the next release 🙏 👍

@sleelin
Copy link
Collaborator

sleelin commented Jan 6, 2025

Hi @spech66,
I've now released v1.3.2 with the above changes included.
Let me know if this resolves the issue for you!

Thanks,
Sam

@spech66
Copy link
Author

spech66 commented Jan 6, 2025

First things I found is that the export require to be default exports.

../node_modules/scimmy/dist/scimmy.d.ts:7:5 - error TS1203: Export assignment cannot be used when targeting ECMAScript modules. Consider using 'export default' or another module format instead.

7     export = SCIMMY.Schemas;
declare module "scimmy/types" {
    import SCIMMY from "scimmy";
    export default SCIMMY.Types;
}
declare module "scimmy/schemas" {
    import SCIMMY from "scimmy";
    export default SCIMMY.Schemas;
}
declare module "scimmy/config" {
    import SCIMMY from "scimmy";
    export default SCIMMY.Config;
}
declare module "scimmy/resources" {
    import SCIMMY from "scimmy";
    export default SCIMMY.Resources;
}
declare module "scimmy/messages" {
    import SCIMMY from "scimmy";
    export default SCIMMY.Messages;
}

@sleelin
Copy link
Collaborator

sleelin commented Jan 6, 2025

Could you please send through a copy of your tsconfig.json? It seems like the TypeScript compiler is running a complete type check on the type declarations for SCIMMY, which I'm pretty sure it shouldn't be doing?

As far as I'm aware, because it's an external module it shouldn't be complaining about mismatched module target syntax - unless you copied the new declaration file into your project?

Thanks,
Sam

@spech66
Copy link
Author

spech66 commented Jan 6, 2025

{
    "extends": "@tsconfig/node20/tsconfig.json",
    "compilerOptions": {
        // add dom, needs to repeat es2023 here as array values of extended config are overwritten, not merged
        // See: https://miyoon.medium.com/array-parameters-in-tsconfig-json-are-always-overwritten-11c80bb514e1
        "lib": [ "es2023", "dom"],
        "types": ["reflect-metadata"],
        "outDir": "dist",
        "rootDir": "src",
        "incremental": true,
        // needed for typeorm and DI decorators
        "emitDecoratorMetadata": true,
        "experimentalDecorators": true,
        "sourceMap": true,
        // pedantic settings
        "alwaysStrict": false,
        "noImplicitAny": true,
        "skipLibCheck": false, // revert default of extended config
    },
    "references": [
        { "path": "../api-shared" }
    ],
    "exclude": [
        "node_modules",
        "!node_modules/@types"
    ],
    "include": [
        "src/**/*"
    ]
}

And the @tsconfig/node20/tsconfig.json

{
  "$schema": "https://json.schemastore.org/tsconfig",
  "_version": "20.1.0",

  "compilerOptions": {
    "lib": ["es2023"],
    "module": "node16",
    "target": "es2022",

    "strict": true,
    "esModuleInterop": true,
    "skipLibCheck": true,
    "moduleResolution": "node16"
  }
}

@sleelin
Copy link
Collaborator

sleelin commented Jan 8, 2025

Thanks @spech66, I was able to reproduce the issue using the supplied tsconfig.json!
I had initially thought the export assignment module declaration pattern was a simple enough means of mixing "namespace" and "module" formats and appease TypeScript, however I guess not! I've updated the ostensibly-typed package to generate the alias module declarations using only the ECMAScript module syntax (i.e. "export default") and have just released v1.3.3, in which the only change should be to the generated type declaration file.

Let me know if this properly resolves the issue!

Thanks,
Sam

@spech66
Copy link
Author

spech66 commented Jan 8, 2025

This looks very promising. Thanks again!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug Something isn't working documentation Improvements or additions to documentation
Development

Successfully merging a pull request may close this issue.

2 participants