import type { MoleculeCreationEvent, MoleculeDisposalEvent, MoleculeTransferEvent, TransactionToken, } from "atom.io" import type { Above, CompoundFrom, CompoundTypedKey, Hierarchy, SingularTypedKey, ValidKey, Vassal, } from "atom.io/experiments/realms" import type { Canonical } from "atom.io/foundations/canonical" import type { stringified } from "atom.io/foundations/json" import { parseJson, stringifyJson } from "atom.io/foundations/json" import { Subject } from "atom.io/foundations/subject" import { disposeFromStore } from "./families/index.ts" import { getFromStore } from "./get-state/index.ts" import { getTrace } from "./get-trace.ts" import { newest } from "./lineage.ts" import type { Store } from "./store/index.ts" import type { RootStore } from "./transaction/index.ts" import { createTransaction, isChildStore } from "./transaction/index.ts" export type Molecule = { readonly key: K readonly stringKey: stringified readonly dependsOn: `all` | `any` readonly subject: Subject } export function simpleCompound(a: string, b: string): string { return [a, b].sort().join(`\u001F`) } export function decomposeCompound( compound: Canonical, ): [type: string, a: string, b: string] | null { if ((typeof compound === `string`) === false) { return null } const [typeTag, components] = compound.split(`==`) if (!components) { return null } const type = typeTag.slice(4) const [a, b] = components.split(`++`) if (type && a && b) { return [type, a, b] } return null } export function makeRootMoleculeInStore( store: Store, key: S, ): S { const molecule = { key, stringKey: stringifyJson(key), dependsOn: `any`, subject: new Subject(), } satisfies Molecule store.molecules.set(stringifyJson(key), molecule) return key } export function allocateIntoStore< H extends Hierarchy, V extends Vassal, A extends Above, >( store: Store, provenance: A, key: V, dependsOn: `all` | `any` = `any`, ): ValidKey { const origin = provenance as Canonical | [Canonical, Canonical] const stringKey = stringifyJson(key) const invalidKeys: stringified[] = [] const target = newest(store) if (Array.isArray(origin)) { for (const formerClaim of origin) { const claimString = stringifyJson(formerClaim) const claim = target.molecules.get(claimString) if (claim) { store.moleculeGraph.set(claimString, stringKey, { source: claimString }) } else { invalidKeys.push(claimString) } } } else { const claimString = stringifyJson(origin) const claim = target.molecules.get(claimString) if (claim) { store.moleculeGraph.set(claimString, stringKey, { source: claimString }) } else { invalidKeys.push(claimString) } } const subject = new Subject() if (invalidKeys.length === 0) { target.molecules.set(stringKey, { key, stringKey, dependsOn, subject }) } const creationEvent: MoleculeCreationEvent = { type: `molecule_creation`, key, provenance: origin, timestamp: Date.now(), } const isTransaction = isChildStore(target) && target.transactionMeta.phase === `building` if (isTransaction) { target.transactionMeta.update.subEvents.push(creationEvent) } else { target.on.moleculeCreation.next(creationEvent) } for (const claim of invalidKeys) { const disposal = store.disposalTraces.buffer.find( (item) => item?.key === claim, ) store.logger.error( `❌`, `key`, key, `allocation failed:`, `Could not allocate to ${claim} in store "${store.config.name}".`, disposal ? `\n ${claim} was most recently disposed\n${disposal.trace}` : `No previous disposal trace for ${claim} was found.`, ) } return key as ValidKey } export function fuseWithinStore< H extends Hierarchy, C extends CompoundFrom, T extends (C extends CompoundTypedKey ? t : never), A extends (C extends CompoundTypedKey ? a : never), B extends (C extends CompoundTypedKey ? b : never), >( store: Store, type: T, sideA: SingularTypedKey, sideB: SingularTypedKey, ): ValidKey> { const compoundKey: CompoundTypedKey = `T$--${type}==${sideA}++${sideB}` const above = [sideA, sideB] as Above, H> allocateIntoStore, Above, H>>( store, above, compoundKey as Vassal, `all`, ) return compoundKey } export function createDeallocateTX< H extends Hierarchy, V extends Exclude, CompoundTypedKey>, >(store: RootStore): TransactionToken<(claim: ValidKey) => void> { return createTransaction(store, { key: `[Internal] deallocate`, do: (_, claim: ValidKey): void => { deallocateFromStore(newest(store), claim) }, }) } export function deallocateFromStore>( target: Store, claim: ValidKey, ): void { const stringKey = stringifyJson(claim) const molecule = target.molecules.get(stringKey) if (!molecule) { const disposal = target.disposalTraces.buffer.find( (item) => item?.key === stringKey, ) target.logger.error( `❌`, `key`, claim, `deallocation failed:`, `Could not find allocation for ${stringKey} in store "${target.config.name}".`, disposal ? `\n This state was most recently deallocated\n${disposal.trace}` : `No previous disposal trace for ${stringKey} was found.`, ) return } molecule.subject.next() const joinKeys = target.keyRefsInJoins.getRelatedKeys(stringKey) if (joinKeys) { for (const joinKey of joinKeys) { const join = target.joins.get(joinKey) if (join) { join.relations.delete(claim) } } } else { const compound = decomposeCompound(claim) if (compound) { const [, a, b] = compound const joinKey = target.keyRefsInJoins.getRelatedKey(simpleCompound(a, b)) if (joinKey) { const join = target.joins.get(joinKey) if (join) { join.relations.delete(a, b) } } } } target.keyRefsInJoins.delete(stringKey) const provenance: stringified[] = [] const values: [string, any][] = [] const relatedMolecules = target.moleculeGraph.getRelationEntries({ downstreamMoleculeKey: stringKey, }) if (relatedMolecules) { for (const [relatedStringKey, { source }] of relatedMolecules) { if (source === stringKey) { const relatedKey = parseJson(relatedStringKey) deallocateFromStore(target, relatedKey) } else { provenance.push(source) } } } const familyKeys = target.moleculeData.getRelatedKeys(molecule.stringKey) if (familyKeys) { for (const familyKey of familyKeys) { const family = target.families.get(familyKey)! const value = getFromStore(target, family, claim) values.push([family.key, value]) disposeFromStore(target, family, claim) } } const disposalEvent: MoleculeDisposalEvent = { type: `molecule_disposal`, key: molecule.key, values, provenance, timestamp: Date.now(), } target.molecules.delete(stringKey) const isTransaction = isChildStore(target) && target.transactionMeta.phase === `building` if (isTransaction) { target.transactionMeta.update.subEvents.push(disposalEvent) } target.moleculeGraph.delete(molecule.stringKey) target.keyRefsInJoins.delete(molecule.stringKey) target.moleculeData.delete(molecule.stringKey) if (!isTransaction) { target.on.moleculeDisposal.next(disposalEvent) } target.molecules.delete(molecule.stringKey) const trace = getTrace(new Error()) target.disposalTraces.add({ key: stringKey, trace }) } export function createClaimTX< H extends Hierarchy, V extends Exclude, CompoundTypedKey>, A extends Above, >( store: RootStore, ): TransactionToken< (newProvenance: A, claim: ValidKey, exclusive?: `exclusive`) => void > { return createTransaction(store, { key: `[Internal] claim`, do: (_, newProvenance, claim, exclusive) => { claimWithinStore(store, newProvenance, claim, exclusive) }, }) } export function claimWithinStore< H extends Hierarchy, V extends Exclude, CompoundTypedKey>, A extends Above, >( store: Store, newProvenance: A, claim: ValidKey, exclusive?: `exclusive`, ): ValidKey { const stringKey = stringifyJson(claim) const target = newest(store) const molecule = target.molecules.get(stringKey) if (!molecule) { const disposal = store.disposalTraces.buffer.find( (item) => item?.key === stringKey, ) store.logger.error( `❌`, `key`, stringKey, `claim failed:`, `Could not allocate to ${stringKey} in store "${store.config.name}".`, disposal ? `\n ${stringKey} was most recently disposed\n${disposal.trace}` : `No previous disposal trace for ${stringKey} was found.`, ) return claim } const newProvenanceKey = stringifyJson(newProvenance as Canonical) const newProvenanceMolecule = target.molecules.get(newProvenanceKey) if (!newProvenanceMolecule) { const disposal = store.disposalTraces.buffer.find( (item) => item?.key === newProvenanceKey, ) store.logger.error( `❌`, `key`, claim, `claim failed:`, `Could not allocate to ${newProvenanceKey} in store "${store.config.name}".`, disposal ? `\n ${newProvenanceKey} was most recently disposed\n${disposal.trace}` : `No previous disposal trace for ${newProvenanceKey} was found.`, ) return claim } const priorProvenance = store.moleculeGraph .getRelationEntries({ downstreamMoleculeKey: molecule.stringKey, }) .filter(([, { source }]) => source !== stringKey) .map(([key]) => parseJson(key)) if (exclusive) { target.moleculeGraph.delete(stringKey) } target.moleculeGraph.set( { upstreamMoleculeKey: newProvenanceMolecule.stringKey, downstreamMoleculeKey: molecule.stringKey, }, { source: newProvenanceMolecule.stringKey, }, ) const transferEvent: MoleculeTransferEvent = { type: `molecule_transfer`, key: molecule.key, exclusive: Boolean(exclusive), from: priorProvenance, to: [newProvenanceMolecule.key], timestamp: Date.now(), } const isTransaction = isChildStore(target) && target.transactionMeta.phase === `building` if (isTransaction) { target.transactionMeta.update.subEvents.push(transferEvent) } return claim }