-
Notifications
You must be signed in to change notification settings - Fork 12.6k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Augment Key during Type Mapping #12754
Comments
Another relevant example is bluebird's promisifyAll. E.g.: type Before = {
foo: (callback: (err, res) => void) => void
bar: (x: string, callback: (err, res) => void) => void
}
// promisifyAll would map Before to After - note all keys have 'Async' appended:
type After = {
fooAsync: () => Promise<any>;
barAsync: (x: string) => Promise<any>;
}
let before: Before;
let after: After = promisifyAll(before);
|
I think unusual complex type operations like this should be supported by programmatic type builder like #9883. |
Digging this one out since I'm wrestling with a use case for this right now: Scoping redux actions. interface Action<Type extends string> {
type: Type
} to avoid clashes with (e.g.) third party libraries, I scope my actions, for example like this: export const createScopedActionType = _.curry((scope: string, type: string) => `${scope}/${type}`);
const createActionInMyScope = createScopedActionType("MyScope");
const MY_ACTION = createActionInMyScope("MY_ACTION"); It is known at compile time that I could obviously assign the resulting value directly, but that would reduce maintainability since with the approach depicted above I can simply reuse my partially applied function. So I only have to define my scope once. If I were to use regular assignments, I would have to change multiple lines of code (and be in danger of missing an instance!) if the name of the scope would change. A compile time evaluation of functions called with literals (if evaluatable, as in: no side effects, no parameter modification => pure functions) should yield the final type/value returned by the function call. C++ has a similar concept with constant expressions ( I'm not sure in how far Typescript would allow the implementation of such a feature, but it would be a great help in keeping code maintainable and modular. |
It might be nice to revisit this issue in light of the 2.8 enhancements. Specifically, I think this issue is the only thing prevent a good implementation of |
In our library, a
For each component we have an additional
There is a lot of error-prone redundancy in here which caused us a lot of trouble in the past. With the new conditional types feature of TypeScript 2.8 I would love to see a linq-style syntax in indexed types:
|
In Loopback, a DB's order clause is specified as:
or
I think this feature is required for TS to type this field appropriately. |
This is very important for things like using Bluebird.js to promisify everything in a module, as @yortus and @Retsam mentioned previously to this. Using The only thing missing for this to work is the feature requested by this issue: support |
@sb-js Looks like this would be a good place to start, as you'd need to add a bit that would parse a plus sign after a mapped type parameter. |
@drsirmrpresidentfathercharles Thanks for the pointer. I will give it a shot this week. |
another case: interface IActionsA {
update: number;
clear: number;
}
interface IActionsB {
update: number;
clear: number;
}
interface INamespaceMap {
'moduleA/': IActionsA;
'moduleA/moduleB/': IActionsB;
}
// No matter how to write it
type ConnectKey<N, T> = N + [ P in keyof T ];
interface Dispatch<Map> {
<N extends keyof Map, K extends keyof Map[N]>(type: ConnectKey<N, K>, value: Map[N][K]): void;
}
let dispatch!: Dispatch<INamespaceMap>;
dispatch('moduleA/update', 0);
dispatch('moduleA/moduleB/delete', 0); This will bring great help to vuex. |
I am writing a library that converts some values to observables: inteface Foo {
bar: any
}
// the library should convert above interface to
inteface Foo2 {
bar$: any
}
// syntax like below would be cool
type Foo2 = {
[(K in keyof Foo) + '$']: Observable<Foo[K]>
}
// such expressions would be useful not only to mutate property names,
// but other strings in type system as well (this behavior is not desired as much, though):
type Foo = 'bar'
type Foo2 = Foo + 'Addition' // gives the string type "barAddition" |
Another case. {
'@media ios': { ... },
'@media (min-width: 350) and (max-width: 500)': { ... }
} Currently I need to manually enumerate all keys used in app: type ExtraStyles = {
'@media ios'?: ExtendedStyle;
'@media (min-width: 350) and (max-width: 500)'?: ExtendedStyle;
...
} It would be great to define universal type with keys matching regexp: type MediaQueryStyles = {
[/@media .+/i]?: ExtendedStyle;
} |
This would help a lot with supporting dot-syntax keys à la Having |
Supporting dot syntax keys would be awesome. For example MongoDB queries are frequently written as |
Yet another case: I was wondering what could be done in relation to pnp/pnpjs#199 in pnpjs. With pnpjs you can query collections of items in SharePoint and project only a subset of the properties of those items that you will need using the function documents.select('Id', 'Title', 'CreatedBy').get(); The library also supports selecting expanded properties of complex types. In the above example, we would be able to get both the documents.select('Id', 'Title', 'CreatedBy/Id', 'CreatedBy/UserName').get(); It is currently impossible to type the |
Any more info on if this will be a possibility? Dot notation mongo queries are one of the last things missing typing in my project =) |
You can cast?: type StyleType = key of styles
const styles = {
base: {},
primary: {},
seconary: {},
primaryHover: {},
}
const MyButton = ({type} : {type: string}]) => (
// ...
return <Button style={
...styles.base,
...styles[props.type],
hover && ...styles[type + "Hover" as StyleType],
}/>
) |
every time you use I'd like to have an ability to express my intentions with a language syntax and don't use |
I know, it was a lame attempt to solve the problem. |
I need this for almost the exact same reason. Adding "meta" fields corresponding to existing fields in an object. Our current code works by using unsafe casts. I would love to be able to do something like this: interface WithMeta<T extends {}> {
[K in keyof T]: T[K];
[K+'_meta' for K in keyof T]: MetaInfo;
} |
@sk- Although I don’t prefer object oriented APIs over pure functional ones, TS is currently closer to OOP languages feature-wise. So for best practices, I would look at other OOP sound languages, without taking any JS/dynamism specific shortcuts. JS libs had always been conformist to the idea that a library must have an easy to understand README.md; that one function should be able to handle everything with tens of overloads if not hundreds (e.g. moment.js). Encoding valuable information in strings, I accept, was another pattern JS libs have been practicing; but I think those practices must be abolished simply because even if TS supports typelevel string manipulations, interop with other communities and languages will be terrible. Syntax fetishism to the degree of encoding information in strings was never a good idea (typewise) and will never be a good idea until it’s proven to be a practical approach by multiple languages. Can TS take that risk by itself? Did someone do the theoretical background work on typelevel string operations and how they would work with polymorphism? Would that require backtracking in the type resolver? Does TS type resolver support backtracking? As strings are (in theory) list of chars, can we somehow reuse type machinery that we already have for arrays/tuples with strings? I know that everybody watching this thread are trying to solve their real world problems for their real world projects, but those projects will be around for couple for years maybe. The language will potentially stay around longer, keep that in mind. |
I think it does - TS has just changed their internal mapping (in 3.9) to: /* @internal */
const enum TypeMapKind {
Simple,
Array,
Function,
Composite,
Merged,
}
/* @internal */
type TypeMapper =
| { kind: TypeMapKind.Simple, source: ts.Type, target: ts.Type }
| { kind: TypeMapKind.Array, sources: readonly ts.Type[], targets: readonly ts.Type[] | undefined }
| { kind: TypeMapKind.Function, func: (t: ts.Type) => ts.Type }
| { kind: TypeMapKind.Composite | TypeMapKind.Merged, mapper1: TypeMapper, mapper2: TypeMapper }; It used to be a simple function to map A->B, but it looks like it now structures the mapping in a far more verbose and structured way. Its possible this work will make doing mutations easier, as the mappings are recursive. At first glance these should be "invertable" if the reverse B->A is needed. If the TS team exposed an ability to manipulate the bindings/mappings and/or "rebind" during a transformation (plugins) its possible the this issue (Augmenting the Keys) could be done in Userland. |
There seem to be an awful lot of issues being marked as a duplicate of this issue. Many of them propose a feature I would call "Concatenated Types" which is, in my opinion, not at all covered by this proposal. The title of this issue suggests that this feature should only be enabled during type mapping, which would make many "Concatenated Types" impossible. For example, without going to the much more complicated regex types, simple types like this would be incredibly helpful: // Helper Types
type Hex = "0" | "1" | "2" | "3" | "4" | "5" | "6" | "7" | "8" | "9" | "A" | "B" | "C" | "D" | "E" | "F";
type Numeric = "0" | "1" | "2" | "3" | "4" | "5" | "6" | "7" | "8" | "9";
// Concatenated Types
type Color = "#" + Hex + Hex + Hex + Hex + Hex + Hex | "#" + Hex + Hex + Hex;
type Email = string + "@" + string + "." + string;
type ZipCode = Numeric + Numeric + Numeric + Numeric + Numeric; If you'd want to go really crazy, a repeat operator could also be helpful, shortening the Color example to type Color = "#" + Repeat<Hex, 6> | "#" + Repeat<Hex, 3> or something similar (I realize that breaks the usual Standard for Utility Types, as type ZipCode = Numeric * 5 which could offer an "infinite repeat" option for something like type FloatString = Numeric + "." + Numeric * * but it would be perfectly reasonable to reserve the This would also change the way this feature is used in mapped types. The original example would then more likely become something like this: type Changed<T> = {
[ P in (keyof T) + "Changed"]?: Function;
} With the addition of optional parts, Concatented Types could become even more useful for purposes like // Simple concatenated type
type RgbValue = Numeric * 2 | "1" + Numeric * 2 | "2" + ("0" | "1" | "2" | "3" | "4") + Numeric | "25" + ("0" | "1" | "2" | "3" | "4" | "5");
// Concatenated types with optional type
type AlphaValue = "0" | "1" | "0"? + "." + Numeric * *;
type Color = "#" + Hex * 6 | "#" + Hex * 3 | "rgba(" + (RgbValue + ", ") * 2 + RgbValue + (", " + AlphaValue)? + ")"; which is not the most beautiful thing, but it would make stuff like this at least possible without writing 16.5 Million Cases. All this would of course only sense on string types, just like it is the case with the original proposal. Something like |
Personally I think what you're describing is blurring the line between structural type and value/formatting. Ultimately TS is to define structure. While TS does blur the line a little with strings, it only does so in-so-far as the strings boil down to a constant. The "structure" in your examples could perfectly be defined (and works now) via Tuples. type Hex = "0" | "1" | "2" | "3" | "4" | "5" | "6" | "7" | "8" | "9" | "A" | "B" | "C" | "D" | "E" | "F";
type Numeric = "0" | "1" | "2" | "3" | "4" | "5" | "6" | "7" | "8" | "9";
type RGB = ["#", Hex, Hex, Hex, Hex, Hex, Hex];
type RGBA = ["#", Hex, Hex, Hex, Hex, Hex, Hex, Hex, Hex];
type Color = RGB | RGBA;
type Email = [string, "@", string, ".", string];
type ZipCode = [Numeric, Numeric, Numeric, Numeric, Numeric]; there is also some support for repeating, with tuple rest operator: type FloatString = [Numeric, '.', ...Numeric[]]; Although it does not currently let you do the following (others trying to solve it): type X3<T> = [T, T, T];
type X6<T> = [T, T, T, T, T, T];
type Color2 = ["#", ...X6<Hex>] | ["#", ...X3<Hex>] // error In my opinion you would be better served by asking for typescript to support strings as an ArrayLike such that: let a: FloatString = "1.23"; is valid - that way, even though you're working with strings - they can be matched against tuple types. i.e. for any string "1.23" it is implicitly treated as a Tuple ["1", ".", "2", "3"] during evaluation against another Tuple type. |
Supporting strings as an ArrayLike seems like a great idea. Tuples alone for this case is more of a hack than anything else. It's perfectly reasonable to not support concatenating string types due to the problems you described. Nevertheless I think it would be a great feature and thought I should mention it here, since most of the issues related to it are marked as duplicate of this issue. Regex types are the alternative, and in my opinion they go too far and open the door to performance degradation. |
Note: I stumbled upon this problem and I want a proper solution too. In the meantime, for people that are having problems with strings, I found an alternative way of thinking about this with type guards for my problem: type pet = "dog" | "cat" | "default";
const intensity: Record<pet, number> = { dog: 5, cat: 9, default: 12 };
const isPet = (s: string): s is pet => intensity[s as pet] !== undefined;
function getIntensityFromLabel(label: string): number {
if (isPet(label)) return intensity[label];
const [reversedDirectionLabel] = label.split("Reversed");
if (isPet(reversedDirectionLabel))
return intensity[reversedDirectionLabel] * -1;
return intensity.default;
}
getIntensityFromLabel("dogReversed"); // -5
getIntensityFromLabel("dog"); // 5
getIntensityFromLabel("iLoveCake"); // 12 The code should be self-explanatory, but the point is that in my function, which would need an augmented type, I use a more loose type (string), and then use type guards to get what I want. Keep in mind that this approach absolutely does not solve the issue, but for some use cases, type guards are good friends. |
I think the minimum required feature here which seems to me pretty reasonable is literal string type concatenation. I believe any other literal type manipulation is far too complex. type a = 'hhh';
type b = 'ggg';
type ab = a + b; // hhhggg Of course with generic support 😄 |
I'd be fine with that, it it would work:
|
When will this feature be added?, it's too necessary for me. |
I'd also love to see something like this. Right now TypeScript is unable to cope with any code that generates functions at runtime with "template-like" dynamic function names. E.g. code that takes a label/key such as "Users" and creates functions for Currently, the only possible way to handle it is to painstakingly define interfaces for every dynamic method, for every varying label/key. Which is obviously infeasible. |
The workaround for that case is to have the API be like |
@Retsam right, obviously that would be the preferred option. But in cases where you can't make that breaking change or don't have control of a library that works this way, changing the API isn't an option. |
This suggestion is now implemented in #40336. |
Aurelia follows the convention that for any field
foo
, whenfoo
is changed by the framework, then an attempt to callfooChanged()
is made. There is (seemingly) no way to describe this convention as a type (automatically) via the Mapped Type functionality alone.I would like to open discussion to the prospect of augmenting property keys during mapping.
For example: via arthimetic:
in this use-case specifically it probably would require #12424 too, but that is beside the point here.
The text was updated successfully, but these errors were encountered: