babel-plugin-tcomb icon indicating copy to clipboard operation
babel-plugin-tcomb copied to clipboard

Situations with metadata, when flow definition placed in external library

Open zerkalica opened this issue 8 years ago • 4 comments

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'

zerkalica avatar Jun 10 '16 14:06 zerkalica

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?

gcanti avatar Jun 10 '16 15:06 gcanti

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

zerkalica avatar Jun 10 '16 16:06 zerkalica

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).

gcanti avatar Jun 10 '16 17:06 gcanti

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      

gcanti avatar Jun 10 '16 17:06 gcanti