UNPKG

@ceramicnetwork/core

Version:

Typescript implementation of the Ceramic protocol

666 lines • 31.1 kB
var __classPrivateFieldGet = (this && this.__classPrivateFieldGet) || function (receiver, state, kind, f) { if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a getter"); if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot read private member from an object whose class did not declare it"); return kind === "m" ? f : kind === "a" ? f.call(receiver) : f ? f.value : state.get(receiver); }; var __classPrivateFieldSet = (this && this.__classPrivateFieldSet) || function (receiver, state, value, kind, f) { if (kind === "m") throw new TypeError("Private method is not writable"); if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a setter"); if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot write private member to an object whose class did not declare it"); return (kind === "a" ? f.call(receiver, value) : f ? f.value = value : state.set(receiver, value)), value; }; var _Repository_deps, _Repository_syncedPinnedStreams; import { CommitID, StreamID } from '@ceramicnetwork/streamid'; import { AnchorStatus, EnvironmentUtils, EventType, StreamUtils, SyncOptions, UnreachableCaseError, } from '@ceramicnetwork/common'; import { SignatureUtils } from '@ceramicnetwork/stream-handler-common'; import { ExecutionQueue } from './execution-queue.js'; import { RunningState } from './running-state.js'; import { Observable, concatMap } from 'rxjs'; import { StateCache } from './state-cache.js'; import { SnapshotState } from './snapshot-state.js'; import { ServiceMetrics as Metrics } from '@ceramicnetwork/observability'; import { OperationType } from './operation-type.js'; import { AnchorRequestStatusName } from '@ceramicnetwork/common'; import { Feed } from '../feed.js'; import { doNotWait } from '../ancillary/do-not-wait.js'; import { Utils } from '../utils.js'; import { ModelInstanceDocument } from '@ceramicnetwork/stream-model-instance'; import { Model } from '@ceramicnetwork/stream-model'; import { FeedAggregationStore } from '../store/feed-aggregation-store.js'; const DEFAULT_LOAD_OPTS = { sync: SyncOptions.PREFER_CACHE, syncTimeoutSeconds: 3 }; const APPLY_ANCHOR_COMMIT_ATTEMPTS = 3; const CACHE_EVICTED_MEMORY = 'cache_eviction_memory'; const CACHE_HIT_LOCAL = 'cache_hit_local'; const CACHE_HIT_MEMORY = 'cache_hit_memory'; const CACHE_HIT_REMOTE = 'cache_hit_remote'; const STREAM_SYNC = 'stream_sync'; const RECON_STORE_USECASE_NAME = 'recon'; const RECON_STORE_CURSOR_KEY = 'cursor'; const IMPORT_CAR_ANCHOR_COMMIT_REQUESTED = 'import_car_anchor_commit'; const IMPORT_CAR_ANCHOR_COMMIT_COMPLETED = 'import_car_anchor_commit_completed'; const IMPORT_CAR_ANCHOR_COMMIT_TIME = 'import_car_anchor_commit_time'; function shouldIndex(state$, index) { const model = state$.state?.metadata?.model; if (!model) return false; return index.shouldIndexStream(model); } function commitAtTime(state, timestamp) { let commitCid = state.log[0].cid; for (const entry of state.log) { if (entry.type === EventType.TIME) { if (entry.timestamp <= timestamp) { commitCid = entry.cid; } else { break; } } } return CommitID.make(StreamUtils.streamIdFromState(state), commitCid); } var SyncStatus; (function (SyncStatus) { SyncStatus[SyncStatus["NOT_SYNCED"] = 0] = "NOT_SYNCED"; SyncStatus[SyncStatus["ALREADY_SYNCED"] = 1] = "ALREADY_SYNCED"; SyncStatus[SyncStatus["DID_SYNC"] = 2] = "DID_SYNC"; })(SyncStatus || (SyncStatus = {})); export class Repository { constructor(cacheLimit, concurrencyLimit, recon, logger) { this.recon = recon; this.logger = logger; _Repository_deps.set(this, void 0); _Repository_syncedPinnedStreams.set(this, new Set()); this.loadingQ = new ExecutionQueue('loading', concurrencyLimit, logger); this.executionQ = new ExecutionQueue('execution', concurrencyLimit, logger); this.inmemory = new StateCache(cacheLimit, (state$) => { if (state$.subscriptionSet.size > 0) { logger.debug(`Stream ${state$.id} evicted from cache while having subscriptions`); } Metrics.count(CACHE_EVICTED_MEMORY, 1); state$.complete(); }); this.updates$ = this.updates$.bind(this); this.streamState = this.streamState.bind(this); this.feedAggregationStore = new FeedAggregationStore(this.logger); this.feed = new Feed(this.feedAggregationStore, this.logger, this.streamState); } injectKVFactory(factory) { this.setDeps({ ...__classPrivateFieldGet(this, _Repository_deps, "f"), kvFactory: factory, }); } async init() { await this.feedAggregationStore.open(__classPrivateFieldGet(this, _Repository_deps, "f").kvFactory); await this.pinStore.open(__classPrivateFieldGet(this, _Repository_deps, "f").kvFactory); await this.anchorRequestStore.open(__classPrivateFieldGet(this, _Repository_deps, "f").kvFactory); await this.index.init(); const reconStore = await __classPrivateFieldGet(this, _Repository_deps, "f").kvFactory.open(RECON_STORE_USECASE_NAME); const cursor = (await reconStore.exists(RECON_STORE_CURSOR_KEY)) ? await reconStore.get(RECON_STORE_CURSOR_KEY) : '0'; const interests = this.index.indexedModels().map((modelData) => modelData.streamID); await this.recon.init(cursor, interests); this.reconEventFeedSubscription = this.recon .pipe(concatMap(this.handleReconEvents.bind(this))) .subscribe(); } get pinStore() { return __classPrivateFieldGet(this, _Repository_deps, "f").pinStore; } get streamLoader() { return __classPrivateFieldGet(this, _Repository_deps, "f").streamLoader; } get streamUpdater() { return __classPrivateFieldGet(this, _Repository_deps, "f").streamUpdater; } get anchorService() { return __classPrivateFieldGet(this, _Repository_deps, "f").anchorService; } get dispatcher() { return __classPrivateFieldGet(this, _Repository_deps, "f").dispatcher; } get anchorRequestStore() { return __classPrivateFieldGet(this, _Repository_deps, "f").anchorRequestStore; } get index() { return __classPrivateFieldGet(this, _Repository_deps, "f").indexing; } setDeps(deps) { __classPrivateFieldSet(this, _Repository_deps, deps, "f"); } async load(streamId, loadOptions = {}, checkCacaoExpiration = true) { const opts = { ...DEFAULT_LOAD_OPTS, ...loadOptions }; const [state$, syncStatus] = await this.loadingQ.forStream(streamId).run(async () => { if (EnvironmentUtils.useRustCeramic()) { const existingState$ = await this.fromMemoryOrStore_UNSAFE(streamId); if (!existingState$) { return [await this._genesisFromNetwork(streamId), SyncStatus.ALREADY_SYNCED]; } return [existingState$, SyncStatus.ALREADY_SYNCED]; } const [existingState$, alreadySynced] = await this._fromMemoryOrStoreWithSyncStatus(streamId); switch (opts.sync) { case SyncOptions.PREFER_CACHE: case SyncOptions.SYNC_ON_ERROR: { if (!existingState$) { return [ await this._loadStreamFromNetwork(streamId, opts.syncTimeoutSeconds), SyncStatus.DID_SYNC, ]; } if (alreadySynced == SyncStatus.ALREADY_SYNCED) { return [existingState$, SyncStatus.ALREADY_SYNCED]; } else { await this._sync(existingState$, opts.syncTimeoutSeconds); return [existingState$, SyncStatus.DID_SYNC]; } } case SyncOptions.NEVER_SYNC: { if (existingState$) { return [existingState$, alreadySynced]; } return [await this._genesisFromNetwork(streamId), SyncStatus.NOT_SYNCED]; } case SyncOptions.SYNC_ALWAYS: { return [ await this._resyncStreamFromNetwork(streamId, opts.syncTimeoutSeconds, existingState$), SyncStatus.DID_SYNC, ]; } default: throw new UnreachableCaseError(opts.sync, 'Invalid sync option'); } }); if (checkCacaoExpiration) { SignatureUtils.checkForCacaoExpiration(state$.state); } if (process.env.CERAMIC_AUDIT_EVENT_PERSISTENCE == 'true') { for (const logEntry of state$.state.log) { try { await Utils.getCommitData(this.dispatcher, logEntry.cid, state$.id); } catch (err) { this.logger.err(`DATA MISSING: Ceramic event persistence auditing found missing data for commit ${logEntry.cid} of stream ${state$.id}: ${err}`); await new Promise((resolve) => setTimeout(resolve, 1000)); process.exit(1); } } } if (syncStatus != SyncStatus.ALREADY_SYNCED) { await this._updateStateIfPinned_safe(state$); if (syncStatus == SyncStatus.DID_SYNC && state$.isPinned) { this.markPinnedAndSynced(state$.id); } } return state$; } _updateStateIfPinned_safe(state$) { return this.executionQ.forStream(state$.id).run(() => { return this._updateStateIfPinned(state$); }); } _clearCache() { this.inmemory.clear(); } async _updateStateIfPinned(state$) { const isPinned = Boolean(await this.pinStore.stateStore.load(state$.id)); const shouldIndex = state$.state.metadata.model && this.index.shouldIndexStream(state$.state.metadata.model); if (isPinned || shouldIndex) { await this._pin_UNSAFE(state$); await this.feedAggregationStore.put(state$.id); } await this._indexStreamIfNeeded(state$); } _fromMemory(streamId) { const state = this.inmemory.get(streamId.toString()); if (state) { Metrics.count(CACHE_HIT_MEMORY, 1); } return state; } async _fromStreamStateStore(streamId) { const streamState = await this.pinStore.stateStore.load(streamId); if (streamState) { Metrics.count(CACHE_HIT_LOCAL, 1); const runningState = new RunningState(streamState, true); this._registerRunningState(runningState); return runningState; } else { return undefined; } } async _fromMemoryOrStoreWithSyncStatus(streamId) { let stream = this._fromMemory(streamId); if (stream) { return [stream, SyncStatus.ALREADY_SYNCED]; } stream = await this._fromStreamStateStore(streamId); if (stream) { return [ stream, this._wasPinnedStreamSynced(streamId) ? SyncStatus.ALREADY_SYNCED : SyncStatus.NOT_SYNCED, ]; } return [null, SyncStatus.NOT_SYNCED]; } async _loadStreamFromNetwork(streamId, syncTimeoutSeconds) { const state = await this.streamLoader.loadStream(streamId, syncTimeoutSeconds); Metrics.count(STREAM_SYNC, 1); const newState$ = new RunningState(state, false); this._registerRunningState(newState$); return newState$; } async _genesisFromNetwork(streamId) { const state = await this.streamLoader.loadGenesisState(streamId); Metrics.count(CACHE_HIT_REMOTE, 1); const state$ = new RunningState(state, false); this._registerRunningState(state$); this.logger.verbose(`Genesis commit for stream ${streamId.toString()} successfully loaded`); return state$; } async _sync(state$, syncTimeoutSeconds) { const syncedState = await this.streamLoader.syncStream(state$.state, syncTimeoutSeconds); state$.next(syncedState); Metrics.count(STREAM_SYNC, 1); } async _resyncStreamFromNetwork(streamId, syncTimeoutSeconds, existingState$) { const resyncedState = existingState$ ? await this.streamLoader.resyncStream(streamId, existingState$.tip, syncTimeoutSeconds) : await this.streamLoader.loadStream(streamId, syncTimeoutSeconds); Metrics.count(STREAM_SYNC, 1); const newState$ = new RunningState(resyncedState, false); this._registerRunningState(newState$); return newState$; } async loadAtCommit(commitId, opts) { let existingState$ = null; try { existingState$ = await this.load(commitId.baseID, opts, false); } catch (err) { this.logger.warn(`Error loading existing state for stream ${commitId.baseID} while loading stream at commit ${commitId.commit}`); } return this.executionQ.forStream(commitId).run(async () => { const stateAtCommit = existingState$ ? await this.streamLoader.resetStateToCommit(existingState$.state, commitId) : await this.streamLoader.stateAtCommit(commitId); SignatureUtils.checkForCacaoExpiration(stateAtCommit); if (existingState$) { if (StreamUtils.isStateSupersetOf(stateAtCommit, existingState$.value)) { existingState$.next(stateAtCommit); await this._updateStateIfPinned(existingState$); } } else { const newState$ = new RunningState(stateAtCommit, false); this._registerRunningState(newState$); await this._updateStateIfPinned(newState$); } return new SnapshotState(stateAtCommit); }); } async loadAtTime(streamId, opts) { const base$ = await this.load(streamId.baseID, opts); const commitId = commitAtTime(base$.state, opts.atTime); return this.loadAtCommit(commitId, opts); } async applyCommit(streamId, commit, opts) { this.logger.verbose(`Repository apply commit to stream ${streamId.toString()}`); if (process.env.CERAMIC_DISABLE_ANCHORING !== 'true') { this.anchorService.assertCASAccessible(); } const state$ = await this.load(streamId); this.logger.verbose(`Repository loaded state for stream ${streamId.toString()}`); return this.executionQ.forStream(streamId).run(async () => { const originalState = state$.state; const updatedState = await this.streamUpdater.applyCommitFromUser(originalState, commit); if (StreamUtils.tipFromState(updatedState).equals(StreamUtils.tipFromState(originalState))) { return state$; } state$.next(updatedState); await this._updateStateIfPinned(state$); await this._applyWriteOpts(state$, opts, OperationType.UPDATE); this.logger.verbose(`Stream ${state$.id} successfully updated to tip ${state$.tip}`); return state$; }); } async handleReconEvents(response) { const { events, cursor } = response; for (const event of events) { const { cid } = event; try { const commitData = await Utils.getCommitData(this.dispatcher, cid); const genesisCid = commitData.commit.id ? commitData.commit.id : cid; const genesisCommitData = commitData.commit.header ? commitData : await Utils.getCommitData(this.dispatcher, genesisCid); if (!genesisCommitData.commit.header.model) { throw new Error(`Model not found in genesis commit header ${genesisCid.toString()} for event for cid ${cid.toString()}`); } const model = StreamID.fromBytes(genesisCommitData.commit.header.model); const type = model.toString() === Model.MODEL.toString() ? Model.STREAM_TYPE_ID : ModelInstanceDocument.STREAM_TYPE_ID; await this.handleUpdateFromNetwork(new StreamID(type, genesisCid), cid, model); } catch (e) { this.logger.err(`Error handling recon event with event for cid ${cid}: ${e}`); } } const reconStore = await __classPrivateFieldGet(this, _Repository_deps, "f").kvFactory.open(RECON_STORE_USECASE_NAME); await reconStore.put(RECON_STORE_CURSOR_KEY, cursor.toString()); } async handleUpdateFromNetwork(streamId, tip, model) { let state$ = await this.fromMemoryOrStore_UNSAFE(streamId); const shouldIndex = model && this.index.shouldIndexStream(model); if (!shouldIndex && !state$) { return; } if (!state$) { state$ = await this.load(streamId); } await this._handleTip(state$, tip); } async _handleTip(state$, cid) { return this.executionQ.forStream(state$.id).run(async () => { this.logger.verbose(`Learned of new tip ${cid} for stream ${state$.id}`); const next = await this.streamUpdater.applyTipFromNetwork(state$.state, cid); if (next) { state$.next(next); await this._updateStateIfPinned(state$); this.logger.verbose(`Stream ${state$.id} successfully updated to tip ${cid}`); return true; } else { return false; } }); } async anchor(state$, opts) { if (!this.anchorService) { throw new Error(`Anchor requested for stream ${state$.id} but anchoring is disabled`); } if (state$.value.anchorStatus == AnchorStatus.ANCHORED) { return; } if (process.env.CERAMIC_DISABLE_ANCHORING === 'true') { return; } const anchorEvent = await this.anchorService.requestAnchor(state$.id, state$.tip); doNotWait(this.handleAnchorEvent(state$, anchorEvent), this.logger); } async handleAnchorEvent(state$, anchorEvent) { const status = anchorEvent.status; switch (status) { case AnchorRequestStatusName.READY: case AnchorRequestStatusName.PENDING: { if (!anchorEvent.cid.equals(state$.tip)) return false; if (state$.state.anchorStatus === AnchorStatus.PENDING) return false; const next = { ...state$.value, anchorStatus: AnchorStatus.PENDING, }; state$.next(next); await this._updateStateIfPinned_safe(state$); return false; } case AnchorRequestStatusName.PROCESSING: { if (!anchorEvent.cid.equals(state$.tip)) return false; if (state$.state.anchorStatus === AnchorStatus.PROCESSING) return false; state$.next({ ...state$.value, anchorStatus: AnchorStatus.PROCESSING }); await this._updateStateIfPinned_safe(state$); return false; } case AnchorRequestStatusName.COMPLETED: { await this._handleAnchorCommit(state$, anchorEvent.cid, anchorEvent.witnessCar); return true; } case AnchorRequestStatusName.FAILED: { this.logger.warn(`Anchor failed for commit ${anchorEvent.cid} of stream ${anchorEvent.streamId}: ${anchorEvent.message}`); if (anchorEvent.cid.equals(state$.tip)) { state$.next({ ...state$.value, anchorStatus: AnchorStatus.FAILED }); return true; } return true; } case AnchorRequestStatusName.REPLACED: { this.logger.verbose(`Anchor request for commit ${anchorEvent.cid} of stream ${anchorEvent.streamId} is replaced`); return true; } default: throw new UnreachableCaseError(status, 'Unknown anchoring state'); } } async _handleAnchorCommit(state$, tip, witnessCAR) { const streamId = StreamUtils.streamIdFromState(state$.state); const anchorCommitCID = witnessCAR.roots[0]; if (!anchorCommitCID) throw new Error(`No anchor commit CID as root`); this.logger.verbose(`Handling anchor commit for ${streamId} with CID ${anchorCommitCID}`); for (let remainingRetries = APPLY_ANCHOR_COMMIT_ATTEMPTS - 1; remainingRetries >= 0; remainingRetries--) { try { if (witnessCAR) { const timeStart = Date.now(); Metrics.count(IMPORT_CAR_ANCHOR_COMMIT_REQUESTED, 1); await this.dispatcher.importCAR(witnessCAR, streamId); Metrics.count(IMPORT_CAR_ANCHOR_COMMIT_COMPLETED, 1); const timeEnd = Date.now(); Metrics.observe(IMPORT_CAR_ANCHOR_COMMIT_TIME, timeEnd - timeStart); this.logger.verbose(`successfully imported CAR file for ${streamId}`); } const applied = await this._handleTip(state$, anchorCommitCID); if (applied) { this._publishTip(state$); if (remainingRetries < APPLY_ANCHOR_COMMIT_ATTEMPTS - 1) { this.logger.imp(`Successfully applied anchor commit ${anchorCommitCID} for stream ${state$.id} after ${APPLY_ANCHOR_COMMIT_ATTEMPTS - remainingRetries} attempts`); } else { this.logger.verbose(`Successfully applied anchor commit ${anchorCommitCID} for stream ${state$.id}`); } } return; } catch (error) { this.logger.warn(`Error while applying anchor commit ${anchorCommitCID} for stream ${state$.id}, ${remainingRetries} retries remain. ${error}`); if (remainingRetries == 0) { this.logger.err(`Anchor failed for commit ${tip} of stream ${state$.id}: ${error}`); if (tip.equals(state$.tip)) { state$.next({ ...state$.value, anchorStatus: AnchorStatus.FAILED }); } throw error; } } } } async _applyWriteOpts(state$, opts, opType) { const anchor = opts.anchor; const publish = opts.publish; if (anchor) { await this.anchor(state$, opts); } if (publish && opType !== OperationType.LOAD) { this._publishTip(state$); } await this._handlePinOpts(state$, opts, opType); } _publishTip(state$) { this.dispatcher.publishTip(state$.id, state$.tip, state$.state.metadata.model); } async _handlePinOpts(state$, opts, opType) { if (opts.pin !== undefined && opType !== OperationType.CREATE) { const pinStr = opts.pin ? 'pin' : 'unpin'; const opStr = opType == OperationType.UPDATE ? 'update' : 'load'; this.logger.warn(`Cannot pin or unpin streams through the CRUD APIs. To change stream pin state use the admin.pin API with an authenticated admin DID. Attempting to ${pinStr} stream ${StreamUtils.streamIdFromState(state$.state).toString()} as part of a ${opStr} operation`); return; } if (opts.pin || (opts.pin === undefined && shouldIndex(state$, this.index)) || (opts.pin === undefined && opType == OperationType.CREATE)) { await this._pin_UNSAFE(state$); } else if (opts.pin === false) { await this.unpin(state$); } } async createStreamFromGenesis(type, genesis, opts = {}) { if (process.env.CERAMIC_DISABLE_ANCHORING !== 'true') { this.anchorService.assertCASAccessible(); } const genesisCid = await this.dispatcher.storeInitEvent(genesis, type); const streamId = new StreamID(type, genesisCid); const state$ = await this.load(streamId, opts); this.logger.verbose(`Created stream from genesis, StreamID: ${streamId.toString()}, genesis CID: ${genesisCid.toString()}`); const opType = state$.state.log.length == 1 ? OperationType.CREATE : OperationType.LOAD; return this.executionQ.forStream(streamId).run(async () => { await this._updateStateIfPinned(state$); await this._applyWriteOpts(state$, opts, opType); return state$; }); } async fromMemoryOrStore(streamId) { return this.loadingQ.forStream(streamId).run(() => { return this.fromMemoryOrStore_UNSAFE(streamId); }); } async fromMemoryOrStore_UNSAFE(streamId) { const fromMemory = this._fromMemory(streamId); if (fromMemory) return fromMemory; return this._fromStreamStateStore(streamId); } async streamState(streamId) { const fromMemory = this.inmemory.get(streamId.toString()); if (fromMemory) { return fromMemory.state; } else { return __classPrivateFieldGet(this, _Repository_deps, "f").pinStore.stateStore.load(streamId); } } _registerRunningState(state$) { this.inmemory.set(state$.id.toString(), state$); } pin(state$, force) { return this.executionQ.forStream(state$.id).run(async () => { return this._pin_UNSAFE(state$, force); }); } _pin_UNSAFE(state$, force) { return this.pinStore.add(state$, force); } async unpin(state$, opts) { if (shouldIndex(state$, this.index)) { throw new Error(`Cannot unpin actively indexed stream (${state$.id.toString()}) with model: ${state$.state.metadata.model}`); } if (opts?.publish) { this._publishTip(state$); } this.markUnpinned(state$.id); return __classPrivateFieldGet(this, _Repository_deps, "f").pinStore.rm(state$); } async listPinned(streamId) { return __classPrivateFieldGet(this, _Repository_deps, "f").pinStore.ls(streamId); } markPinnedAndSynced(streamId) { __classPrivateFieldGet(this, _Repository_syncedPinnedStreams, "f").add(streamId.toString()); } markUnpinned(streamId) { __classPrivateFieldGet(this, _Repository_syncedPinnedStreams, "f").delete(streamId.toString()); } _wasPinnedStreamSynced(streamId) { return __classPrivateFieldGet(this, _Repository_syncedPinnedStreams, "f").has(streamId.toString()); } async randomPinnedStreamState() { const res = await __classPrivateFieldGet(this, _Repository_deps, "f").pinStore.stateStore.listStoredStreamIDs(null, 1); if (res.length == 0) { return null; } if (res.length > 1) { throw new Error(`Expected a single streamID from the state store, but got ${res.length} streamIDs instead`); } const [streamID] = res; return __classPrivateFieldGet(this, _Repository_deps, "f").pinStore.stateStore.load(StreamID.fromString(streamID)); } async _indexStreamIfNeeded(state$) { if (!state$.value.metadata.model) { return; } const asDate = (unixTimestamp) => { return unixTimestamp ? new Date(unixTimestamp * 1000) : null; }; const lastAnchor = asDate(StreamUtils.anchorTimestampFromState(state$.value)); const firstAnchor = asDate(state$.value.log.find((log) => log.type == EventType.TIME)?.timestamp); const streamContent = { model: state$.value.metadata.model, streamID: state$.id, controller: state$.value.metadata.controllers[0], streamContent: state$.value.content, tip: state$.tip, lastAnchor: lastAnchor, firstAnchor: firstAnchor, shouldIndex: state$.value.metadata.shouldIndex, }; await this.index.indexStream(streamContent); } updates$(init) { return new Observable((subscriber) => { const id = new StreamID(init.type, init.log[0].cid); this.fromMemoryOrStore(id) .then((found) => { const state$ = found || new RunningState(init, false); if (!found) { this._registerRunningState(state$); } this.inmemory.endure(id.toString(), state$); const subscription = state$.subscribe(subscriber); state$.add(subscription); subscription.add(() => { if (state$.subscriptionSet.size === 0) { this.inmemory.free(id.toString()); } }); }) .catch((error) => { this.logger.err(`An error occurred in updates$ for StreamID ${id}: ${error}`); subscriber.error(error); }); }); } anchorLoopHandler() { const fromMemoryOrStoreSafe = this.fromMemoryOrStore.bind(this); const handleAnchorEvent = this.handleAnchorEvent.bind(this); return { async handle(event) { const state$ = await fromMemoryOrStoreSafe(event.streamId); if (!state$) return true; return handleAnchorEvent(state$, event); }, }; } async close() { if (this.reconEventFeedSubscription) this.reconEventFeedSubscription.unsubscribe(); await this.recon.stop(); await this.loadingQ.close(); await this.executionQ.close(); Array.from(this.inmemory).forEach(([id, stream]) => { this.inmemory.delete(id); stream.complete(); }); await this.feedAggregationStore.close(); await __classPrivateFieldGet(this, _Repository_deps, "f").pinStore.close(); await __classPrivateFieldGet(this, _Repository_deps, "f").anchorRequestStore.close(); await this.index.close(); } } _Repository_deps = new WeakMap(), _Repository_syncedPinnedStreams = new WeakMap(); //# sourceMappingURL=repository.js.map