import * as plugins from './plugins.js'; import type { IFlexDatabaseDescriptor, IFlexPublicProviderConnection, } from './interfaces.flexipc.js'; import { hasFlexExactKeys, isFlexPlainObject } from './interfaces.flexipc.js'; import { assertFlexProviderConnectionDocument, type IFlexProviderConnectionDocument, } from './classes.flexmodels.private.js'; import { FlexPublicCandidateModel, FlexPublicMessageRecordModel, FlexPublicSessionRecordModel, type IFlexPublicCandidateDocument, type IFlexPublicCandidateManifest, type IFlexPublicMessageRecordDocument, type IFlexPublicSessionRecordDocument, type IFlexRecordManifest, assertFlexPublicCandidateDocument, assertFlexPublicMessageRecordDocument, assertFlexPublicSessionRecordDocument, flexPublicHeadId, flexPublicPageBytesLimit, flexPublicPageLimit, initFlexPublicModels, isFlexPublicCandidateManifest, registerFlexPublicModels, } from './classes.flexmodels.public.js'; type TFlexMessage = plugins.flexharness.IFlexMessage; type TFlexSession = plugins.flexharness.IFlexSession; export interface IFlexProjectionReaderOptions { database: IFlexDatabaseDescriptor; controllerId: string; } export interface IFlexProjectionPageOptions { limit?: number; cursor?: string; } export interface IFlexProjectedSessionPage { candidateId: string; revision: number; sessions: TFlexSession[]; nextCursor?: string; truncated: boolean; } export interface IFlexProjectedMessagePage { candidateId: string; revision: number; messages: IFlexProjectedMessage[]; nextCursor?: string; truncated: boolean; } export interface IFlexProjectedMessage { messageIndex: number; message: TFlexMessage; } export interface IFlexProjectedSessionSnapshot { candidateId: string; revision: number; sessions: TFlexSession[]; truncated: boolean; } export interface IFlexProjectedMessageSnapshot { candidateId: string; revision: number; messages: IFlexProjectedMessage[]; truncated: boolean; } interface IFlexPublicHeadView { id: string; controllerId: string; storageKey: string; revision: number; currentPublicCandidateId: string; publicCandidates: IFlexPublicCandidateManifest[]; } interface IFlexProjectionCursor { version: 2; kind: 'sessions' | 'messages'; controllerId: string; scopeId: string; candidateId: string; revision: number; anchorId: string; anchorTimestamp?: string; anchorMessageIndex?: number; sessionId?: string; } interface IFlexCandidateSelection { head: IFlexPublicHeadView; manifest: IFlexPublicCandidateManifest; candidate: IFlexPublicCandidateDocument; } const sha256 = (valueArg: Uint8Array | string): string => plugins.crypto.createHash('sha256').update(valueArg).digest('hex'); const serializedBytes = (valueArg: unknown): Buffer => Buffer.from(JSON.stringify(valueArg), 'utf8'); const isNonNegativeInteger = (valueArg: unknown): valueArg is number => Number.isSafeInteger(valueArg) && (valueArg as number) >= 0; const isBoundedString = (valueArg: unknown, maximumBytesArg: number): valueArg is string => typeof valueArg === 'string' && valueArg.length > 0 && Buffer.byteLength(valueArg, 'utf8') <= maximumBytesArg; const isIsoDate = (valueArg: unknown): valueArg is string => typeof valueArg === 'string' && Number.isFinite(Date.parse(valueArg)) && new Date(valueArg).toISOString() === valueArg; function assertPublicHeadView(valueArg: unknown): asserts valueArg is IFlexPublicHeadView { if ( !isFlexPlainObject(valueArg) || !hasFlexExactKeys(valueArg, [ 'id', 'controllerId', 'storageKey', 'revision', 'currentPublicCandidateId', 'publicCandidates', ]) || !isBoundedString(valueArg.id, 1024) || !isBoundedString(valueArg.controllerId, 512) || !isBoundedString(valueArg.storageKey, 512) || !isNonNegativeInteger(valueArg.revision) || !isBoundedString(valueArg.currentPublicCandidateId, 64) || !Array.isArray(valueArg.publicCandidates) || valueArg.publicCandidates.length < 1 || valueArg.publicCandidates.length > 4 || !valueArg.publicCandidates.every(isFlexPublicCandidateManifest) || valueArg.publicCandidates[0]?.candidateId !== valueArg.currentPublicCandidateId || valueArg.publicCandidates[0]?.revision !== valueArg.revision ) throw new FlexProjectionFormatError(); } const projectCandidate = (modelArg: FlexPublicCandidateModel): IFlexPublicCandidateDocument => ({ id: modelArg.id, controllerId: modelArg.controllerId, scopeId: modelArg.scopeId, candidateId: modelArg.candidateId, revision: modelArg.revision, sha256: modelArg.sha256, bytes: modelArg.bytes, sessionRecords: modelArg.sessionRecords, messageRecords: modelArg.messageRecords, sessionsTruncated: modelArg.sessionsTruncated, messagesTruncated: modelArg.messagesTruncated, messageBytesTruncated: modelArg.messageBytesTruncated, visibleDigest: modelArg.visibleDigest, scopeSource: modelArg.scopeSource, projectionSources: modelArg.projectionSources, createdAt: modelArg.createdAt, }); const projectSessionRecord = ( modelArg: FlexPublicSessionRecordModel, ): IFlexPublicSessionRecordDocument => ({ id: modelArg.id, controllerId: modelArg.controllerId, scopeId: modelArg.scopeId, candidateId: modelArg.candidateId, revision: modelArg.revision, session: modelArg.session, createdAt: modelArg.createdAt, }); const projectMessageRecord = ( modelArg: FlexPublicMessageRecordModel, ): IFlexPublicMessageRecordDocument => ({ id: modelArg.id, controllerId: modelArg.controllerId, scopeId: modelArg.scopeId, candidateId: modelArg.candidateId, revision: modelArg.revision, messageIndex: modelArg.messageIndex, message: modelArg.message, createdAt: modelArg.createdAt, }); const compareNewestSession = ( leftArg: IFlexPublicSessionRecordDocument, rightArg: IFlexPublicSessionRecordDocument, ): number => rightArg.session.updatedAt.localeCompare(leftArg.session.updatedAt) || rightArg.session.createdAt.localeCompare(leftArg.session.createdAt) || leftArg.session.sessionId.localeCompare(rightArg.session.sessionId); const compareNewestMessage = ( leftArg: IFlexPublicMessageRecordDocument, rightArg: IFlexPublicMessageRecordDocument, ): number => rightArg.messageIndex - leftArg.messageIndex; const validateLimit = (valueArg: number | undefined): number => { const selected = valueArg ?? flexPublicPageLimit; if (!Number.isSafeInteger(selected) || selected < 1 || selected > flexPublicPageLimit) { throw new FlexProjectionCursorError(); } return selected; }; const manifestBytes = (manifestArg: IFlexPublicCandidateManifest): Buffer => serializedBytes({ candidateId: manifestArg.candidateId, revision: manifestArg.revision, sessionRecords: manifestArg.sessionRecords.map(({ id, sha256: digest, bytes }) => ({ id, sha256: digest, bytes, })), messageRecords: manifestArg.messageRecords.map(({ id, sha256: digest, bytes }) => ({ id, sha256: digest, bytes, })), sessionsTruncated: manifestArg.sessionsTruncated, messagesTruncated: manifestArg.messagesTruncated, messageBytesTruncated: manifestArg.messageBytesTruncated, visibleDigest: manifestArg.visibleDigest, scopeSource: manifestArg.scopeSource, projectionSources: manifestArg.projectionSources, }); const verifyRecordManifest = (documentArg: { id: string }, manifestArg: IFlexRecordManifest): void => { const bytes = serializedBytes(documentArg); if ( documentArg.id !== manifestArg.id || bytes.byteLength !== manifestArg.bytes || sha256(bytes) !== manifestArg.sha256 ) throw new FlexProjectionFormatError(); }; const recordManifestsEqual = ( leftArg: IFlexRecordManifest[], rightArg: IFlexRecordManifest[], ): boolean => leftArg.length === rightArg.length && leftArg.every((left, index) => { const right = rightArg[index]; return right !== undefined && left.id === right.id && left.sha256 === right.sha256 && left.bytes === right.bytes; }); export class FlexProjectionCursorError extends Error { public readonly code = 'FLEX_PROJECTION_CURSOR_INVALID'; constructor() { super('The Flex projection cursor is invalid.'); } } export class FlexProjectionStaleCursorError extends Error { public readonly code = 'FLEX_PROJECTION_CURSOR_STALE'; constructor() { super('The Flex projection cursor refers to an evicted candidate.'); } } export class FlexProjectionNotFoundError extends Error { public readonly code = 'FLEX_PROJECTION_NOT_FOUND'; constructor() { super('The requested Flex projection was not found.'); } } export class FlexProjectionFormatError extends Error { public readonly code = 'FLEX_PROJECTION_FORMAT_INVALID'; constructor() { super('The Flex public projection is invalid.'); } } export class FlexProjectionReader { private readonly database: plugins.smartdata.SmartdataDb; private readonly controllerId: string; private readonly manager: { db: plugins.smartdata.SmartdataDb }; private initialized = false; private databaseOpened = false; private initPromise?: Promise; private closePromise?: Promise; private closed = false; constructor(optionsArg: IFlexProjectionReaderOptions) { this.controllerId = optionsArg.controllerId; this.database = new plugins.smartdata.SmartdataDb(optionsArg.database); this.manager = { db: this.database }; registerFlexPublicModels(this.manager); } public async init(): Promise { if (this.closed) throw new Error('The Flex projection reader is closed.'); if (this.initialized) return; if (this.initPromise) return this.initPromise; const initPromise = (async () => { try { this.databaseOpened = true; await this.database.init(); await initFlexPublicModels(); if (this.closed) throw new Error('The Flex projection reader was closed during startup.'); this.initialized = true; } catch (errorArg) { if (this.databaseOpened) { try { await this.database.close(); this.databaseOpened = false; } catch { // close() retains retry ownership for a database that did not close. } } throw errorArg; } })(); this.initPromise = initPromise; try { await initPromise; } finally { if (this.initPromise === initPromise) this.initPromise = undefined; } } public async close(): Promise { if (this.closePromise) return this.closePromise; this.closed = true; const closePromise = (async () => { if (this.initPromise) await this.initPromise.catch(() => undefined); if (this.databaseOpened) { await this.database.close(); this.databaseOpened = false; } this.initialized = false; })(); this.closePromise = closePromise; try { await closePromise; } finally { if (this.closePromise === closePromise) this.closePromise = undefined; } } public async listSessionPage( scopeIdArg: string, optionsArg: IFlexProjectionPageOptions = {}, signalArg?: AbortSignal, ): Promise { const limit = validateLimit(optionsArg.limit); const cursor = optionsArg.cursor ? this.parseCursor(optionsArg.cursor, 'sessions', scopeIdArg) : undefined; const selection = await this.selectCandidate(scopeIdArg, cursor, signalArg); const records = await this.loadSessionRecords(selection, signalArg); const startIndex = cursor ? this.findAnchorIndex( records, cursor.anchorId, cursor.anchorTimestamp!, (record) => record.session.sessionId, (record) => record.session.updatedAt, ) + 1 : 0; let selected = records.slice(startIndex, startIndex + limit); let page = this.createSessionPage(selection, selected, startIndex + selected.length < records.length); while (serializedBytes(page).byteLength > flexPublicPageBytesLimit && selected.length > 0) { selected = selected.slice(0, -1); page = this.createSessionPage(selection, selected, true); } if (selected.length === 0 && records.length > startIndex) throw new FlexProjectionFormatError(); return page; } public async listAllSessions( scopeIdArg: string, signalArg?: AbortSignal, ): Promise { const selection = await this.selectCandidate(scopeIdArg, undefined, signalArg); const records = await this.loadSessionRecords(selection, signalArg); return { candidateId: selection.manifest.candidateId, revision: selection.manifest.revision, sessions: records.map((record) => JSON.parse(JSON.stringify(record.session)) as TFlexSession), truncated: selection.manifest.sessionsTruncated, }; } public async getSession( scopeIdArg: string, sessionIdArg: string, signalArg?: AbortSignal, ): Promise { const selection = await this.selectCandidate(scopeIdArg, undefined, signalArg); const records = await this.loadSessionRecords(selection, signalArg); const record = records.find((entry) => entry.session.sessionId === sessionIdArg); if (!record) throw new FlexProjectionNotFoundError(); return JSON.parse(JSON.stringify(record.session)) as TFlexSession; } public async listMessagePage( scopeIdArg: string, sessionIdArg: string, optionsArg: IFlexProjectionPageOptions = {}, signalArg?: AbortSignal, ): Promise { const limit = validateLimit(optionsArg.limit); const cursor = optionsArg.cursor ? this.parseCursor(optionsArg.cursor, 'messages', scopeIdArg, sessionIdArg) : undefined; const selection = await this.selectCandidate(scopeIdArg, cursor, signalArg); const records = (await this.loadMessageRecords(selection, signalArg)) .filter((entry) => entry.message.sessionId === sessionIdArg) .sort(compareNewestMessage); const startIndex = cursor ? records.findIndex((record) => ( record.message.messageId === cursor.anchorId && record.messageIndex === cursor.anchorMessageIndex )) + 1 : 0; if (cursor && startIndex === 0) throw new FlexProjectionCursorError(); let newestFirst = records.slice(startIndex, startIndex + limit); let page = this.createMessagePage( selection, sessionIdArg, newestFirst, startIndex + newestFirst.length < records.length, ); while (serializedBytes(page).byteLength > flexPublicPageBytesLimit && newestFirst.length > 0) { newestFirst = newestFirst.slice(0, -1); page = this.createMessagePage(selection, sessionIdArg, newestFirst, true); } if (newestFirst.length === 0 && records.length > startIndex) throw new FlexProjectionFormatError(); return page; } public async listAllSessionMessages( scopeIdArg: string, sessionIdArg: string, signalArg?: AbortSignal, ): Promise { const selection = await this.selectCandidate(scopeIdArg, undefined, signalArg); const records = (await this.loadMessageRecords(selection, signalArg)) .filter((entry) => entry.message.sessionId === sessionIdArg) .sort(compareNewestMessage); return { candidateId: selection.manifest.candidateId, revision: selection.manifest.revision, messages: records.map((record) => ({ messageIndex: record.messageIndex, message: JSON.parse(JSON.stringify(record.message)) as TFlexMessage, })).reverse(), truncated: selection.manifest.messagesTruncated || selection.manifest.messageBytesTruncated, }; } public async getMessage( scopeIdArg: string, sessionIdArg: string, messageIdArg: string, signalArg?: AbortSignal, ): Promise { const selection = await this.selectCandidate(scopeIdArg, undefined, signalArg); const records = await this.loadMessageRecords(selection, signalArg); const record = records.find((entry) => ( entry.message.sessionId === sessionIdArg && entry.message.messageId === messageIdArg )); if (!record) throw new FlexProjectionNotFoundError(); return { messageIndex: record.messageIndex, message: JSON.parse(JSON.stringify(record.message)) as TFlexMessage, }; } public async listProviderConnections( signalArg?: AbortSignal, ): Promise { signalArg?.throwIfAborted(); await this.init(); signalArg?.throwIfAborted(); const cursor = this.database.mongoDb.collection('flex_provider_connections').find( { controllerId: this.controllerId }, { projection: { _id: 0, id: 1, controllerId: 1, providerId: 1, state: 1, generation: 1, createdAt: 1, updatedAt: 1, account: 1, }, sort: { updatedAt: -1, id: 1 }, limit: 513, ...(signalArg === undefined ? {} : { signal: signalArg }), }, ); try { const rawDocuments = await cursor.toArray() as unknown[]; if (rawDocuments.length > 512) { throw new Error('The persisted Flex provider connection limit was exceeded.'); } return rawDocuments.map((rawDocument) => { assertFlexProviderConnectionDocument(rawDocument); const document = rawDocument as IFlexProviderConnectionDocument; if (document.controllerId !== this.controllerId) throw new FlexProjectionFormatError(); return { loginId: document.id, providerId: document.providerId, status: document.state, ...(document.account === undefined ? {} : { account: JSON.parse(JSON.stringify(document.account)) as typeof document.account }), }; }); } finally { await cursor.close(); } } private async selectCandidate( scopeIdArg: string, cursorArg?: IFlexProjectionCursor, signalArg?: AbortSignal, ): Promise { signalArg?.throwIfAborted(); await this.init(); signalArg?.throwIfAborted(); if (cursorArg) { const head = await this.loadHead(scopeIdArg, signalArg); const manifest = head.publicCandidates.find((candidate) => ( candidate.candidateId === cursorArg.candidateId && candidate.revision === cursorArg.revision )); if (!manifest) throw new FlexProjectionStaleCursorError(); const candidate = await this.loadCandidate(scopeIdArg, manifest, signalArg); if (!candidate) throw new FlexProjectionStaleCursorError(); return { head, manifest, candidate }; } let head = await this.loadHead(scopeIdArg, signalArg); let manifest = head.publicCandidates.find( (candidate) => candidate.candidateId === head.currentPublicCandidateId, ); if (!manifest) throw new FlexProjectionFormatError(); let candidate = await this.loadCandidate(scopeIdArg, manifest, signalArg); if (!candidate) { head = await this.loadHead(scopeIdArg, signalArg); manifest = head.publicCandidates.find( (entry) => entry.candidateId === head.currentPublicCandidateId, ); if (!manifest) throw new FlexProjectionFormatError(); candidate = await this.loadCandidate(scopeIdArg, manifest, signalArg); } if (!candidate) throw new FlexProjectionFormatError(); return { head, manifest, candidate }; } private async loadHead( scopeIdArg: string, signalArg?: AbortSignal, ): Promise { const raw = await this.database.mongoDb.collection('flex_public_heads').findOne( { id: flexPublicHeadId(this.controllerId, scopeIdArg) }, { projection: { _id: 0, id: 1, controllerId: 1, storageKey: 1, revision: 1, currentPublicCandidateId: 1, publicCandidates: 1, }, ...(signalArg === undefined ? {} : { signal: signalArg }), }, ) as unknown; if (!raw) throw new FlexProjectionNotFoundError(); assertPublicHeadView(raw); if (raw.controllerId !== this.controllerId || raw.storageKey !== scopeIdArg) { throw new FlexProjectionFormatError(); } return raw; } private async loadCandidate( scopeIdArg: string, manifestArg: IFlexPublicCandidateManifest, signalArg?: AbortSignal, ): Promise { const raw = await FlexPublicCandidateModel.collection.mongoDbCollection.findOne({ candidateId: manifestArg.candidateId, controllerId: this.controllerId, scopeId: scopeIdArg, }, { projection: { _id: 0 }, ...(signalArg === undefined ? {} : { signal: signalArg }), }); if (!raw) return undefined; const candidate = projectCandidate(raw as unknown as FlexPublicCandidateModel); assertFlexPublicCandidateDocument(candidate); const bytes = manifestBytes(manifestArg); if ( candidate.candidateId !== manifestArg.candidateId || candidate.revision !== manifestArg.revision || candidate.sha256 !== manifestArg.sha256 || candidate.bytes !== manifestArg.bytes || !recordManifestsEqual(candidate.sessionRecords, manifestArg.sessionRecords) || !recordManifestsEqual(candidate.messageRecords, manifestArg.messageRecords) || candidate.sessionsTruncated !== manifestArg.sessionsTruncated || candidate.messagesTruncated !== manifestArg.messagesTruncated || candidate.messageBytesTruncated !== manifestArg.messageBytesTruncated || bytes.byteLength !== manifestArg.bytes || sha256(bytes) !== manifestArg.sha256 ) throw new FlexProjectionFormatError(); return candidate; } private async loadSessionRecords( selectionArg: IFlexCandidateSelection, signalArg?: AbortSignal, ): Promise { const cursor = FlexPublicSessionRecordModel.collection.mongoDbCollection.find({ candidateId: selectionArg.candidate.candidateId, controllerId: this.controllerId, scopeId: selectionArg.head.storageKey, }, { projection: { _id: 0 }, ...(signalArg === undefined ? {} : { signal: signalArg }), }); try { const documents = await cursor.toArray(); if (documents.length !== selectionArg.manifest.sessionRecords.length) { throw new FlexProjectionFormatError(); } const manifestById = new Map( selectionArg.manifest.sessionRecords.map((manifest) => [manifest.id, manifest]), ); return documents.map((raw) => { const document = projectSessionRecord(raw as unknown as FlexPublicSessionRecordModel); assertFlexPublicSessionRecordDocument(document); const manifest = manifestById.get(document.id); if (!manifest || document.revision !== selectionArg.manifest.revision) { throw new FlexProjectionFormatError(); } verifyRecordManifest(document, manifest); return document; }).sort(compareNewestSession); } finally { await cursor.close(); } } private async loadMessageRecords( selectionArg: IFlexCandidateSelection, signalArg?: AbortSignal, ): Promise { const cursor = FlexPublicMessageRecordModel.collection.mongoDbCollection.find({ candidateId: selectionArg.candidate.candidateId, controllerId: this.controllerId, scopeId: selectionArg.head.storageKey, }, { projection: { _id: 0 }, ...(signalArg === undefined ? {} : { signal: signalArg }), }); try { const documents = await cursor.toArray(); if (documents.length !== selectionArg.manifest.messageRecords.length) { throw new FlexProjectionFormatError(); } const manifestById = new Map( selectionArg.manifest.messageRecords.map((manifest) => [manifest.id, manifest]), ); const records = documents.map((raw) => { const document = projectMessageRecord(raw as unknown as FlexPublicMessageRecordModel); assertFlexPublicMessageRecordDocument(document); const manifest = manifestById.get(document.id); if (!manifest || document.revision !== selectionArg.manifest.revision) { throw new FlexProjectionFormatError(); } verifyRecordManifest(document, manifest); return document; }); if (new Set(records.map((record) => ( `${record.message.sessionId}\0${record.messageIndex}` ))).size !== records.length) throw new FlexProjectionFormatError(); return records; } finally { await cursor.close(); } } private createSessionPage( selectionArg: IFlexCandidateSelection, recordsArg: IFlexPublicSessionRecordDocument[], hasMoreArg: boolean, ): IFlexProjectedSessionPage { const anchor = recordsArg.at(-1); return { candidateId: selectionArg.manifest.candidateId, revision: selectionArg.manifest.revision, sessions: recordsArg.map((record) => JSON.parse(JSON.stringify(record.session)) as TFlexSession), ...(hasMoreArg && anchor ? { nextCursor: this.encodeCursor({ version: 2, kind: 'sessions', controllerId: this.controllerId, scopeId: selectionArg.head.storageKey, candidateId: selectionArg.manifest.candidateId, revision: selectionArg.manifest.revision, anchorId: anchor.session.sessionId, anchorTimestamp: anchor.session.updatedAt, }), } : {}), truncated: selectionArg.manifest.sessionsTruncated, }; } private createMessagePage( selectionArg: IFlexCandidateSelection, sessionIdArg: string, newestFirstArg: IFlexPublicMessageRecordDocument[], hasMoreArg: boolean, ): IFlexProjectedMessagePage { const anchor = newestFirstArg.at(-1); return { candidateId: selectionArg.manifest.candidateId, revision: selectionArg.manifest.revision, messages: newestFirstArg .map((record) => ({ messageIndex: record.messageIndex, message: JSON.parse(JSON.stringify(record.message)) as TFlexMessage, })) .reverse(), ...(hasMoreArg && anchor ? { nextCursor: this.encodeCursor({ version: 2, kind: 'messages', controllerId: this.controllerId, scopeId: selectionArg.head.storageKey, candidateId: selectionArg.manifest.candidateId, revision: selectionArg.manifest.revision, anchorId: anchor.message.messageId, anchorMessageIndex: anchor.messageIndex, sessionId: sessionIdArg, }), } : {}), truncated: selectionArg.manifest.messagesTruncated || selectionArg.manifest.messageBytesTruncated, }; } private encodeCursor(cursorArg: IFlexProjectionCursor): string { return serializedBytes(cursorArg).toString('base64url'); } private parseCursor( cursorArg: string, kindArg: IFlexProjectionCursor['kind'], scopeIdArg: string, sessionIdArg?: string, ): IFlexProjectionCursor { if (!isBoundedString(cursorArg, 4096)) throw new FlexProjectionCursorError(); let parsed: unknown; try { const bytes = Buffer.from(cursorArg, 'base64url'); if (bytes.toString('base64url') !== cursorArg) throw new Error('non-canonical'); parsed = JSON.parse(bytes.toString('utf8')) as unknown; } catch { throw new FlexProjectionCursorError(); } if ( !isFlexPlainObject(parsed) || !hasFlexExactKeys( parsed, [ 'version', 'kind', 'controllerId', 'scopeId', 'candidateId', 'revision', 'anchorId', ], ['sessionId', 'anchorTimestamp', 'anchorMessageIndex'], ) || parsed.version !== 2 || parsed.kind !== kindArg || parsed.controllerId !== this.controllerId || parsed.scopeId !== scopeIdArg || !isBoundedString(parsed.candidateId, 64) || !isNonNegativeInteger(parsed.revision) || !isBoundedString(parsed.anchorId, 512) || (kindArg === 'messages' && ( parsed.sessionId !== sessionIdArg || !isNonNegativeInteger(parsed.anchorMessageIndex) || parsed.anchorTimestamp !== undefined )) || (kindArg === 'sessions' && ( parsed.sessionId !== undefined || !isIsoDate(parsed.anchorTimestamp) || parsed.anchorMessageIndex !== undefined )) ) throw new FlexProjectionCursorError(); return parsed as unknown as IFlexProjectionCursor; } private findAnchorIndex( recordsArg: TRecord[], anchorIdArg: string, anchorTimestampArg: string, idArg: (recordArg: TRecord) => string, timestampArg: (recordArg: TRecord) => string, ): number { const index = recordsArg.findIndex((record) => ( idArg(record) === anchorIdArg && timestampArg(record) === anchorTimestampArg )); if (index < 0) throw new FlexProjectionCursorError(); return index; } }