Export more TypeScript types at package root level
Is your feature request related to a problem? Please describe.
When we use near-api-js in TypeScript, we have to find out individual sub-directories inside the package to import types.
import
{Account, KeyPair, Near, utils}
from 'near-api-js';
import
{ InMemoryKeyStore, UnencryptedFileSystemKeyStore, }
from 'near-api-js/lib/key_stores';
import
{NearConfig}
from 'near-api-js/lib/near';
import
{ FinalExecutionOutcome, JsonRpcProvider, }
from 'near-api-js/lib/providers';
import
{ ExecutionStatusBasic, FinalExecutionStatusBasic, Finality, }
from 'near-api-js/lib/providers/provider';
import
{ addKey, createAccount, fullAccessKey, transfer, }
from 'near-api-js/lib/transaction';
import
{PublicKey}
from 'near-api-js/lib/utils';
import
{parseNearAmount}
from 'near-api-js/lib/utils/format';
import
{base_decode}
from 'near-api-js/lib/utils/serialize';
Describe the solution you'd like
We would like to import such types from the package directly, such as:
import
{Account, KeyPair, Near, utils, InMemoryKeyStore, UnencryptedFileSystemKeyStore, NearConfig, FinalExecutionOutcome, JsonRpcProvider, }
from 'near-api-js';
If you really want to separate such types, please use TypeScript namespaces:
export namespace keys {
export class PublicKey {}
}
Describe alternatives you've considered See my imports above.
Additional context Add any other context or screenshots about the feature request here.
I'm not sure if it's a great idea to export all classes, interfaces, and functions to index.ts.
Have you considered using this approach?
https://github.com/ref-finance/ref-ui/blob/b077c129cc5169c8a5ddc08e95b42c31aac91a78/src/services/wrap-near.ts#L59
@volovyk-s I'm not proposing to flatten all classes/interfaces/functions to index.ts. As I stated in the issue, the primary concern is that the current typing forces users of near-api-js to understand the internal directory structure as we have to import types as follows:
import {NearConfig} from 'near-api-js/lib/near';
import {
FinalExecutionOutcome,
JsonRpcProvider,
} from 'near-api-js/lib/providers';
import {
ExecutionStatusBasic,
FinalExecutionStatusBasic,
Finality,
} from 'near-api-js/lib/providers/provider';
There are two problems with the approach above:
- It makes the internal path part of the api contract and any refactoring can lead to breaking changes
- It makes it hard for VSCode to help import such types
If you prefer to have some hierarchy/isolation for typing, namespace is the way to go, such as:
export namespace utils {
export function parseNearAmount(...) {};
// or export const parseNearAmount = parseNearAmount;
}
@austinabell in your opinion what is the best practice?
@austinabell in your opinion what is the best practice?
Not quite sure what would be best tbh, probably best to discuss the tradeoffs. I haven't kept up to date with js API standards very closely recently but I will try to share an opinion.
My intuition would be that this could be a good approach, but it might depend on some internals I haven't looked into yet. @raymondfeng brings up a good point that these imports are based on the internal directory structure, which not only could be difficult for a new user to pick up but could mitigate the breaking changes if there ever was a need to refactor in the future.
Just to chime in, I'm not a fan of using namespaces in modern Typescript, and from my memory, the TS team doesn't recommend them in most cases for modern TS/JS either. Namespaces are represented by objects in the global scope so you can end up with naming conflicts, and can make it impossible to do tree-shaking across the codebase.
Modules provide similar benefits as far as scoping types, but without any possibility of name conflicts at the global scope.
We can accomplish the same basic result as namespaces do, by doing a little re-structuring of the directory structure and creating a single file that imports our internal modules, and exports those for consumption by users at the root level of the package (e.g. index.js), to give them a consistent place to import types from that (a) doesn't include file paths and (b) doesn't create globals to do so.
To see what this looks like as far as a real codebase goes, rxjs is a pretty fine example...
https://github.com/ReactiveX/rxjs/blob/master/src/index.ts
This way consumers of the package can import whatever types they want, while those types are still defined in their individual modules, and no global namespace is declared for near-api-js types to 'live' in. :). It also makes it obvious to consumers that if they start plucking types from our 'internal' modules, they are playing with fire ;)
My first preference is to export public facing types at root level. Namespaces can be used for more isolations if needed. Please note that exported artifacts from a package does not create global conflicts as they be imported as a different name. For example:
import {Account as NearAccount} from 'near-api-js';
I think that flattening interfaces would be pretty good idea for both near-api-js and near-sdk-as.
Like there isn't that much stuff in these libraries to create separate subnamespaces, they exist mostly for historical reasons and generally just result in extra inconvenience. And if anything it's best to avoid name clashes for public classes / functions in one library in any case, as it would confuse users.
I pretty much would prefer to import stuff like formatNearAmount directly on top level instead of having to look where it is located every time.