Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 5 additions & 5 deletions src/hashes/hasher.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,10 @@
import * as Digest from './digest.js'

/**
* @template T
* @typedef {import('./interface').Await<T>} Await<T>
*/

/**
* @template {string} Name
* @template {number} Code
Expand Down Expand Up @@ -54,8 +59,3 @@ export class Hasher {
* @template {number} Alg
* @typedef {import('./interface').MultihashHasher} MultihashHasher
*/

/**
* @template T
* @typedef {Promise<T>|T} Await
*/
23 changes: 21 additions & 2 deletions src/hashes/interface.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
// # Multihash

export type Await<T> = Promise<T> | T

/**
* Represents a multihash digest which carries information about the
* hashing algorithm and an actual hash digest.
Expand Down Expand Up @@ -37,14 +39,30 @@ export interface MultihashDigest<Code extends number = number> {
*/
export interface MultihashHasher<Code extends number = number> {
/**
* Takes binary `input` and returns it (multi) hash digest. Return value is
* Takes binary `input` and returns it multihash digest. Return value is
* either promise of a digest or a digest. This way general use can `await`
* while performance critical code may asses return value to decide whether
* await is needed.
*
* @param {Uint8Array} input
*/
digest(input: Uint8Array): Await<MultihashDigest>

/**
* Takes binary `input` and returns it plain hash digest. Return value is
* either promise of a digest or a digest. This way general use can `await`
* while performance critical code may asses return value to decide whether
* await is needed.
*
* This is distinct from `digest()` which returns a multihash (prefixed)
* digest for this hasher as it only returns the encoded bytes that may
* otherwise be obtained from `digest().digest`. Only use `encode()` if you
* need to use this hasher as a standard hash digest generator; multihashes
* should otherwise be preferred.
*
* @param {Uint8Array} input
*/
digest(input: Uint8Array): Promise<MultihashDigest> | MultihashDigest
encode(input: Uint8Array): Await<Uint8Array>
Copy link
Copy Markdown
Contributor

@Gozala Gozala Feb 17, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this would be a mistake as suddenly passing func({ digest }) will no longer satisfy requirements of the function even if it does not care about encode.

Proper way to address underlying problem is not to constraint interface further by increasing it’s surface, but rather to make our type annotations more precise. E.g it is ok to say that our multihashers implement both MultihashHasher interface along with some Encoder interface.

We can even define new interfaces here that extend both to reference it across the board. We really should avoid bloating the interface further so that it’s easier to be a valid implementation of. As shown in example it can actually be a TS breaking change, as some code may become invalid.

P.S. I’m kind of regretting name field which in most code is unecessary contstraint.


/**
* Name of the multihash
Expand All @@ -69,4 +87,5 @@ export interface MultihashHasher<Code extends number = number> {
*/
export interface SyncMultihashHasher<Code extends number = number> extends MultihashHasher<Code> {
digest(input: Uint8Array): MultihashDigest
encode(input: Uint8Array): Uint8Array
}
38 changes: 38 additions & 0 deletions test/ts-use/src/main.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,11 @@
import * as Block from 'multiformats/block'
import { sha256 } from 'multiformats/hashes/sha2'
import { identity } from 'multiformats/hashes/identity'
import * as json from 'multiformats/codecs/json'
import { bytes } from 'multiformats'
import { MultihashHasher } from 'multiformats/hashes/hasher'
import { MultihashDigest } from 'multiformats/cid'
import { SyncMultihashHasher } from 'multiformats/hashes/interface'

const main = async () => {
const block = await Block.encode({
Expand All @@ -10,6 +15,39 @@ const main = async () => {
})

console.log(block)

console.log('async hasher')
await executeAsyncHasher(sha256, 'hash')

console.log('sync hasher')
executeSyncHasher(identity, 'hash')
}

async function executeAsyncHasher<Alg extends number> (hasher : MultihashHasher<Alg>, input : string) {
const mhdigest : MultihashDigest = await hasher.digest(new TextEncoder().encode(input))
const digest : Uint8Array = await hasher.encode(new TextEncoder().encode(input))
console.log('multihash:', bytes.toHex(mhdigest.bytes))
console.log('digest: ', bytes.toHex(digest))
if (bytes.toHex(mhdigest.digest) !== bytes.toHex(digest)) {
throw new Error('busted interface')
}
}

function executeSyncHasher<Alg extends number> (hasher : SyncMultihashHasher<Alg>, input : string) {
const mhdigest : MultihashDigest = hasher.digest(new TextEncoder().encode(input))
const digest : Uint8Array = hasher.encode(new TextEncoder().encode(input))
console.log('multihash:', bytes.toHex(mhdigest.bytes))
console.log('digest: ', bytes.toHex(digest))
if (bytes.toHex(mhdigest.digest) !== bytes.toHex(digest)) {
throw new Error('busted interface')
}
}

export default main

/*
main().catch((e) => {
console.error(e)
process.exit(1)
})
*/