babel-plugin-tcomb
babel-plugin-tcomb copied to clipboard
Situations with metadata, when flow definition placed in external library
By https://github.com/gcanti/babel-plugin-tcomb/issues/11
Problem:
Library declaration:
// node_modules/myLib/flow-typed/interface.js
declare module 'myLib' {
declare interface User {
name: string;
}
}
In application we can't access type metadata:
// app.js
import type {User} from 'myLib'
function (user: User) {
// ...
}
Type system is separate from javascript. Flow or typescript does not help, types as value keys not supported.
Only tricks, both with some restrictions:
Threat type name + import path as unique string key
myLib interface declaration:
// @flow
// node_modules/myLib/flow-typed/interfaces.js
declare interface User {
name: string;
}
In entry point of myLib add type reflection to singleton tcombRegistry object by this key.
// @flow
// node_modules/myLib/index.js
import type {User} from 'myLib'
import {$tcombGetMeta} from 'tcomb'
$tcombSetMeta('User.myLib', definition)
In application access tcombRegistry by generated key.
// @flow
// app.js
import type {User} from 'myLib'
import {$tcombGetMeta} from 'tcomb'
$tcombGetMeta('User.myLib')
Use export type
Declarations for interfaces in flowtype looks like workaround. No common way to declare interface for internal and external usage.
Internal interface:
// @flow
// myLib/src/i/pub.js
export type User = {
name: string;
}
Before publishing, we can preprocess this interface with separate configuration, only with babel-plugin-tcomb. Preprocessed file placed to myLib/i, instead of myLib/src/i. Directory 'myLib/i' included to files section of package.json.
// @flow
// myLib/i/pub.js
export type User = {
name: string;
}
export $tcombUserMeta = {}
In application:
// @flow
// app.js
import type {User} from 'myLib/i/pub'
import {$tcombUserMeta} from 'myLib/i/pub'
In application we can't access type metadata
Type system is separate from javascript. Flow or typescript does not help, types as value keys not supported.
@zerkalica sorry but I don't understand, what's the problem are you talking about? And why is relevant to this plugin?
We need to generate tcomb-metada in myLib interface for assertion, right?. But it's impossible in node_modules/myLib/flow-typed/interface.js, because declarations file is not a real npm module, we can't attach and share metadata to type declaration.
import type { User } from 'myLib'
function (user: User) {}
var myLib = require('myLib');
function f(user) {
_assert(user, typeof myLib.User !== "undefined" ? myLib.User : t.Any, 'user');
console.log(user);
}
Entry point is not automatically generated. We need manually create User export.
//myLib/index.js
import type {User as IUser} from 'myLib'
import type { $Reify } from 'tcomb'
const User = (({}: any): $Reify<IUser>)
export User
AFAIK there are 2 cases:
-
myLib
is a library you own, i.e. it's a submodule of your app or you can import the source code and is typed -
myLib
is a third party library that you don't own
In the second case, I guess there's nothing you can do. The published npm module doesn't contain type annotations. All you have is (maybe) a definition file. So if you are using Flow you can still leverage its static type checking, while runtime type checking is out of the question (fallback to t.Any
).
If myLib
is a library that exports tcomb types you can have both (static and runtime) if you do this:
// myLib/index.js
var t = require('tcomb');
exports.User = t.interface({
name: t.String
});
// definition file for myLib
declare module 'myLib' {
declare type User = {
name: string
};
declare var exports: {
User: User
};
}
usage
import type { User } from 'myLib';
function f(user: User) {
console.log(user)
}
f({}) // tcomb throws [tcomb] Invalid value undefined supplied to user: {name: String}/name: String
// flow throws
index.js:5
5: function f(user: User) {
^^^^ property `name`. Property not found in
9: f({})
^^ object literal