-
-
Save infinite-system/12269d96f06b7abe9ad050ec85838bbe to your computer and use it in GitHub Desktop.
class Mapping { | |
constructor (public from: ObjectConstructor) { | |
this.to = from | |
} | |
to: ObjectConstructor | |
scope = 'singleton' | |
onActivation: (resolved, ...args) => {} | |
} | |
export class Kernel { | |
mappings = new Map() | |
instances = new Map() | |
current: Mapping | |
bind (from) { | |
const mapping = new Mapping(from) | |
this.current = mapping | |
this.mappings.set(from, mapping) | |
return this | |
} | |
to (to) { | |
this.current.to = to | |
return this | |
} | |
transient () { | |
this.current.scope = 'transient' | |
return this | |
} | |
singleton () { | |
this.current.scope = 'singleton' | |
return this | |
} | |
onActivation (callback) { | |
this.current.onActivation = callback | |
return this | |
} | |
} | |
export const kernel = new Kernel() | |
export function get<T extends abstract new (...args: any) => any> ( | |
className: T, | |
...args: T extends { new (...args: infer P): any } ? P : never[] | |
): any { | |
const mapping = kernel.mappings.get(className) | |
if (!mapping) { | |
const constructorName = className?.constructor?.name || String(className) | |
throw new Error('Kernel mapping does not exist for ' + constructorName) | |
} | |
// Handle singleton | |
if (mapping.scope === 'singleton') { | |
// Instance exists? Return it. | |
if (kernel.instances.has(className)) | |
return kernel.instances.get(className) | |
// Instance does not exist? Create it. | |
const newInstance = mapping.onActivation | |
? mapping.onActivation(mapping.to, ...args) | |
: new mapping.to(...args) | |
// Save instance. | |
kernel.instances.set(className, newInstance) | |
return newInstance | |
} else { | |
// Handle transient | |
return mapping.onActivation | |
? mapping.onActivation(mapping.to, ...args) | |
: new mapping.to(...args) | |
} | |
} |
You can use .onActivation
to transform the final object that gets returned from the container like this
kernel.bind(RouterRepository).onActivation((resolvedClass, ...args) => {
return iVueBuilder(false, resolvedClass, ...args)
})
I use it to return reactive instances of objects in Vue integration for Infinite UI Architecture.
How does it compare (in your own perspective) to other solutions mentioned in this space, like https://github.com/nicojs/typed-inject?
Hey Carl-Erik!
This was just an initial draft of the Kernel, there is a new version as part of my vue integration package, you can take a look here:
https://github.com/infinite-system/infinite-vue/blob/main/src/kernel.ts
It's a bit different from the version here, the singletons and transients in the example gist share the same namespace, in the newer version in the link, the singletons and transients have their own scope, so you can define:
bind(ClassName).to(SomeOtherClass).singleton()
bind(ClassName).to(SomeAnotherClass).transient()
// To use these you do:
const className = get(ClassName, ...args) // will return a singleton SomeOtherClass instance
const classNameTransient = make(ClassName, ...args) // will return a new SomeAnotherClass instance each time
So I changed the design a bit, that I have 2 functions to use the container(get, make) rather than 1 to make it explicit whether you are using a singleton or a transient.
The main difference is my api is very succinct, most frameworks api for this is very verbose and I don't like it, too much typing involved, as well as doesn't fully suit my needs.
These are just examples, so you can build your own, it's not that difficult if you follow the right concepts.
I am not sure how it fully compares to others, but I get full typescript support in my approach, especially the updated version in the link.
Also my method supports auto-binding.
const someClass = get(SomeClass) // if there is no binding for SomeClass, it will auto-bind to itself and create new singleton instance
const someClassTransient = make(SomeClassTransient) // if there is no binding for SomeClass, it will auto-bind to itself and create a new transient instance
This is useful, so that you do not have to bind anything in startup, unless you want to change the behavior of those classes.
Usage: