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 { BindingMap, BindingTuple, BindingTuples, Bindings, ComponentScope, CreateInjectorProps, Injectable, Molecule, MoleculeConstructor, MoleculeGetter, MoleculeInjector, MoleculeInterface, MoleculeOrInterface, MoleculeScope, ScopeGetter, ScopeTuple, createInjector, createScope, getDefaultInjector, molecule, moleculeInterface, onMount, onUnmount, resetDefaultInjector, use };