diff --git a/CHANGELOG.md b/CHANGELOG.md index 609cd8c..9480225 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,11 @@ # deverything +## 4.9.0 + +### Minor Changes + +- singleton + ## 4.8.0 ### Minor Changes diff --git a/package.json b/package.json index ef06d05..e221eaa 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "deverything", - "version": "4.8.0", + "version": "4.9.0", "description": "Everything you need for Dev", "main": "./dist/index.js", "module": "./dist/index.mjs", diff --git a/src/helpers/singleton.ts b/src/helpers/singleton.ts new file mode 100644 index 0000000..786cd30 --- /dev/null +++ b/src/helpers/singleton.ts @@ -0,0 +1,40 @@ +import { isPromise } from "../validators/isPromise"; + +/** + * Creates a lazily-initialized singleton from a factory function. + * Works with both sync and async factories — the return type mirrors the factory's. + * + * For async factories, concurrent callers during initialization share the same + * in-flight promise rather than triggering multiple factory calls. If the promise + * rejects, the singleton resets so the next call retries. + */ +export const singleton = (factory: () => Result): (() => Result) => { + let hasInstance = false; + let instance!: Result; + + return () => { + if (hasInstance) { + return instance; + } + + const result = factory(); + + if (isPromise(result)) { + hasInstance = true; + instance = result.then( + (value) => value, + (error) => { + hasInstance = false; + throw error; + } + ) as Result; + + return instance; + } + + // not a promise + hasInstance = true; + instance = result; + return result; + }; +};