Arcanox
Arcanox

Reputation: 1555

How to import files which has nested namespace in typescript?

I'm converting an existing Node.js project to be fully TypeScript-based. I previously had a static Sql class (an object with sub-objects containing MySQL helper functions). An example would be Sql.user.findById. The Sql class also had a query function for executing raw MySQL queries without needing to expose the underlying MySQL connection.

What I would like now is a namespace named Sql containing the query function, and then in the files that used to contain the sub-classes, I would like namespaces such as Sql.User that export functions like findById.

The problem is, I can't figure out how to nest namespaces like this while being able to access query in the nested namespaces.

Right now what I have is this:

sql.ts:

import * as connection from "./connection";

export * from "./queries/confirmEmail";
export * from "./queries/resetPassword";
export * from "./queries/user";

namespace Sql {
    export function query( ...args: any[] ) {
        return connection.query( args );
    }

    export class Errors {
        static confirmCodeExpired = "45001";
        static confirmCodeNonExistent = "45002";
        static userAlreadyConfirmed = "45003";
    }
}

./queries/resetPassword.ts:

import "../sql";

namespace Sql.ResetPassword {
    type InsertCodeArgs = {
        code: string;
        userid: number;
        canChange: boolean;
    }

    export function insertCode( args: InsertCodeArgs, cb: Nodeback<void> ) {
        Sql.query(
            "CALL insert_reset_code( :code, :userid, :canChange );",
            {
                code: args.code,
                userid: args.userid,
                canChange: args.canChange ? 1 : 0
            },
            cb
        );
    }

    export function removeCode( code: string, cb: Nodeback<void> ) {
        Sql.query(
            "DELETE FROM `resetcodes` WHERE `code` = :code;",
            { code: code },
            cb
        );
    }

    export function checkCodeValid( code: string, cb: Nodeback<boolean> ) {
        Sql.query(
            [
                "SELECT NOW() <= `c`.`expires` AS `valid`, `c`.`canchange` as `canchange`, `u`.`id` AS `userid` FROM `resetcodes` AS `c` ",
                "INNER JOIN `users` AS `u`",
                "ON `u`.`id` = `c`.`userid`",
                "WHERE `code` = :code LIMIT 1;"
            ].join( " " ),
            { code: code },
            ( err, data ) => {
                if ( err ) return cb( err );
                if ( !data || data.length === 0 ) return cb( null, null );

                return cb( null, data[ 0 ].valid > 0, data[ 0 ].userid, data[ 0 ].canchange > 0 );
            }
        );
    }
};

When I try and compile, however, I get a number of errors that look like this:

src\sql\queries\resetPassword.ts(11,13): error TS2339: Property 'query' does not exist on type 'typeof Sql'.

How can I "backreference" the query function from the parent file? I can't import { Sql } from "../sql" of course, because then Sql is a duplicate definition, and I get an error that src/sql/sql.ts has no exported member "Sql".

Upvotes: 5

Views: 3706

Answers (1)

Arcanox
Arcanox

Reputation: 1555

I solved my namespace referencing problem by alias-importing the parent namespace in the child namespace files:

import { Sql as sql } from "../sql";

...

namespace Sql.User {
    export function ... {
        sql.query(...);
    }
}

This allows the Sql.User namespace to use functions in the Sql namespace from another file. I still don't know how to merge the namespaces into one so that Sql.User and its siblings can be imported simply by importing sql.ts. I need to create another question for that.

Upvotes: 5

Related Questions