@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/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
- * @note {@link BlobRef} is just a {@link LexMap} with a specific shape.
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
- * @defaults to `true`
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
- * @note {@link LegacyBlobRef} is just a {@link LexMap} with a specific shape.
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
- * @defaults to `false`
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
- * Enumerates all {@link BlobRef}s (and, optionally, {@link LegacyBlobRef}s)
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
- TCode extends number,
77
- TMultihashCode extends number,
76
+ TCodec extends number,
77
+ THashCode extends number,
78
78
  >(
79
79
  version: TVersion,
80
- code: TCode,
81
- multihashCode: TMultihashCode,
80
+ code: TCodec,
81
+ hashCode: THashCode,
82
82
  digest: Uint8Array,
83
- ): Cid<TVersion, TCode, TMultihashCode> {
83
+ ): Cid<TVersion, TCodec, THashCode> {
84
84
  return {
85
85
  version,
86
86
  code,
87
- multihash: { code: multihashCode, digest },
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
- DAG_CBOR_MULTICODEC,
8
- RAW_MULTICODEC,
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
- RAW_MULTICODEC,
32
- SHA256_MULTIHASH,
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(RAW_MULTICODEC, digest)
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(RAW_MULTICODEC, digest)
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(RAW_MULTICODEC, digest)
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(DAG_CBOR_MULTICODEC, digest)
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(RAW_MULTICODEC, digest)
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(RAW_MULTICODEC, digest)
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(DAG_CBOR_MULTICODEC, digest)
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(RAW_MULTICODEC)
322
- expect(cid.multihash.code).toBe(SHA256_MULTIHASH)
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