Skip to content

Instantly share code, notes, and snippets.

@gaievskyi
Last active May 3, 2024 12:01
Show Gist options
  • Save gaievskyi/5714a13698ae099b6e69a632104c211c to your computer and use it in GitHub Desktop.
Save gaievskyi/5714a13698ae099b6e69a632104c211c to your computer and use it in GitHub Desktop.
Make a class Singleton using decorator (via Proxy). Enable decorators by setting "experimentalDecorators": true in your tsconfig.json.
import { describe, expect, it } from "bun:test"
import { Singleton } from "./singleton"
describe("@Singleton", () => {
it("makes class singleton", () => {
@Singleton
class Foo {
constructor(public bar: string) {}
}
class Bar {}
const SingletonBar = Singleton(Bar)
const foo1 = new Foo("bar")
const foo2 = new Foo("??????")
const bar1 = new SingletonBar()
const bar2 = new SingletonBar()
expect(foo2).toBe(foo1)
expect(bar2).toBe(bar1)
expect(foo1.bar).toBe("bar")
expect(foo2.bar).toBe("bar")
})
})
export const SINGLETON_KEY = Symbol("Singleton Key")
export type Singleton<T extends new (...args: any[]) => any> = T & {
[SINGLETON_KEY]: T extends new (...args: any[]) => infer I ? I : never
}
export function Singleton<T extends new (...args: any[]) => any>(
classTarget: T
) {
return new Proxy(classTarget, {
construct(target: Singleton<T>, argumentsList, newTarget) {
if (target.prototype !== newTarget.prototype) {
return Reflect.construct(target, argumentsList, newTarget)
}
if (!target[SINGLETON_KEY]) {
target[SINGLETON_KEY] = Reflect.construct(
target,
argumentsList,
newTarget
)
}
return target[SINGLETON_KEY]
},
})
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment