typed-immutable / typed-immutable Goto Github PK
View Code? Open in Web Editor NEWImmutable and structurally typed data
License: MIT License
Immutable and structurally typed data
License: MIT License
Example:
var {Record, List} = require("typed-immutable");
var Point = Record({x: Number(0), y: Number(0)})
var Points = List(Point, "Points")
ps = Points.of({x:3}, {y: 5})
ps.map(a => a.x || null);
this throws a "TypeError: Cannot read property 'constructor' of null"
I believe what is happening here is that the type inferer is not correctly returning a new List(Maybe(Number))
.
Im proposing somethings like custom type Called Enum
(I didnt test this)
const Enum = (values) =>{
if (typeof(values) !== 'array') {
return TypeError(`"${value}" is not an array`);
}
const map = {};
values.each((val) => {
map[val] = val;
});
var validValues = values.toString();
return Typed(`Enum(${ validValues})`, value => {
if (!map[value]){
return TypeError(`"${value}" in ${validValues}`);
}
return value
})
}
var enRecord = Record({
enumField: Enum(['A','B'])
})
enRecord.set('enumField', 'B') //Succeed
enRecord.set('enumField', 'C') //Throw an Error
Is there any other workarounds to this?
The polyfill for Symbol (https://github.com/typed-immutable/typed-immutable/blob/master/src/typed.js#L3) always overwrites the native Symbol type since the var
declaration is hoisted outside of the if statement, so that polyfill actually will act as the following:
var Symbol
if (typeof(Symbol) === 'undefined') {
Symbol = hint => `@@${hint}`
Symbol.for = Symbol
}
This means that you cannot use Symbol
as a type, i.e. Map(Symbol, String)
will fail.
I am afraid I do not have time to work on this project, so if someone want's to step up and move it forward review / land pull requests respond to issues etc.
Is there any way to handle recursive types? I.e.
import {Record, List} from 'typed-immutable';
var Node = Record({
data: String,
subNodes: List(Node)
});
One can't do the above, as Node is not defined yet, when defining subNodes. Is there a way to accomplish this now, or do you have plans for this capability?
One possibility would be to add a constant, say "CurrentType", that would tell Record to use it's type for that member when creating new records. I.e.:
import {Record, List, CurrentType} from 'typed-immutable';
var Node = Record({
data: String,
subNodes: List(CurrentType)
});
Thoughts?
I'm somewhat new to react/redux and have been struggling with how to handle "derived data." A trivialized example is given raw data like
let person = {first: 'Bob', last: 'Fish'}
what is the appropriate way to define person.fullName
? Or should I just have personUtils.fullName(person)
? It seems if I were to use typed-immutable to define the structure of my data I could also define methods/ property-getters to define derived data.
Good idea/ bad idea?
While using this library with immutable-js, we are encountering a typedError "setSize may only downsize".
But there could be cases in which list size might increase. Why there is this restriction of downsize?
After using typed-immutable on multiple projects now the biggest problem that comes up is tracking down why a type error occurs - particularly when restoring a large nested structure from a raw JS object.
As an example:
const Address = Record({
line1: String,
city: String,
country: String,
});
const Person = Record({
id: Number,
name: String,
email: String,
age: Number,
address: Address,
});
const People = new Map(Number, Person);
new People([[1, {
id: 1,
name: 'Bob',
email: '[email protected]',
age: 100,
address: {
line1: 5,
}
}]]);
currently gives an error
TypeError: Invalid value: Invalid value for "address" field:
Invalid value for "line1" field:
"undefined" is not a string
This is ok when you have the context and know what structures it's referring to be in many cases you don't or the fields are generic enough that it's hard to identify where it comes from. It's also very useful to know the data that failed.
I've been looking at improving them, eg. the above is now:
TypeError: Entry in Map(Number, Person) failed to satisfy value type:
Key:
1
Value:
{
"id": 1,
"name": "Bob",
"email": "[email protected]",
"age": 100,
"address": {
"line1": 5
}
}
Failed to create instance of Person
Value for field 'address' must satisfy type 'Address'
Failed to create instance of Address
Value for field 'line1' must satisfy type 'String'
Invalid value: "5" is not a string
My only concern with adding this is that it will be a bit slower to generate this level of detail (eg. doing a JSON.stringify
on value). In my use cases so far it wouldn't matter as I never catch the TypeError
's - if they occur it's a bug that we fix.
Does anyone have any thoughts about this? Should it be opt in? Am I overthinking it? I also haven't measured anything yet - I think it may only become a problem if you were generating a lot of these (eg. iterating a large list attempting to instantiating record's and handling any errors).
immutable-js 3.7.6 implements List.insert(): https://github.com/facebook/immutable-js/releases/tag/3.7.6
Please consider upgrading from 3.7.0
It'd be nice to be able to coerce convertible types to other types.
const PersonRecord = Record({
id: String,
phone: Coerce(Union(Number, String), String),
}, 'Person');
In this hypothetical example if we did the following:
const p1 = PersonRecord({ id: 0, phone: 12318001800 });
accessing p1.phone
would give us a string.
We'd have to keep a map of how we get from one type to others. This could be hard-coded or supplied at run-time.
I have gone through today and tried to do "low hanging fruit" things, and move along the pull requests. We now only have three that are active, and two should be merged soon. Once #18 and #29 are merged, which can hopefully be in a couple of days, I would like to do a release.
I will write up a changelog and such and make a pull request beforehand, so everyone will be able to see it. Does anyone have any objections to this?
Hello,
thanks for this useful library !
Is it possible to switch/if/match on an Union type to determine the underlying concrete type ?
Any plans to supports function signatures as parameters to a record? and maybe enums as well?
At the moment if you define types with a following signatures:
const X = Record({x: 0}, 'X')
const Y = Record({y: 0}, 'Y')
const C = Record({ data: Union(X, Y) })
Then type union interprets data
value not always as one would expected:
C({data: {x: 2}}) // => Typed.Record({data: Union(X, Y)})({ "data": X({ "x": 2 }) })
C({data: X({x: 5})}) // => Typed.Record({data: Union(X, Y)})({ "data": X({ "x": 5 }) })
C({data: Y({y: 3})}) // => Typed.Record({data: Union(X, Y)})({ "data": Y({ "y": 3 }) })
C({data: {y: 2}}) // => Typed.Record({data: Union(X, Y)})({ "data": X({ "x": 0 }) })
Most likely in last statement data
field was expected to be an instance of Y
instead of X
, although given that {y: 2}
can be read both as X
and Y
it was interpreted as a first type in the union X
.
It maybe better to disallow passing untyped values all together in order to avoid this issue.
Calling flatMap on a List doesn't work. Can get around it by just doing a map then a flatten.
var {Record, List} = require("typed-immutable")
var Item = Record({
ids: List(Number)
});
var Items = List(Item);
var a = new Item({ids: [1,2,3,4]})
var b = new Item({ids: [5,6]})
var items = new Items([a, b])
// Array [1, 2, 3, 4, 5, 6]
items.map(item => item.ids).flatten().toJS();
// doesn't work
// TypeError: Invalid value: Invalid data structure "1" was passed to Typed.Record({ids: Typed.List(Number)})
items.flatMap(item => item.ids).toJS()
// Using immutable List directly works
var immutable = require('immutable');
items = immutable.List([a,b]);
// Array [1, 2, 3, 4, 5, 6]
items.flatMap(item => item.ids).toJS()
At the moment if you define types with a following signatures:
const X = Record({x: 0}, 'X')
const Y = Record({y: 0}, 'Y')
const C = Record({ data: Union(X, Y) })
Then type union interprets data
value not always as one would expected:
C({data: {x: 2}}) // => Typed.Record({data: Union(X, Y)})({ "data": X({ "x": 2 }) })
C({data: X({x: 5})}) // => Typed.Record({data: Union(X, Y)})({ "data": X({ "x": 5 }) })
C({data: Y({y: 3})}) // => Typed.Record({data: Union(X, Y)})({ "data": Y({ "y": 3 }) })
C({data: {y: 2}}) // => Typed.Record({data: Union(X, Y)})({ "data": X({ "x": 0 }) })
Most likely in last statement data
field was expected to be an instance of Y
instead of X
, although given that {y: 2}
can be read both as X
and Y
it was interpreted as a first type in the union X
. While this is bad (see #2) it does not seem to be a common issue in my experience.
What happens far more often in my experience is that new type is defined which supposed to be added to a type union (but for whatever reasons it was not added to type union):
const Z = Record({ z: 0 })
C({data: Z({z: 5})}) // => Typed.Record({data: Union(X, Y)})({ "data": X({ "x": 0 }) })
Now surprise and a problem is far worse in this case, as passed data
field had a type but it got casted to a completely different type. There is also no simple way to avoid or spot this issue.
Really nice project.
One of the main benefits to having types in code is that during compile time (or during your transpiling process) you can be warned of places where you have set the wrong type or called using the wrong type.
Typescript for example will warn you if you do something like:
Point({x: "1", y: 1});
The above will output an error explaining you have used a string instead of number type.
Would building a task to check the codes type safety be within the scope of this project?
Maybe using esprima and the parsing down the tree?
It's something I might try and help with if you feel it's within the scope, but thought I would ask as it would be good to get your opinions on this before starting extra work on this.
Currently used patten in most code is something along these lines:
const Model = Record({value: Number})
const Increment = Record({label: '+'});
const Decrement = Record({label: '-'});
const Action = Union(Increment, Decrement);
const update = (model, action) =>
action instanceof Increment ? model.update('value', x => x + 1) :
action instanceof Decrement ? model.update('value', x => x - 1) :
model;
With a pattern matching it should be possible to turn it to something like this:
const update = Match(Match._, Match.pattern)
.case(Increment, (model, _) => model.update('value', x => x + 1))
.case(Decrement, (model, _) => model.update('value', x => x + 1))
.default((model, _) => model)
Hi,
I'm using typed-immutables
in a react project. I have a type called Card
and it's passed as a prop to CardComponent
. Similarly there's CardList
and CardListComponent
const Card = Record({
id: String,
title: String
}, 'Card')
export const CardList = List(Card, 'CardList')
export default Card
How would I validate them with react PropTypes? Are there any methods that I can use, that go as follows
const CardComponent = (card) => {
return (
<div>${card.title}</div>
)
}
CardComponent.propTypes = {
card: Card.isRequired // or Card.isOptional
}
How do I go about doing those validations now? Validations for CardList
seem to be harder. What would be a good way to implement these validations?
I have merged #24 and we now have automated testing on all pull requests. I also set up master
as a protected branch in GitHub, which means it is no longer possible to push to it directly; anything has to be a pull request and Travis tests must pass.
I have not set up Travis to auto-publish to npm. I can do that if we want, although I might want some coaching from @udnisap about the right way to do that.
I also think it would be useful to have a couple of process discussions. What should our rule on PR merging be? This is a pretty small project, so I think that, in general, the rule should be a +1 from any other person.
I would also like to take a pass through the code and add more commenting (and probably a linter), as well as do a thorough sweep through the documentation. Would that be something that you all would welcome, or find annoying?
Finally, would it be valuable to have a (presumably low volume) Slack (or similar) channel, or do we want to continue to use GitHub for discussion? I know I am not always the best at checking GitHub; not sure if that is a concern for others.
Is there any way to handle recursive types? I.e.
var Node = Record({
I noticed there seems to be support for Map but it's not documented anywhere and has to be imported from 'typed-immutable/lib/map'. Any reason for that (ie. experimental, has issues)?
A declarative, efficient, and flexible JavaScript library for building user interfaces.
๐ Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. ๐๐๐
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google โค๏ธ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.