@atproto/lex-data 0.0.10 → 0.0.12
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 +14 -0
- package/dist/blob.d.ts +143 -6
- package/dist/blob.d.ts.map +1 -1
- package/dist/blob.js +23 -0
- package/dist/blob.js.map +1 -1
- package/dist/cid.d.ts +211 -27
- package/dist/cid.d.ts.map +1 -1
- package/dist/cid.js +115 -19
- package/dist/cid.js.map +1 -1
- package/dist/lex-equals.d.ts +38 -0
- package/dist/lex-equals.d.ts.map +1 -1
- package/dist/lex-equals.js +38 -0
- package/dist/lex-equals.js.map +1 -1
- package/dist/lex-error.d.ts +80 -1
- package/dist/lex-error.d.ts.map +1 -1
- package/dist/lex-error.js +49 -1
- package/dist/lex-error.js.map +1 -1
- package/dist/lex.d.ts +184 -2
- package/dist/lex.d.ts.map +1 -1
- package/dist/lex.js +113 -34
- package/dist/lex.js.map +1 -1
- package/dist/object.d.ts +54 -4
- package/dist/object.d.ts.map +1 -1
- package/dist/object.js +54 -4
- package/dist/object.js.map +1 -1
- package/dist/uint8array.d.ts +96 -4
- package/dist/uint8array.d.ts.map +1 -1
- package/dist/uint8array.js +96 -4
- package/dist/uint8array.js.map +1 -1
- package/dist/utf8.d.ts +77 -0
- package/dist/utf8.d.ts.map +1 -1
- package/dist/utf8.js +77 -0
- package/dist/utf8.js.map +1 -1
- package/package.json +2 -2
- package/src/blob.ts +143 -6
- package/src/cid-implementation.test.ts +7 -13
- package/src/cid.test.ts +14 -14
- package/src/cid.ts +228 -52
- package/src/lex-equals.ts +38 -0
- package/src/lex-error.ts +80 -1
- package/src/lex.test.ts +3 -3
- package/src/lex.ts +198 -30
- package/src/object.ts +54 -4
- package/src/uint8array.ts +96 -4
- package/src/utf8.ts +77 -0
package/src/blob.ts
CHANGED
|
@@ -3,7 +3,28 @@ import { LexValue } from './lex.js'
|
|
|
3
3
|
import { isPlainObject, isPlainProto } from './object.js'
|
|
4
4
|
|
|
5
5
|
/**
|
|
6
|
-
*
|
|
6
|
+
* Reference to binary data (like images, videos, etc.) in the AT Protocol data model.
|
|
7
|
+
*
|
|
8
|
+
* A BlobRef is a {@link LexMap} with a specific structure that identifies binary
|
|
9
|
+
* content by its content hash (CID), along with metadata about the content type
|
|
10
|
+
* and size.
|
|
11
|
+
*
|
|
12
|
+
* @typeParam Ref - The type of CID reference, defaults to any {@link Cid}
|
|
13
|
+
*
|
|
14
|
+
* @example
|
|
15
|
+
* ```typescript
|
|
16
|
+
* import type { BlobRef } from '@atproto/lex-data'
|
|
17
|
+
*
|
|
18
|
+
* const imageRef: BlobRef = {
|
|
19
|
+
* $type: 'blob',
|
|
20
|
+
* mimeType: 'image/jpeg',
|
|
21
|
+
* ref: cid, // CID of the blob content
|
|
22
|
+
* size: 12345
|
|
23
|
+
* }
|
|
24
|
+
* ```
|
|
25
|
+
*
|
|
26
|
+
* @see {@link isBlobRef} to check if a value is a valid BlobRef
|
|
27
|
+
* @see {@link LegacyBlobRef} for the older blob reference format
|
|
7
28
|
*/
|
|
8
29
|
export type BlobRef<Ref extends Cid = Cid> = {
|
|
9
30
|
$type: 'blob'
|
|
@@ -12,17 +33,25 @@ export type BlobRef<Ref extends Cid = Cid> = {
|
|
|
12
33
|
size: number
|
|
13
34
|
}
|
|
14
35
|
|
|
36
|
+
/**
|
|
37
|
+
* Options for validating a {@link BlobRef}.
|
|
38
|
+
*/
|
|
15
39
|
export type BlobRefCheckOptions = {
|
|
16
40
|
/**
|
|
17
41
|
* If `false`, skips strict CID validation of {@link BlobRef.ref}, allowing
|
|
18
42
|
* any valid CID. Otherwise, validates that the CID is v1, uses the raw
|
|
19
43
|
* multicodec, and has a sha256 multihash.
|
|
20
44
|
*
|
|
21
|
-
* @
|
|
45
|
+
* @default true
|
|
22
46
|
*/
|
|
23
47
|
strict?: boolean
|
|
24
48
|
}
|
|
25
49
|
|
|
50
|
+
/**
|
|
51
|
+
* Infers the BlobRef type based on the check options.
|
|
52
|
+
*
|
|
53
|
+
* @typeParam TOptions - The options used for checking
|
|
54
|
+
*/
|
|
26
55
|
export type InferCheckedBlobRef<TOptions extends BlobRefCheckOptions> =
|
|
27
56
|
TOptions extends { strict: false }
|
|
28
57
|
? BlobRef
|
|
@@ -30,6 +59,34 @@ export type InferCheckedBlobRef<TOptions extends BlobRefCheckOptions> =
|
|
|
30
59
|
? BlobRef
|
|
31
60
|
: BlobRef<RawCid>
|
|
32
61
|
|
|
62
|
+
/**
|
|
63
|
+
* Type guard to check if a value is a valid {@link BlobRef}.
|
|
64
|
+
*
|
|
65
|
+
* Validates the structure of the input including:
|
|
66
|
+
* - `$type` must be `'blob'`
|
|
67
|
+
* - `mimeType` must be a valid MIME type string (containing '/')
|
|
68
|
+
* - `size` must be a non-negative safe integer
|
|
69
|
+
* - `ref` must be a valid CID (strict validation by default)
|
|
70
|
+
*
|
|
71
|
+
* @param input - The value to check
|
|
72
|
+
* @param options - Optional validation options
|
|
73
|
+
* @returns `true` if the input is a valid BlobRef
|
|
74
|
+
*
|
|
75
|
+
* @example
|
|
76
|
+
* ```typescript
|
|
77
|
+
* import { isBlobRef } from '@atproto/lex-data'
|
|
78
|
+
*
|
|
79
|
+
* if (isBlobRef(data)) {
|
|
80
|
+
* console.log(data.mimeType) // e.g., 'image/jpeg'
|
|
81
|
+
* console.log(data.size) // e.g., 12345
|
|
82
|
+
* }
|
|
83
|
+
*
|
|
84
|
+
* // Allow any valid CID (not just raw CIDs)
|
|
85
|
+
* if (isBlobRef(data, { strict: false })) {
|
|
86
|
+
* // ...
|
|
87
|
+
* }
|
|
88
|
+
* ```
|
|
89
|
+
*/
|
|
33
90
|
export function isBlobRef(input: unknown): input is BlobRef<RawCid>
|
|
34
91
|
export function isBlobRef<TOptions extends BlobRefCheckOptions>(
|
|
35
92
|
input: unknown,
|
|
@@ -89,13 +146,53 @@ export function isBlobRef(
|
|
|
89
146
|
}
|
|
90
147
|
|
|
91
148
|
/**
|
|
92
|
-
*
|
|
149
|
+
* Legacy format for blob references used in older AT Protocol data.
|
|
150
|
+
*
|
|
151
|
+
* This is the older format that stores the CID as a string rather than
|
|
152
|
+
* as a structured CID object. New code should use {@link BlobRef} instead.
|
|
153
|
+
*
|
|
154
|
+
* @example
|
|
155
|
+
* ```typescript
|
|
156
|
+
* import type { LegacyBlobRef } from '@atproto/lex-data'
|
|
157
|
+
*
|
|
158
|
+
* const legacyRef: LegacyBlobRef = {
|
|
159
|
+
* cid: 'bafyreib...',
|
|
160
|
+
* mimeType: 'image/jpeg'
|
|
161
|
+
* }
|
|
162
|
+
* ```
|
|
163
|
+
*
|
|
164
|
+
* @see {@link isLegacyBlobRef} to check if a value is a LegacyBlobRef
|
|
165
|
+
* @see {@link BlobRef} for the current blob reference format
|
|
166
|
+
* @deprecated Use {@link BlobRef} for new code
|
|
93
167
|
*/
|
|
94
168
|
export type LegacyBlobRef = {
|
|
95
169
|
cid: string
|
|
96
170
|
mimeType: string
|
|
97
171
|
}
|
|
98
172
|
|
|
173
|
+
/**
|
|
174
|
+
* Type guard to check if a value is a valid {@link LegacyBlobRef}.
|
|
175
|
+
*
|
|
176
|
+
* Validates the structure of the input:
|
|
177
|
+
* - `cid` must be a valid CID string
|
|
178
|
+
* - `mimeType` must be a non-empty string
|
|
179
|
+
* - No additional properties allowed
|
|
180
|
+
*
|
|
181
|
+
* @param input - The value to check
|
|
182
|
+
* @returns `true` if the input is a valid LegacyBlobRef
|
|
183
|
+
*
|
|
184
|
+
* @example
|
|
185
|
+
* ```typescript
|
|
186
|
+
* import { isLegacyBlobRef } from '@atproto/lex-data'
|
|
187
|
+
*
|
|
188
|
+
* if (isLegacyBlobRef(data)) {
|
|
189
|
+
* console.log(data.cid) // CID as string
|
|
190
|
+
* console.log(data.mimeType) // e.g., 'image/jpeg'
|
|
191
|
+
* }
|
|
192
|
+
* ```
|
|
193
|
+
*
|
|
194
|
+
* @see {@link isBlobRef} for checking the current blob reference format
|
|
195
|
+
*/
|
|
99
196
|
export function isLegacyBlobRef(input: unknown): input is LegacyBlobRef {
|
|
100
197
|
if (!isPlainObject(input)) {
|
|
101
198
|
return false
|
|
@@ -123,13 +220,24 @@ export function isLegacyBlobRef(input: unknown): input is LegacyBlobRef {
|
|
|
123
220
|
return true
|
|
124
221
|
}
|
|
125
222
|
|
|
223
|
+
/**
|
|
224
|
+
* Options for enumerating blob references within a {@link LexValue}.
|
|
225
|
+
*/
|
|
126
226
|
export type EnumBlobRefsOptions = BlobRefCheckOptions & {
|
|
127
227
|
/**
|
|
128
|
-
* @
|
|
228
|
+
* If `true`, also yields {@link LegacyBlobRef} objects in addition to
|
|
229
|
+
* {@link BlobRef} objects.
|
|
230
|
+
*
|
|
231
|
+
* @default false
|
|
129
232
|
*/
|
|
130
233
|
allowLegacy?: boolean
|
|
131
234
|
}
|
|
132
235
|
|
|
236
|
+
/**
|
|
237
|
+
* Infers the yielded type of {@link enumBlobRefs} based on options.
|
|
238
|
+
*
|
|
239
|
+
* @typeParam TOptions - The options used for enumeration
|
|
240
|
+
*/
|
|
133
241
|
export type InferEnumBlobRefs<TOptions extends EnumBlobRefsOptions> =
|
|
134
242
|
TOptions extends { allowLegacy: true }
|
|
135
243
|
? InferCheckedBlobRef<TOptions> | LegacyBlobRef
|
|
@@ -138,8 +246,37 @@ export type InferEnumBlobRefs<TOptions extends EnumBlobRefsOptions> =
|
|
|
138
246
|
: InferCheckedBlobRef<TOptions>
|
|
139
247
|
|
|
140
248
|
/**
|
|
141
|
-
*
|
|
142
|
-
* found within a {@link LexValue}.
|
|
249
|
+
* Generator that enumerates all {@link BlobRef}s (and, optionally,
|
|
250
|
+
* {@link LegacyBlobRef}s) found within a {@link LexValue}.
|
|
251
|
+
*
|
|
252
|
+
* Performs a deep traversal of the input value, yielding any blob references
|
|
253
|
+
* found. This is useful for extracting all media references from a record.
|
|
254
|
+
*
|
|
255
|
+
* @param input - The LexValue to search for blob references
|
|
256
|
+
* @param options - Optional configuration for the enumeration
|
|
257
|
+
* @yields Each blob reference found in the input
|
|
258
|
+
*
|
|
259
|
+
* @example
|
|
260
|
+
* ```typescript
|
|
261
|
+
* import { enumBlobRefs } from '@atproto/lex-data'
|
|
262
|
+
*
|
|
263
|
+
* const record = {
|
|
264
|
+
* text: 'Hello',
|
|
265
|
+
* images: [
|
|
266
|
+
* { $type: 'blob', mimeType: 'image/jpeg', ref: cid1, size: 1000 },
|
|
267
|
+
* { $type: 'blob', mimeType: 'image/png', ref: cid2, size: 2000 }
|
|
268
|
+
* ]
|
|
269
|
+
* }
|
|
270
|
+
*
|
|
271
|
+
* for (const blobRef of enumBlobRefs(record)) {
|
|
272
|
+
* console.log(blobRef.mimeType, blobRef.size)
|
|
273
|
+
* }
|
|
274
|
+
*
|
|
275
|
+
* // Include legacy blob references
|
|
276
|
+
* for (const ref of enumBlobRefs(record, { allowLegacy: true })) {
|
|
277
|
+
* // ref may be BlobRef or LegacyBlobRef
|
|
278
|
+
* }
|
|
279
|
+
* ```
|
|
143
280
|
*/
|
|
144
281
|
export function enumBlobRefs(
|
|
145
282
|
input: LexValue,
|
|
@@ -73,25 +73,19 @@ describe(BytesCid, () => {
|
|
|
73
73
|
*/
|
|
74
74
|
export function createCustomCid<
|
|
75
75
|
TVersion extends 0 | 1,
|
|
76
|
-
|
|
77
|
-
|
|
76
|
+
TCodec extends number,
|
|
77
|
+
THashCode extends number,
|
|
78
78
|
>(
|
|
79
79
|
version: TVersion,
|
|
80
|
-
code:
|
|
81
|
-
|
|
80
|
+
code: TCodec,
|
|
81
|
+
hashCode: THashCode,
|
|
82
82
|
digest: Uint8Array,
|
|
83
|
-
): Cid<TVersion,
|
|
83
|
+
): Cid<TVersion, TCodec, THashCode> {
|
|
84
84
|
return {
|
|
85
85
|
version,
|
|
86
86
|
code,
|
|
87
|
-
multihash: { code:
|
|
88
|
-
bytes: new Uint8Array([
|
|
89
|
-
version,
|
|
90
|
-
code,
|
|
91
|
-
multihashCode,
|
|
92
|
-
digest.length,
|
|
93
|
-
...digest,
|
|
94
|
-
]),
|
|
87
|
+
multihash: { code: hashCode, digest },
|
|
88
|
+
bytes: new Uint8Array([version, code, hashCode, digest.length, ...digest]),
|
|
95
89
|
toString,
|
|
96
90
|
equals,
|
|
97
91
|
}
|
package/src/cid.test.ts
CHANGED
|
@@ -3,10 +3,10 @@ import { sha256, sha512 } from 'multiformats/hashes/sha2'
|
|
|
3
3
|
import { describe, expect, it } from 'vitest'
|
|
4
4
|
import { BytesCid, createCustomCid } from './cid-implementation.test.js'
|
|
5
5
|
import {
|
|
6
|
+
CBOR_DATA_CODEC,
|
|
6
7
|
Cid,
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
SHA256_MULTIHASH,
|
|
8
|
+
RAW_DATA_CODEC,
|
|
9
|
+
SHA256_HASH_CODE,
|
|
10
10
|
asMultiformatsCID,
|
|
11
11
|
cidForRawHash,
|
|
12
12
|
decodeCid,
|
|
@@ -28,8 +28,8 @@ const rawCid = parseCid(rawCidStr, { flavor: 'raw' })
|
|
|
28
28
|
|
|
29
29
|
const rawCidLike: Cid = createCustomCid(
|
|
30
30
|
1,
|
|
31
|
-
|
|
32
|
-
|
|
31
|
+
RAW_DATA_CODEC,
|
|
32
|
+
SHA256_HASH_CODE,
|
|
33
33
|
rawCid.multihash.digest,
|
|
34
34
|
)
|
|
35
35
|
const rawBytesCid = new BytesCid(rawCid.bytes)
|
|
@@ -49,7 +49,7 @@ describe(isCid, () => {
|
|
|
49
49
|
it('returns true for CID v0 and v1', async () => {
|
|
50
50
|
const digest = await sha256.digest(Buffer.from('hello world'))
|
|
51
51
|
const cidV0 = CID.createV0(digest)
|
|
52
|
-
const cidV1 = CID.createV1(
|
|
52
|
+
const cidV1 = CID.createV1(RAW_DATA_CODEC, digest)
|
|
53
53
|
expect(isCid(cidV0)).toBe(true)
|
|
54
54
|
expect(isCid(cidV1)).toBe(true)
|
|
55
55
|
})
|
|
@@ -65,13 +65,13 @@ describe(isCid, () => {
|
|
|
65
65
|
describe('raw', () => {
|
|
66
66
|
it('validated "raw" cids', async () => {
|
|
67
67
|
const digest = await sha256.digest(Buffer.from('hello world'))
|
|
68
|
-
const cid = CID.createV1(
|
|
68
|
+
const cid = CID.createV1(RAW_DATA_CODEC, digest)
|
|
69
69
|
expect(isCid(cid, { flavor: 'raw' })).toBe(true)
|
|
70
70
|
})
|
|
71
71
|
|
|
72
72
|
it('allows other hash algorithms', async () => {
|
|
73
73
|
const digest = await sha512.digest(Buffer.from('hello world'))
|
|
74
|
-
const cid = CID.createV1(
|
|
74
|
+
const cid = CID.createV1(RAW_DATA_CODEC, digest)
|
|
75
75
|
expect(isCid(cid, { flavor: 'raw' })).toBe(true)
|
|
76
76
|
})
|
|
77
77
|
|
|
@@ -91,13 +91,13 @@ describe(isCid, () => {
|
|
|
91
91
|
describe('cbor', () => {
|
|
92
92
|
it('validated "cbor" cids', async () => {
|
|
93
93
|
const digest = await sha256.digest(Buffer.from('hello world'))
|
|
94
|
-
const cid = CID.createV1(
|
|
94
|
+
const cid = CID.createV1(CBOR_DATA_CODEC, digest)
|
|
95
95
|
expect(isCid(cid, { flavor: 'cbor' })).toBe(true)
|
|
96
96
|
})
|
|
97
97
|
|
|
98
98
|
it('rejects CIDs with invalid hash algorithm', async () => {
|
|
99
99
|
const digest = await sha512.digest(Buffer.from('hello world'))
|
|
100
|
-
const cid = CID.createV1(
|
|
100
|
+
const cid = CID.createV1(RAW_DATA_CODEC, digest)
|
|
101
101
|
expect(isCid(cid, { flavor: 'cbor' })).toBe(false)
|
|
102
102
|
})
|
|
103
103
|
|
|
@@ -266,7 +266,7 @@ describe(isCidForBytes, () => {
|
|
|
266
266
|
for (const hasher of [sha256, sha512]) {
|
|
267
267
|
const data = new TextEncoder().encode('hello world')
|
|
268
268
|
const digest = await hasher.digest(data)
|
|
269
|
-
const cid = CID.createV1(
|
|
269
|
+
const cid = CID.createV1(RAW_DATA_CODEC, digest)
|
|
270
270
|
expect(await isCidForBytes(cid, data)).toBe(true)
|
|
271
271
|
|
|
272
272
|
data[0] = data[0] ^ 0xff
|
|
@@ -281,7 +281,7 @@ describe(isCidForBytes, () => {
|
|
|
281
281
|
// @NOTE this is not valid CBOR, but sufficient for testing the hash
|
|
282
282
|
const data = new TextEncoder().encode('hello world')
|
|
283
283
|
const digest = await hasher.digest(data)
|
|
284
|
-
const cid = CID.createV1(
|
|
284
|
+
const cid = CID.createV1(CBOR_DATA_CODEC, digest)
|
|
285
285
|
expect(await isCidForBytes(cid, data)).toBe(true)
|
|
286
286
|
|
|
287
287
|
data[0] = data[0] ^ 0xff
|
|
@@ -318,8 +318,8 @@ describe(cidForRawHash, () => {
|
|
|
318
318
|
it('creates a RawCid from a SHA-256 hash', () => {
|
|
319
319
|
const hash = new Uint8Array(32)
|
|
320
320
|
const cid = cidForRawHash(hash)
|
|
321
|
-
expect(cid.code).toBe(
|
|
322
|
-
expect(cid.multihash.code).toBe(
|
|
321
|
+
expect(cid.code).toBe(RAW_DATA_CODEC)
|
|
322
|
+
expect(cid.multihash.code).toBe(SHA256_HASH_CODE)
|
|
323
323
|
expect(ui8Equals(cid.multihash.digest, hash)).toBe(true)
|
|
324
324
|
})
|
|
325
325
|
|