@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.
@@ -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
- parseCidString,
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
- const cid = parseCid(
18
- 'bafyreidfayvfuwqa7qlnopdjiqrxzs6blmoeu4rujcjtnci5beludirz2a',
19
- )
20
- expect(isCid(cid)).toBe(true)
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 cidStr = 'bafyreidfayvfuwqa7qlnopdjiqrxzs6blmoeu4rujcjtnci5beludirz2a'
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(cidStr)
174
+ expect(decodedCid.toString()).toBe(cborCidStr)
100
175
  })
101
176
  })
102
177
 
103
178
  describe(parseCid, () => {
104
179
  it('parses valid CIDs', () => {
105
- const cidStr = 'bafyreidfayvfuwqa7qlnopdjiqrxzs6blmoeu4rujcjtnci5beludirz2a'
106
- const cid = parseCid(cidStr)
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(parseCidString, () => {
189
+ describe(parseCidSafe, () => {
117
190
  it('parses valid CIDs', () => {
118
- const cidStr = 'bafyreidfayvfuwqa7qlnopdjiqrxzs6blmoeu4rujcjtnci5beludirz2a'
119
- const cid = parseCidString(cidStr)
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
- const invalidCidStr = 'invalidcidstring'
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
- const cidStr = 'bafyreidfayvfuwqa7qlnopdjiqrxzs6blmoeu4rujcjtnci5beludirz2a'
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
+ })