rtfeldman / seamless-immutable

Immutable data structures for JavaScript which are backwards-compatible with normal JS Arrays and Objects.
BSD 3-Clause "New" or "Revised" License
5.37k stars 195 forks source link

Typescript typings #108

Open jonaskello opened 8 years ago

jonaskello commented 8 years ago

I think this is a nice lib! Unfortunately my project uses typescript so I cannot use it without typings. I did some searches but came up empty so I don't think it exists? The closes I found was this question on stack overflow.

So it seems some ppl are already working on typings for this lib. I think it would be nice if we could co-ordinate the work here.

tyoh commented 8 years ago

+1

zivni commented 8 years ago

Well, this my first try. but it isn't very good:

declare module seamlessImmutable {
    interface ImmutableCommonMethods<T>{
        setIn?<U extends T>(keys: Array<string|number>, value: any): T | U;
        merge?<U>(obj: U): T & U;
    }

    interface ImmutableObjectMethods<T> extends ImmutableCommonMethods<T> {
        set?<U extends T>(key: string, value: any): U;
        asMutable?(): T;
        without?<U>(key: string): U;
        without?<U>(keys: string[]): U;
        without?<U>(...args: string[]): U;
        without?<U>(keyFunction: (value: any, key: string) => boolean): U;
    }

    interface ImmutableArrayMethods<T> extends ImmutableCommonMethods<T> {
        set?<T>(index: number, value: any): Array<T>;
        asMutable?(): Array<T>;
        asObject?<U>(toKeyValue: (item: T) => Array<Array<any>>): U;
        flatMap?<U>(mapFunction: (item: T) => Array<U>)

    }
}
    declare function SI<T>(obj: T, options?): T & seamlessImmutable.ImmutableObjectMethods<T>;
    declare function SI<T>(obj: Array<T>, options?): Array<T> & seamlessImmutable.ImmutableArrayMethods<T>;
rtfeldman commented 8 years ago

Cool! Seems like a useful addition. :smiley_cat:

panKt commented 8 years ago

+1, would be great to have it here!

jonaskello commented 8 years ago

Here is what I have come up with so far. It only allows usage of the lib in typescript. It does not provide compile time error for accessing read-only data.

seamless-immutable.d.ts:

declare module 'seamless-immutable' {

  type Immutable = {
    (obj:any, options?:any):any;
    isImmutable(target:any):boolean;
    ImmutableError(message:string):Error;
  }

  var Immutable:Immutable;
  export = Immutable;

}

Usage:

import * as Immutable from 'seamless-immutable';

var array = Immutable(["totally", "immutable", {hammer: "Can’t Touch This"}]);
array[1] = "I'm going to mutate you!"

The last line in usage will give a runtime error. It would of course be much better if it gave a compile time error. @zivni your typings look interesting but I am not sure how to use them?

Ciantic commented 8 years ago

I'm looking for this as well, but my idea is a bit like the last one above I think.

Use seamless-immutable as a wrapper, and then use lodash or other things to "mutate" and after that freeze again with Immutable.

This way I don't need any API basically more than it's just a function that returns the same thing it took in I think...

That is the theory at least.

Ciantic commented 8 years ago

@jonaskello I think you should define with generic and return with it too:

declare module "seamless-immutable" {

  type Immutable = {
    <T>(obj: T, options?: any): T;
    isImmutable(target: any): boolean;
    ImmutableError(message: string): Error;
  }

  var Immutable: Immutable;
  export = Immutable;
}

So that it type checks / autocompletes the object it returns.

jonaskello commented 8 years ago

Here is a version that combines the additions @Ciantic made with the methods defined by @zivni. It enables typed usage of the original type plus the extra methods that SI provides. I think what is left is to remove the mutation methods that SI bans. However I am not sure how to solve that or if it is even possible?

declare module 'seamless-immutable' {

  interface ImmutableCommonMethods<T>{
    setIn?<U extends T>(keys: Array<string|number>, value: any): T | U;
    merge?<U>(obj: U): T & U;
  }

  interface ImmutableObjectMethods<T> extends ImmutableCommonMethods<T> {
    set?<U extends T>(key: string, value: any): U;
    asMutable?(): T;
    without?<U>(key: string): U;
    without?<U>(keys: string[]): U;
    without?<U>(...args: string[]): U;
    without?<U>(keyFunction: (value: any, key: string) => boolean): U;
  }

  interface ImmutableArrayMethods<T> extends ImmutableCommonMethods<T> {
    set?<T>(index: number, value: any): Array<T>;
    asMutable?(): Array<T>;
    asObject?<U>(toKeyValue: (item: T) => Array<Array<any>>): U;
    flatMap?<U>(mapFunction: (item: T) => Array<U>)
  }

  type Immutable = {
    <T>(obj: Array<T>, options?: any): Array<T> & ImmutableArrayMethods<T>;
    <T>(obj: T, options?: any): T & ImmutableObjectMethods<T>;
    isImmutable(target: any): boolean;
    ImmutableError(message: string): Error;
  }

  var Immutable:Immutable;
  export = Immutable;

}
Ciantic commented 8 years ago

@jonaskello I couldn't get the simple d.ts working when transpiling to es6, only thing working is in this format:

declare module "seamless-immutable" {
    export default function <T>(obj: T, options?: any): T;
    export function isImmutable(target: any): boolean;
    export function ImmutableError(message: string): Error;
    // ...
}

Btw, I've been thinking about something awesome, if JavaScript had API like this:

var frozenObject = Object.freeze({ something: [1,2,3,4,5] });

var mutatedFrozenObject = Object.mutate(frozenObject, (mutateObject) => {
    mutateObject.something[1] = 99;
    mutateObject.newValue = "test";
});

mutatedFrozenObject === {
    something: [1, 99, 3, 4, 5],
    newValue: "test"
};

The API would be probably simple enough for JavaScript engines to optimize the mutation function to work really fast, and it would allow to use all normal libraries to mutate frozen object.

Best of all, it would be 100% type safe way to mutate.

zivni commented 8 years ago

I made a small change to what @jonaskello did to this:

declare module SeamlessImmutable {
    interface DeepMutate {
        deep: boolean
    }

    interface ImmutableCommonMethods<T>{
        setIn?<U extends T>(keys: Array<string|number>, value: any): T | U;
        merge?<U>(obj: U): T & U;
    }

    interface ImmutableObjectMethods<T> extends ImmutableCommonMethods<T> {
        set?<U extends T>(key: string, value: any): U;
        asMutable?(): T;
        asMutable?(DeepMutate): T;
        without?<U>(key: string): U;
        without?<U>(keys: string[]): U;
        without?<U>(...args: string[]): U;
        without?<U>(keyFunction: (value: any, key: string) => boolean): U;
    }

    interface ImmutableArrayMethods<T> extends ImmutableCommonMethods<T> {
        set?<T>(index: number, value: any): Array<T>;
        asMutable?(): Array<T>;
        asMutable?(DeepMutate): Array<T>;
        asObject?<U>(toKeyValue: (item: T) => Array<Array<any>>): U;
        flatMap?<U>(mapFunction: (item: T) => Array<U>)

    }
}

declare module 'seamless-immutable' {
  type Immutable = {
    <T>(obj: Array<T>, options?: any): Array<T> & SeamlessImmutable.ImmutableArrayMethods<T>;
    <T>(obj: T, options?: any): T & SeamlessImmutable.ImmutableObjectMethods<T>;
    isImmutable(target: any): boolean;
    ImmutableError(message: string): Error;
  }

  var Immutable:Immutable;
  export = Immutable;
}

This way I can import using import * as SI from "seamless-immutable" and use it with state = SI<MyType>({...}) but I can also type my functions / variables:

let state: MyType & SeamlessImmutable.ImmutableObjectMethods<MyType >;
...
state = SI<MyType>({...})

and can create helper functions:

export function update<T>(obj:T & SeamlessImmutable.ImmutableObjectMethods<T>, update:any):T {
    return obj.merge(update);
}

export function removeProperty<T>(obj:T & SeamlessImmutable.ImmutableObjectMethods<T>, propertyName:string):T{
    return obj.without<T>(propertyName);
}

I only wish I could find a way to declare ImmutableObjectMethods<T> to include T's members so I wouldn't need to use the & operator everywhere

pleimann commented 8 years ago

@zivni You can declare a type which is the intersection of MyType and SeamlessImmutable.ImmutableObjectMethods so you won't need to use the '&' everywhere.

interface MyType {
    code: number;
    message: string;
};

type ImmutableMyType = MyType & SeamlessImmutable.ImmutableObjectMethods<MyType>;

let mt: ImmutableMyType = {
    code: 1000,
    message: 'It works!'
};

mt = mt.merge({message: 'It still works!'});
dsebastien commented 8 years ago

Did you guys make any progress on this one?

We've decided to use seamless-immutable @ work but we would like to have typings. I'm willing to help work on the typings, so if you have any update, don't hesitate to share (gist?)

jonaskello commented 8 years ago

Here are the latest typings we have, although we do not use them on our project yet:

declare module 'seamless-immutable' {
    interface AsMutableOptions {
        deep:boolean;
    }

    interface ImmutableCommonMethods<T> {
        setIn<U extends T>(keys:Array<string|number>, value:any):T | U;
        merge<U>(obj:U):T & U;
    }

    export interface ImmutableObjectMethods<T> extends ImmutableCommonMethods<T> {
        asMutable():T;
        asMutable(asMutableOptions:AsMutableOptions):T;
        set<U extends T>(key:string, value:any):U;
        setIn<U extends T>(key:Array<string>, value:any):U;
        update<U extends T>(key:string, updaterFunction:(value:any) => any):U;
        update<U extends T>(key:string, updaterFunction:(value:any, additionalParameter1:any) => any, arg1:any):U;
        update<U extends T>(key:string, updaterFunction:(value:any, additionalParameter1:any, additionalParameter2:any) => any, arg1:any, arg2:any):U;
        update<U extends T>(key:string, updaterFunction:(value:any, additionalParameter1:any, additionalParameter2:any, additionalParameter3:any) => any, arg1:any, arg2:any, arg3:any):U;
        update<U extends T>(key:string, updaterFunction:(value:any, additionalParameter1:any, additionalParameter2:any, additionalParameter3:any, additionalParameter4:any) => any, arg1:any, arg2:any, arg3:any, arg4:any):U;
        updateIn<U extends T>(key:Array<string>, updaterFunction:(value:any) => any):U;
        updateIn<U extends T>(key:Array<string>, updaterFunction:(value:any, additionalParameter1:any) => any, arg1:any):U;
        updateIn<U extends T>(key:Array<string>, updaterFunction:(value:any, additionalParameter1:any, additionalParameter2:any) => any, arg1:any, arg2:any):U;
        updateIn<U extends T>(key:Array<string>, updaterFunction:(value:any, additionalParameter1:any, additionalParameter2:any, additionalParameter3:any) => any, arg1:any, arg2:any, arg3:any):U;
        updateIn<U extends T>(key:Array<string>, updaterFunction:(value:any, additionalParameter1:any, additionalParameter2:any, additionalParameter3:any, additionalParameter4:any) => any, arg1:any, arg2:any, arg3:any):U;
        without<U>(key:string):U;
        without<U>(keys:string[]):U;
        without<U>(...args:string[]):U;
        without<U>(keyFunction:(value:any, key:string) => boolean):U;
    }

    export interface ImmutableArrayMethods<T> extends ImmutableCommonMethods<T>, Array<T> {
        set<T>(index:number, value:any):Array<T>;
        asMutable():Array<T>;
        asMutable(asMutableOptions:AsMutableOptions):Array<T>;
        asObject<U>(toKeyValue:(item:T) => Array<Array<any>>):U;
        flatMap<U>(mapFunction:(item:T) => Array<U>):U;
    }

    export function from<T>(obj: Array<T>, options?: any): Array<T> & ImmutableArrayMethods<T>;
    export function from<T>(obj: T, options?: any): T & ImmutableObjectMethods<T>;

    export function isImmutable(target: any): boolean;
    export function ImmutableError(message: string): Error;
}

If I recall correctly we had trouble with the default exported function so we skipped doing typings for that and instead went with using the from() method as it is a named export which is easier to do typings for. So usage would be something like:

import * as SI from 'seamless-immutable'

const myImmutable = SI.from(myObj);

As a side note I am also looking into alternate ways of achieving immutability in typescript. I have found two ways so far. First you can in ts 2.0 (or at least in 1.9 pre-release) declare your interfaces as readonly like this:

interface Foo {
    readonly x:string,
    readonly y:number
}

Second you can try to achieve immutability by tslint rules. I am doing some work on that here.

zivni commented 8 years ago

Thanks for the update. I like to keep the typing (interfaces and so) in a separate module. that way I can just reference the directly without import. With type Immutable = { <T>(obj: Array<T>, options?: any)... as I posted above I can use SI(myObj) without the from function.

Here is my typing after I've included missing typing that @jonaskello added:

declare module SeamlessImmutable {
    interface DeepMutate {
        deep: boolean
    }
    interface ImmutableCommonMethods<T> {
        setIn?<U extends T>(keys: Array<string | number>, value: any): T | U;
        merge?<U>(obj: U, options?: DeepMutate): T & U;
    }

    interface ImmutableObjectMethods<T> extends ImmutableCommonMethods<T> {
        set?<U extends T>(key: string, value: any): U;
        asMutable?(): T;
        asMutable?(AsMutableOptions): T;
        without?<U>(key: string): U;
        without?<U>(keys: string[]): U;
        without?<U>(...args: string[]): U;
        without?<U>(keyFunction: (value: any, key: string) => boolean): U;
    }

    interface ImmutableArrayMethods<T> extends ImmutableCommonMethods<T>, Array<T> {
        set?<T>(index: number, value: any): Array<T>;
        asMutable?(): Array<T>;
        asMutable?(DeepMutate): Array<T>;
        asObject?<U>(toKeyValue: (item: T) => Array<Array<any>>): U;
        flatMap?<U>(mapFunction: (item: T) => Array<U>)
    }
}

declare module 'seamless-immutable' {
    type Immutable = {
        <T>(obj: Array<T>, options?: any): SeamlessImmutable.ImmutableArrayMethods<T>;
        <T>(obj: T, options?: any): T & SeamlessImmutable.ImmutableObjectMethods<T>;
        isImmutable(target: any): boolean;
        ImmutableError(message: string): Error;
        from<T>(obj: Array<T>, options?: any): SeamlessImmutable.ImmutableArrayMethods<T>;
        from<T>(obj: T, options?: any): T & SeamlessImmutable.ImmutableObjectMethods<T>;
    }

    var Immutable: Immutable;
    export = Immutable;
}
dsebastien commented 8 years ago

Thanks a lot @jonaskello. I spent all morning battling against those typings and couldn't find a way to re-export a type corresponding to the Immutable object returned by seamless-immutable... just like you said.

Your comment helped me move forward.

Here's what I've ended up with:

declare module "seamless-immutable" {
                interface ImmutableCommonMethods<T>{
                               setIn?(propertyPath: Array<string|number>, value: any): Immutable<T>;
                               merge?(part: T): Immutable<T>;
                }

                export interface ImmutableObject<T> extends ImmutableCommonMethods<T> {
                               set(property: string, value: any): Immutable<T>;
                               setIn(propertyPath:Array<string>, value:any):Immutable<T>;

                               asMutable(): T;
                               asMutable(opts:AsMutableOptions): T;

                               update(property:string, updaterFunction:(value:any) => any):Immutable<T>;
                               update(property:string, updaterFunction:(value:any, additionalParameter1:any) => any, arg1:any):Immutable<T>;
                               update(property:string, updaterFunction:(value:any, additionalParameter1:any, additionalParameter2:any) => any, arg1:any, arg2:any):Immutable<T>;
                               update(property:string, updaterFunction:(value:any, additionalParameter1:any, additionalParameter2:any, additionalParameter3:any) => any, arg1:any, arg2:any, arg3:any):Immutable<T>;
                               update(property:string, updaterFunction:(value:any, additionalParameter1:any, additionalParameter2:any, additionalParameter3:any, additionalParameter4:any) => any, arg1:any, arg2:any, arg3:any, arg4:any):Immutable<T>;

                               updateIn(propertyPath:Array<string>, updaterFunction:(value:any) => any):Immutable<T>;
                               updateIn(propertyPath:Array<string>, updaterFunction:(value:any, additionalParameter1:any) => any, arg1:any):Immutable<T>;
                               updateIn(propertyPath:Array<string>, updaterFunction:(value:any, additionalParameter1:any, additionalParameter2:any) => any, arg1:any, arg2:any):Immutable<T>;
                               updateIn(propertyPath:Array<string>, updaterFunction:(value:any, additionalParameter1:any, additionalParameter2:any, additionalParameter3:any) => any, arg1:any, arg2:any, arg3:any):Immutable<T>;
                               updateIn(propertyPath:Array<string>, updaterFunction:(value:any, additionalParameter1:any, additionalParameter2:any, additionalParameter3:any, additionalParameter4:any) => any, arg1:any, arg2:any, arg3:any):Immutable<T>;

                               without(property:string):Immutable<T>;
                               without(propertyPath:string[]):Immutable<T>;
                               without(...properties:string[]):Immutable<T>;
                               without(filter:(value:any, key:string) => boolean):Immutable<T>;
                }

                export interface ImmutableArray<T> extends ImmutableCommonMethods<T> {
                               set(index: number, value: any): ImmutableArray<T>;
                               asMutable(): Array<T>;
                               asMutable(opts:AsMutableOptions): Array<T>;
                               asObject(toKeyValue: (item: T) => Array<Array<any>>): ImmutableArray<T>;
                               flatMap(mapFunction: (item: T) => ImmutableArray<T>): any;

                               // TODO review methods (missing ones for arrays?)
                }

                interface Options {
                               prototype?: any;
                }

                interface AsMutableOptions {
                               deep: boolean;
                }

                // an immutable object is both of Type T (i.e., looks like a normal T) and of type Immutable<T>
                export type Immutable<T> = T & (ImmutableObject<T> | ImmutableArray<T>);

                // TODO it would be ideal to be able to expose that type and have the variable available from client code
                // couldn't figure out how to do this unfortunately
                /*
                export type SeamlessImmutable = {
                               <T>(obj: T, options?: Options): T & ImmutableObject<T>;
                               <T>(obj: Array<T>, options?: Options): Array<T> & ImmutableArray<T>;
                               from:SeamlessImmutable;
                               isImmutable(target: any): boolean;
                               ImmutableError(message: string): Error;
                };

                export const Immutable: SeamlessImmutable;
                */

                export function from<T>(obj: T, options?: Options): T & ImmutableObject<T>;
                export function from<T>(obj: Array<T>, options?: Options): Array<T> & ImmutableArray<T>;

                export function isImmutable(target: any): boolean;
                export function ImmutableError(message: string): Error;
}

I'll try to take some time and publish those typings as an npm package. I also made a "global" version for cases where seamless-immutable is loaded as a script tag (i.e., when it attaches Immutable to the window object).

dsebastien commented 8 years ago

The advantage of the above is that in my code I can import the same way you mentionned, but also import the Immutable type separately, which makes for a nicer TS API:

import * as SeamlessImmutable from "seamless-immutable";
import {Immutable} from "seamless-immutable";
...
const immutablePerson:Immutable<Person> = SeamlessImmutable.from(new Person());
...
jonaskello commented 8 years ago

@zivni You may already be aware of this but if you want your typings global like that I would recommend using the namespace syntax instead. The module without quotes syntax is not preferred anymore (since ts 1.5). So instead of declare module SeamlessImmutable { ...} I would recommend declare namespace SeamlessImmutable { ...}. They are totally equivalent so technically it does not matter. You can read more in the official ts documentation:

A note about terminology: It’s important to note that in TypeScript 1.5, the nomenclature has changed. “Internal modules” are now “namespaces”. “External modules” are now simply “modules”, as to align with ECMAScript 2015’s terminology, (namely that module X { is equivalent to the now-preferred namespace X {).

MarkusKramer commented 8 years ago

Nice work. @dsebastien could you add your file to tsd/npm so that anyone can use it easily?

mnasyrov commented 8 years ago

It may be better to include the typings into seamless-immutable package itself (https://www.typescriptlang.org/docs/handbook/typings-for-npm-packages.html), thus no external tsd/npm package will be required.

dsebastien commented 8 years ago

Actually I think typings should be in their own package. That's the way the TypeScript team pushes forward now with TS 2.0+ (see here: https://blogs.msdn.microsoft.com/typescript/2016/06/15/the-future-of-declaration-files/)

The reason is that people don't necessarily want to release a new version of a library because the typings are incomplete or buggy. I guess there are pros and cons to both approaches

jonaskello commented 8 years ago

@dsebastien I was under the impression that the way moving forward is that the typings author should make a PR to the DefinitelyTyped repo, not produce his own npm package. Microsoft will then export DefinitelyTyped to @types npm packages automatically. Could you quote where it says they should be put in their own package?

mnasyrov commented 8 years ago

@dsebastien I think it is good when something forces authors to keep complete and reliable typings).

dsebastien commented 8 years ago

@jonaskello might be the latest news, I don't know ^^ @mnasyrov well I believe in TypeScript so I see its value, but developers of pure JS libs might simply not care enough ^^

cvle commented 7 years ago

I'm experimenting with the types from @dsebastien.

I added this definition:

export function from<T, U>(obj: Array<T>, options?: Options): Array<T & U> & ImmutableArray<T>;

This lets me do the following:

const articleList = SeamlessImmutable.from<Article, ImmutableObject<Article>>(new Array<Article>(...articles));
articleList[0].set("title", "anotherTitle");

Which is quite handy, otherwise I would need to do some ugly type casting to get the correct type for the elements in the array.

Maybe the types in ImmutableArray can be more strict like in set(index: number, value: any): ImmutableArray<T>;, my thought was it should be set(index: number, value: T): ImmutableArray<T>;, but I'm a relative new user of this library, so I'm not really sure.

It'd be awesome if someone has the time to work on a PR on https://github.com/DefinitelyTyped/DefinitelyTyped.

alex3165 commented 7 years ago

I have opened this PR https://github.com/DefinitelyTyped/DefinitelyTyped/pull/11717 from @dsebastien work, I did some questionable choices and I am happy to reply to any questions. I hope it will help :)

alex3165 commented 7 years ago

For information the PR has been merged, feel free to open a new PR on DefinitelyTyped to improve the type definition file in case there are some quirks.

rtfeldman commented 7 years ago

Awesome! If @alex3165 if you'd like to open a PR to seamless-immutable to add a link to the typings in our README, I would be happy to accept it.

Thanks for all the hard work on this!

cubabit commented 7 years ago

How are you supposed to use the type definition in https://github.com/alex3165/DefinitelyTyped/blob/14740e2ee4239622065cbc674d793586f5cf24b7/seamless-immutable/seamless-immutable.d.ts?

I have tried these:

import Immutable from "seamless-immutable";
import {Immutable} from "seamless-immutable";
import * as Immutable from "seamless-immutable";

but neither give me an Immutable function I can use:

const state = Immutable({});

TypeScript says error TS2349: Cannot invoke an expression whose type lacks a call signature.

alex3165 commented 7 years ago

If you use typings to manage your type declaration file :

typings install dt~seamless-immutable --save --global

or now with typescript 2 you can use npm to manage the types :

npm install --save-dev @types/seamless-immutable

Then you have to import seamless-immutable like this :

import * as SI from 'seamless-immutable';
cubabit commented 7 years ago

Thanks, but now If I try and do:

const initialState = SI.Immutable({});

I get:

src/reducers/bookings/reducer.ts(10,22): error TS2349: Cannot invoke an expression whose type lacks a call signature. src/reducers/calendar/reducer.ts(10,25): error TS2339: Property 'Immutable' does not exist on type 'typeof SeamlessImmutable'.

I thought, looking at the code at https://github.com/rtfeldman/seamless-immutable there should be an Immutable function available?

alex3165 commented 7 years ago

Try:

// for an object
SI.from({});
// for an array
SI.from([])
cubabit commented 7 years ago

OK great - Thank you! At least I can get moving now! But why does using the typescript definition mean you can't used the documented interface?

alex3165 commented 7 years ago

I have just ported the last type definition file from above to the definitelyTyped registry and cleaned it a bit so I am not too sure about this from choice. It is something that need to be fixed or documented indeed.

draunkin commented 7 years ago

@alex3165 I had just installed the definitions from npm install @types/seamless-immutable and I get a different file than that hosted on DefinitelyTyped. It seems the one installed using @types... is out of date as I could not get it to work properly with an error stating the from function was not available. The new definition you have at DefinitelyTyped works because it declares the 'seamless-immutable' module correctly. Took me a while to figure out what was going on, not sure the process but maybe you have to update the one installed by @types...

Hope this helps anyone else struggling with it. Cheers

jonaskello commented 7 years ago

Sine typescript 2.0 It is now possible to achieve immutability at compile-time by just using the readonly keyword and ReadonlyArray. That is what I have been doing lately. I also made tslint-immutable which uses tslint rules to make sure that readonly is enforced.

MichaelTontchev commented 7 years ago

@jonaskello readonly is great, but the problem is that it can't be made recursive yet, pending https://github.com/Microsoft/TypeScript/issues/10725

As to your library, I haven't used it yet, but it looks nice. I just wish that you could limit the linting rules to certain files: https://github.com/jonaskello/tslint-immutable/issues/25

babakness commented 7 years ago

Commenting to follow this conversation.

toranb commented 7 years ago

I ended up writing a new type definition file for seamless-immutable 7 that works well with TypeScript 2.4

I've had success writing product types like you see below

import { ImmutableObject } from 'seamless-immutable';

type ImmutableState<T> = ImmutableObject<T> & T;

export default ((state: ImmutableState<UserState>, action: Action): ImmutableState<UserState> => {
  switch(action.type) {
    case 'ASSIGN_USER': {
      return state.merge({number: action.number});
    }
    default: {
      return state || initialState;
    }
  }
});

For anyone interested, I threw together a full example app with redux/typescript 2.4/seamless 7.1.2

Connormiha commented 6 years ago

@toranb What about Immutable.static?

toranb commented 6 years ago

@Connormiha I'm down to add something for it - my hacking was mostly for methods I was using (if this is something you are interested in providing type defs for I'd be willing to update it). Might ping me on that repo instead to start a thread about it

lifehackett commented 6 years ago

@toranb @Connormiha where did y'all end up with the static typings?

toranb commented 6 years ago

@lifehackett I've got a privately maintained typedef for this lib that works w/ typescript 2.7

https://github.com/toranb/seamless-immutable-tsdefs/commits/master

note: I'm not actively using much typescript these days but I did manage to throw together a simple reducer using these^ typedefs and seamless immutable a while back

https://github.com/toranb/ember-redux-yelp/blob/branches/seamlessAndTypeScript/app/reducers/users.ts

lifehackett commented 6 years ago

Thanks for the response @toranb . I don't see a fix for the static typings in your typedef repo though. The typedefs in this lib and your repo work fine for something like state.set('foo', 'bar'), but fall apart when you use the static syntax (which is recommended by the seamless-immutable authors) like Immutable.set(state, 'foo', 'bar').

I am able to extend it locally like this

declare module 'seamless-immutable' {
  export function set<K extends keyof T>(obj: ImmutableObject<T>, property: K, value: T[K]): ImmutableObject<T>;
  export function set<TValue>(obj: ImmutableObject<T>, property: string, value: TValue): ImmutableObject<T>;
}

Not sure if that is exactly the correct signature as this is my first foray into TS, but it does remove the TS errors. I'll look to contribute these back to the repo when I have some time to flush them all out, but thought I'd check that it hadn't already been done first. Thanks again

toranb commented 6 years ago

@lifehackett fair point! I've also not tried this yet w/ typescript v2.8+

I'm happy to add you as a contributor to that one off typedefs repo if you get deep into it ;)

Fire7 commented 6 years ago

Why do getIn return type Immutable<T> , not ImmutableObject<T> ?

interface IInterface {
  str: string;
}

type TImmutableType = ImmutableObject<IInterface>;

const immutableObject: TImmutableType = Immutable({
  str: 'simple string' 
});

const getString = (): string => immutableObject.getIn(['str']).asMutable();
                      ^^^^^^^
TS2322: Type 'string | string[]' is not assignable to type 'string'. 
  Type 'string[]' is not assignable to type 'string'

Is it a bag? Do we need to duplicate typings of getIn in ImmutableArray ? Or how I can fix it?