Skip to content

Commit 9ed4302

Browse files
authored
exported some CID utility functions
* exported some CID utility functions * fixed a bug caught by TypeScript linter * brought code coverage for `cid.ts` to 100%
1 parent 576fda4 commit 9ed4302

File tree

8 files changed

+81
-35
lines changed

8 files changed

+81
-35
lines changed

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
# Decentralized Web Node (DWN) SDK
44

55
Code Coverage
6-
![Statements](https://img.shields.io/badge/statements-94.36%25-brightgreen.svg?style=flat) ![Branches](https://img.shields.io/badge/branches-94.95%25-brightgreen.svg?style=flat) ![Functions](https://img.shields.io/badge/functions-92.3%25-brightgreen.svg?style=flat) ![Lines](https://img.shields.io/badge/lines-94.36%25-brightgreen.svg?style=flat)
6+
![Statements](https://img.shields.io/badge/statements-94.58%25-brightgreen.svg?style=flat) ![Branches](https://img.shields.io/badge/branches-95.7%25-brightgreen.svg?style=flat) ![Functions](https://img.shields.io/badge/functions-92.34%25-brightgreen.svg?style=flat) ![Lines](https://img.shields.io/badge/lines-94.58%25-brightgreen.svg?style=flat)
77

88
## Introduction
99

package-lock.json

Lines changed: 2 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@tbd54566975/dwn-sdk-js",
3-
"version": "0.0.22",
3+
"version": "0.0.23",
44
"description": "A reference implementation of https://identity.foundation/decentralized-web-node/spec/",
55
"type": "module",
66
"types": "./dist/esm/src/index.d.ts",
@@ -132,4 +132,4 @@
132132
"url": "https://github.com/TBD54566975/dwn-sdk-js/issues"
133133
},
134134
"homepage": "https://github.com/TBD54566975/dwn-sdk-js#readme"
135-
}
135+
}

src/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ export type { HooksWriteMessage } from './interfaces/hooks/types.js';
1515
export type { ProtocolDefinition, ProtocolRuleSet, ProtocolsConfigureMessage, ProtocolsQueryMessage } from './interfaces/protocols/types.js';
1616
export type { RecordsDeleteMessage, RecordsQueryMessage, RecordsWriteMessage } from './interfaces/records/types.js';
1717
export { AllowAllTenantGate, TenantGate } from './core/tenant-gate.js';
18+
export { Cid } from './utils/cid.js';
1819
export { DataStore } from './store/data-store.js';
1920
export { DateSort } from './interfaces/records/messages/records-query.js';
2021
export { DataStream } from './utils/data-stream.js';

src/interfaces/records/handlers/records-write.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,7 @@ export class RecordsWriteHandler implements MethodHandler {
5454
const newMessageIsInitialWrite = await recordsWrite.isInitialWrite();
5555
if (!newMessageIsInitialWrite) {
5656
try {
57-
const initialWrite = RecordsWrite.getInitialWrite(existingMessages);
57+
const initialWrite = await RecordsWrite.getInitialWrite(existingMessages);
5858
RecordsWrite.verifyEqualityOfImmutableProperties(initialWrite, incomingMessage);
5959
} catch (e) {
6060
return new MessageReply({

src/interfaces/records/messages/records-write.ts

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ import { ProtocolAuthorization } from '../../../core/protocol-authorization.js';
1111
import { removeUndefinedProperties } from '../../../utils/object.js';
1212

1313
import { authorize, validateAuthorizationIntegrity } from '../../../core/auth.js';
14-
import { computeCid, computeDagPbCid } from '../../../utils/cid.js';
14+
import { Cid, computeCid } from '../../../utils/cid.js';
1515
import { DwnInterfaceName, DwnMethodName } from '../../../core/message.js';
1616
import { GeneralJws, SignatureInput } from '../../../jose/jws/general/types.js';
1717

@@ -73,7 +73,7 @@ export class RecordsWrite extends Message {
7373
/**
7474
* Creates a RecordsWrite message.
7575
* @param options.recordId If `undefined`, will be auto-filled as a originating message as convenience for developer.
76-
* @param options.data Readable stream of the data to be stored. Must specify `option.dataCid` if `undefined`.
76+
* @param options.data Data used to compute the `dataCid`. Must specify `option.dataCid` if `undefined`.
7777
* @param options.dataCid CID of the data that is already stored in the DWN. Must specify `option.data` if `undefined`.
7878
* @param options.dateCreated If `undefined`, it will be auto-filled with current time.
7979
* @param options.dateModified If `undefined`, it will be auto-filled with current time.
@@ -85,7 +85,7 @@ export class RecordsWrite extends Message {
8585
options.data !== undefined && options.dataCid !== undefined) {
8686
throw new Error('one and only one parameter between `data` and `dataCid` is allowed');
8787
}
88-
const dataCid = options.dataCid ?? await computeDagPbCid(options.data);
88+
const dataCid = options.dataCid ?? await Cid.computeDagPbCidFromBytes(options.data!);
8989

9090
const descriptor: RecordsWriteDescriptor = {
9191
interface : DwnInterfaceName.Records,
@@ -177,7 +177,7 @@ export class RecordsWrite extends Message {
177177
// inherit published value from parent if neither published nor datePublished is specified
178178
const published = options.published ?? (options.datePublished ? true : unsignedMessage.descriptor.published);
179179
// use current time if published but no explicit time given
180-
let datePublished = undefined;
180+
let datePublished: string | undefined = undefined;
181181
// if given explicitly published dated
182182
if (options.datePublished) {
183183
datePublished = options.datePublished;
@@ -396,14 +396,14 @@ export class RecordsWrite extends Message {
396396
/**
397397
* Gets the initial write from the given list or record write.
398398
*/
399-
public static getInitialWrite(messages: BaseMessage[]): RecordsWriteMessage{
399+
public static async getInitialWrite(messages: BaseMessage[]): Promise<RecordsWriteMessage>{
400400
for (const message of messages) {
401-
if (RecordsWrite.isInitialWrite(message)) {
401+
if (await RecordsWrite.isInitialWrite(message)) {
402402
return message as RecordsWriteMessage;
403403
}
404404
}
405405

406-
throw new Error(`initial write is not found `);
406+
throw new Error(`initial write is not found`);
407407
}
408408

409409
/**

src/utils/cid.ts

Lines changed: 33 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import * as cbor from '@ipld/dag-cbor';
22

33
import { CID } from 'multiformats/cid';
44
import { importer } from 'ipfs-unixfs-importer';
5+
import { Readable } from 'stream';
56
import { sha256 } from 'multiformats/hashes/sha2';
67

78
// a map of all supported CID hashing algorithms. This map is used to select the appropriate hasher
@@ -16,20 +17,6 @@ const codecs = {
1617
[cbor.code]: cbor
1718
};
1819

19-
20-
/**
21-
* @returns V1 CID of the DAG comprised by chunking data into unixfs dag-pb encoded blocks
22-
*/
23-
export async function computeDagPbCid(content: Uint8Array): Promise<string> {
24-
const asyncDataBlocks = importer([{ content }], undefined, { onlyHash: true, cidVersion: 1 });
25-
26-
// NOTE: the last block contains the root CID
27-
let block;
28-
for await (block of asyncDataBlocks) { ; }
29-
30-
return block.cid.toString();
31-
}
32-
3320
/**
3421
* Computes a V1 CID for the provided payload
3522
* @param payload
@@ -71,3 +58,35 @@ export function parseCid(str: string): CID {
7158

7259
return cid;
7360
}
61+
62+
63+
/**
64+
* Utility class for creating CIDs. Exported for the convenience of developers.
65+
*/
66+
export class Cid {
67+
/**
68+
* @returns V1 CID of the DAG comprised by chunking data into unixfs DAG-PB encoded blocks
69+
*/
70+
public static async computeDagPbCidFromBytes(content: Uint8Array): Promise<string> {
71+
const asyncDataBlocks = importer([{ content }], undefined, { onlyHash: true, cidVersion: 1 });
72+
73+
// NOTE: the last block contains the root CID
74+
let block;
75+
for await (block of asyncDataBlocks) { ; }
76+
77+
return block.cid.toString();
78+
}
79+
80+
/**
81+
* @returns V1 CID of the DAG comprised by chunking data into unixfs DAG-PB encoded blocks
82+
*/
83+
public static async computeDagPbCidFromStream(dataStream: Readable): Promise<string> {
84+
const asyncDataBlocks = importer([{ content: dataStream }], undefined, { onlyHash: true, cidVersion: 1 });
85+
86+
// NOTE: the last block contains the root CID
87+
let block;
88+
for await (block of asyncDataBlocks) { ; }
89+
90+
return block.cid.toString();
91+
}
92+
}

tests/utils/cid.spec.ts

Lines changed: 34 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -4,20 +4,42 @@ import * as cbor from '@ipld/dag-cbor';
44
import chaiAsPromised from 'chai-as-promised';
55
import chai, { expect } from 'chai';
66

7-
import { computeCid } from '../../src/utils/cid.js';
7+
import { DataStream } from '../../src/index.js';
88
import { sha256 } from 'multiformats/hashes/sha2';
99
import { TestDataGenerator } from '../utils/test-data-generator.js';
10+
import { Cid, computeCid, parseCid } from '../../src/utils/cid.js';
1011

1112
// extend chai to test promises
1213
chai.use(chaiAsPromised);
1314

1415
describe('CID', () => {
16+
it('should yield the same CID using either computeDagPbCidFromBytes() & computeDagPbCidFromStream()', async () => {
17+
const randomBytes = TestDataGenerator.randomBytes(500_000);
18+
const randomByteStream = await DataStream.fromBytes(randomBytes);
19+
20+
const cid1 = await Cid.computeDagPbCidFromBytes(randomBytes);
21+
const cid2 = await Cid.computeDagPbCidFromStream(randomByteStream);
22+
expect(cid1).to.equal(cid2);
23+
});
24+
1525
describe('computeCid', () => {
16-
xit('throws an error if codec is not supported');
17-
xit('throws an error if multihasher is not supported');
18-
xit('generates a cbor/sha256 v1 cid by default');
26+
it('throws an error if codec is not supported', async () => {
27+
const anyTestData = {
28+
a: TestDataGenerator.randomString(32),
29+
};
30+
const computeCidPromise = computeCid(anyTestData, 'unknownCodec');
31+
await expect(computeCidPromise).to.be.rejectedWith('codec [unknownCodec] not supported');
32+
});
1933

20-
it(' should generate a CBOR SHA256 CID identical to IPFS block encoding algorithm', async () => {
34+
it('throws an error if multihasher is not supported', async () => {
35+
const anyTestData = {
36+
a: TestDataGenerator.randomString(32),
37+
};
38+
const computeCidPromise = computeCid(anyTestData, '113', 'unknownHashingAlgorithm'); // 113 = CBOR
39+
await expect(computeCidPromise).to.be.rejectedWith('multihash code [unknownHashingAlgorithm] not supported');
40+
});
41+
42+
it('should by default generate a CBOR SHA256 CID identical to IPFS block encoding algorithm', async () => {
2143
const anyTestData = {
2244
a : TestDataGenerator.randomString(32),
2345
b : TestDataGenerator.randomString(32),
@@ -49,8 +71,12 @@ describe('CID', () => {
4971
});
5072

5173
describe('parseCid', () => {
52-
xit('throws an error if codec is not supported');
53-
xit('throws an error if multihasher is not supported');
54-
xit('parses provided str into a V1 cid');
74+
it('throws an error if codec is not supported', async () => {
75+
expect(() => parseCid('bafybeihzdcfjv55kxiz7sxwxaxbnjgj7rm2amvrxpi67jpwkgygjzoh72y')).to.throw('codec [112] not supported'); // a DAG-PB CID
76+
});
77+
78+
it('throws an error if multihasher is not supported', async () => {
79+
expect(() => parseCid('bafy2bzacec2qlo3cohxyaoulipd3hurlq6pspvmpvmnmqsxfg4vbumpq3ufag')).to.throw('multihash code [45600] not supported'); // 45600 = BLAKE2b-256 CID
80+
});
5581
});
5682
});

0 commit comments

Comments
 (0)