declare const TypeInferSymbol: unique symbol; /** * Calling this function creates an implicit dependency between the * molecule that called it and the scope that's requested. */ declare type ScopeGetter = { (scope: MoleculeScope): Value; }; /** * Calling this function creates an implicit dependency between the * molecule that called it and the molecule or interface that's requested. */ declare type MoleculeGetter = { (mol: MoleculeOrInterface): Value; }; /** * A molecule constructor is the function that produces a new instance of a dependency. These functions should be idempotent * and not mutate any outside state. * * When a constructor calls the `mol` function, it implicitly created a dependency to the molecule it uses. If it calls the * `scope` function, it implicitly created a dependency to the scope. * */ declare type MoleculeConstructor = (mol: MoleculeGetter, scope: ScopeGetter) => T; /** * A molecule object. * * This can be used as a reference to create objects by calling `useMolecule` * in one of the frontend integrations. * * Create a {@link Molecule} by callig {@link molecule} * * ```ts * export const RandomNumberMolecule = molecule(()=>Math.random()); * ``` * * @typeParam T - the type of object that will be provided by this molecule */ declare type Molecule = { displayName?: string; } & Record & { [TypeInferSymbol]?: T; }; /** * A molecule interface object. * * This can be used as a reference to create objects by calling `useMolecule` * in one of the frontend integrations. * * @typeParam T - the type of object that will be provided by this interface */ declare type MoleculeInterface = { displayName?: string; } & Record & { [TypeInferSymbol]?: T; }; /** * Either a {@link MoleculeInterface} or a {@link Molecule} */ declare type MoleculeOrInterface = MoleculeInterface | Molecule; /** * Create a new molecule * * Molecules are the core building block of bunshi. They are functions that return a value. * Molecules can depend on other molecules. When molecules depend on other molecules, anything * that they depend on will be automatically created. * * Molecules can also depend on scopes. When a molecule depends on a scope, then an instance will be * created for each scope. In other words, your molecule function will be run once per unique scope, * instead of once globally for your application. * * * Rules of molecules: * * - A molecule without any dependencies or scopes will only be called once. * - A molecule that depends on scope (a scoped molecule) will be called once per unique scope. * - A molecule that depends on a *scoped* molecule will be called once per unique scope of it’s dependency. * - If a molecule calls `scope` then it will be a scoped molecule. * - If a molecule calls `mol` then it will depend on that molecule. * Create a global molecule * ```ts * const globalMolecule = molecule(()=>Math.random()); * ``` * Create a dependent molecule * ```ts * const dependentMolecule = molecule(()=>`My dependency: ${use(globalMolecule)}`); * const dependentMolecule = molecule((mol)=>`My dependency: ${mol(globalMolecule)}`); * ``` * Create a scoped molecule * ```ts * const formScopedMolecule = molecule(()=>use(formScope)); * const formScopedMolecule = molecule((_,scope)=>scope(formScope)); * ```* * * @param construct - A callback function called to create molecule instances * @returns a molecule */ declare function molecule(construct: MoleculeConstructor): Molecule; /** * Create a new molecule interface. * * Molecule interfaces don't define an implementation, only a reference that other molecules * can depend on for an implementation. Before an interface can be used, an implementation needs * to be provided in the bindings to an {@link MoleculeInjector}. If no bindings for an interface * exist, then an error will be thrown the firs time it is used. * * Interfaces can be bound to molecules, scoped molecules, or molecules that simply wrap scopes. * * Interfaces are useful for decoupling your application and for library authors when the implementation * that your molecule relies on is unknown or variable. For example if your writing a molecule that relies * on router state, but there are many possible routers and no standard routers. * * Avoid interfaces when your library or application has a good default implementation. For example, if * your molecule relies on making HTTP requests, then it's better to have a molecule that provides http * access via `fetch` than an empty interface. Since molecules can also be replaced in bindings, it is more * convenient for consumers of your molecules to have a default implementation provided. * * @typeParam T - the typescript interface that this interface provides */ declare function moleculeInterface(): MoleculeInterface; declare type ScopeTuple = readonly [MoleculeScope, T]; declare type BindingTuple = readonly [MoleculeOrInterface, Molecule]; declare type BindingTuples = Array>; declare type BindingMap = Map, Molecule>; declare type Bindings = BindingTuples | BindingMap; declare type Injectable = MoleculeOrInterface | MoleculeScope; /** * A scope that can be used to create scoped molecules. * * When a molecule depends on a scope, this it becomes a scoped molecule * and the molecule will be called to provide a value once per unique scope value. * * Create a {@link MoleculeScope} by calling {@link createScope} * * ```ts * export const UserScope = createScope("user1") * ``` */ declare type MoleculeScope = { defaultValue: T; displayName?: string; defaultTuple: ScopeTuple; } & Record; /** * Create a {@link MoleculeScope} * * A scope tuple is a combination of both a scope key and a scope value. For example, * a scope key would be "User" and the scope value would be "user1@example.com" * * ```ts * export const UserScope = createScope("user1") * ``` * * @typeParam T - the type that this scope provides * @param defaultValue * @returns a new unique {@link MoleculeScope} */ declare function createScope(defaultValue: T, options?: { debugLabel?: string; }): MoleculeScope; /** * Component scope is handled in a special way. * * Unlike other scopes, it will always have a unique value for every component it's used in. * That makes it possible to create component-scoped state using molecules in the same * way that global state or other scoped state is created. * * @example * Counter state (React) * ```ts * const CounterM = molecule((_, scope) => { * scope(ComponentScope); * return atom(0); * }); * * // State will NOT be shared between components * const useCounter = () => useAtom(useMolecule(CounterM)); * * // The above is equivalent to: * // const useCounter = useAtom(useRef(atom(0)).current); * * // The above is also equivalent to: * // const useCounter = useState(0) * ``` * @example * Counter state (Vue) * ```ts * const CounterM = molecule((_, scope) => { * scope(ComponentScope); * return ref(0); * }); * * // State will NOT be shared between components * const useCounter = () => useMolecule(CounterM); * * // The above is equivalent to: * // const useCounter = () => ref(0) * ``` */ declare const ComponentScope: MoleculeScope; declare type CleanupCallback = () => unknown; declare type MountedCallback = () => CleanupCallback | void; /** * Registers a lifecyle callback for when a molecule is used (mounted). * * This lifecycle will be called everytime this molecule is used in a new * scope. * * For example, if your molecule is scoped by some `UserScope` * then `onMount` will be called for "User A" and "User B". * * ```ts * molecule(()=>{ * let i = 0; * onMount(()=>{ * const id = setInterval(() => console.log("Ticking...", i++),1000); * return () => clearInterval(id); * }) * return i; * }) * ``` * * @param fn - A callback to run when a molecule is used */ declare function onMount(fn: MountedCallback): void; /** * Registers a lifecyle callback for when a molecule is released (unmounted). * * This lifecycle will be called everytime this molecule that is used for a * scope has been released. This helps provide an opportunity to cleanup or * stop anything that is internally used. * * For example, if your molecule is scoped by some `UserScope` * then `onUnmount` will be called for when "User A" and "User B" * scopes are released. * * @param fn - A callback to run when a molecule is unmounted */ declare function onUnmount(fn: CleanupCallback): void; /** * Use a dependency for this molecule. When you call `use`, then Bunshi will * automatically register that your molecule now depends on what you passed in. * * If you depend on a scoped molecule, or a scope, then that will change * how many instances of a molecule will be created. See [Scopes](https://www.bunshi.org/concepts/scopes/) * for more details on scoping. * * * Use a {@link MoleculeScope}: * ```ts * molecule(()=>use(UserScope)); * ``` * * Use a {@link Molecule}: * ```ts * molecule(()=>use(UserMolecule)); * ``` * * Use a {@link MoleculeInterface}: * ```ts * molecule(()=>use(NetworkMolecule)); * ``` * * @param dependency - A dependency for this molecule to use, either another molecule, interface or scope * @returns the value of the dependency */ declare function use(dependency: MoleculeOrInterface | MoleculeScope): T; /** * The value stored in the molecule cache */ declare type MoleculeCacheValue = { deps: Deps; value: unknown; isMounted: boolean; path: (AnyScopeTuple | AnyMolecule)[]; instanceId: symbol; }; declare type Deps = { allScopes: Set; defaultScopes: Set; mountedCallbacks: Set; buddies: MoleculeCacheValue[]; }; declare type AnyMoleculeScope = MoleculeScope; declare type AnyScopeTuple = ScopeTuple; declare type AnyMolecule = Molecule; interface Instrumentation { getInternal(m: AnyMolecule): void; subscribe(m: AnyMolecule, next: MoleculeCacheValue): void; unsubscribe(m: AnyMolecule, next: MoleculeCacheValue): void; mounted(...args: unknown[]): void; cleanup(next: MoleculeCacheValue): void; executed(m: AnyMolecule, next: Partial): void; stage1CacheHit(m: AnyMolecule, next: MoleculeCacheValue): void; stage1CacheMiss(...args: unknown[]): void; stage2CacheHit(m: AnyMolecule, next: MoleculeCacheValue): void; stage2CacheMiss(...args: unknown[]): void; scopeStopWithCleanup(...args: unknown[]): void; scopeStopWithoutCleanup(...args: unknown[]): void; scopeRunCleanup(...args: unknown[]): void; } declare type ScopeSubscription = { tuples: AnyScopeTuple[]; expand(next: AnyScopeTuple[]): AnyScopeTuple[]; addCleanups(cleanups: Set): void; start(): AnyScopeTuple[]; stop(): void; }; declare type Unsub = () => unknown; /** * Builds the graphs of molecules that make up your application. * * The injector tracks the dependencies for each molecule and uses bindings to inject them. * * This "behind-the-scenes" operation is what distinguishes dependency injection from its cousin, the service locator pattern. * * From Dependency Injection: https://en.wikipedia.org/wiki/Dependency_injection * * > The injector, sometimes also called an assembler, container, provider or factory, introduces services to the client. * > The role of injectors is to construct and connect complex object graphs, where objects may be both clients and services. * > The injector itself may be many objects working together, but must not be the client, as this would create a circular dependency. * > Because dependency injection separates how objects are constructed from how they are used, * > it often diminishes the importance of the `new` keyword found in most object-oriented languages. * > Because the framework handles creating services, the programmer tends to only directly construct value objects which represents entities * > in the program's domain (such as an Employee object in a business app or an Order object in a shopping app). * * This is the core of bunshi, although you may rarely interact with it directly. */ declare type MoleculeInjector = { /** * Get the molecule value for an optional scope. Expects scope tuples to be memoized ahead of time. * * @param molecule * @param scopes */ get(molecule: MoleculeOrInterface, ...scopes: AnyScopeTuple[]): T; /** * Use a molecule, and memoizes scope tuples. * * Returns a function to cleanup scope tuples. * * @param molecule * @param scopes */ use(molecule: MoleculeOrInterface, ...scopes: AnyScopeTuple[]): [T, Unsub]; /** * Use a molecule, and memoizes scope tuples. * * Returns a function to cleanup scope tuples. * * @param molecule * @param scopes */ useLazily(molecule: MoleculeOrInterface, ...scopes: AnyScopeTuple[]): [T, { start: () => T; stop: Unsub; }]; /** * Use and memoize scopes. * * Returns a function to cleanup scope tuples. * * @param scopes */ useScopes(...scopes: AnyScopeTuple[]): [AnyScopeTuple[], Unsub]; /** * Use and memoize scopes. * * Returns a function to cleanup scope tuples. * * @param scopes */ createSubscription(): ScopeSubscription; } & Record; /** * Optional properties for creating a {@link MoleculeInjector} via {@link createInjector} */ declare type CreateInjectorProps = { /** * A set of bindings to replace the implemenation of a {@link MoleculeInterface} or * a {@link Molecule} with another {@link Molecule}. * * Bindings are useful for swapping out implementations of molecules during testing, * and for library authors to create shareable molecules that may not have a default * implementation */ bindings?: Bindings; /** * Instrumentation to observe the internals of the caches * uses in the injector */ instrumentation?: Instrumentation; /** * Parent injector to inherit molecules and cache from. * When a molecule or interface is not found in this injector, * it will be looked up in the parent injector. * The child injector shares the parent's cache to preserve singleton behavior. */ parent?: MoleculeInjector; }; /** * Creates a {@link MoleculeInjector} * * This is the core stateful component of `bunshi` and can have interfaces bound to implementations here. * * @example * Create an injector with bindings * * ```ts * const NumberMolecule = moleculeInterface(); * const RandomNumberMolecule = molecule(()=>Math.random()); * * const injector = createInjector({ * bindings:[[NumberMolecule,RandomNumberMolecule]] * }) * ``` */ declare function createInjector(injectorProps?: CreateInjectorProps): MoleculeInjector; /** * Returns the globally defined {@link MoleculeInjector} * * @returns */ declare const getDefaultInjector: () => MoleculeInjector; /** * Resets the globally defined default injector * * Useful for tests */ declare const resetDefaultInjector: (injectorProps?: CreateInjectorProps | undefined) => void; export { type BindingMap, type BindingTuple, type BindingTuples, type Bindings, ComponentScope, type CreateInjectorProps, type Injectable, type Molecule, type MoleculeConstructor, type MoleculeGetter, type MoleculeInjector, type MoleculeInterface, type MoleculeOrInterface, type MoleculeScope, type ScopeGetter, type ScopeTuple, createInjector, createScope, getDefaultInjector, molecule, moleculeInterface, onMount, onUnmount, resetDefaultInjector, use };