A custom "iterable" class
Published: 2023-07-08 06:31:28

Let's say you want to create a custom class, this class has a constructor parameter. For this example let's say we want to manage a "person", more like a "Person".

```ts
type PlainPerson = {
  name: string,
  age: number,
}
```

Let's say from this plain object we want to add some cool getters or methods:

```ts
class Person {
  constructor (public value: PlainPerson) {}
  get name () {
    return this.value.name;
  }
  get age () {
    return this.value.age;
  }
  get isOver20 () {
    return this.value.age >= 20
  }
}
```

Let's now say I want to iterate over many people. I can easily use an Array. 

```ts
const john = new Person({ name: 'john', age: 30 })
const people = [john]
const filteredPeople = people.filter(person => person.isOver20)
```

And this works, this is perfectly acceptable. But what if you wanted more?

What if you wanted a collection of `Person` to do more then just what a normal array could provide?

What if I wanted to add all ages up for all the "people"?

Then I might consider a `People` class as well.

```ts
class People {
  get totalAge () {
    return value.reduce((acc, person) => person.age + acc, 0)
  }
}
```

But now the question is what should the constructor of `People` recieve? Should it recieve `Person`, or `PlainPerson`? Should it recieve an array of these? Can they be spread? Can `People` recieve many `People` and flatten them down? 

I found myself asking these questions and decided to create something that would fix this issue and create a basic reproducable structure for any custom iterable. It kind of works like this:

```ts
const [Person, People] = FactoryIterable()
```

This returns two standard classes that you could use similar to how I described above. A `Person` and `People` they don't have any custom methods yet. In order to add them we need to extend these base-classes. So it may be best to rename them. I usually use the prefix `Proto`. 

```ts
const [ProtoPerson, ProtoPeople] = FactoryIterable<PlainPerson>({
  multipleConverter: (value: PlainPerson | PlainPerson[] | undefined): PlainPerson[] => {
    if (value instanceof Array) {
      return value;
    } else if (typeof value === "object") {
      return [value];
    }
    throw new Error("Invalid value");
  },
})

class Person extends ProtoPerson {
  get name () {
    return this.value.name;
  }
  get age () {
    return this.value.age;
  }
  get isOver20 () {
    return this.value.age >= 20
  }
}

class People extends ProtoPeople<typeof Person, typeof People> {
  arrayMethods = this.generate(Person, People);
  get totalAge () {
    return value.reduce((acc, person) => person.age + acc, 0)
  }
}
```

The coolest thing about how these work is the input resolving. A `Person` can take `PlainPerson` and

```ts
const plainJohn = { name: "John", age: 20 }
const john = new Person(plainJohn)
const john2 = new Person(john)

assertEquals(john.value, plainJohn)
assertEquals(john2.value, plainJohn)

const a = new People(plainJohn)
const b = new People(john)
const c = new People(a)
const d = new People(b)
const e = new People([plainJohn])
const f = new People([john])
const g = new People([a])

assertEquals(a.value, [plainJohn])
assertEquals(b.value, [plainJohn])
assertEquals(c.value, [plainJohn])
assertEquals(d.value, [plainJohn])
assertEquals(e.value, [plainJohn])
assertEquals(f.value, [plainJohn])
assertEquals(g.value, [plainJohn])
```

That's a pretty bare-bones overview of how you'd use it. There are a could of extra details when it comes to input types and even circular input types, as well as adding generics. I have a couple examples of `FactoryIterable` in the wild as well as the source code. 

Here's a main working test that demos this Person / People class https://github.com/reggi/tree_lint2/blob/main/factory_iterable/test.ts

Here's the source https://github.com/reggi/tree_lint2/blob/main/factory_iterable/mod.ts

Here are a couple other examples:

Node:
https://github.com/reggi/tree_lint2/blob/main/graph/node/mod.ts

EdgeNode https://github.com/reggi/tree_lint2/blob/main/graph/edge_node/mod.ts

Edge https://github.com/reggi/tree_lint2/blob/main/graph/edge/mod.ts