import type { AtomToken, Logger, MoleculeCreationEvent, MoleculeDisposalEvent, SelectorToken, TimelineToken, TransactionToken, } from "atom.io" import { AtomIOLogger } from "atom.io" import type { Canonical } from "atom.io/foundations/canonical" import type { stringified } from "atom.io/foundations/json" import { Junction } from "atom.io/foundations/junction" import { StatefulSubject, Subject } from "atom.io/foundations/subject" import type { Join } from "../join/index.ts" import type { Lineage } from "../lineage.ts" import type { Molecule } from "../molecule.ts" import type { Tracker, Transceiver } from "../mutable/index.ts" import { getJsonTokenFromStore, getUpdateToken } from "../mutable/index.ts" import type { OperationProgress } from "../operation.ts" import { isReservedIntrospectionKey } from "../reserved-keys.ts" import type { Atom, HeldSelectorFamily, MutableAtomFamily, PureSelectorFamily, ReadonlySelector, RegularAtomFamily, WritableSelector, } from "../state-types.ts" import type { Timeline, TimelineFamily } from "../timeline/index.ts" import type { ChildStore, RootStore, RootTransactionMeta, Transaction, TransactionProgress, } from "../transaction/index.ts" import { isRootStore } from "../transaction/index.ts" import type { Fn } from "../utility-types.ts" import { CircularBuffer } from "./circular-buffer.ts" export type StoreConfig = { name: string lifespan: `ephemeral` | `immortal` isProduction: boolean } export class Store implements Lineage { public parent: ChildStore | RootStore | null = null public child: ChildStore | null = null public valueMap: Map = new Map() public defaults: Map = new Map() public atoms: Map> = new Map() public writableSelectors: Map> = new Map() public readonlySelectors: Map> = new Map() public atomsThatAreDefault: Set = new Set() public selectorAtoms: Junction<`selectorKey`, string, `atomKey`, string> = new Junction({ between: [`selectorKey`, `atomKey`], cardinality: `n:n`, }) public selectorGraph: Junction< `upstreamSelectorKey`, string, `downstreamSelectorKey`, string, { source: string } > = new Junction( { between: [`upstreamSelectorKey`, `downstreamSelectorKey`], cardinality: `n:n`, }, { makeContentKey: (...keys) => keys.sort().join(`:`), }, ) public trackers: Map>> = new Map() public families: Map< string, | HeldSelectorFamily | MutableAtomFamily | PureSelectorFamily | RegularAtomFamily > = new Map() public joins: Map> = new Map() public transactions: Map> = new Map() public transactionMeta: RootTransactionMeta | TransactionProgress = { phase: `idle`, } public timelines: Map> = new Map() public timelineFamilies: Map> = new Map() public timelineTopics: Junction< `timelineKey`, string, `topicKey`, string, { topicType: `atom_family` | `atom` | `molecule_family` | `molecule` } > = new Junction({ between: [`timelineKey`, `topicKey`], cardinality: `1:n`, }) public disposalTraces: CircularBuffer<{ key: string; trace: string }> = new CircularBuffer(100) public molecules: Map> = new Map() public moleculeGraph: Junction< `upstreamMoleculeKey`, stringified | `"root"`, `downstreamMoleculeKey`, stringified, { source: stringified } > = new Junction( { between: [`upstreamMoleculeKey`, `downstreamMoleculeKey`], cardinality: `n:n`, }, { makeContentKey: (...keys) => keys.sort().join(`:`), }, ) public moleculeData: Junction< `moleculeKey`, stringified, `stateFamilyKey`, string > = new Junction( { between: [`moleculeKey`, `stateFamilyKey`], cardinality: `n:n`, }, { makeContentKey: (...keys) => keys.sort().join(`:`), }, ) public keyRefsInJoins: Junction< `moleculeKey`, stringified, `joinKey`, string > = new Junction( { between: [`moleculeKey`, `joinKey`], cardinality: `n:n`, }, { makeContentKey: (...keys) => keys.sort().join(`:`), }, ) public miscResources: Map = new Map() public on: StoreEventCarrier = { atomCreation: new Subject(), atomDisposal: new Subject(), selectorCreation: new Subject(), selectorDisposal: new Subject(), timelineCreation: new Subject(), timelineDisposal: new Subject(), transactionCreation: new Subject(), transactionApplying: new StatefulSubject(null), operationClose: new Subject(), moleculeCreation: new Subject(), moleculeDisposal: new Subject(), } public operation: OperationProgress = { open: false } public config: StoreConfig = { name: `IMPLICIT_STORE`, lifespan: `ephemeral`, isProduction: globalThis.process?.env?.[`NODE_ENV`] === `production`, } public loggers: AtomIOLogger[] = [ new AtomIOLogger(`warn`, (_, __, key) => !isReservedIntrospectionKey(key)), ] public logger: Logger = { error: (...messages) => { for (const logger of this.loggers) logger.error(...messages) }, info: (...messages) => { for (const logger of this.loggers) logger.info(...messages) }, warn: (...messages) => { for (const logger of this.loggers) logger.warn(...messages) }, } public constructor(config: Store[`config`], store: Store | null = null) { this.config = { ...store?.config, ...config, } if (store !== null) { this.operation = { ...store?.operation } if (isRootStore(store)) { this.transactionMeta = { phase: `idle` } } for (const [, family] of store.families) { if ( family.internalRoles?.includes(`mutable`) || family.internalRoles?.includes(`join`) ) { continue } family.install(this as RootStore) } const mutableHelpers = new Set() for (const [, atom] of store.atoms) { if (mutableHelpers.has(atom.key)) { continue } atom.install(this as RootStore) if (atom.type === `mutable_atom`) { const originalJsonToken = getJsonTokenFromStore(store, atom) const originalUpdateToken = getUpdateToken(atom) mutableHelpers.add(originalJsonToken.key) mutableHelpers.add(originalUpdateToken.key) } } for (const [, selector] of store.readonlySelectors) { selector.install(this as RootStore) } for (const [, selector] of store.writableSelectors) { if (mutableHelpers.has(selector.key)) { continue } selector.install(this as RootStore) } for (const [, tx] of store.transactions) { tx.install(this as RootStore) } for (const [, timelineFamily] of store.timelineFamilies) { timelineFamily.install(this as RootStore) } for (const [, timeline] of store.timelines) { timeline.install(this as RootStore) } } } } export type StoreEventCarrier = { atomCreation: Subject> atomDisposal: Subject> selectorCreation: Subject> selectorDisposal: Subject> timelineCreation: Subject> timelineDisposal: Subject> transactionCreation: Subject> transactionApplying: StatefulSubject | null> operationClose: Subject moleculeCreation: Subject moleculeDisposal: Subject } declare global { var ATOM_IO_IMPLICIT_STORE: RootStore | undefined } export const IMPLICIT: { readonly STORE: RootStore } = { get STORE(): RootStore { globalThis.ATOM_IO_IMPLICIT_STORE ??= new Store({ name: `IMPLICIT_STORE`, lifespan: `ephemeral`, isProduction: globalThis.process?.env?.[`NODE_ENV`] === `production`, }) as RootStore return globalThis.ATOM_IO_IMPLICIT_STORE }, } export function clearStore(store: Store): void { const { config } = store for (const disposable of store.miscResources.values()) { disposable[Symbol.dispose]() } Object.assign(store, new Store(config)) store.config = config }