@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.
- package/CHANGELOG.md +20 -0
- package/dist/blob.d.ts +39 -5
- package/dist/blob.d.ts.map +1 -1
- package/dist/blob.js +45 -16
- package/dist/blob.js.map +1 -1
- package/dist/cid.d.ts +83 -14
- package/dist/cid.d.ts.map +1 -1
- package/dist/cid.js +94 -35
- package/dist/cid.js.map +1 -1
- package/dist/lex-equals.js +1 -1
- package/dist/lex-equals.js.map +1 -1
- package/dist/uint8array-from-base64.d.ts.map +1 -1
- package/dist/uint8array-from-base64.js +1 -1
- package/dist/uint8array-from-base64.js.map +1 -1
- package/dist/uint8array-to-base64.d.ts.map +1 -1
- package/dist/uint8array-to-base64.js +2 -2
- package/dist/uint8array-to-base64.js.map +1 -1
- package/dist/utf8-from-base64.d.ts +4 -0
- package/dist/utf8-from-base64.d.ts.map +1 -0
- package/dist/utf8-from-base64.js +18 -0
- package/dist/utf8-from-base64.js.map +1 -0
- package/dist/utf8-to-base64.d.ts +4 -0
- package/dist/utf8-to-base64.d.ts.map +1 -0
- package/dist/utf8-to-base64.js +20 -0
- package/dist/utf8-to-base64.js.map +1 -0
- package/dist/utf8.d.ts +3 -0
- package/dist/utf8.d.ts.map +1 -1
- package/dist/utf8.js +16 -3
- package/dist/utf8.js.map +1 -1
- package/package.json +1 -1
- package/src/blob.test.ts +150 -25
- package/src/blob.ts +111 -27
- package/src/cid.test.ts +50 -33
- package/src/cid.ts +200 -35
- package/src/lex-equals.ts +2 -2
- package/src/uint8array-from-base64.ts +1 -1
- package/src/uint8array-to-base64.test.ts +2 -2
- package/src/uint8array-to-base64.ts +2 -2
- package/src/utf8-from-base64.test.ts +39 -0
- package/src/utf8-from-base64.ts +23 -0
- package/src/utf8-grapheme-len.test.ts +2 -2
- package/src/utf8-len.test.ts +2 -2
- package/src/utf8-to-base64.test.ts +35 -0
- package/src/utf8-to-base64.ts +22 -0
- package/src/utf8.ts +23 -2
package/src/blob.ts
CHANGED
|
@@ -1,25 +1,47 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
3
|
-
|
|
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:
|
|
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?:
|
|
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 =
|
|
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
|
-
|
|
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
|
-
|
|
6
|
-
|
|
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(
|
|
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('
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
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
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
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
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
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
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
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(
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
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
|
|
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
|
|
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
|
|
19
|
-
* {@link
|
|
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
|
-
|
|
73
|
-
|
|
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
|
|
77
|
-
return
|
|
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
|
-
|
|
81
|
-
|
|
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
|
|
85
|
-
return
|
|
121
|
+
export function isCborCid(cid: Cid): cid is CborCid {
|
|
122
|
+
return cid.code === DAG_CBOR_MULTICODEC && isDaslCid(cid)
|
|
86
123
|
}
|
|
87
124
|
|
|
88
|
-
export
|
|
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
|
|
91
|
-
):
|
|
92
|
-
|
|
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
|
|
145
|
+
return null
|
|
95
146
|
}
|
|
96
147
|
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
return
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
return
|
|
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
|
-
|
|
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
|
-
|
|
113
|
-
|
|
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
|
-
|
|
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(
|
|
125
|
-
|
|
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 {
|
|
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
|
|
47
|
+
return ifCid(b)?.equals(a) === true
|
|
48
48
|
} else if (isCid(b)) {
|
|
49
49
|
return false
|
|
50
50
|
}
|
|
@@ -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
|
|
17
|
+
assert(toBase64, 'toBase64 implementation should not be null')
|
|
18
18
|
|
|
19
|
-
describe(toBase64
|
|
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
|
+
}
|