From c8e653dcfeb17cad838cfee10cf95bd461cad2e0 Mon Sep 17 00:00:00 2001
From: Meno Abels <meno.abels@adviser.com>
Date: Tue, 30 Jul 2024 08:26:18 +0200
Subject: [PATCH 1/2] feat: enable the BlockEncoder to use promise API in
 encode/decode       methods.       Why we need that:        
 https://developer.mozilla.org/en-US/docs/Web/API/SubtleCrypto       To enable
 the async a breaking change to createUnsafe is       required.       The
 existing signature is not able to pass a promise.       Therefore the tests
 need to change.       For me, measuring the blast radius of the change is
 impossible.       If the blast radius is too big then there is still the
 possibility to       create a new method createUnsafeAsync and throw an error
       in the createUnsafe if the decode sends a promise.

---
 package.json                     |  3 +-
 src/block.ts                     | 12 ++++----
 src/codecs/interface.ts          |  4 +--
 test/test-block.spec.ts          | 47 ++++++++++++++++++++++++--------
 test/test-multibase-spec.spec.ts |  6 ++--
 5 files changed, 49 insertions(+), 23 deletions(-)

diff --git a/package.json b/package.json
index 1fb02fba..77489cf5 100644
--- a/package.json
+++ b/package.json
@@ -278,7 +278,8 @@
     "aegir": "^43.0.1",
     "buffer": "^6.0.3",
     "cids": "^1.1.9",
-    "crypto-hash": "^3.0.0"
+    "crypto-hash": "^3.0.0",
+    "mocha": "^10.7.0"
   },
   "aegir": {
     "test": {
diff --git a/src/block.ts b/src/block.ts
index ae59ff13..3e2f9504 100644
--- a/src/block.ts
+++ b/src/block.ts
@@ -141,7 +141,7 @@ export async function encode <T, Code extends number, Alg extends number> ({ val
   if (typeof value === 'undefined') throw new Error('Missing required argument "value"')
   if (codec == null || hasher == null) throw new Error('Missing required argument: codec or hasher')
 
-  const bytes = codec.encode(value)
+  const bytes = await Promise.resolve(codec.encode(value))
   const hash = await hasher.digest(bytes)
   // eslint-disable-next-line @typescript-eslint/no-unnecessary-type-assertion
   const cid = CID.create(
@@ -168,7 +168,7 @@ export async function decode <T, Code extends number, Alg extends number> ({ byt
   if (bytes == null) throw new Error('Missing required argument "bytes"')
   if (codec == null || hasher == null) throw new Error('Missing required argument: codec or hasher')
 
-  const value = codec.decode(bytes)
+  const value = await Promise.resolve(codec.decode(bytes))
   const hash = await hasher.digest(bytes)
   // eslint-disable-next-line @typescript-eslint/no-unnecessary-type-assertion
   const cid = CID.create(1, codec.code, hash) as CID<T, Code, Alg, 1>
@@ -194,10 +194,10 @@ type CreateUnsafeInput <T, Code extends number, Alg extends number, V extends AP
  * @template Alg - multicodec code corresponding to the hashing algorithm used in CID creation.
  * @template V - CID version
  */
-export function createUnsafe <T, Code extends number, Alg extends number, V extends API.Version> ({ bytes, cid, value: maybeValue, codec }: CreateUnsafeInput<T, Code, Alg, V>): API.BlockView<T, Code, Alg, V> {
-  const value = maybeValue !== undefined
+export async function createUnsafe <T, Code extends number, Alg extends number, V extends API.Version> ({ bytes, cid, value: maybeValue, codec }: CreateUnsafeInput<T, Code, Alg, V>): Promise<API.BlockView<T, Code, Alg, V>> {
+  const value = await Promise.resolve(maybeValue !== undefined
     ? maybeValue
-    : (codec?.decode(bytes))
+    : (codec?.decode(bytes)))
 
   if (value === undefined) throw new Error('Missing required argument, must either provide "value" or "codec"')
 
@@ -224,7 +224,7 @@ interface CreateInput <T, Code extends number, Alg extends number, V extends API
 export async function create <T, Code extends number, Alg extends number, V extends API.Version> ({ bytes, cid, hasher, codec }: CreateInput<T, Code, Alg, V>): Promise<API.BlockView<T, Code, Alg, V>> {
   if (bytes == null) throw new Error('Missing required argument "bytes"')
   if (hasher == null) throw new Error('Missing required argument "hasher"')
-  const value = codec.decode(bytes)
+  const value = await Promise.resolve(codec.decode(bytes))
   const hash = await hasher.digest(bytes)
   if (!binary.equals(cid.multihash.bytes, hash.bytes)) {
     throw new Error('CID hash does not match bytes')
diff --git a/src/codecs/interface.ts b/src/codecs/interface.ts
index 8e5d9d1a..61b5394e 100644
--- a/src/codecs/interface.ts
+++ b/src/codecs/interface.ts
@@ -6,7 +6,7 @@ import type { ArrayBufferView, ByteView } from '../block/interface.js'
 export interface BlockEncoder<Code extends number, T> {
   name: string
   code: Code
-  encode(data: T): ByteView<T>
+  encode(data: T): ByteView<T> | PromiseLike<ByteView<T>>
 }
 
 /**
@@ -14,7 +14,7 @@ export interface BlockEncoder<Code extends number, T> {
  */
 export interface BlockDecoder<Code extends number, T> {
   code: Code
-  decode(bytes: ByteView<T> | ArrayBufferView<T>): T
+  decode(bytes: ByteView<T> | ArrayBufferView<T>): T | PromiseLike<T>
 }
 
 /**
diff --git a/test/test-block.spec.ts b/test/test-block.spec.ts
index 120be93c..30c07e8e 100644
--- a/test/test-block.spec.ts
+++ b/test/test-block.spec.ts
@@ -10,6 +10,20 @@ const fixture = { hello: 'world' }
 const link = CID.parse('bafyreidykglsfhoixmivffc5uwhcgshx4j465xwqntbmu43nb2dzqwfvae')
 const buff = bytes.fromString('sadf')
 
+describe('promise-resolve-semantics', () => {
+  it('should resolve to the value', async () => {
+    const value = await Promise.resolve('hello')
+    assert.equal(value, 'hello')
+  })
+  it('should resolve to the promise value', async () => {
+    // eslint-disable-next-line promise/param-names
+    const value = await Promise.resolve(new Promise<string>(function (rs: (value: string) => void) {
+      setTimeout(function () { rs('hello') }, 10)
+    }))
+    assert.equal(value, 'hello')
+  })
+})
+
 describe('block', () => {
   it('basic encode/decode roundtrip', async () => {
     const block = await main.encode({ value: fixture, codec, hasher })
@@ -23,7 +37,7 @@ describe('block', () => {
 
   it('createUnsafe', async () => {
     const block = await main.encode({ value: fixture, codec, hasher })
-    const block2 = main.createUnsafe({ bytes: block.bytes, cid: block.cid, codec })
+    const block2 = await main.createUnsafe({ bytes: block.bytes, cid: block.cid, codec })
     assert.deepStrictEqual(block.cid.equals(block2.cid), true)
   })
 
@@ -36,25 +50,29 @@ describe('block', () => {
       // @ts-expect-error - 'string' is not assignable to parameter of type 'ArrayLike<number>'
       bytes: Uint8Array.from('1234')
     }
-    // @ts-expect-error - 'boolean' is not assignable to type 'CID'
-    const block = main.createUnsafe({ value, codec, hasher, cid: true, bytes: true })
 
-    it('links', () => {
+    it('links', async () => {
       const expected = ['link', 'arr/0']
+      // @ts-expect-error - 'boolean' is not assignable to type 'CID'
+      const block = await main.createUnsafe({ value, codec, hasher, cid: true, bytes: true })
       for (const [path, cid] of block.links()) {
         assert.deepStrictEqual(path, expected.shift())
         assert.deepStrictEqual(cid.toString(), link.toString())
       }
     })
 
-    it('tree', () => {
+    it('tree', async () => {
       const expected = ['link', 'nope', 'arr', 'arr/0', 'obj', 'obj/arr', 'obj/arr/0', 'obj/arr/0/obj', 'bytes']
+      // @ts-expect-error - 'boolean' is not assignable to type 'CID'
+      const block = await main.createUnsafe({ value, codec, hasher, cid: true, bytes: true })
       for (const path of block.tree()) {
         assert.deepStrictEqual(path, expected.shift())
       }
     })
 
-    it('get', () => {
+    it('get', async () => {
+      // @ts-expect-error - 'boolean' is not assignable to type 'CID'
+      const block = await main.createUnsafe({ value, codec, hasher, cid: true, bytes: true })
       let ret = block.get('link/test')
       assert.deepStrictEqual(ret.remaining, 'test')
       assert.deepStrictEqual(String(ret.value), link.toString())
@@ -63,8 +81,8 @@ describe('block', () => {
       assert.deepStrictEqual(ret, { value: 'skip' })
     })
 
-    it('null links/tree', () => {
-      const block = main.createUnsafe({
+    it('null links/tree', async () => {
+      const block = await main.createUnsafe({
         value: null,
         codec,
         hasher,
@@ -95,9 +113,9 @@ describe('block', () => {
     assert.equal(links[0][1].toString(), link.toString())
   })
 
-  it('kitchen sink', () => {
+  it('kitchen sink', async () => {
     const sink = { one: { two: { arr: [true, false, null], three: 3, buff, link } } }
-    const block = main.createUnsafe({
+    const block = await main.createUnsafe({
       value: sink,
       codec,
       // @ts-expect-error - 'boolean' is not assignable to type 'ByteView<unknown>'
@@ -132,8 +150,13 @@ describe('block', () => {
     })
 
     it('createUnsafe', async () => {
-      // @ts-expect-error testing invalid usage
-      assert.throws(() => main.createUnsafe({}), 'Missing required argument, must either provide "value" or "codec"')
+      try {
+        // @ts-expect-error testing invalid usage
+        await main.createUnsafe({})
+        assert(false, 'Missing required argument, must either provide "value" or "codec"')
+      } catch (/** @type {Error} */ err) {
+        /* c8 ignore next */
+      }
     })
 
     it('create', async () => {
diff --git a/test/test-multibase-spec.spec.ts b/test/test-multibase-spec.spec.ts
index 1f7f2523..db5dbece 100644
--- a/test/test-multibase-spec.spec.ts
+++ b/test/test-multibase-spec.spec.ts
@@ -174,9 +174,11 @@ describe('spec test', () => {
   }
 
   for (const base of Object.values(bases)) {
-    it('should fail decode with invalid char', function () {
+    // eslint-disable-next-line @typescript-eslint/method-signature-style
+    it('should fail decode with invalid char', function (this: { skip: () => void }) {
       if (base.name === 'identity') {
-        return this.skip()
+        this.skip()
+        return
       }
 
       assert.throws(() => base.decode(base.prefix + '^!@$%!#$%@#y'), `Non-${base.name} character`)

From a83bcfd8607668eb2828bc91e8b54ecfb96d8b5b Mon Sep 17 00:00:00 2001
From: Meno Abels <meno.abels@adviser.com>
Date: Tue, 30 Jul 2024 13:21:43 +0200
Subject: [PATCH 2/2] fix: remove patch of package.json

---
 package.json | 3 +--
 1 file changed, 1 insertion(+), 2 deletions(-)

diff --git a/package.json b/package.json
index 77489cf5..1fb02fba 100644
--- a/package.json
+++ b/package.json
@@ -278,8 +278,7 @@
     "aegir": "^43.0.1",
     "buffer": "^6.0.3",
     "cids": "^1.1.9",
-    "crypto-hash": "^3.0.0",
-    "mocha": "^10.7.0"
+    "crypto-hash": "^3.0.0"
   },
   "aegir": {
     "test": {