import { ICommit , ICommitDetails , ICreateCommitParams , ICreateTreeEntry } from "@fluidframework/gitresources" ; import { IGitCache , IGitManager } from "@fluidframework/server-services-client" ; import { IDatabaseManager , IDeliState , IDocument , IDocumentDetails , IDocumentStorage , IScribe , ITenantManager , } from "@fluidframework/server-services-core" ; import { ISummaryTree , ICommittedProposal , ITreeEntry , SummaryType , ISnapshotTreeEx , SummaryObject , } from "@fluidframework/protocol-definitions" ; import { IQuorumSnapshot , getQuorumTreeEntries , mergeAppAndProtocolTree , getGitMode , getGitType , } from "@fluidframework/protocol-base" ; import { gitHashFile , IsoBuffer , Uint8ArrayToString } from "@fluidframework/common-utils" ; export class TestDocumentStorage implements IDocumentStorage { constructor ( private readonly databaseManager , private readonly tenantManager ) { } /**
     * Retrieves database details for the given document
     */ public async getDocument ( tenantId , documentId ) { const collection = await this . databaseManager . getDocumentCollection ( ) ; return collection . findOne ( { documentId , tenantId } ) ; } public async getOrCreateDocument ( tenantId , documentId ) { const getOrCreateP = this . getOrCreateObject ( tenantId , documentId ) ; return getOrCreateP ; } public async createDocument ( tenantId , documentId , summary , sequenceNumber , term , initialHash , values ) { const tenant = await this . tenantManager . getTenant ( tenantId , documentId ) ; const gitManager = tenant . gitManager ; const blobsShaCache = new Set < string > ( ) ; const handle = await writeSummaryTree ( gitManager , summary , blobsShaCache , undefined ) ; const quorumSnapshot = { members : [ ] , proposals : [ ] , values } ; const entries = getQuorumTreeEntries ( documentId , sequenceNumber , sequenceNumber , term , quorumSnapshot ) ; const [ protocolTree , appSummaryTree ] = await Promise . all ( [ gitManager . createTree ( { entries } ) , gitManager . getTree ( handle , false ) , ] ) ; const newTreeEntries = mergeAppAndProtocolTree ( appSummaryTree , protocolTree ) ; const gitTree = await gitManager . createGitTree ( { tree : newTreeEntries } ) ; const commitParams = { author : { date : new Date ( ) . toISOString ( ) , email : "dummy@microsoft.com" , name : "Routerlicious Service" } , message : "New document" , parents : [ ] , tree : gitTree . sha } ; const commit = await gitManager . createCommit ( commitParams ) ; await gitManager . createRef ( documentId , commit . sha ) ; const deli = { clients : undefined , durableSequenceNumber : sequenceNumber , expHash1 : initialHash , logOffset : - 1 , sequenceNumber , epoch : undefined , term : 1 , lastSentMSN : 0 , nackMessages : undefined , successfullyStartedLambdas : [ ] } ; const scribe = { logOffset : - 1 , minimumSequenceNumber : sequenceNumber , protocolState : { members : [ ] , minimumSequenceNumber : sequenceNumber , proposals : [ ] , sequenceNumber , values } , sequenceNumber , lastClientSummaryHead : undefined , lastSummarySequenceNumber : 0 } ; const collection = await this . databaseManager . getDocumentCollection ( ) ; const result = await collection . findOrCreate ( { documentId , tenantId } , { createTime : Date . now ( ) , deli : JSON . stringify ( deli ) , documentId , scribe : JSON . stringify ( scribe ) , tenantId , version : "0.1" } ) ; return result ; } public async getLatestVersion ( tenantId , documentId ) { const versions = await this . getVersions ( tenantId , documentId , 1 ) ; if ( ! versions . length ) { return null ; } const latest = versions [ 0 ] ; return { author : latest . commit . author , committer : latest . commit . committer , message : latest . commit . message , parents : latest . parents , sha : latest . sha , tree : latest . commit . tree , url : latest . url } ; } public async getVersions ( tenantId , documentId , count ) { const tenant = await this . tenantManager . getTenant ( tenantId , documentId ) ; const gitManager = tenant . gitManager ; return gitManager . getCommits ( documentId , count ) ; } public async getVersion ( tenantId , documentId , sha ) { const tenant = await this . tenantManager . getTenant ( tenantId , documentId ) ; const gitManager = tenant . gitManager ; return gitManager . getCommit ( sha ) ; } public async getFullTree ( tenantId , documentId ) { throw new Error ( "Method not implemented." ) ; } private async getOrCreateObject ( tenantId , documentId ) { const collection = await this . databaseManager . getDocumentCollection ( ) ; const result = await collection . findOrCreate ( { documentId , tenantId } , { createTime : Date . now ( ) , deli : undefined , documentId , scribe : undefined , tenantId , version : "0.1" } ) ; return result ; } } param manager param summaryTree param blobsShaCache param snapshot export async function writeSummaryTree ( manager , summaryTree , blobsShaCache , snapshot ) { const entries = await Promise . all ( Object . keys ( summaryTree . tree ) . map ( async ( key ) => { const entry = summaryTree . tree [ key ] ; const pathHandle = await writeSummaryTreeObject ( manager , blobsShaCache , key , entry , snapshot ) ; const treeEntry = { mode : getGitMode ( entry ) , path : encodeURIComponent ( key ) , sha : pathHandle , type : getGitType ( entry ) } ; return treeEntry ; } ) ) ; const treeHandle = await manager . createGitTree ( { tree : entries } ) ; return treeHandle . sha ; } async function writeSummaryTreeObject ( manager , blobsShaCache , key , object , snapshot , currentPath = "" ) { switch ( object . type ) { case SummaryType . Blob : { return writeSummaryBlob ( object . content , blobsShaCache , manager ) ; } case SummaryType . Handle : { if ( snapshot === undefined ) { throw Error ( "Parent summary does not exist to reference by handle." ) ; } return getIdFromPath ( object . handleType , object . handle , snapshot ) ; } case SummaryType . Tree : { return writeSummaryTree ( manager , object , blobsShaCache , snapshot ?. trees [ key ] ) ; } default : throw Error ( `Unexpected summary object type: "${ object . type }".` ) ; } } function getIdFromPath ( handleType , handlePath , fullSnapshot ) { const path = handlePath . split ( "/" ) . map ( ( part ) => decodeURIComponent ( part ) ) ; if ( path [ 0 ] === "" ) { path . shift ( ) ; } return getIdFromPathCore ( handleType , path , fullSnapshot ) ; } function getIdFromPathCore ( handleType , path , snapshot ) { const key = path [ 0 ] ; if ( path . length === 1 ) { switch ( handleType ) { case SummaryType . Blob : { return snapshot . blobs [ key ] ; } case SummaryType . Tree : { return snapshot . trees [ key ] ?. id ; } default : throw Error ( `Unexpected handle summary object type: "${ handleType }".` ) ; } } return getIdFromPathCore ( handleType , path . slice ( 1 ) , snapshot ) ; } async function writeSummaryBlob ( content , blobsShaCache , manager ) { const { parsedContent , encoding } = typeof content === "string" ? { parsedContent : content , encoding : "utf-8" } : { parsedContent : Uint8ArrayToString ( content , "base64" ) , encoding : "base64" } ; const hash = await gitHashFile ( IsoBuffer . from ( parsedContent , encoding ) ) ; if ( ! blobsShaCache . has ( hash ) ) { const blob = await manager . createBlob ( parsedContent , encoding ) ; blobsShaCache . add ( blob . sha ) ; } return hash ; }