Expanding the scope of store key restrictions with a flexible late-binding function

Within the code snippet below, a

StoreOp<"vanilla">
is intended to function on a Store with "vanilla" as a flag, along with other flags. Unfortunately, the current constraints are incorrect, restricting a
StoreOp<"vanilla">
to only operate on a
FlagStore<"vanilla">
instead of also allowing it to work on a
FlagStore<"vanilla", "chocolate">
.

The calls to setVanilla and setChocolate are generating errors due to this mismatch. A successful resolution would eliminate these errors, as noted in the comments against the final four lines of code.

An example of the error message displayed is...

Argument of type 'Store<{ vanilla: boolean; chocolate: boolean; raspberry: boolean; }>' is not assignable to parameter of type 'FlagStore<"vanilla">'.
  Types of property 'write' are incompatible.

I am seeking guidance on how to define constraints for the generic function type StoreOp, and the corresponding factory function createSetterOp, so that any Store containing the Operated flags AMONG its Stored flags is deemed a valid late-bound argument for the StoreOp. Presently, the constraint

S extends FlagStore<Operated>
is set in the wrong direction, and I am struggling to determine how to specify it in the opposite direction - where S includes Operated and additional flags.

A StoreOp should be able to rely on each of its Operated flags having a respective boolean value in the store, without being concerned about the availability of other flags. In defining the generic function StoreOp, Operated should be assignable to

Stored</code, rather than vice versa. I anticipate the late-bound typing of the generic function against the actual store will ensure that an edit made by a <code>StoreOp
implementation aligns with the Store's type, triggering a compiler error if it fails to copy over the other (unknown) flags when updating the store.

My broader API requires inference from both the Stored flags and the Operated flags to dictate other typings. The crux lies in expanding the StoreOp type effectively.

You can find the code below in this Typescript Playground

Answer №1

Despite the absence of direct support in TypeScript for a lower bound constraint as requested on this issue, there is a workaround using the union operator. Instead of V extends Flag super T, you can use T | U where U extends Flag. Take a look at how StoreOp should be defined:

type StoreOp<T extends Flag> =
  <U extends Flag>(store: FlagStore<T | U>) => void;

The problem arises when assigning a StoreOp<X> to a StoreOp<Y> even if X and Y are incompatible due to a compiler shortcut that assumes StoreOp<T> is bivariant in

T</code instead of <em>invariant</em>. This can be addressed with variance annotations as shown below:</p>
<pre><code>type StoreOp<in out T extends Flag> =
  <U extends Flag>(store: FlagStore<T | U>) => void;

You can modify the modifiers to achieve covariance or contravariance. The key is to guide the compiler correctly when comparing different instances of StoreOp.


Let's put it into practice:

setVanilla(manyStore); // valid
setVanilla(fewStore); // valid
setChocolate(manyStore); // valid
setChocolate(fewStore); // results in an error

// error anticipated
const shouldFail: StoreOp<"chocolate"> = setVanilla; 

Looks like everything is working as expected!

Here's the link to test the code yourself.

Similar questions

If you have not found the answer to your question or you are interested in this topic, then look at other similar questions below or use the search

When implementing useReducer with TypeScript, the error "Argument of type '(type, action) => { state: (...}' is not assignable to parameter of type 'ReducerWithoutAction<any>'" may occur

Recently, I decided to delve into learning TypeScript by building a simple shopping cart application. If you want to check out the code, feel free to visit my GitHub repository: https://github.com/CsarGomez/shopping-cart-reducers-tx I've encountered ...

Error: An unexpected token < was caught in the TypeScript Express code

Using TypeScript to compile and run an Express server that simply serves an HTML file. However, encountering an error in the response under the network tab in Chrome for app.js: Uncaught SyntaxError: Unexpected token '<' Below is the server c ...

What benefits does redux-thunk offer?

Why is redux-thunk necessary? It seems like using a thunk just adds an extra layer of complexity by wrapping expressions and using middleware. The sample code from redux-thunk further confuses the process. import thunk from 'redux-thunk'; // No ...

Timestamps are no longer recognized in Highstock charts once data has been added

In my highstock chart, I am pulling data from a REST api and everything appears correct. However, there is no data available between 19:00 and 05:00. I would like this absence of data to be reflected in the chart without cropping out that time span from th ...

Refreshing functional component upon change in property of different class [MVVM]

I've been tasked with incorporating MVVM into React for a class assignment. To achieve this, I've opted to use functional components for the view and TypeScript classes for the ViewModel. However, despite successfully updating properties in the V ...

The Challenge of Iterating Through an Array of Objects in Angular Components using TypeScript

Could someone please explain why I am unable to iterate through this array? Initially, everything seems to be working fine in the ngOnInit. I have an array that is successfully displayed in the template. However, when checking in ngAfterViewInit, the conso ...

Neglectful TypeScript null checks overlooking array.length verification

When TypeScript is compiled with strict null checks, the code snippet below does not pass type checking even though it appears to be correct: const arr: number[] = [1, 2, 3] const f = (n: number) => { } while (arr.length) { f(arr.pop()) } The comp ...

Tips for specifying which project starts the setup process in Playwright

I have multiple projects in my playwright.config file, all of which have the same setup project as a dependency. Is there a way to determine at runtime which parent project is initiating the setup process? playwright.config projects: [ { name: " ...

"The Promise of Generics in Typescript: Unlocking Unique Return

In my current project, I am working on implementing a function that will always return a fulfilled promise of the same "type" that is passed to it as a parameter. This means that if I call the function with a boolean parameter, it should return a fulfille ...

Generating dynamic types based on conditions in TypeScript

I have a starting type called BaseType, and I am looking to dynamically extend it by combining multiple types based on a discriminating value. For example, if the discriminating value is set to "foo" or "bar", I would create separate types like FooType and ...

Unpacking a props object results in an undefined value

I've been struggling to set up a data-grid in react because I'm facing issues with accessing the data from my props. Whenever I try to access or destructure the prop, it shows up as "undefined" in my console. This problem only arises when the p ...

What sets apart using (@Inject(Http) http: Http) from not using it?

Following a recent query, I now have a new question. What sets apart these two approaches? Here is the original code I used: import {Http, HTTP_PROVIDERS} from 'angular2/http'; @Component({ viewProviders: [HTTP_PROVIDERS], ..// constructor(h ...

Tips for retrieving a reactive variable from a setup() method?

I'm currently working on a small notes app and using Vue3 + Typescript to enhance my skills. The following code snippet demonstrates how to dynamically create and display an Array of notes: <template> <q-layout> <div v-for ...

What is the purpose of manually creating the vscode.d.ts file in the vscode source code?

Manually writing .d.ts files is typically only necessary when working with existing .js files. If you're developing a TypeScript project, it's recommended not to write .d.ts files by hand, as the compiler with the --declaration option can auto-ge ...

Tips for programmatically focusing on a v-textarea using Vuetify and TypeScript

It feels impossible right now, I've attempted some unconventional methods like: (this.refs.vtextarea as any).textarea.focus() ((this.refs.vtextarea as Vue).$el as HTMLElement).focus() and so on... Javascript source code is quite complex for me to ...

What is the process of incorporating a JavaScript node module into TypeScript?

Having trouble importing the xml2js module as I keep getting a 404 error stating that it's not found. import xml2js from 'xml2js'; Any suggestions on how to properly import JavaScript modules located in the node_modules directory when work ...

Using Angular, a class can serve as a method within another class

I am new to Angular2 and encountering a problem. I have developed a component called info. In the info.component.ts file, I am initializing objects in the following way: import { Comm } from '../shared/comments'; ... const VASACOM: Comm = { na ...

What's the best way to replicate a specific effect across multiple fields using just a single eye button?

Hey everyone, I've been experimenting with creating an eye button effect. I was able to implement one with the following code: const [password, setPassword] = useState('') const [show, setShow] = useState(false) <RecoveryGroup> ...

Adding conditional props to a style object in @emotion/react with Typescript is as simple as utilizing a ternary operator

https://i.sstatic.net/KWrCP.png Can someone help me understand how to use props to control my CSS in emotion? ...

What is the correct way to type this object conversion?

I'm trying to ensure type safety for a specific converting scenario. The goal is to map over the palette object and convert any entries to key-value pairs, similar to how tailwindcss handles color configurations. However, I am facing an issue where th ...