To achieve this, you'll need to construct the type of object that you want. The following code allows you to do that.
class Builder<T extends object = {}> {
private _obj: any
constructor(t: T) {
this._obj = { ...t }
Object.keys(t).forEach((key) => (this as any)[key] = (t as any)[key])
}
public set<K extends string, V>(key: K, value: V): Builder<SetResult<T, K, V>> & SetResult<T, K, V> {
(this._obj)[key] = value
return new Builder<SetResult<T, K, V>>(this._obj) as Builder<SetResult<T, K, V>> & SetResult<T, K, V>
}
}
type SetResult<T, K extends string, V> = T & { [k in K]: V }
Use the code this way:
const a = new Builder({})
.set('abc', 55)
.set('def', 'name')
.set('my_complex_variable', { ID: '1234', exists: false })
console.log(a.abc)
console.log(a.def)
console.log(a.my_complex_variable.ID)
The way it works is quite straightforward. Each call to the set
function returns a new Builder
object, which contains all the previous set
calls. Note that each of the fields you access is strongly typed too: abc
is a number, def
is a string. There is some fairly nasty casting to the any
type, but you could potentially clean that up if needed
Here's link to the working code
Edit: I just noticed that if you use a variable name with hyphens in it, Intellisense won't bring it up as a suggestion if you use .
. You can still get type checking on the field, but you'll have to access it using the indexer:
const a = new Builder({}).set('my-variable', 12)
console.log(a['my-variable'])