@atproto/lex-data 0.0.7 → 0.0.8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +12 -0
- package/dist/cid.d.ts +73 -48
- package/dist/cid.d.ts.map +1 -1
- package/dist/cid.js +132 -39
- package/dist/cid.js.map +1 -1
- package/package.json +1 -1
- package/src/cid-implementation.test.ts +137 -0
- package/src/cid.test.ts +125 -23
- package/src/cid.ts +228 -99
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
/* eslint-disable import/no-deprecated */
|
|
2
|
+
|
|
3
|
+
import { base32 } from 'multiformats/bases/base32'
|
|
4
|
+
import { CID } from 'multiformats/cid'
|
|
5
|
+
import { create as createDigest } from 'multiformats/hashes/digest'
|
|
6
|
+
import { assert, describe, expect, it } from 'vitest'
|
|
7
|
+
import { Cid } from './cid.js'
|
|
8
|
+
import { ui8Equals } from './uint8array.js'
|
|
9
|
+
|
|
10
|
+
export class BytesCid implements Cid {
|
|
11
|
+
constructor(readonly bytes: Uint8Array) {
|
|
12
|
+
if (this.bytes.length < 4) {
|
|
13
|
+
throw new Error('CID bytes are too short')
|
|
14
|
+
}
|
|
15
|
+
if (this.bytes[0] > 1) {
|
|
16
|
+
throw new Error('Unsupported CID version')
|
|
17
|
+
}
|
|
18
|
+
if (this.bytes.length !== 4 + this.bytes[3]) {
|
|
19
|
+
throw new Error('CID bytes length mismatch')
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
get version() {
|
|
24
|
+
return this.bytes[0] as 0 | 1
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
get code() {
|
|
28
|
+
return this.bytes[1]
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
get multihash() {
|
|
32
|
+
const code = this.bytes[2]
|
|
33
|
+
const digest = this.bytes.subarray(4)
|
|
34
|
+
return { code, digest }
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
equals(other: Cid): boolean {
|
|
38
|
+
return ui8Equals(this.bytes, other.bytes)
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
toString(): string {
|
|
42
|
+
return base32.encode(this.bytes)
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
describe(BytesCid, () => {
|
|
47
|
+
it('creates a BytesCid from valid bytes', () => {
|
|
48
|
+
const bytes = new Uint8Array([1, 0x55, 0x12, 3, 1, 2, 3])
|
|
49
|
+
const cid = new BytesCid(bytes)
|
|
50
|
+
|
|
51
|
+
assert(cid.version === 1)
|
|
52
|
+
assert(cid.code === 0x55)
|
|
53
|
+
assert(cid.multihash.code === 0x12)
|
|
54
|
+
assert(ui8Equals(cid.multihash.digest, new Uint8Array([1, 2, 3])))
|
|
55
|
+
assert(ui8Equals(cid.bytes, bytes))
|
|
56
|
+
assert(typeof cid.toString === 'function')
|
|
57
|
+
assert(typeof cid.equals === 'function')
|
|
58
|
+
})
|
|
59
|
+
|
|
60
|
+
it('throws an error for invalid CID bytes', () => {
|
|
61
|
+
expect(
|
|
62
|
+
() => new BytesCid(new Uint8Array([2, 0x55, 0x12, 3, 1, 2, 3])),
|
|
63
|
+
).toThrowError('Unsupported CID version')
|
|
64
|
+
expect(() => new BytesCid(new Uint8Array([1, 0x55, 0x12]))).toThrowError(
|
|
65
|
+
'CID bytes are too short',
|
|
66
|
+
)
|
|
67
|
+
expect(
|
|
68
|
+
() => new BytesCid(new Uint8Array([1, 0x55, 0x12, 4, 1, 2, 3])),
|
|
69
|
+
).toThrowError('CID bytes length mismatch')
|
|
70
|
+
})
|
|
71
|
+
})
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* A minimal custom implementation of the `Cid` interface for testing purposes.
|
|
75
|
+
*/
|
|
76
|
+
export function createCustomCid<
|
|
77
|
+
TVersion extends 0 | 1,
|
|
78
|
+
TCode extends number,
|
|
79
|
+
TMultihashCode extends number,
|
|
80
|
+
>(
|
|
81
|
+
version: TVersion,
|
|
82
|
+
code: TCode,
|
|
83
|
+
multihashCode: TMultihashCode,
|
|
84
|
+
digest: Uint8Array,
|
|
85
|
+
): Cid<TVersion, TCode, TMultihashCode> {
|
|
86
|
+
return {
|
|
87
|
+
version,
|
|
88
|
+
code,
|
|
89
|
+
multihash: { code: multihashCode, digest },
|
|
90
|
+
bytes: new Uint8Array([
|
|
91
|
+
version,
|
|
92
|
+
code,
|
|
93
|
+
multihashCode,
|
|
94
|
+
digest.length,
|
|
95
|
+
...digest,
|
|
96
|
+
]),
|
|
97
|
+
toString,
|
|
98
|
+
equals,
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
function equals(this: Cid, other: Cid): boolean {
|
|
103
|
+
return (
|
|
104
|
+
this.version === other.version &&
|
|
105
|
+
this.code === other.code &&
|
|
106
|
+
this.multihash.code === other.multihash.code &&
|
|
107
|
+
ui8Equals(this.multihash.digest, other.multihash.digest)
|
|
108
|
+
)
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
function toString(this: Cid): string {
|
|
112
|
+
return CID.create(
|
|
113
|
+
this.version,
|
|
114
|
+
this.code,
|
|
115
|
+
createDigest(this.multihash.code, this.multihash.digest),
|
|
116
|
+
).toString()
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
describe(createCustomCid, () => {
|
|
120
|
+
it('creates a CID with the specified properties', () => {
|
|
121
|
+
const digest = new Uint8Array([1, 2, 3, 4, 5])
|
|
122
|
+
const customCid = createCustomCid(1, 0x55, 0x12, digest)
|
|
123
|
+
|
|
124
|
+
assert(customCid.version === 1)
|
|
125
|
+
assert(customCid.code === 0x55)
|
|
126
|
+
assert(customCid.multihash.code === 0x12)
|
|
127
|
+
assert(ui8Equals(customCid.multihash.digest, digest))
|
|
128
|
+
assert(
|
|
129
|
+
ui8Equals(
|
|
130
|
+
customCid.bytes,
|
|
131
|
+
new Uint8Array([1, 0x55, 0x12, 5, 1, 2, 3, 4, 5]),
|
|
132
|
+
),
|
|
133
|
+
)
|
|
134
|
+
assert(typeof customCid.toString === 'function')
|
|
135
|
+
assert(typeof customCid.equals === 'function')
|
|
136
|
+
})
|
|
137
|
+
})
|
package/src/cid.test.ts
CHANGED
|
@@ -1,23 +1,50 @@
|
|
|
1
|
+
/* eslint-disable import/no-deprecated */
|
|
2
|
+
|
|
1
3
|
import { CID } from 'multiformats/cid'
|
|
2
4
|
import { sha256, sha512 } from 'multiformats/hashes/sha2'
|
|
3
5
|
import { describe, expect, it } from 'vitest'
|
|
6
|
+
import { BytesCid, createCustomCid } from './cid-implementation.test.js'
|
|
4
7
|
import {
|
|
8
|
+
Cid,
|
|
5
9
|
DAG_CBOR_MULTICODEC,
|
|
6
10
|
RAW_MULTICODEC,
|
|
11
|
+
SHA256_MULTIHASH,
|
|
12
|
+
asMultiformatsCID,
|
|
13
|
+
cidForRawHash,
|
|
7
14
|
decodeCid,
|
|
8
15
|
ensureValidCidString,
|
|
9
16
|
isCid,
|
|
10
17
|
parseCid,
|
|
11
|
-
|
|
18
|
+
parseCidSafe,
|
|
12
19
|
} from './cid.js'
|
|
20
|
+
import { ui8Equals } from './uint8array.js'
|
|
21
|
+
|
|
22
|
+
const invalidCidStr = 'invalidcidstring'
|
|
23
|
+
|
|
24
|
+
const cborCidStr = 'bafyreidfayvfuwqa7qlnopdjiqrxzs6blmoeu4rujcjtnci5beludirz2a'
|
|
25
|
+
const cborCid = parseCid(cborCidStr, { flavor: 'cbor' })
|
|
26
|
+
|
|
27
|
+
const rawCidStr = 'bafkreifjjcie6lypi6ny7amxnfftagclbuxndqonfipmb64f2km2devei4'
|
|
28
|
+
const rawCid = parseCid(rawCidStr, { flavor: 'raw' })
|
|
29
|
+
|
|
30
|
+
const rawCidCustom: Cid = createCustomCid(
|
|
31
|
+
1,
|
|
32
|
+
RAW_MULTICODEC,
|
|
33
|
+
SHA256_MULTIHASH,
|
|
34
|
+
rawCid.multihash.digest,
|
|
35
|
+
)
|
|
36
|
+
const rawCidCustomBytes = new BytesCid(rawCid.bytes)
|
|
13
37
|
|
|
14
38
|
describe(isCid, () => {
|
|
15
39
|
describe('non-strict mode', () => {
|
|
16
40
|
it('returns true for parsed CIDs', () => {
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
41
|
+
expect(isCid(cborCid)).toBe(true)
|
|
42
|
+
expect(isCid(rawCid)).toBe(true)
|
|
43
|
+
})
|
|
44
|
+
|
|
45
|
+
it('returns true for custom compatible CID implementations', () => {
|
|
46
|
+
expect(isCid(rawCidCustom)).toBe(true)
|
|
47
|
+
expect(isCid(rawCidCustomBytes)).toBe(true)
|
|
21
48
|
})
|
|
22
49
|
|
|
23
50
|
it('returns true for CID v0 and v1', async () => {
|
|
@@ -88,56 +115,131 @@ describe(isCid, () => {
|
|
|
88
115
|
})
|
|
89
116
|
})
|
|
90
117
|
})
|
|
118
|
+
|
|
119
|
+
describe('alternative cid implementations', () => {
|
|
120
|
+
it('accepts compatible CID implementations', () => {
|
|
121
|
+
expect(isCid(rawCidCustom)).toBe(true)
|
|
122
|
+
})
|
|
123
|
+
|
|
124
|
+
it('rejects non-matching version', () => {
|
|
125
|
+
expect(isCid({ ...rawCidCustom, version: 0 })).toBe(false)
|
|
126
|
+
})
|
|
127
|
+
|
|
128
|
+
it('rejects non-matching code', () => {
|
|
129
|
+
expect(isCid({ ...rawCidCustom, code: 0 })).toBe(false)
|
|
130
|
+
})
|
|
131
|
+
|
|
132
|
+
it('rejects non-matching multihash code', () => {
|
|
133
|
+
expect(
|
|
134
|
+
isCid({
|
|
135
|
+
...rawCidCustom,
|
|
136
|
+
multihash: { ...rawCidCustom.multihash, code: 0 },
|
|
137
|
+
}),
|
|
138
|
+
).toBe(false)
|
|
139
|
+
})
|
|
140
|
+
|
|
141
|
+
it('rejects non-matching multihash digest', () => {
|
|
142
|
+
const differentDigest = new Uint8Array(32)
|
|
143
|
+
differentDigest[0] = 1
|
|
144
|
+
expect(
|
|
145
|
+
isCid({
|
|
146
|
+
...rawCidCustom,
|
|
147
|
+
multihash: { ...rawCidCustom.multihash, digest: differentDigest },
|
|
148
|
+
}),
|
|
149
|
+
).toBe(false)
|
|
150
|
+
})
|
|
151
|
+
|
|
152
|
+
it('rejects objects without equals method', () => {
|
|
153
|
+
expect(isCid({ ...rawCidCustom, equals: undefined })).toBe(false)
|
|
154
|
+
})
|
|
155
|
+
|
|
156
|
+
it('rejects object with throwing equals method', () => {
|
|
157
|
+
expect(
|
|
158
|
+
isCid({
|
|
159
|
+
...rawCidCustom,
|
|
160
|
+
equals: () => {
|
|
161
|
+
throw new Error('fail')
|
|
162
|
+
},
|
|
163
|
+
}),
|
|
164
|
+
).toBe(false)
|
|
165
|
+
})
|
|
166
|
+
})
|
|
91
167
|
})
|
|
92
168
|
|
|
93
169
|
describe(decodeCid, () => {
|
|
94
170
|
it('decodes CID from bytes', () => {
|
|
95
|
-
const
|
|
96
|
-
const cid = parseCid(cidStr)
|
|
171
|
+
const cid = parseCid(cborCidStr)
|
|
97
172
|
const bytes = cid.bytes
|
|
98
173
|
const decodedCid = decodeCid(bytes)
|
|
99
|
-
expect(decodedCid.toString()).toBe(
|
|
174
|
+
expect(decodedCid.toString()).toBe(cborCidStr)
|
|
100
175
|
})
|
|
101
176
|
})
|
|
102
177
|
|
|
103
178
|
describe(parseCid, () => {
|
|
104
179
|
it('parses valid CIDs', () => {
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
expect(cid.toString()).toBe(cidStr)
|
|
180
|
+
expect(parseCid(cborCidStr).toString()).toBe(cborCidStr)
|
|
181
|
+
expect(parseCid(rawCidStr).toString()).toBe(rawCidStr)
|
|
108
182
|
})
|
|
109
183
|
|
|
110
184
|
it('throws for invalid CIDs', () => {
|
|
111
|
-
const invalidCidStr = 'invalidcidstring'
|
|
112
185
|
expect(() => parseCid(invalidCidStr)).toThrow()
|
|
113
186
|
})
|
|
114
187
|
})
|
|
115
188
|
|
|
116
|
-
describe(
|
|
189
|
+
describe(parseCidSafe, () => {
|
|
117
190
|
it('parses valid CIDs', () => {
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
expect(cid).toBeDefined()
|
|
121
|
-
expect(cid!.toString()).toBe(cidStr)
|
|
191
|
+
expect(parseCidSafe(cborCidStr)?.toString()).toBe(cborCidStr)
|
|
192
|
+
expect(parseCidSafe(rawCidStr)?.toString()).toBe(rawCidStr)
|
|
122
193
|
})
|
|
123
194
|
|
|
124
195
|
it('returns undefined for invalid CIDs', () => {
|
|
125
|
-
|
|
126
|
-
const cid = parseCidString(invalidCidStr)
|
|
127
|
-
expect(cid).toBeUndefined()
|
|
196
|
+
expect(parseCidSafe(invalidCidStr)).toBeNull()
|
|
128
197
|
})
|
|
129
198
|
})
|
|
130
199
|
|
|
131
200
|
describe(ensureValidCidString, () => {
|
|
132
201
|
it('does not throw for valid CIDs', () => {
|
|
133
|
-
|
|
134
|
-
expect(() => ensureValidCidString(cidStr)).not.toThrow()
|
|
202
|
+
expect(() => ensureValidCidString(cborCidStr)).not.toThrow()
|
|
135
203
|
})
|
|
136
204
|
|
|
137
205
|
it('throws for invalid CIDs', () => {
|
|
138
|
-
const invalidCidStr = 'invalidcidstring'
|
|
139
206
|
expect(() => ensureValidCidString(invalidCidStr)).toThrow(
|
|
140
207
|
'Invalid CID string',
|
|
141
208
|
)
|
|
142
209
|
})
|
|
143
210
|
})
|
|
211
|
+
|
|
212
|
+
describe(cidForRawHash, () => {
|
|
213
|
+
it('creates a RawCid from a SHA-256 hash', () => {
|
|
214
|
+
const hash = new Uint8Array(32)
|
|
215
|
+
const cid = cidForRawHash(hash)
|
|
216
|
+
expect(cid.code).toBe(RAW_MULTICODEC)
|
|
217
|
+
expect(cid.multihash.code).toBe(SHA256_MULTIHASH)
|
|
218
|
+
expect(ui8Equals(cid.multihash.digest, hash)).toBe(true)
|
|
219
|
+
})
|
|
220
|
+
|
|
221
|
+
it('rejects hashes on invalid lengths', () => {
|
|
222
|
+
expect(() => cidForRawHash(new Uint8Array(31))).toThrow(
|
|
223
|
+
'Invalid SHA-256 hash length',
|
|
224
|
+
)
|
|
225
|
+
expect(() => cidForRawHash(new Uint8Array(33))).toThrow(
|
|
226
|
+
'Invalid SHA-256 hash length',
|
|
227
|
+
)
|
|
228
|
+
})
|
|
229
|
+
})
|
|
230
|
+
|
|
231
|
+
describe(asMultiformatsCID, () => {
|
|
232
|
+
it('converts compatible CID to multiformats CID', () => {
|
|
233
|
+
for (const cid of [cborCid, rawCid, rawCidCustom, rawCidCustomBytes]) {
|
|
234
|
+
expect(asMultiformatsCID(cid)).toBeInstanceOf(CID)
|
|
235
|
+
expect(asMultiformatsCID(cid)).toMatchObject({
|
|
236
|
+
version: cid.version,
|
|
237
|
+
code: cid.code,
|
|
238
|
+
multihash: {
|
|
239
|
+
code: cid.multihash.code,
|
|
240
|
+
digest: cid.multihash.digest,
|
|
241
|
+
},
|
|
242
|
+
})
|
|
243
|
+
}
|
|
244
|
+
})
|
|
245
|
+
})
|