0

I put together a simple Store implementation which wraps an immutable javascript 'root' item as state.

A partition operation allows a child Store to be derived from a parent store using a key. The new Store's root state is the child of the original root state as found at that key. The child store otherwise functions the same as the root store.

The implementation seems to work fine, but unfortunately I have a compilation error for the partition operation when (for other reasons) I'm required to limit possible root items to extends object. Of course only some child items will satisfy this constraint, meaning typescript spots a possible error from recursive partitioning.

I think I need to limit partition key arguments to keys which resolve to a child that extends object. I am unsure how to do so.

Everything is working well in terms of implementation and typings until this intervention.

A definition which looked positive and compiled (before adding the object constraint) is this one...

type Partitioner<State> = <Key extends keyof State>(key:Key) => Store<State[Key]>;

interface Store<State> {
    read():State
    write(state:State):void;
    partition: Partitioner<State>
}

class BasicStore<State> implements Store<State> {
    state:State;
    constructor(state:State){
        this.state = state;
    }
    read = () => this.state;
    write = (state:State) => this.state = state;
    partition:Partitioner<State> = <Key extends keyof State>(key:Key) => new BasicPartition(this, key);
}

export class BasicPartition<State, Key extends keyof State>
  implements Store<State[Key]> {
  constructor(readonly store: Store<State>, readonly key: Key) {}
  read = () => this.store.read()[this.key];
  write = (state:State[Key]) => this.store.write({
      ...this.store.read(),
      [this.key]:state
  })
  partition:Partitioner<State[Key]> = <SubKey extends keyof State[Key]>(subKey:SubKey) => new BasicPartition(this, subKey);
}

See typescript playground

The compilation error can be seen in the below version of the same code where BasicStore and BasicPartition limit their State to extends object...

type Partitioner<State> = <Key extends keyof State>(key:Key) => Store<State[Key]>;

interface Store<State> {
    read():State
    write(state:State):void;
    partition: Partitioner<State>
}

class BasicStore<State extends object> implements Store<State> {
    state:State;
    constructor(state:State){
        this.state = state;
    }
    read = () => this.state;
    write = (state:State) => this.state = state;
    partition:Partitioner<State> = <Key extends keyof State>(key:Key) => new BasicPartition(this, key);
}

export class BasicPartition<State extends object, Key extends keyof State>
  implements Store<State[Key]> {
  constructor(readonly store: Store<State>, readonly key: Key) {}
  read = () => this.store.read()[this.key];
  write = (state:State[Key]) => this.store.write({
      ...this.store.read(),
      [this.key]:state
  })
  partition:Partitioner<State[Key]> = <SubKey extends keyof State[Key]>(subKey:SubKey) => new BasicPartition(this, subKey);
}

An error is reported from the last line Argument of type 'this' is not assignable to parameter of type 'Store<object>' See typescript playground

How can I limit valid Key arguments to the partition function so that State[Key] is always an object and therefore eliminate the invalid code path. In pseudo-code I think the Partitioner generic argument might look something like Key extends keyof State where State[Key] extends object. Where this union resolved to never there would be no valid arguments to call partition at all.

cefn
  • 2,895
  • 19
  • 28
  • You mean something like [this](https://tsplay.dev/Wyv3Jw) – Eldar Apr 27 '21 at 01:40
  • That works really well. I'd failed to understand that I could narrow the extends without actually excluding types having non-object children, it's just that any non-object children can't be seen by the partitioned store. I wrote up a runnable example based on your code [here](https://tsplay.dev/Nalnow) which I would accept as an answer. It proves you can have a State type with non-object children and it's fine. – cefn Apr 27 '21 at 07:58
  • Unfortunately tsplay.dev is no longer with us. Was just getting back to this project to use your fix. I remember the approach though so can recreate. – cefn Apr 27 '21 at 20:47

0 Answers0