@ceramicnetwork/core
Version:
Typescript implementation of the Ceramic protocol
666 lines • 31.1 kB
JavaScript
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