@atproto/lex-data 0.0.5 → 0.0.7

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.
Files changed (45) hide show
  1. package/CHANGELOG.md +20 -0
  2. package/dist/blob.d.ts +39 -5
  3. package/dist/blob.d.ts.map +1 -1
  4. package/dist/blob.js +45 -16
  5. package/dist/blob.js.map +1 -1
  6. package/dist/cid.d.ts +83 -14
  7. package/dist/cid.d.ts.map +1 -1
  8. package/dist/cid.js +94 -35
  9. package/dist/cid.js.map +1 -1
  10. package/dist/lex-equals.js +1 -1
  11. package/dist/lex-equals.js.map +1 -1
  12. package/dist/uint8array-from-base64.d.ts.map +1 -1
  13. package/dist/uint8array-from-base64.js +1 -1
  14. package/dist/uint8array-from-base64.js.map +1 -1
  15. package/dist/uint8array-to-base64.d.ts.map +1 -1
  16. package/dist/uint8array-to-base64.js +2 -2
  17. package/dist/uint8array-to-base64.js.map +1 -1
  18. package/dist/utf8-from-base64.d.ts +4 -0
  19. package/dist/utf8-from-base64.d.ts.map +1 -0
  20. package/dist/utf8-from-base64.js +18 -0
  21. package/dist/utf8-from-base64.js.map +1 -0
  22. package/dist/utf8-to-base64.d.ts +4 -0
  23. package/dist/utf8-to-base64.d.ts.map +1 -0
  24. package/dist/utf8-to-base64.js +20 -0
  25. package/dist/utf8-to-base64.js.map +1 -0
  26. package/dist/utf8.d.ts +3 -0
  27. package/dist/utf8.d.ts.map +1 -1
  28. package/dist/utf8.js +16 -3
  29. package/dist/utf8.js.map +1 -1
  30. package/package.json +1 -1
  31. package/src/blob.test.ts +150 -25
  32. package/src/blob.ts +111 -27
  33. package/src/cid.test.ts +50 -33
  34. package/src/cid.ts +200 -35
  35. package/src/lex-equals.ts +2 -2
  36. package/src/uint8array-from-base64.ts +1 -1
  37. package/src/uint8array-to-base64.test.ts +2 -2
  38. package/src/uint8array-to-base64.ts +2 -2
  39. package/src/utf8-from-base64.test.ts +39 -0
  40. package/src/utf8-from-base64.ts +23 -0
  41. package/src/utf8-grapheme-len.test.ts +2 -2
  42. package/src/utf8-len.test.ts +2 -2
  43. package/src/utf8-to-base64.test.ts +35 -0
  44. package/src/utf8-to-base64.ts +22 -0
  45. package/src/utf8.ts +23 -2
package/src/blob.ts CHANGED
@@ -1,25 +1,47 @@
1
- import {
2
- Cid,
3
- RAW_BIN_MULTICODEC,
4
- SHA2_256_MULTIHASH_CODE,
5
- asCid,
6
- parseCid,
7
- } from './cid.js'
8
- import { isPlainObject } from './object.js'
1
+ import { Cid, RawCid, ifCid, validateCidString } from './cid.js'
2
+ import { LexValue } from './lex.js'
3
+ import { isPlainObject, isPlainProto } from './object.js'
9
4
 
10
5
  /**
11
6
  * @note {@link BlobRef} is just a {@link LexMap} with a specific shape.
12
7
  */
13
- export type BlobRef = {
8
+ export type BlobRef<Ref extends Cid = Cid> = {
14
9
  $type: 'blob'
15
10
  mimeType: string
16
- ref: Cid
11
+ ref: Ref
17
12
  size: number
18
13
  }
19
14
 
15
+ export type BlobRefCheckOptions = {
16
+ /**
17
+ * If `false`, skips strict CID validation of {@link BlobRef.ref}, allowing
18
+ * any valid CID. Otherwise, validates that the CID is v1, uses the raw
19
+ * multicodec, and has a sha256 multihash.
20
+ *
21
+ * @defaults to `true`
22
+ */
23
+ strict?: boolean
24
+ }
25
+
26
+ export type InferCheckedBlobRef<TOptions extends BlobRefCheckOptions> =
27
+ TOptions extends { strict: false }
28
+ ? BlobRef
29
+ : { strict: boolean } extends TOptions
30
+ ? BlobRef
31
+ : BlobRef<RawCid>
32
+
33
+ export function isBlobRef(input: unknown): input is BlobRef<RawCid>
34
+ export function isBlobRef<TOptions extends BlobRefCheckOptions>(
35
+ input: unknown,
36
+ options: TOptions,
37
+ ): input is InferCheckedBlobRef<TOptions>
38
+ export function isBlobRef(
39
+ input: unknown,
40
+ options?: BlobRefCheckOptions,
41
+ ): input is BlobRef
20
42
  export function isBlobRef(
21
43
  input: unknown,
22
- options?: { strict?: boolean },
44
+ options?: BlobRefCheckOptions,
23
45
  ): input is BlobRef {
24
46
  if (!isPlainObject(input)) {
25
47
  return false
@@ -54,23 +76,15 @@ export function isBlobRef(
54
76
  }
55
77
  }
56
78
 
57
- const cid = asCid(ref)
79
+ const cid = ifCid(
80
+ ref,
81
+ // Strict unless explicitly disabled
82
+ options?.strict === false ? undefined : { flavor: 'raw' },
83
+ )
58
84
  if (!cid) {
59
85
  return false
60
86
  }
61
87
 
62
- if (options?.strict) {
63
- if (cid.version !== 1) {
64
- return false
65
- }
66
- if (cid.code !== RAW_BIN_MULTICODEC) {
67
- return false
68
- }
69
- if (cid.multihash.code !== SHA2_256_MULTIHASH_CODE) {
70
- return false
71
- }
72
- }
73
-
74
88
  return true
75
89
  }
76
90
 
@@ -102,11 +116,81 @@ export function isLegacyBlobRef(input: unknown): input is LegacyBlobRef {
102
116
  }
103
117
  }
104
118
 
105
- try {
106
- parseCid(cid)
107
- } catch {
119
+ if (!validateCidString(cid)) {
108
120
  return false
109
121
  }
110
122
 
111
123
  return true
112
124
  }
125
+
126
+ export type EnumBlobRefsOptions = BlobRefCheckOptions & {
127
+ /**
128
+ * @defaults to `false`
129
+ */
130
+ allowLegacy?: boolean
131
+ }
132
+
133
+ export type InferEnumBlobRefs<TOptions extends EnumBlobRefsOptions> =
134
+ TOptions extends { allowLegacy: true }
135
+ ? InferCheckedBlobRef<TOptions> | LegacyBlobRef
136
+ : { allowLegacy: boolean } extends TOptions
137
+ ? InferCheckedBlobRef<TOptions> | LegacyBlobRef
138
+ : InferCheckedBlobRef<TOptions>
139
+
140
+ /**
141
+ * Enumerates all {@link BlobRef}s (and, optionally, {@link LegacyBlobRef}s)
142
+ * found within a {@link LexValue}.
143
+ */
144
+ export function enumBlobRefs(
145
+ input: LexValue,
146
+ ): Generator<BlobRef<RawCid>, void, unknown>
147
+ export function enumBlobRefs<TOptions extends EnumBlobRefsOptions>(
148
+ input: LexValue,
149
+ options: TOptions,
150
+ ): Generator<InferEnumBlobRefs<TOptions>, void, unknown>
151
+ export function enumBlobRefs(
152
+ input: LexValue,
153
+ options?: EnumBlobRefsOptions,
154
+ ): Generator<BlobRef | LegacyBlobRef, void, unknown>
155
+ export function* enumBlobRefs(
156
+ input: LexValue,
157
+ options?: EnumBlobRefsOptions,
158
+ ): Generator<BlobRef | LegacyBlobRef, void, unknown> {
159
+ // LegacyBlobRef not included by default
160
+ const includeLegacy = options?.allowLegacy === true
161
+
162
+ // Using a stack to avoid recursion depth issues.
163
+ const stack: LexValue[] = [input]
164
+
165
+ // Since we are using a stack, we could end-up in an infinite loop with cyclic
166
+ // structures. Cyclic structures are not valid LexValues and should, thus,
167
+ // never occur, but let's be safe.
168
+ const visited = new Set<object>()
169
+
170
+ do {
171
+ const value = stack.pop()!
172
+
173
+ if (value != null && typeof value === 'object') {
174
+ if (Array.isArray(value)) {
175
+ if (visited.has(value)) continue
176
+ visited.add(value)
177
+ stack.push(...value)
178
+ } else if (isPlainProto(value)) {
179
+ if (visited.has(value)) continue
180
+ visited.add(value)
181
+ if (isBlobRef(value, options)) {
182
+ yield value
183
+ } else if (includeLegacy && isLegacyBlobRef(value)) {
184
+ yield value
185
+ } else {
186
+ for (const v of Object.values(value)) {
187
+ if (v != null) stack.push(v)
188
+ }
189
+ }
190
+ }
191
+ }
192
+ } while (stack.length > 0)
193
+
194
+ // Optimization: ease GC's work
195
+ visited.clear()
196
+ }
package/src/cid.test.ts CHANGED
@@ -2,8 +2,8 @@ import { CID } from 'multiformats/cid'
2
2
  import { sha256, sha512 } from 'multiformats/hashes/sha2'
3
3
  import { describe, expect, it } from 'vitest'
4
4
  import {
5
- RAW_BIN_MULTICODEC,
6
- createCid,
5
+ DAG_CBOR_MULTICODEC,
6
+ RAW_MULTICODEC,
7
7
  decodeCid,
8
8
  ensureValidCidString,
9
9
  isCid,
@@ -23,7 +23,7 @@ describe(isCid, () => {
23
23
  it('returns true for CID v0 and v1', async () => {
24
24
  const digest = await sha256.digest(Buffer.from('hello world'))
25
25
  const cidV0 = CID.createV0(digest)
26
- const cidV1 = CID.createV1(RAW_BIN_MULTICODEC, digest)
26
+ const cidV1 = CID.createV1(RAW_MULTICODEC, digest)
27
27
  expect(isCid(cidV0)).toBe(true)
28
28
  expect(isCid(cidV1)).toBe(true)
29
29
  })
@@ -35,41 +35,58 @@ describe(isCid, () => {
35
35
  })
36
36
  })
37
37
 
38
- describe('strict mode', () => {
39
- it('returns true for valid CIDs in strict mode', async () => {
40
- const digest = await sha256.digest(Buffer.from('hello world'))
41
- const cid = CID.createV1(RAW_BIN_MULTICODEC, digest)
42
- expect(isCid(cid, { strict: true })).toBe(true)
43
- })
38
+ describe('flavors', () => {
39
+ describe('raw', () => {
40
+ it('validated "raw" cids', async () => {
41
+ const digest = await sha256.digest(Buffer.from('hello world'))
42
+ const cid = CID.createV1(RAW_MULTICODEC, digest)
43
+ expect(isCid(cid, { flavor: 'raw' })).toBe(true)
44
+ })
44
45
 
45
- it('rejects CID v0 when strict option is set', async () => {
46
- const digest = await sha256.digest(Buffer.from('hello world'))
47
- const cid = CID.createV0(digest)
48
- expect(isCid(cid, { strict: true })).toBe(false)
49
- })
46
+ it('allows other hash algorithms', async () => {
47
+ const digest = await sha512.digest(Buffer.from('hello world'))
48
+ const cid = CID.createV1(RAW_MULTICODEC, digest)
49
+ expect(isCid(cid, { flavor: 'raw' })).toBe(true)
50
+ })
50
51
 
51
- it('rejects CIDs with invalid hash algorithm', async () => {
52
- const digest = await sha512.digest(Buffer.from('hello world'))
53
- const cid = CID.createV1(RAW_BIN_MULTICODEC, digest)
54
- expect(isCid(cid, { strict: true })).toBe(false)
55
- })
52
+ it('rejects CID v0 when strict option is set', async () => {
53
+ const digest = await sha256.digest(Buffer.from('hello world'))
54
+ const cid = CID.createV0(digest)
55
+ expect(isCid(cid, { flavor: 'raw' })).toBe(false)
56
+ })
56
57
 
57
- it('rejects CIDs with invalid code', async () => {
58
- const digest = await sha256.digest(Buffer.from('hello world'))
59
- const cid = CID.createV1(3333, digest)
60
- expect(isCid(cid, { strict: true })).toBe(false)
58
+ it('rejects CIDs with invalid code', async () => {
59
+ const digest = await sha256.digest(Buffer.from('hello world'))
60
+ const cid = CID.createV1(3333, digest)
61
+ expect(isCid(cid, { flavor: 'raw' })).toBe(false)
62
+ })
61
63
  })
62
- })
63
- })
64
64
 
65
- describe(createCid, () => {
66
- it('creates a valid CID v1', async () => {
67
- const digest = await sha256.digest(Buffer.from('hello world'))
68
- const cid = createCid(RAW_BIN_MULTICODEC, digest)
69
- expect(cid.version).toBe(1)
70
- expect(cid.code).toBe(RAW_BIN_MULTICODEC)
71
- expect(cid.multihash.code).toBe(sha256.code)
72
- expect(cid.multihash.digest).toEqual(digest.digest)
65
+ describe('cbor', () => {
66
+ it('validated "cbor" cids', async () => {
67
+ const digest = await sha256.digest(Buffer.from('hello world'))
68
+ const cid = CID.createV1(DAG_CBOR_MULTICODEC, digest)
69
+ expect(isCid(cid, { flavor: 'cbor' })).toBe(true)
70
+ })
71
+
72
+ it('rejects CIDs with invalid hash algorithm', async () => {
73
+ const digest = await sha512.digest(Buffer.from('hello world'))
74
+ const cid = CID.createV1(RAW_MULTICODEC, digest)
75
+ expect(isCid(cid, { flavor: 'cbor' })).toBe(false)
76
+ })
77
+
78
+ it('rejects CID v0 when strict option is set', async () => {
79
+ const digest = await sha256.digest(Buffer.from('hello world'))
80
+ const cid = CID.createV0(digest)
81
+ expect(isCid(cid, { flavor: 'cbor' })).toBe(false)
82
+ })
83
+
84
+ it('rejects CIDs with invalid code', async () => {
85
+ const digest = await sha256.digest(Buffer.from('hello world'))
86
+ const cid = CID.createV1(3333, digest)
87
+ expect(isCid(cid, { flavor: 'cbor' })).toBe(false)
88
+ })
89
+ })
73
90
  })
74
91
  })
75
92
 
package/src/cid.ts CHANGED
@@ -1,9 +1,18 @@
1
1
  import { CID } from 'multiformats/cid'
2
+ import {
3
+ create as createDigest,
4
+ equals as digestEquals,
5
+ } from 'multiformats/hashes/digest'
6
+ import { sha256, sha512 } from 'multiformats/hashes/sha2'
2
7
 
3
- export const DAG_CBOR_MULTICODEC = 0x71
4
- export const RAW_BIN_MULTICODEC = 0x55
8
+ export const DAG_CBOR_MULTICODEC = 0x71 // DRISL conformant DAG-CBOR
9
+ export type DAG_CBOR_MULTICODEC = typeof DAG_CBOR_MULTICODEC
5
10
 
6
- export const SHA2_256_MULTIHASH_CODE = 0x12
11
+ export const RAW_MULTICODEC = 0x55 // raw binary codec used in DASL CIDs
12
+ export type RAW_MULTICODEC = typeof RAW_MULTICODEC
13
+
14
+ export const SHA256_MULTIHASH = sha256.code
15
+ export type SHA256_MULTIHASH = typeof SHA256_MULTIHASH
7
16
 
8
17
  export type MultihashDigest<Code extends number = number> = {
9
18
  code: Code
@@ -15,8 +24,8 @@ export type MultihashDigest<Code extends number = number> = {
15
24
  declare module 'multiformats/cid' {
16
25
  /**
17
26
  * @deprecated use the {@link Cid} interface from `@atproto/lex-data`, and
18
- * related helpers ({@link asCid}, {@link parseCid}, {@link decodeCid},
19
- * {@link createCid}, {@link isCid}), instead.
27
+ * related helpers ({@link isCid}, {@link ifCid}, {@link asCid},
28
+ * {@link parseCid}, {@link decodeCid}), instead.
20
29
  *
21
30
  * This is marked as deprecated because we want to discourage direct usage of
22
31
  * `multiformats/cid` in dependent packages, and instead have them rely on the
@@ -69,60 +78,216 @@ export interface Cid {
69
78
  toString(): string
70
79
  }
71
80
 
72
- export function asCid(value: unknown): Cid | null {
73
- return CID.asCID(value)
81
+ /**
82
+ * Represents the cid of raw binary data (like media blobs).
83
+ * @see {@link https://atproto.com/specs/data-model#link-and-cid-formats ATproto Data Model - Link and CID Formats}
84
+ */
85
+ export interface RawCid extends Cid {
86
+ version: 1
87
+ code: RAW_MULTICODEC
88
+ }
89
+
90
+ export function isRawCid(cid: Cid): cid is RawCid {
91
+ return cid.version === 1 && cid.code === RAW_MULTICODEC
92
+ }
93
+
94
+ /**
95
+ * Represents a DASL compliant CID.
96
+ * @see {@link https://dasl.ing/cid.html DASL-CIDs}
97
+ */
98
+ export interface DaslCid extends Cid {
99
+ version: 1
100
+ code: RAW_MULTICODEC | DAG_CBOR_MULTICODEC
101
+ multihash: MultihashDigest<SHA256_MULTIHASH>
74
102
  }
75
103
 
76
- export function parseCid(input: string): Cid {
77
- return CID.parse(input)
104
+ export function isDaslCid(cid: Cid): cid is DaslCid {
105
+ return (
106
+ cid.version === 1 &&
107
+ (cid.code === RAW_MULTICODEC || cid.code === DAG_CBOR_MULTICODEC) &&
108
+ cid.multihash.code === SHA256_MULTIHASH &&
109
+ cid.multihash.size === 32 // Should always be 32 bytes (256 bits) for SHA-256
110
+ )
78
111
  }
79
112
 
80
- export function decodeCid(bytes: Uint8Array): Cid {
81
- return CID.decode(bytes)
113
+ /**
114
+ * Represents the cid of ATProto DAG-CBOR data (like repository MST nodes).
115
+ * @see {@link https://atproto.com/specs/data-model#link-and-cid-formats ATproto Data Model - Link and CID Formats}
116
+ */
117
+ export interface CborCid extends DaslCid {
118
+ code: DAG_CBOR_MULTICODEC
82
119
  }
83
120
 
84
- export function createCid(code: number, digest: MultihashDigest): Cid {
85
- return CID.createV1(code, digest)
121
+ export function isCborCid(cid: Cid): cid is CborCid {
122
+ return cid.code === DAG_CBOR_MULTICODEC && isDaslCid(cid)
86
123
  }
87
124
 
88
- export function isCid(
125
+ export type CidCheckOptions = {
126
+ flavor?: 'raw' | 'cbor' | 'dasl'
127
+ }
128
+ export type InferCheckedCid<TOptions> = TOptions extends { flavor: 'raw' }
129
+ ? RawCid
130
+ : TOptions extends { flavor: 'cbor' }
131
+ ? CborCid
132
+ : Cid
133
+
134
+ /**
135
+ * Coerces the input value to a Cid, or returns null if not possible.
136
+ */
137
+ export function ifCid<TOptions extends CidCheckOptions>(
89
138
  value: unknown,
90
- options?: { strict?: boolean },
91
- ): value is Cid {
92
- const cid = asCid(value)
139
+ options: TOptions,
140
+ ): InferCheckedCid<TOptions> | null
141
+ export function ifCid(value: unknown, options?: CidCheckOptions): Cid | null
142
+ export function ifCid(value: unknown, options?: CidCheckOptions): Cid | null {
143
+ const cid = CID.asCID(value)
93
144
  if (!cid) {
94
- return false
145
+ return null
95
146
  }
96
147
 
97
- if (options?.strict) {
98
- if (cid.version !== 1) {
99
- return false
100
- }
101
- if (cid.code !== RAW_BIN_MULTICODEC && cid.code !== DAG_CBOR_MULTICODEC) {
102
- return false
103
- }
104
- if (cid.multihash.code !== SHA2_256_MULTIHASH_CODE) {
105
- return false
106
- }
148
+ switch (options?.flavor) {
149
+ case 'cbor':
150
+ return isCborCid(cid) ? cid : null
151
+ case 'raw':
152
+ return isRawCid(cid) ? cid : null
153
+ case 'dasl':
154
+ return isDaslCid(cid) ? cid : null
155
+ default:
156
+ return cid
107
157
  }
158
+ }
108
159
 
109
- return true
160
+ export function isCid<TOptions extends CidCheckOptions>(
161
+ value: unknown,
162
+ options: TOptions,
163
+ ): value is InferCheckedCid<TOptions>
164
+ export function isCid(value: unknown, options?: CidCheckOptions): value is Cid
165
+ export function isCid(value: unknown, options?: CidCheckOptions): value is Cid {
166
+ return ifCid(value, options) !== null
110
167
  }
111
168
 
112
- export function validateCidString(input: string): boolean {
113
- return parseCidString(input)?.toString() === input
169
+ /**
170
+ * Coerces the input value to a Cid, or throws if not possible.
171
+ */
172
+ export function asCid<TOptions extends CidCheckOptions>(
173
+ value: unknown,
174
+ options: TOptions,
175
+ ): InferCheckedCid<TOptions>
176
+ export function asCid(value: unknown, options?: CidCheckOptions): Cid
177
+ export function asCid(value: unknown, options?: CidCheckOptions): Cid {
178
+ const cid = ifCid(value, options)
179
+ if (cid) return cid
180
+ throw new Error('Not a valid CID')
114
181
  }
115
182
 
116
- export function parseCidString(input: string): Cid | undefined {
183
+ /**
184
+ * Parses a CID string into a Cid object.
185
+ *
186
+ * @throws if the input is not a valid CID string.
187
+ */
188
+ export function parseCid<TOptions extends CidCheckOptions>(
189
+ input: string,
190
+ options: TOptions,
191
+ ): InferCheckedCid<TOptions>
192
+ export function parseCid(input: string, options?: CidCheckOptions): Cid
193
+ export function parseCid(input: string, options?: CidCheckOptions): Cid {
194
+ const cid = CID.parse(input)
195
+ return asCid(cid, options)
196
+ }
197
+
198
+ /**
199
+ * Decodes a CID from its binary representation.
200
+ *
201
+ * @see {@link https://dasl.ing/cid.html DASL-CIDs}
202
+ * @throws if the input do not represent a valid DASL {@link Cid}
203
+ */
204
+ export function decodeCid<TOptions extends CidCheckOptions>(
205
+ cidBytes: Uint8Array,
206
+ options: TOptions,
207
+ ): InferCheckedCid<TOptions>
208
+ export function decodeCid(cidBytes: Uint8Array, options?: CidCheckOptions): Cid
209
+ export function decodeCid(
210
+ cidBytes: Uint8Array,
211
+ options?: CidCheckOptions,
212
+ ): Cid {
213
+ const cid = CID.decode(cidBytes)
214
+ return asCid(cid, options)
215
+ }
216
+
217
+ export function validateCidString(
218
+ input: string,
219
+ options?: CidCheckOptions,
220
+ ): boolean {
221
+ return parseCidString(input, options)?.toString() === input
222
+ }
223
+
224
+ export function parseCidString<TOptions extends CidCheckOptions>(
225
+ input: string,
226
+ options: TOptions,
227
+ ): InferCheckedCid<TOptions> | undefined
228
+ export function parseCidString(
229
+ input: string,
230
+ options?: CidCheckOptions,
231
+ ): Cid | undefined
232
+ export function parseCidString(
233
+ input: string,
234
+ options?: CidCheckOptions,
235
+ ): Cid | undefined {
117
236
  try {
118
- return parseCid(input)
237
+ return parseCid(input, options)
119
238
  } catch {
120
239
  return undefined
121
240
  }
122
241
  }
123
242
 
124
- export function ensureValidCidString(input: string): void {
125
- if (!validateCidString(input)) {
243
+ export function ensureValidCidString(
244
+ input: string,
245
+ options?: CidCheckOptions,
246
+ ): void {
247
+ if (!validateCidString(input, options)) {
126
248
  throw new Error(`Invalid CID string`)
127
249
  }
128
250
  }
251
+
252
+ /**
253
+ * Verifies whether the multihash of a given {@link cid} matches the hash of the provided {@link bytes}.
254
+ * @params cid The CID to match against the bytes.
255
+ * @params bytes The bytes to verify.
256
+ * @returns true if the CID matches the bytes, false otherwise.
257
+ */
258
+ export async function isCidForBytes(
259
+ cid: Cid,
260
+ bytes: Uint8Array,
261
+ ): Promise<boolean> {
262
+ if (cid.multihash.code === sha256.code) {
263
+ const digest = await sha256.digest(bytes)
264
+ return digestEquals(cid.multihash, digest)
265
+ }
266
+
267
+ if (cid.multihash.code === sha512.code) {
268
+ const digest = await sha512.digest(bytes)
269
+ return digestEquals(cid.multihash, digest)
270
+ }
271
+
272
+ // Don't know how to verify other multihash codes
273
+ throw new Error('Unsupported CID multihash')
274
+ }
275
+
276
+ export async function cidForCbor(bytes: Uint8Array): Promise<CborCid> {
277
+ const digest = await sha256.digest(bytes)
278
+ return CID.createV1(DAG_CBOR_MULTICODEC, digest) as CborCid
279
+ }
280
+
281
+ export async function cidForRawBytes(bytes: Uint8Array): Promise<RawCid> {
282
+ const digest = await sha256.digest(bytes)
283
+ return CID.createV1(RAW_MULTICODEC, digest) as RawCid
284
+ }
285
+
286
+ export function cidForRawHash(hash: Uint8Array): RawCid {
287
+ // Fool-proofing
288
+ if (hash.length !== 32) {
289
+ throw new Error(`Invalid SHA-256 hash length: ${hash.length}`)
290
+ }
291
+ const digest = createDigest(sha256.code, hash)
292
+ return CID.createV1(RAW_MULTICODEC, digest) as RawCid
293
+ }
package/src/lex-equals.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { asCid, isCid } from './cid.js'
1
+ import { ifCid, isCid } from './cid.js'
2
2
  import { LexValue } from './lex.js'
3
3
  import { isPlainObject } from './object.js'
4
4
  import { ui8Equals } from './uint8array.js'
@@ -44,7 +44,7 @@ export function lexEquals(a: LexValue, b: LexValue): boolean {
44
44
  if (isCid(a)) {
45
45
  // @NOTE CID.equals returns its argument when it is falsy (e.g. null or
46
46
  // undefined) so we need to explicitly check that the output is "true".
47
- return asCid(a)!.equals(asCid(b)) === true
47
+ return ifCid(b)?.equals(a) === true
48
48
  } else if (isCid(b)) {
49
49
  return false
50
50
  }
@@ -31,7 +31,7 @@ export const fromBase64Native =
31
31
  lastChunkHandling: 'loose',
32
32
  })
33
33
  }
34
- : null
34
+ : /* v8 ignore next -- @preserve */ null
35
35
 
36
36
  export const fromBase64Node = Buffer
37
37
  ? function fromBase64Node(
@@ -14,9 +14,9 @@ for (const toBase64 of [
14
14
  ] as const) {
15
15
  // Tests should run in NodeJS where implementations are either available or
16
16
  // polyfilled (see core-js imports above).
17
- assert(toBase64 !== null, 'toBase64 implementation should not be null')
17
+ assert(toBase64, 'toBase64 implementation should not be null')
18
18
 
19
- describe(toBase64.name, () => {
19
+ describe(toBase64, () => {
20
20
  describe('basic encoding', () => {
21
21
  it('encodes empty Uint8Array', () => {
22
22
  const encoded = toBase64(new Uint8Array(0))
@@ -25,7 +25,7 @@ export const toBase64Native =
25
25
  ): string {
26
26
  return bytes.toBase64!({ alphabet, omitPadding: true })
27
27
  }
28
- : null
28
+ : /* v8 ignore next -- @preserve */ null
29
29
 
30
30
  export const toBase64Node = Buffer
31
31
  ? function toBase64Node(
@@ -45,7 +45,7 @@ export const toBase64Node = Buffer
45
45
  : b64.slice(0, -1) // '='
46
46
  : b64
47
47
  }
48
- : null
48
+ : /* v8 ignore next -- @preserve */ null
49
49
 
50
50
  export function toBase64Ponyfill(
51
51
  bytes: Uint8Array,
@@ -0,0 +1,39 @@
1
+ import { assert, describe, expect, it } from 'vitest'
2
+ import {
3
+ utf8FromBase64Node,
4
+ utf8FromBase64Ponyfill,
5
+ } from './utf8-from-base64.js'
6
+
7
+ const strings = [
8
+ 'Hello, World!',
9
+ '¡Hola, Mundo!',
10
+ 'こんにちは世界',
11
+ '😀👩‍💻🌍',
12
+ '',
13
+ '𓀀𓁐𓂀𓃰𓄿𓅱𓆑𓇋𓈖𓉔𓊃𓋴𓌳𓍿𓎛𓏏',
14
+ ]
15
+
16
+ for (const utf8FromBase64 of [
17
+ utf8FromBase64Node,
18
+ utf8FromBase64Ponyfill,
19
+ ] as const) {
20
+ assert(utf8FromBase64, 'implementation should not be null')
21
+
22
+ describe(utf8FromBase64, () => {
23
+ it('decodes base64 to utf8 string', () => {
24
+ for (const text of strings) {
25
+ const b64 = Buffer.from(text, 'utf8').toString('base64')
26
+ const decoded = utf8FromBase64(b64, 'base64')
27
+ expect(decoded).toBe(text)
28
+ }
29
+ })
30
+
31
+ it('decodes base64url to utf8 string', () => {
32
+ for (const text of strings) {
33
+ const b64u = Buffer.from(text, 'utf8').toString('base64url')
34
+ const decoded = utf8FromBase64(b64u, 'base64url')
35
+ expect(decoded).toBe(text)
36
+ }
37
+ })
38
+ })
39
+ }
@@ -0,0 +1,23 @@
1
+ import { fromString } from 'uint8arrays/from-string'
2
+ import { NodeJSBuffer } from './lib/nodejs-buffer.js'
3
+ import { Base64Alphabet } from './uint8array-base64.js'
4
+
5
+ const Buffer = NodeJSBuffer
6
+
7
+ export const utf8FromBase64Node = Buffer
8
+ ? function utf8FromBase64Node(
9
+ b64: string,
10
+ alphabet: Base64Alphabet = 'base64',
11
+ ): string {
12
+ return Buffer.from(b64, alphabet).toString('utf8')
13
+ }
14
+ : /* v8 ignore next -- @preserve */ null
15
+
16
+ const textDecoder = /*#__PURE__*/ new TextDecoder()
17
+ export function utf8FromBase64Ponyfill(
18
+ b64: string,
19
+ alphabet?: Base64Alphabet,
20
+ ): string {
21
+ const bytes = fromString(b64, alphabet)
22
+ return textDecoder.decode(bytes)
23
+ }