Skip to content

Commit

Permalink
feat: Unbundle DocID into DocID and CommitID (#1009)
Browse files Browse the repository at this point in the history
* chore(docid): no more declarations.d.ts

* chore(docid): doc id in own file

* chore(docid): extract constants in own file

* feat(docid): always use base36

* fix(docid): adjust tests

* fix(docid): rename test file

* fix(docid): Drop DocId#multihash

* feat(docid): Introduce CommitId (dummy one for now)

* feat(docid): No commit

* feat(docid): _doctype => #doctype, _cid => #cid

* feat(docid): static methods for DocID

* feat(docid): doctypes registry

* feat(docid): ripple doctypes registry to CommitID

* chore(docid): better tests for CommitId

* chore(docid): better tests for CommitId and DocId

* chore(docid): CommitId => CommitID

* feat(docid): DocID#baseID

* feat(docid): add doc comments, and expose CommentID

* chore(docid): More documentation

* chore(docid): Get back proper docid encoding in README

* feat(docid): CommitID requires commit when parsing

* feat(docid): Introduce DocRef

* chore: Adjust tests and code for DocID, CommitID

* chore(core): Remove dev comments

* chore(core): Remove more dev comments

* chore: Rename `travel` to `atCommit`

* feat: DocID.fromString and DocID.fromBytes are strict now

No commit information!

* feat: Throw proper error when referencing schema without commit

* Update packages/core/src/document.ts

Co-authored-by: Spencer T Brody <[email protected]>

* feat: CommitID inspection returns its string representation

* feat: memoize more calculated properties of DocID and CommitID

Co-authored-by: Spencer T Brody <[email protected]>
  • Loading branch information
ukstv and Spencer T Brody authored Feb 17, 2021
1 parent dd58039 commit c2707f2
Show file tree
Hide file tree
Showing 35 changed files with 1,361 additions and 1,050 deletions.
8 changes: 4 additions & 4 deletions packages/3id-did-resolver/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,11 @@ import type { ParsedDID, DIDResolver, DIDDocument } from 'did-resolver'
import { Doctype } from "@ceramicnetwork/common"
import LegacyResolver from './legacyResolver'
import * as u8a from 'uint8arrays'
import DocID from '@ceramicnetwork/docid'
import { DocID, CommitID } from '@ceramicnetwork/docid'
import CID from 'cids'

interface Ceramic {
loadDocument(docId: DocID): Promise<Doctype>;
loadDocument(docId: DocID | CommitID): Promise<Doctype>;
createDocument(type: string, content: any, opts: any): Promise<Doctype>;
}

Expand Down Expand Up @@ -101,8 +101,8 @@ const legacyResolve = async (ceramic: Ceramic, didId: string, commit?: string):
}

const resolve = async (ceramic: Ceramic, didId: string, commit?: string, v03ID?: string): Promise<DIDDocument | null> => {
const docId = DocID.fromString(didId, commit)
const doctype = await ceramic.loadDocument(docId)
const commitId = DocID.fromString(didId).atCommit(commit)
const doctype = await ceramic.loadDocument(commitId)
return wrapDocument(doctype.content, `did:3:${v03ID || didId}`)
}

Expand Down
14 changes: 7 additions & 7 deletions packages/cli/src/__tests__/ceramic-daemon.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -212,47 +212,47 @@ describe('Ceramic interop: core <> http-client', () => {
expect(doc.state.anchorStatus).toEqual(AnchorStatus.ANCHORED)

// Load genesis commit
const v0Id = DocID.fromOther(doc.id, doc.id.cid)
const v0Id = doc.id.atCommit(doc.id.cid)
const docV0Core = await core.loadDocument(v0Id)
const docV0Client = await client.loadDocument(v0Id)
expect(docV0Core.content).toEqual(content1)
expect(docV0Core.state.log.length).toEqual(1)
expect(DoctypeUtils.serializeState(docV0Core.state)).toEqual(DoctypeUtils.serializeState(docV0Client.state))

// Load v1 (anchor on top of genesis commit)
const v1Id = DocID.fromOther(doc.id, doc.state.log[1].cid)
const v1Id = doc.id.atCommit(doc.state.log[1].cid)
const docV1Core = await core.loadDocument(v1Id)
const docV1Client = await client.loadDocument(v1Id)
expect(docV1Core.content).toEqual(content1)
expect(docV1Core.state.log.length).toEqual(2)
expect(DoctypeUtils.serializeState(docV1Core.state)).toEqual(DoctypeUtils.serializeState(docV1Client.state))

// Load v2
const v2Id = DocID.fromOther(doc.id, doc.state.log[2].cid)
const v2Id = doc.id.atCommit(doc.state.log[2].cid)
const docV2Core = await core.loadDocument(v2Id)
const docV2Client = await client.loadDocument(v2Id)
expect(docV2Core.content).toEqual(content2)
expect(docV2Core.state.log.length).toEqual(3)
expect(DoctypeUtils.serializeState(docV2Core.state)).toEqual(DoctypeUtils.serializeState(docV2Client.state))

// Load v3 (anchor on top of v2)
const v3Id = DocID.fromOther(doc.id, doc.state.log[3].cid)
const v3Id = doc.id.atCommit(doc.state.log[3].cid)
const docV3Core = await core.loadDocument(v3Id)
const docV3Client = await client.loadDocument(v3Id)
expect(docV3Core.content).toEqual(content2)
expect(docV3Core.state.log.length).toEqual(4)
expect(DoctypeUtils.serializeState(docV3Core.state)).toEqual(DoctypeUtils.serializeState(docV3Client.state))

// Load v4
const v4Id = DocID.fromOther(doc.id, doc.state.log[4].cid)
const v4Id = doc.id.atCommit(doc.state.log[4].cid)
const docV4Core = await core.loadDocument(v4Id)
const docV4Client = await client.loadDocument(v4Id)
expect(docV4Core.content).toEqual(content3)
expect(docV4Core.state.log.length).toEqual(5)
expect(DoctypeUtils.serializeState(docV4Core.state)).toEqual(DoctypeUtils.serializeState(docV4Client.state))

// Load v5
const v5Id = DocID.fromOther(doc.id, doc.state.log[5].cid)
const v5Id = doc.id.atCommit(doc.state.log[5].cid)
const docV5Core = await core.loadDocument(v5Id)
const docV5Client = await client.loadDocument(v5Id)
expect(docV5Core.content).toEqual(content3)
Expand Down Expand Up @@ -291,7 +291,7 @@ describe('Ceramic interop: core <> http-client', () => {
{
docId: docA.id,
paths: ['/c']
},
},
{
docId: docB.id.toString(),
paths: ['/d']
Expand Down
7 changes: 0 additions & 7 deletions packages/cli/src/ceramic-cli-utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -137,13 +137,6 @@ export class CeramicCliUtils {
*/
static async change(docId: string, content: string, controllers: string, schemaDocId?: string): Promise<void> {
const id = DocID.fromString(docId)

const commit = id.commit
if (commit) {
console.error(`No commits allowed. Invalid docId: ${id.toString()}`)
return
}

await CeramicCliUtils._runWithCeramic(async (ceramic: CeramicClient) => {
const parsedControllers = CeramicCliUtils._parseControllers(controllers)
const parsedContent = CeramicCliUtils._parseContent(content)
Expand Down
4 changes: 2 additions & 2 deletions packages/common/src/ceramic-api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import {
DocParams,
CeramicCommit
} from "./doctype"
import DocID from '@ceramicnetwork/docid'
import { DocID, CommitID } from '@ceramicnetwork/docid'

/**
* Describes Ceramic pinning functionality
Expand Down Expand Up @@ -70,7 +70,7 @@ export interface CeramicApi {
* @param docId - Document ID
* @param opts - Initialization options
*/
loadDocument<T extends Doctype>(docId: DocID | string, opts?: DocOpts): Promise<T>;
loadDocument<T extends Doctype>(docId: DocID | CommitID | string, opts?: DocOpts): Promise<T>;

/**
* Load all document commits by document ID
Expand Down
14 changes: 2 additions & 12 deletions packages/common/src/doc-cache.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { LRUMap } from 'lru_map'
import DocID from "@ceramicnetwork/docid"
import { DocID } from "@ceramicnetwork/docid"

import { DocStateHolder } from "./doctype"

Expand Down Expand Up @@ -27,14 +27,7 @@ export class DocCache {
* @param doc - DocStateHolder instance
*/
put(doc: DocStateHolder): void {
if (doc.id.commit == null) {
this._baseDocCache.set(doc.id.baseID.toString(), doc)
return;
}

if (this._cacheCommits) {
this._commitDocCache.set(doc.id.toString(), doc)
}
this._baseDocCache.set(doc.id.baseID.toString(), doc)
}

/**
Expand All @@ -59,9 +52,6 @@ export class DocCache {
* Puts to pinned
*/
pin(doc: DocStateHolder) {
if (doc.id.commit != null) {
throw new Error(`Cannot pin a specific commit. Doc ID ${doc.id.toString()}`)
}
this._pinnedDocCache[doc.id.toString()] = doc;
this._baseDocCache.delete(doc.id.toString())
}
Expand Down
14 changes: 7 additions & 7 deletions packages/common/src/doctype.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import CID from 'cids'
import cloneDeep from 'lodash.clonedeep'
import { EventEmitter } from "events"
import type { Context } from "./context"
import DocID from '@ceramicnetwork/docid'
import { DocID, CommitID } from '@ceramicnetwork/docid'
import type { DagJWSResult, DagJWS } from 'dids'

/**
Expand Down Expand Up @@ -178,24 +178,24 @@ export abstract class Doctype extends EventEmitter implements DocStateHolder {
return this._state.log[this._state.log.length - 1].cid
}

get commitId(): DocID {
return DocID.fromOther(this.id, this.tip)
get commitId(): CommitID {
return this.id.atCommit(this.tip)
}

/**
* Lists available commits
*/
get allCommitIds(): Array<DocID> {
return this._state.log.map(({ cid }) => DocID.fromOther(this.id, cid))
get allCommitIds(): Array<CommitID> {
return this._state.log.map(({ cid }) => this.id.atCommit(cid))
}

/**
* Lists available commits that correspond to anchor commits
*/
get anchorCommitIds(): Array<DocID> {
get anchorCommitIds(): Array<CommitID> {
return this._state.log
.filter(({ type }) => type === CommitType.ANCHOR)
.map(({ cid }) => DocID.fromOther(this.id, cid))
.map(({ cid }) => this.id.atCommit(cid))
}

get state(): DocState {
Expand Down
24 changes: 8 additions & 16 deletions packages/core/src/__tests__/ceramic-api.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ const generateStringOfSize = (size): string => {
}

describe('Ceramic API', () => {
jest.setTimeout(15000)
jest.setTimeout(60000)
let ipfs: IpfsApi;

const DOCTYPE_TILE = 'tile'
Expand Down Expand Up @@ -119,7 +119,7 @@ describe('Ceramic API', () => {
expect(docOg.content).toEqual({ test: 'abcde' })
expect(docOg.state.anchorStatus).toEqual(AnchorStatus.ANCHORED)

const docV1Id = DocID.fromOther(docOg.id, docOg.state.log[1].cid.toString())
const docV1Id = docOg.id.atCommit(docOg.state.log[1].cid)
const docV1 = await ceramic.loadDocument<TileDoctype>(docV1Id)
expect(docV1.state).toEqual(stateOg)
expect(docV1.content).toEqual({ test: 321 })
Expand All @@ -133,17 +133,15 @@ describe('Ceramic API', () => {
expect(e.message).toEqual('Historical document commits cannot be modified. Load the document without specifying a commit to make updates.')
}

// // try to call Ceramic API directly
try {
await expect( async () => {
const updateRecord = await TileDoctype._makeCommit(docV1, ceramic.context.did, { content: { test: 'fghj' } })
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
await ceramic.context.api.applyRecord(docV1Id, updateRecord)
throw new Error('Should not be able to update commit')
} catch (e) {
expect(e.message).toEqual('Historical document commits cannot be modified. Load the document without specifying a commit to make updates.')
}
}).rejects.toThrow(/Not DocID/)

// checkout not anchored commit
const docV2Id = DocID.fromOther(docOg.id, docOg.state.log[2].cid.toString())
const docV2Id = docOg.id.atCommit(docOg.state.log[2].cid)
const docV2 = await ceramic.loadDocument<TileDoctype>(docV2Id)
expect(docV2.content).toEqual({ test: "abcde" })
expect(docV2.state.anchorStatus).toEqual(AnchorStatus.NOT_REQUESTED)
Expand Down Expand Up @@ -199,20 +197,14 @@ describe('Ceramic API', () => {
metadata: { controllers: [controller] }
})

expect(schemaDoc.id.commit).toBeFalsy()
const schemaDocIdWithoutCommit = schemaDoc.id.toString()
const tileDocParams: TileParams = {
metadata: {
schema: schemaDocIdWithoutCommit, controllers: [controller]
}, content: { a: "test" }
}

try {
await ceramic.createDocument<TileDoctype>(DOCTYPE_TILE, tileDocParams)
throw new Error("Should not be able to assign a schema without specifying the schema commit")
} catch (e) {
expect(e.message).toEqual("Commit missing when loading schema document")
}
await expect(ceramic.createDocument(DOCTYPE_TILE, tileDocParams)).rejects.toThrow('Commit missing when loading schema document')
})

it('can create document with invalid schema if validation is not set', async () => {
Expand Down
11 changes: 5 additions & 6 deletions packages/core/src/__tests__/ceramic.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ import tmp from 'tmp-promise'
import { DoctypeUtils, DocState, Doctype, IpfsApi } from "@ceramicnetwork/common"
import { TileDoctype } from "@ceramicnetwork/doctype-tile"
import * as u8a from 'uint8arrays'
import DocID from "@ceramicnetwork/docid"
import { createIPFS, swarmConnect } from './ipfs-util';

jest.mock('../store/level-state-store')
Expand Down Expand Up @@ -353,15 +352,15 @@ describe('Ceramic integration', () => {
await anchor(ceramic1)
await syncDoc(doctype1)

const prevCommitDocId1 = DocID.fromOther(doctype1.id, doctype1.state.log[3].cid.toString())
const prevCommitDocId1 = doctype1.id.atCommit(doctype1.state.log[3].cid)

const loadedDoctype1 = await ceramic2.loadDocument(prevCommitDocId1)
expect(loadedDoctype1).toBeDefined()

expect(getDocFromCacheSpy2).toBeCalledTimes(2)
expect(getDocFromCacheSpy2).toBeCalledTimes(1)
expect(putDocToCacheSpy2).toBeCalledTimes(2)
expect(docCache2._baseDocCache.has(prevCommitDocId1.baseID.toString())).toBeTruthy()
expect(docCache2._commitDocCache.has(prevCommitDocId1.toString())).toBeTruthy()
expect(docCache2._commitDocCache.has(prevCommitDocId1.toString())).toBeFalsy()

await ceramic1.close()
await ceramic2.close()
Expand Down Expand Up @@ -399,12 +398,12 @@ describe('Ceramic integration', () => {
await anchor(ceramic1)
await syncDoc(doctype1)

const prevCommitDocId1 = DocID.fromOther(doctype1.id, doctype1.state.log[3].cid.toString())
const prevCommitDocId1 = doctype1.id.atCommit(doctype1.state.log[3].cid)

const loadedDoctype1 = await ceramic2.loadDocument(prevCommitDocId1)
expect(loadedDoctype1).toBeDefined()

expect(getDocFromCacheSpy2).toBeCalledTimes(2)
expect(getDocFromCacheSpy2).toBeCalledTimes(1)
expect(putDocToCacheSpy2).toBeCalledTimes(2)
expect(docCache2._baseDocCache.has(prevCommitDocId1.baseID.toString())).toBeTruthy()
expect(docCache2._commitDocCache).toBeNull()
Expand Down
19 changes: 9 additions & 10 deletions packages/core/src/__tests__/document.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ import Document from '../document'
import tmp from 'tmp-promise'
import Dispatcher from '../dispatcher'
import Ceramic from "../ceramic"
import DocID from '@ceramicnetwork/docid'
import { Context, PinningBackend } from "@ceramicnetwork/common"
import { AnchorStatus, DocOpts, SignatureStatus } from "@ceramicnetwork/common"
import { AnchorService } from "@ceramicnetwork/common"
Expand Down Expand Up @@ -254,7 +253,7 @@ describe('Document', () => {
const commit0 = doc.commitId
expect(commits).toEqual([commit0])

expect(commit0).toEqual(DocID.fromOther(doc.id, doc.id.cid))
expect(commit0.equals(doc.id.atCommit(doc.id.cid))).toBeTruthy()
expect(anchorCommits.length).toEqual(0)

await anchorUpdate(anchorService, doc)
Expand All @@ -264,7 +263,7 @@ describe('Document', () => {
expect(commits.length).toEqual(2)
expect(anchorCommits.length).toEqual(1)
const commit1 = doc.commitId
expect(commit1).not.toEqual(commit0)
expect(commit1.equals(commit0)).toBeFalsy()
expect(commit1).toEqual(commits[1])
expect(commit1).toEqual(anchorCommits[0])

Expand All @@ -282,7 +281,7 @@ describe('Document', () => {
expect(commits.length).toEqual(3)
expect(anchorCommits.length).toEqual(1)
const commit2 = doc.commitId
expect(commit2).not.toEqual(commit1)
expect(commit2.equals(commit1)).toBeFalsy()
expect(commit2).toEqual(commits[2])

await anchorUpdate(anchorService, doc)
Expand All @@ -292,7 +291,7 @@ describe('Document', () => {
expect(commits.length).toEqual(4)
expect(anchorCommits.length).toEqual(2)
const commit3 = doc.commitId
expect(commit3).not.toEqual(commit2)
expect(commit3.equals(commit2)).toBeFalsy()
expect(commit3).toEqual(commits[3])
expect(commit3).toEqual(anchorCommits[1])

Expand All @@ -310,13 +309,13 @@ describe('Document', () => {
expect(commits.length).toEqual(5)
expect(anchorCommits.length).toEqual(2)
const commit4 = doc.commitId
expect(commit4).not.toEqual(commit3)
expect(commit4.equals(commit3)).toBeFalsy()
expect(commit4).toEqual(commits[4])
expect(commit4).not.toEqual(anchorCommits[1])
expect(commit4.equals(anchorCommits[1])).toBeFalsy()
expect(doc.state.log.length).toEqual(5)

// try to load a non-existing commit
const nonExistentCommitID = DocID.fromOther(doc.id, new CID('bafybeig6xv5nwphfmvcnektpnojts33jqcuam7bmye2pb54adnrtccjlsu'))
const nonExistentCommitID = doc.id.atCommit(new CID('bafybeig6xv5nwphfmvcnektpnojts33jqcuam7bmye2pb54adnrtccjlsu'))
try {
await Document.loadAtCommit(nonExistentCommitID, doc)
fail('Should not be able to fetch non-existing commit')
Expand Down Expand Up @@ -420,11 +419,11 @@ describe('Document', () => {
expect(doc1.content).toEqual(newContent)

// Loading valid commit works
const docAtValidCommit = await Document.loadAtCommit(DocID.fromOther(docId, tipValidUpdate), doc1)
const docAtValidCommit = await Document.loadAtCommit(docId.atCommit(tipValidUpdate), doc1)
expect(docAtValidCommit.content).toEqual(newContent)

// Loading invalid commit fails
await expect(Document.loadAtCommit(DocID.fromOther(docId, tipInvalidUpdate), doc1)).rejects.toThrow(
await expect(Document.loadAtCommit(docId.atCommit(tipInvalidUpdate), doc1)).rejects.toThrow(
`Requested commit CID ${tipInvalidUpdate.toString()} not found in the log for document ${docId.toString()}`
)
})
Expand Down
Loading

0 comments on commit c2707f2

Please sign in to comment.