Skip to content

Commit a98adaa

Browse files
committed
retain CID type info in Block impl
1 parent 91f780e commit a98adaa

File tree

9 files changed

+183
-59
lines changed

9 files changed

+183
-59
lines changed

src/block.js

Lines changed: 48 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ const readonly = ({ enumerable = true, configurable = false } = {}) => ({
1313
* @template T
1414
* @param {T} source
1515
* @param {Array<string|number>} base
16-
* @returns {Iterable<[string, CID]>}
16+
* @returns {Iterable<[string, API.CIDView]>}
1717
*/
1818
const links = function * (source, base) {
1919
if (source == null) return
@@ -74,6 +74,7 @@ const tree = function * (source, base) {
7474
* @template T
7575
* @param {T} source
7676
* @param {string[]} path
77+
* @return {API.BlockCursorView}
7778
*/
7879
const get = (source, path) => {
7980
/** @type {Record<string, any>} */
@@ -92,19 +93,23 @@ const get = (source, path) => {
9293
}
9394

9495
/**
95-
* @template T
96+
* @template {unknown} T - Logical type of the data encoded in the block
97+
* @template {number} C - multicodec code corresponding to codec used to encode the block
98+
* @template {number} A - multicodec code corresponding to the hashing algorithm used in CID creation.
99+
* @template {API.CIDVersion} V - CID version
100+
* @implements {API.BlockView<T, C, A, V>}
96101
*/
97102
class Block {
98103
/**
99104
* @param {Object} options
100-
* @param {API.CID} options.cid
105+
* @param {API.CIDView<C, A, V>} options.cid
101106
* @param {API.ByteView<T>} options.bytes
102107
* @param {T} options.value
103108
*/
104109
constructor ({ cid, bytes, value }) {
105-
if (!cid || !bytes || typeof value === 'undefined') throw new Error('Missing required argument')
110+
if (!cid || !bytes || typeof value === 'undefined') { throw new Error('Missing required argument') }
106111

107-
this.cid = /** @type {CID} */(cid)
112+
this.cid = cid
108113
this.bytes = bytes
109114
this.value = value
110115
this.asBlock = this
@@ -127,43 +132,47 @@ class Block {
127132
}
128133

129134
/**
130-
* @param {string} [path]
131-
*/
135+
* @param {string} [path]
136+
*/
132137
get (path = '/') {
133138
return get(this.value, path.split('/').filter(Boolean))
134139
}
135140
}
136141

137142
/**
138-
* @template T
139-
* @template {number} Code
140-
* @template {number} Alg
143+
* @template {unknown} T - Logical type of the data encoded in the block
144+
* @template {number} Code - multicodec code corresponding to codec used to encode the block
145+
* @template {number} Alg - multicodec code corresponding to the hashing algorithm used in CID creation.
141146
* @param {Object} options
142147
* @param {T} options.value
143148
* @param {API.BlockEncoder<Code, T>} options.codec
144149
* @param {API.MultihashHasher<Alg>} options.hasher
145-
* @returns {Promise<Block<T>>}
150+
* @returns {Promise<API.BlockView<T, Code, Alg>>}
146151
*/
147152
const encode = async ({ value, codec, hasher }) => {
148153
if (typeof value === 'undefined') throw new Error('Missing required argument "value"')
149154
if (!codec || !hasher) throw new Error('Missing required argument: codec or hasher')
150155

151156
const bytes = codec.encode(value)
152157
const hash = await hasher.digest(bytes)
153-
const cid = CID.create(1, codec.code, hash)
158+
const cid = CID.create(
159+
1,
160+
codec.code,
161+
hash
162+
)
154163

155164
return new Block({ value, bytes, cid })
156165
}
157166

158167
/**
159-
* @template T
160-
* @template {number} Code
161-
* @template {number} Alg
168+
* @template {unknown} T - Logical type of the data encoded in the block
169+
* @template {number} Code - multicodec code corresponding to codec used to encode the block
170+
* @template {number} Alg - multicodec code corresponding to the hashing algorithm used in CID creation.
162171
* @param {Object} options
163172
* @param {API.ByteView<T>} options.bytes
164173
* @param {API.BlockDecoder<Code, T>} options.codec
165174
* @param {API.MultihashHasher<Alg>} options.hasher
166-
* @returns {Promise<Block<T>>}
175+
* @returns {Promise<API.BlockView<T, Code, Alg>>}
167176
*/
168177
const decode = async ({ bytes, codec, hasher }) => {
169178
if (!bytes) throw new Error('Missing required argument "bytes"')
@@ -182,10 +191,12 @@ const decode = async ({ bytes, codec, hasher }) => {
182191
*/
183192

184193
/**
185-
* @template T
186-
* @template {number} Code
187-
* @param {{ cid: API.CID, value:T, codec?: API.BlockDecoder<Code, T>, bytes: API.ByteView<T> }|{cid:API.CID, bytes:API.ByteView<T>, value?:void, codec:API.BlockDecoder<Code, T>}} options
188-
* @returns {Block<T>}
194+
* @template {unknown} T - Logical type of the data encoded in the block
195+
* @template {number} Code - multicodec code corresponding to codec used to encode the block
196+
* @template {number} Alg - multicodec code corresponding to the hashing algorithm used in CID creation.
197+
* @template {API.CIDVersion} V - CID version
198+
* @param {{ cid: API.Link<T, Code, Alg, V>, value:T, codec?: API.BlockDecoder<Code, T>, bytes: API.ByteView<T> }|{cid:API.Link<T, Code, Alg, V>, bytes:API.ByteView<T>, value?:void, codec:API.BlockDecoder<Code, T>}} options
199+
* @returns {API.BlockView<T, Code, Alg, V>}
189200
*/
190201
const createUnsafe = ({ bytes, cid, value: maybeValue, codec }) => {
191202
const value = maybeValue !== undefined
@@ -194,19 +205,24 @@ const createUnsafe = ({ bytes, cid, value: maybeValue, codec }) => {
194205

195206
if (value === undefined) throw new Error('Missing required argument, must either provide "value" or "codec"')
196207

197-
return new Block({ cid, bytes, value })
208+
return new Block({
209+
cid: /** @type {API.CIDView<Code, Alg, V>} */(cid),
210+
bytes,
211+
value
212+
})
198213
}
199214

200215
/**
201-
* @template T
202-
* @template {number} Code
203-
* @template {number} Alg
216+
* @template {unknown} T - Logical type of the data encoded in the block
217+
* @template {number} Code - multicodec code corresponding to codec used to encode the block
218+
* @template {number} Alg - multicodec code corresponding to the hashing algorithm used in CID creation.
219+
* @template {API.CIDVersion} V - CID version
204220
* @param {Object} options
205-
* @param {API.CID<Code, Alg>} options.cid
221+
* @param {API.CID<Code, Alg, V>} options.cid
206222
* @param {API.ByteView<T>} options.bytes
207223
* @param {API.BlockDecoder<Code, T>} options.codec
208224
* @param {API.MultihashHasher<Alg>} options.hasher
209-
* @returns {Promise<Block<T>>}
225+
* @returns {Promise<API.BlockView<T, Code, Alg, V>>}
210226
*/
211227
const create = async ({ bytes, cid, hasher, codec }) => {
212228
if (!bytes) throw new Error('Missing required argument "bytes"')
@@ -217,7 +233,12 @@ const create = async ({ bytes, cid, hasher, codec }) => {
217233
throw new Error('CID hash does not match bytes')
218234
}
219235

220-
return createUnsafe({ bytes, cid, value, codec })
236+
return createUnsafe({
237+
bytes,
238+
cid,
239+
value,
240+
codec
241+
})
221242
}
222243

223244
export { encode, decode, create, createUnsafe, Block }

src/block/interface.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
// this is dummy module overlayed by interface.ts

src/block/interface.ts

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
/* eslint-disable no-use-before-define */
2+
import { Link, CIDView, CIDVersion } from '../cid/interface.js'
3+
4+
/**
5+
* A byte-encoded representation of some type of `Data`.
6+
*
7+
* A `ByteView` is essentially a `Uint8Array` that's been "tagged" with
8+
* a `Data` type parameter indicating the type of encoded data.
9+
*
10+
* For example, a `ByteView<{ hello: "world" }>` is a `Uint8Array` containing a
11+
* binary representation of a `{hello: "world"}.
12+
*/
13+
export interface ByteView<Data> extends Uint8Array, Phantom<Data> {}
14+
15+
/**
16+
* A utility type to retain an unused type parameter `T`.
17+
* Similar to [phantom type parameters in Rust](https://doc.rust-lang.org/rust-by-example/generics/phantom.html).
18+
*
19+
* Capturing unused type parameters allows us to define "nominal types," which
20+
* TypeScript does not natively support. Nominal types in turn allow us to capture
21+
* semantics not represented in the actual type structure, without requring us to define
22+
* new classes or pay additional runtime costs.
23+
*
24+
* For a concrete example, see {@link ByteView}, which extends the `Uint8Array` type to capture
25+
* type information about the structure of the data encoded into the array.
26+
*/
27+
export interface Phantom<T> {
28+
// This field can not be represented because field name is non-existings
29+
// unique symbol. But given that field is optional any object will valid
30+
// type contstraint.
31+
[Marker]?: T
32+
}
33+
declare const Marker: unique symbol
34+
35+
/**
36+
* Represents an IPLD block (including its CID) that can be decoded to data of
37+
* type `T`.
38+
*
39+
* @template T - Logical type of the data encoded in the block
40+
* @template C - multicodec code corresponding to codec used to encode the block
41+
* @template A - multicodec code corresponding to the hashing algorithm used in CID creation.
42+
* @template V - CID version
43+
*/
44+
export interface Block<
45+
T = unknown,
46+
C extends number = number,
47+
A extends number = number,
48+
V extends CIDVersion = 1
49+
> {
50+
bytes: ByteView<T>
51+
cid: Link<T, C, A, V>
52+
}
53+
54+
export type BlockCursorView =
55+
| { value: unknown, remaining?: undefined }
56+
| { value: CIDView, remaining: string }
57+
58+
export interface BlockView<
59+
T = unknown,
60+
C extends number = number,
61+
A extends number = number,
62+
V extends CIDVersion = 1
63+
> extends Block<T, C, A, V> {
64+
cid: CIDView<C, A, V> & Link<T, C, A, V>
65+
66+
links(): Iterable<[string, CIDView]>
67+
tree(): Iterable<string>
68+
get(path:string): BlockCursorView
69+
}

src/cid.js

Lines changed: 27 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -14,8 +14,8 @@ export * from './cid/interface.js'
1414
* @template {number} Format
1515
* @template {number} Alg
1616
* @template {API.CIDVersion} Version
17-
* @template T
18-
* @param {API.CID<Format, Alg, Version>|T} input
17+
* @template {unknown} U
18+
* @param {API.CID<Format, Alg, Version>|U} input
1919
* @returns {API.CID<Format, Alg, Version>|null}
2020
*/
2121
export const asCID = (input) => {
@@ -342,9 +342,9 @@ export class CID {
342342
throw new Error('Cannot convert non sha2-256 multihash CID to CIDv0')
343343
}
344344

345-
return /** @type {CID<API.DAG_PB, API.SHA_256, 0>} */(createV0(
346-
/** @type {API.MultihashDigest<API.SHA_256>} */ (multihash)
347-
))
345+
return /** @type {CID<API.DAG_PB, API.SHA_256, 0>} */ (
346+
createV0(/** @type {API.MultihashDigest<API.SHA_256>} */ (multihash))
347+
)
348348
}
349349
default: {
350350
throw Error(
@@ -362,7 +362,9 @@ export class CID {
362362
case 0: {
363363
const { code, digest } = this.multihash
364364
const multihash = Digest.create(code, digest)
365-
return /** @type {CID<Format, Alg, 1>} */(createV1(this.code, multihash))
365+
return /** @type {CID<Format, Alg, 1>} */ (
366+
createV1(this.code, multihash)
367+
)
366368
}
367369
case 1: {
368370
return /** @type {CID<Format, Alg, 1>} */ (this)
@@ -445,10 +447,15 @@ export class CID {
445447
}
446448

447449
/**
448-
* @param {any} value
450+
* @template {number} C
451+
* @template {number} A
452+
* @template {API.CIDVersion} V
453+
* @template {unknown} U
454+
* @param {API.CID<C, A, V>|U} value
455+
* @returns {CID<C, A, V>|null}
449456
*/
450457
static asCID (value) {
451-
return /** @type {CID|null} */(asCID(value))
458+
return /** @type {CID<C, A, V>|null} */ (asCID(value))
452459
}
453460

454461
/**
@@ -460,7 +467,9 @@ export class CID {
460467
* @param {API.MultihashDigest<Alg>} digest - (Multi)hash of the of the content.
461468
*/
462469
static create (version, code, digest) {
463-
return /** @type {CID<Format, Alg, Version>} */(create(version, code, digest))
470+
return /** @type {CID<Format, Alg, Version>} */ (
471+
create(version, code, digest)
472+
)
464473
}
465474

466475
/**
@@ -472,12 +481,12 @@ export class CID {
472481
}
473482

474483
/**
475-
* Simplified version of `create` for CIDv1.
476-
* @template {number} Code
477-
* @template {number} Alg
478-
* @param {Code} code - Content encoding format code.
479-
* @param {API.MultihashDigest<Alg>} digest - Miltihash of the content.
480-
*/
484+
* Simplified version of `create` for CIDv1.
485+
* @template {number} Code
486+
* @template {number} Alg
487+
* @param {Code} code - Content encoding format code.
488+
* @param {API.MultihashDigest<Alg>} digest - Miltihash of the content.
489+
*/
481490
static createV1 (code, digest) {
482491
return CID.create(1, code, digest)
483492
}
@@ -487,14 +496,14 @@ export class CID {
487496
*/
488497

489498
static decode (bytes) {
490-
return /** @type {CID} */(decode(bytes))
499+
return /** @type {CID} */ (decode(bytes))
491500
}
492501

493502
/**
494503
* @param {Uint8Array} bytes
495504
*/
496505
static decodeFirst (bytes) {
497-
return /** @type {[CID, Uint8Array]} */(decodeFirst(bytes))
506+
return /** @type {[CID, Uint8Array]} */ (decodeFirst(bytes))
498507
}
499508

500509
/**
@@ -510,7 +519,7 @@ export class CID {
510519
* @param {API.MultibaseDecoder<Prefix>} [base]
511520
*/
512521
static parse (source, base) {
513-
return /** @type {CID} */(parse(source, base))
522+
return /** @type {CID} */ (parse(source, base))
514523
}
515524
}
516525

src/cid/interface.ts

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
1+
/* eslint-disable @typescript-eslint/no-unnecessary-type-constraint */
12
/* eslint-disable no-use-before-define */
23
import type { MultihashDigest } from '../hashes/interface'
34
import type { MultibaseEncoder, MultibaseDecoder } from '../bases/interface'
5+
import type { Phantom } from '../block/interface'
6+
export type { CID as CIDView } from '../cid'
47

58
export type { MultihashDigest, MultibaseEncoder, MultibaseDecoder }
69
export type CIDVersion = 0 | 1
@@ -26,12 +29,28 @@ export interface CID<
2629
equals(other: unknown): other is CID<Format, Alg, Version>
2730

2831
toString(base?: MultibaseEncoder<string>): string
29-
toJSON(): {version: Version, code:Format, hash:Uint8Array}
32+
toJSON(): { version: Version, code:Format, hash:Uint8Array }
3033

3134
toV0(): CIDv0
3235
toV1(): CIDv1
3336
}
3437

38+
/**
39+
* Represents an IPLD link to a specific data of type `T`.
40+
*
41+
* @template T - Logical type of the data being linked to.
42+
* @template C - multicodec code corresponding to a codec linked data is encoded with
43+
* @template A - multicodec code corresponding to the hashing algorithm of the CID
44+
* @template V - CID version
45+
*/
46+
export interface Link<
47+
T extends unknown = unknown,
48+
C extends number = number,
49+
A extends number = number,
50+
V extends CIDVersion = 1
51+
> extends CID<C, A, V>, Phantom<T> {
52+
}
53+
3554
export interface CIDv0 extends CID<DAG_PB, SHA_256, 0> {
3655
readonly version: 0
3756
}

0 commit comments

Comments
 (0)