typescriptnpmtypeses6-modulestype-declaration

Sharing TypeScript type declarations via npm package


I'm in need of sharing some TypeScript types between my React client and my Express REST API in order to keep the code clean and DRY. Since this is a private proejct I wouldn't share these types through the @types repository, so I've followed the guide on the TypeScript website and this is the result...

Everything is working just fine in the React client: I've installed the types as a dev dependency and used them flawlessly.

In the Express API I get this error and I presume it has something to do with how I structured my package.

What am I doing wrong? As ignorant as I am I'd suppose it's related with how the modules are loaded, but I can't figure out precisely what may be causing the error.

> cross-env NODE_ENV=production node dist/index.js

internal/modules/cjs/loader.js:834
  throw err;
  ^

Error: Cannot find module '@revodigital/suiteods-types'

How I import the module inside the API code

import { AuthEntity, Roles } from '@revodigital/suiteods-types';

@Model()
export class AuthEntityModel implements AuthEntity {
  /* ... */

  role: Roles;

  /* ... */
}


Package tree
suiteods-types
  |_index.d.ts
  |_package.json
  |_README.md
  |_tsconfig.json

index.d.ts

export = Ods;
export as namespace Ods;

declare namespace Ods {
  /* ... */
  interface AuthEntity extends DomainObject {
    email: string;
    password: string;
    role: Roles;
    instanceId: string;
  }

  enum Roles {
    BASE,
    STUDENT,
    BUSINESS,
    INSTRUCTOR,
    ADMIN
  }
  /* ... */
}

package.json

{
  "name": "@revodigital/suiteods-types",
  "version": "0.1.1",
  "description": "Type declarations for suiteods project",
  "types": "index.d.ts",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "author": "Revo Digital",
  "license": "ISC",
  "repository": {
    "type": "git",
    "url": "git+https://github.com/revodigital/suiteods-types.git"
  },
  "bugs": {
    "url": "https://github.com/revodigital/suiteods-types/issues"
  },
  "homepage": "https://github.com/revodigital/suiteods-types#readme"
}

tsconfig.json

{
  "compilerOptions": {
    "module": "commonjs",
    "lib": [
      "es6"
    ],
    "noImplicitAny": true,
    "noImplicitThis": true,
    "strictNullChecks": true,
    "strictFunctionTypes": true,
    "baseUrl": "../",
    "typeRoots": [
      "../"
    ],
    "types": [],
    "noEmit": true,
    "forceConsistentCasingInFileNames": true
  },
  "files": [
    "index.d.ts"
  ]
}

Update

Confused on how to get rid of the namespace, and still getting the same error on the module, now installed as `dependency` and not as `devDependency`. The file structure is the same as above. Thanks in advance for the help.

updated and complete index.d.ts

export = Ods;
export as namespace Ods;

declare namespace Ods {

  type IdType = 'Carta Identità' | 'Passaporto' | 'Patente'
  
  type ODSModule = 'SCUOLA' | 'OPERATORI' | 'DRONI' | 'ODS_ROOT'

  type Role = 'BASE' | 'STUDENTE' | 'ISTRUTTORE' | 'AMMINISTRATORE' | 'UTENTE_AZIENDALE'
  
  type UserScope = 'INTERNAL' | 'WHOLE'


  interface Address {
    street: string;
    city: string;
    province: string;
    CAP: string;
  }

  interface Credentials {
    email: string;
    password: string;
  }

  interface LoggedEntity {
    authEntity: AuthEntity;
    baseUser: BaseUser;
  }

  interface ModulesInstancesMap {
    SCUOLA: string;
    OPERATORI: string;
    DRONI: string;
    ODS_ROOT: string;
  }

  interface MultiTenantController {

  }

  interface Tenant {
    _id: string;
    role: Role | ODSModule;
  }

  interface TenantInfo {
    tenant: Tenant;
    relativeGodRole: Role;
  }

  interface AuthEntity extends DomainObject {
    email: string;
    password: string;
    role: Role;
    instanceId: string;
  }

  interface BaseUser extends DomainObject {
    firstName: string;
    lastName: string;
    phone: string;
    address: Address;
    scope: UserScope;
  }

  interface BelongsToModule {
    module: ODSModule;
  }

  interface Business extends DomainObject {
    businessName: string;
    pIva: string;
    tel: string;
    pec: string;
    recipientCode: string;
    address: Address;
  }

  interface DomainObject {
    _id: string;
  }

  interface HasTenant {
    tenantInfo: TenantInfo;
  }

  interface Instructor extends BaseUser {
    licenseCode: string;
  }

  interface InternalWholeSuiteUser extends BaseUser {
    modulesInstancesMap: ModulesInstancesMap;
  }

  interface InternalModuleUser extends BaseUser, BelongsToModule {
    moduleInstanceId: string;
  }

  interface School extends Business, HasTenant {
    cApr: number;
  }


  interface Student extends BaseUser {
    stateIssuedIdNumber: string;
    stateIssuedIsType: IdType;
    job: string;
    businessId?: string;
  }
}


Solution

  • In addition to the precious @Paleo edits, adding an index.js file with the content that follows solved the issue.


    index.js

    module.exports = {};
    

    Updated file structure

    suiteods-types
      |_index.d.ts
      |_index.js
      |_package.json
      |_README.md
      |_tsconfig.json
    
    

    See GitHub repo for full code.


    So... How to share TS Types via npm

    If you want to share some TypeScript types (E.G. between a client and a server like in my case) the steps (that worked for me) are the ones that follows.
    1. Create a new folder and init it as an npm package with npm init
    2. Extrapolate all the types you want to share between the entities and group them in an index.d.ts file
    3. Make the declaration "pure types"-only, in my case converting the enums into types (and doing a bit of refactoring to adapt the rest of the code) was enough
    4. Add tsconfig.json (see my question above for an example)
    5. Add an index.js containing only module.exports = {}
    6. Publish it (see links below)
    7. Install it as dependency, so `npm i --save @yourscope/yourpkg
    8. Consume it when needed

    To publish the package I've used npm and GitHub packages. See these links...