Suite of AT Protocol TypeScript libraries built on web standards
21
fork

Configure Feed

Select the types of activity you want to include in your feed.

common tests

+2595 -119
+348 -26
common/ipld-multi.ts
··· 1 1 import { decodeCbor } from "@std/cbor"; 2 2 import { CID } from "multiformats/cid"; 3 3 4 + // Define the possible CBOR value types 5 + export type CborPrimitive = 6 + | string 7 + | number 8 + | bigint 9 + | boolean 10 + | null 11 + | undefined; 12 + export type CborArray = CborValue[]; 13 + export type CborObject = { [key: string]: CborValue }; 14 + export type CborValue = 15 + | CborPrimitive 16 + | CborArray 17 + | CborObject 18 + | CID 19 + | Uint8Array; 20 + 21 + // Type for CBOR tag structure 22 + interface CborTag<T = unknown> { 23 + tag: number; 24 + value: T; 25 + } 26 + 27 + // Type guard for CBOR tags 28 + function isCborTag(value: unknown): value is CborTag { 29 + return ( 30 + value !== null && 31 + typeof value === "object" && 32 + "tag" in value && 33 + "value" in value && 34 + typeof (value as CborTag).tag === "number" 35 + ); 36 + } 37 + 38 + // Type guard for CID tag specifically 39 + function isCidTag(value: unknown): value is CborTag<Uint8Array> { 40 + return isCborTag(value) && value.tag === 42 && 41 + value.value instanceof Uint8Array; 42 + } 43 + 4 44 // Custom CBOR decoder that handles CIDs and multiple values 5 45 class CborMultiDecoder { 6 46 private buffer: Uint8Array; ··· 17 57 return CID.decode(bytes.subarray(1)); // ignore leading 0x00 18 58 } 19 59 20 - private decodeValue(): unknown { 60 + private decodeValue(): CborValue { 21 61 // Find the next complete CBOR value 22 62 const remaining = this.buffer.subarray(this.position); 23 63 ··· 34 74 return this.processValue(decoded); 35 75 } 36 76 37 - private processValue(value: unknown): unknown { 77 + private processValue(value: unknown): CborValue { 38 78 // Handle CID tag 42 if present 39 - if ( 40 - value && typeof value === "object" && "tag" in value && 41 - "value" in value && 42 - (value as { tag: number }).tag === 42 43 - ) { 44 - return this.decodeCid((value as { value: Uint8Array }).value); 79 + if (isCidTag(value)) { 80 + return this.decodeCid(value.value); 45 81 } 46 82 47 - // Recursively process arrays and objects 83 + // Handle other CBOR tags (convert to regular values for now) 84 + if (isCborTag(value)) { 85 + return this.processValue(value.value); 86 + } 87 + 88 + // Recursively process arrays 48 89 if (Array.isArray(value)) { 49 - return value.map((item) => this.processValue(item)); 90 + return value.map((item): CborValue => this.processValue(item)); 50 91 } 51 92 52 - if (value && typeof value === "object") { 53 - const result: Record<string, unknown> = {}; 93 + // Recursively process objects 94 + if ( 95 + value !== null && typeof value === "object" && 96 + !(value instanceof Uint8Array) 97 + ) { 98 + const result: CborObject = {}; 54 99 for (const [key, val] of Object.entries(value)) { 55 100 result[key] = this.processValue(val); 56 101 } 57 102 return result; 58 103 } 59 104 60 - return value; 105 + // Handle primitives and Uint8Array 106 + if ( 107 + typeof value === "string" || 108 + typeof value === "number" || 109 + typeof value === "bigint" || 110 + typeof value === "boolean" || 111 + value === null || 112 + value === undefined || 113 + value instanceof Uint8Array 114 + ) { 115 + return value; 116 + } 117 + 118 + // Fallback for any other types - shouldn't happen with valid CBOR 119 + throw new Error(`Unsupported CBOR value type: ${typeof value}`); 61 120 } 62 121 63 - decodeMultiple(): unknown[] { 64 - const decoded: unknown[] = []; 122 + decodeMultiple(): CborValue[] { 123 + const decoded: CborValue[] = []; 124 + this.position = 0; 125 + 126 + // Parse CBOR values manually to handle concatenated data 127 + while (this.position < this.buffer.length) { 128 + try { 129 + const remaining = this.buffer.subarray(this.position); 130 + if (remaining.length === 0) break; 65 131 66 - // Note: This is a simplified implementation 67 - // @std/cbor doesn't have native support for decoding multiple concatenated CBOR values 68 - // A more robust implementation would need to parse the CBOR structure manually 69 - try { 70 - const value = decodeCbor(this.buffer); 71 - decoded.push(this.processValue(value)); 72 - } catch (error) { 73 - throw new Error(`Failed to decode CBOR: ${error}`); 132 + // Parse the next CBOR value and track how many bytes we consumed 133 + const startPos = this.position; 134 + const value = this.parseNextCborValue(); 135 + decoded.push(this.processValue(value)); 136 + 137 + // If position didn't advance, we're stuck - break to avoid infinite loop 138 + if (this.position === startPos) { 139 + break; 140 + } 141 + } catch (error) { 142 + throw new Error(`Failed to decode CBOR: ${error}`); 143 + } 74 144 } 75 145 76 146 return decoded; 77 147 } 148 + 149 + private parseNextCborValue(): unknown { 150 + if (this.position >= this.buffer.length) { 151 + throw new Error("Unexpected end of CBOR data"); 152 + } 153 + 154 + const _startPos = this.position; 155 + const byte = this.buffer[this.position]; 156 + const majorType = (byte >> 5) & 0x07; 157 + const additionalInfo = byte & 0x1f; 158 + 159 + this.position++; 160 + 161 + switch (majorType) { 162 + case 0: { // Unsigned integer 163 + return this.readUnsignedInt(additionalInfo); 164 + } 165 + case 1: { // Negative integer 166 + const unsignedInt = this.readUnsignedInt(additionalInfo); 167 + if (typeof unsignedInt === "bigint") { 168 + const negativeValue = -1n - unsignedInt; 169 + // Convert to number if within safe integer range 170 + if (negativeValue >= BigInt(Number.MIN_SAFE_INTEGER)) { 171 + return Number(negativeValue); 172 + } 173 + return negativeValue; 174 + } 175 + return -1 - unsignedInt; 176 + } 177 + case 2: { // Byte string 178 + return this.readByteString(additionalInfo); 179 + } 180 + case 3: { // Text string 181 + return this.readTextString(additionalInfo); 182 + } 183 + case 4: { // Array 184 + return this.readArray(additionalInfo); 185 + } 186 + case 5: { // Map 187 + return this.readMap(additionalInfo); 188 + } 189 + case 6: { // Tag 190 + return this.readTag(additionalInfo); 191 + } 192 + case 7: { // Float/Simple/Break 193 + return this.readFloatOrSimple(additionalInfo); 194 + } 195 + default: 196 + throw new Error(`Unknown CBOR major type: ${majorType}`); 197 + } 198 + } 199 + 200 + private readUnsignedInt(additionalInfo: number): number | bigint { 201 + if (additionalInfo < 24) { 202 + return additionalInfo; 203 + } else if (additionalInfo === 24) { 204 + return this.readUint8(); 205 + } else if (additionalInfo === 25) { 206 + return this.readUint16(); 207 + } else if (additionalInfo === 26) { 208 + return this.readUint32(); 209 + } else if (additionalInfo === 27) { 210 + const bigIntValue = this.readUint64(); 211 + // Convert to number if within safe integer range 212 + if (bigIntValue <= BigInt(Number.MAX_SAFE_INTEGER)) { 213 + return Number(bigIntValue); 214 + } 215 + return bigIntValue; 216 + } else { 217 + throw new Error( 218 + `Invalid additional info for unsigned int: ${additionalInfo}`, 219 + ); 220 + } 221 + } 222 + 223 + private readUint8(): number { 224 + if (this.position >= this.buffer.length) throw new Error("Unexpected end"); 225 + return this.buffer[this.position++]; 226 + } 227 + 228 + private readUint16(): number { 229 + if (this.position + 1 >= this.buffer.length) { 230 + throw new Error("Unexpected end"); 231 + } 232 + const value = (this.buffer[this.position] << 8) | 233 + this.buffer[this.position + 1]; 234 + this.position += 2; 235 + return value; 236 + } 237 + 238 + private readUint32(): number { 239 + if (this.position + 3 >= this.buffer.length) { 240 + throw new Error("Unexpected end"); 241 + } 242 + const value = (this.buffer[this.position] << 24) | 243 + (this.buffer[this.position + 1] << 16) | 244 + (this.buffer[this.position + 2] << 8) | 245 + this.buffer[this.position + 3]; 246 + this.position += 4; 247 + return value >>> 0; // Convert to unsigned 248 + } 249 + 250 + private readUint64(): bigint { 251 + if (this.position + 7 >= this.buffer.length) { 252 + throw new Error("Unexpected end"); 253 + } 254 + let value = 0n; 255 + for (let i = 0; i < 8; i++) { 256 + value = (value << 8n) | BigInt(this.buffer[this.position + i]); 257 + } 258 + this.position += 8; 259 + return value; 260 + } 261 + 262 + private readByteString(additionalInfo: number): Uint8Array { 263 + const length = this.readUnsignedInt(additionalInfo); 264 + if (typeof length === "bigint") { 265 + throw new Error("Byte string too large"); 266 + } 267 + const lengthNum = Number(length); 268 + if (this.position + lengthNum > this.buffer.length) { 269 + throw new Error("Unexpected end of byte string"); 270 + } 271 + const result = this.buffer.subarray( 272 + this.position, 273 + this.position + lengthNum, 274 + ); 275 + this.position += lengthNum; 276 + return result; 277 + } 278 + 279 + private readTextString(additionalInfo: number): string { 280 + const bytes = this.readByteString(additionalInfo); 281 + return new TextDecoder().decode(bytes); 282 + } 283 + 284 + private readArray(additionalInfo: number): unknown[] { 285 + const length = this.readUnsignedInt(additionalInfo); 286 + if (typeof length === "bigint") { 287 + throw new Error("Array too large"); 288 + } 289 + const lengthNum = Number(length); 290 + const result: unknown[] = []; 291 + for (let i = 0; i < lengthNum; i++) { 292 + result.push(this.parseNextCborValue()); 293 + } 294 + return result; 295 + } 296 + 297 + private readMap(additionalInfo: number): Record<string, unknown> { 298 + const length = this.readUnsignedInt(additionalInfo); 299 + if (typeof length === "bigint") { 300 + throw new Error("Map too large"); 301 + } 302 + const lengthNum = Number(length); 303 + const result: Record<string, unknown> = {}; 304 + for (let i = 0; i < lengthNum; i++) { 305 + const key = this.parseNextCborValue(); 306 + const value = this.parseNextCborValue(); 307 + if (typeof key !== "string") { 308 + throw new Error(`Map key must be string, got ${typeof key}`); 309 + } 310 + result[key] = value; 311 + } 312 + return result; 313 + } 314 + 315 + private readTag(additionalInfo: number): { tag: number; value: unknown } { 316 + const tag = this.readUnsignedInt(additionalInfo); 317 + const value = this.parseNextCborValue(); 318 + return { 319 + tag: typeof tag === "bigint" ? Number(tag) : tag, 320 + value, 321 + }; 322 + } 323 + 324 + private readFloatOrSimple(additionalInfo: number): unknown { 325 + if (additionalInfo < 20) { 326 + // Simple values 0-19 are unassigned 327 + throw new Error(`Unassigned simple value: ${additionalInfo}`); 328 + } else if (additionalInfo === 20) { 329 + return false; 330 + } else if (additionalInfo === 21) { 331 + return true; 332 + } else if (additionalInfo === 22) { 333 + return null; 334 + } else if (additionalInfo === 23) { 335 + return undefined; 336 + } else if (additionalInfo === 25) { 337 + // Half-precision float (16-bit) 338 + const value = this.readUint16(); 339 + return this.decodeFloat16(value); 340 + } else if (additionalInfo === 26) { 341 + // Single-precision float (32-bit) 342 + const bytes = new Uint8Array(4); 343 + for (let i = 0; i < 4; i++) { 344 + bytes[i] = this.buffer[this.position + i]; 345 + } 346 + this.position += 4; 347 + return new DataView(bytes.buffer).getFloat32(0, false); 348 + } else if (additionalInfo === 27) { 349 + // Double-precision float (64-bit) 350 + const bytes = new Uint8Array(8); 351 + for (let i = 0; i < 8; i++) { 352 + bytes[i] = this.buffer[this.position + i]; 353 + } 354 + this.position += 8; 355 + return new DataView(bytes.buffer).getFloat64(0, false); 356 + } else { 357 + throw new Error( 358 + `Invalid additional info for float/simple: ${additionalInfo}`, 359 + ); 360 + } 361 + } 362 + 363 + private decodeFloat16(value: number): number { 364 + const sign = (value & 0x8000) ? -1 : 1; 365 + const exponent = (value & 0x7c00) >> 10; 366 + const fraction = value & 0x03ff; 367 + 368 + if (exponent === 0) { 369 + return sign * Math.pow(2, -14) * (fraction / Math.pow(2, 10)); 370 + } else if (exponent === 0x1f) { 371 + return fraction ? NaN : sign * Infinity; 372 + } else { 373 + return sign * Math.pow(2, exponent - 15) * 374 + (1 + fraction / Math.pow(2, 10)); 375 + } 376 + } 78 377 } 79 378 80 - export const cborDecodeMulti = (encoded: Uint8Array): unknown[] => { 379 + // Generic version that allows callers to specify expected return type 380 + export function cborDecodeMulti<T extends CborValue = CborValue>( 381 + encoded: Uint8Array, 382 + ): T[] { 81 383 const decoder = new CborMultiDecoder(encoded); 82 - return decoder.decodeMultiple(); 83 - }; 384 + return decoder.decodeMultiple() as T[]; 385 + } 386 + 387 + // Convenience function for decoding a single value 388 + export function cborDecodeSingle<T extends CborValue = CborValue>( 389 + encoded: Uint8Array, 390 + ): T { 391 + const results = cborDecodeMulti<T>(encoded); 392 + if (results.length !== 1) { 393 + throw new Error(`Expected single value, got ${results.length} values`); 394 + } 395 + return results[0]; 396 + } 397 + 398 + // Type-specific decoders for common use cases 399 + export function cborDecodeMultiAsObjects(encoded: Uint8Array): CborObject[] { 400 + return cborDecodeMulti<CborObject>(encoded); 401 + } 402 + 403 + export function cborDecodeMultiAsArrays(encoded: Uint8Array): CborArray[] { 404 + return cborDecodeMulti<CborArray>(encoded); 405 + }
+1 -1
common/ipld.ts
··· 184 184 // convert bytes 185 185 if (val instanceof Uint8Array) { 186 186 return { 187 - $bytes: btoa(String.fromCharCode(...val)), 187 + $bytes: btoa(String.fromCharCode(...val)).replace(/=+$/, ""), 188 188 }; 189 189 } 190 190 // convert cids
+2
common/mod.ts
··· 1 1 export * as check from "./check.ts"; 2 + export * as util from "./util.ts"; 2 3 3 4 export * from "./env.ts"; 4 5 export * from "./fs.ts"; ··· 13 14 export * from "./logger.ts"; 14 15 export * from "./dates.ts"; 15 16 export * from "./util.ts"; 17 + export * from "./retry.ts";
+59
common/retry.ts
··· 1 + import { wait } from "./util.ts"; 2 + 3 + export type RetryOptions = { 4 + maxRetries?: number; 5 + getWaitMs?: (n: number) => number | null; 6 + }; 7 + 8 + export async function retry<T>( 9 + fn: () => Promise<T> | T, 10 + opts: RetryOptions & { 11 + retryable?: (err: unknown) => boolean; 12 + } = {}, 13 + ): Promise<T> { 14 + const { maxRetries = 3, retryable = () => true, getWaitMs = backoffMs } = 15 + opts; 16 + let retries = 0; 17 + let doneError: unknown; 18 + while (!doneError) { 19 + try { 20 + return await fn(); 21 + } catch (err) { 22 + const waitMs = getWaitMs(retries); 23 + const willRetry = retries < maxRetries && waitMs !== null && 24 + retryable(err); 25 + if (willRetry) { 26 + retries += 1; 27 + if (waitMs !== 0) { 28 + await wait(waitMs); 29 + } 30 + } else { 31 + doneError = err; 32 + } 33 + } 34 + } 35 + throw doneError; 36 + } 37 + 38 + export function createRetryable(retryable: (err: unknown) => boolean) { 39 + return <T>(fn: () => Promise<T>, opts?: RetryOptions) => 40 + retry(fn, { ...opts, retryable }); 41 + } 42 + 43 + // Waits exponential backoff with max and jitter: ~100, ~200, ~400, ~800, ~1000, ~1000, ... 44 + export function backoffMs(n: number, multiplier = 100, max = 1000) { 45 + const exponentialMs = Math.pow(2, n) * multiplier; 46 + const ms = Math.min(exponentialMs, max); 47 + return jitter(ms); 48 + } 49 + 50 + // Adds randomness +/-15% of value 51 + function jitter(value: number) { 52 + const delta = value * 0.15; 53 + return value + randomRange(-delta, delta); 54 + } 55 + 56 + function randomRange(from: number, to: number) { 57 + const rand = Math.random() * (to - from); 58 + return rand + from; 59 + }
+2 -2
common/streams.ts
··· 2 2 import { Buffer } from "jsr:@std/io"; 3 3 4 4 export const forwardStreamErrors = (..._streams: ReadableStream[]) => { 5 - // Web Streams don't have the same error forwarding mechanism as Node streams 5 + // Web Streams don't have the same error forwarding mechanism as streams 6 6 // This is a no-op in the Web Streams world since error handling is done differently 7 7 }; 8 8 ··· 60 60 }; 61 61 62 62 // streamToBuffer identifier name already taken by @atproto/common-web 63 - export const streamToNodeBuffer = async ( 63 + export const streamToBuffer = async ( 64 64 stream: 65 65 | Iterable<Uint8Array> 66 66 | AsyncIterable<Uint8Array>
+14 -10
common/strings.ts
··· 47 47 const parsed = langTag.match(bcp47Regexp); 48 48 if (!parsed?.groups) return null; 49 49 const parts = parsed.groups; 50 - return { 51 - grandfathered: parts.grandfathered, 52 - language: parts.language, 53 - extlang: parts.extlang, 54 - script: parts.script, 55 - region: parts.region, 56 - variant: parts.variant, 57 - extension: parts.extension, 58 - privateUse: parts.privateUseA || parts.privateUseB, 59 - }; 50 + const result: LanguageTag = {}; 51 + 52 + if (parts.grandfathered) result.grandfathered = parts.grandfathered; 53 + if (parts.language) result.language = parts.language; 54 + if (parts.extlang) result.extlang = parts.extlang; 55 + if (parts.script) result.script = parts.script; 56 + if (parts.region) result.region = parts.region; 57 + if (parts.variant) result.variant = parts.variant; 58 + if (parts.extension) result.extension = parts.extension; 59 + if (parts.privateUseA || parts.privateUseB) { 60 + result.privateUse = parts.privateUseA || parts.privateUseB; 61 + } 62 + 63 + return result; 60 64 }; 61 65 62 66 export const validateLanguage = (langTag: string): boolean => {
+82
common/tests/check_test.ts
··· 1 + import { ZodError } from "zod"; 2 + import { check } from "../mod.ts"; 3 + import { assertEquals, assertThrows } from "jsr:@std/assert"; 4 + 5 + Deno.test("checks object against definition", () => { 6 + const checkable: check.Checkable<boolean> = { 7 + parse(obj) { 8 + return Boolean(obj); 9 + }, 10 + safeParse(obj) { 11 + return { 12 + success: true, 13 + data: Boolean(obj), 14 + }; 15 + }, 16 + }; 17 + 18 + assertEquals(check.is(true, checkable), true); 19 + }); 20 + 21 + Deno.test("handles failed checks", () => { 22 + const checkable: check.Checkable<boolean> = { 23 + parse(obj) { 24 + return Boolean(obj); 25 + }, 26 + safeParse() { 27 + return { 28 + success: false, 29 + error: new ZodError([]), 30 + }; 31 + }, 32 + }; 33 + 34 + assertEquals(check.is(true, checkable), false); 35 + }); 36 + 37 + Deno.test("returns value on success", () => { 38 + const checkable: check.Checkable<boolean> = { 39 + parse(obj) { 40 + return Boolean(obj); 41 + }, 42 + safeParse(obj) { 43 + return { 44 + success: true, 45 + data: Boolean(obj), 46 + }; 47 + }, 48 + }; 49 + 50 + assertEquals(check.assure(checkable, true), true); 51 + }); 52 + 53 + Deno.test("throws on failure", () => { 54 + const err = new Error("foo"); 55 + const checkable: check.Checkable<boolean> = { 56 + parse() { 57 + throw err; 58 + }, 59 + safeParse() { 60 + throw err; 61 + }, 62 + }; 63 + 64 + assertThrows(() => check.assure(checkable, true), err.message); 65 + }); 66 + 67 + const falseTestValues: unknown[] = [null, undefined, "foo", 123, true]; 68 + 69 + for (const obj of falseTestValues) { 70 + Deno.test(`isObject returns false for ${obj}`, () => { 71 + assertEquals(check.isObject(obj), false); 72 + }); 73 + } 74 + 75 + Deno.test("isObject returns true for objects", () => { 76 + assertEquals(check.isObject({}), true); 77 + }); 78 + 79 + Deno.test("isObject returns true for instances of classes", () => { 80 + const obj = new (class {})(); 81 + assertEquals(check.isObject(obj), true); 82 + });
+1349
common/tests/interop/ipld-vectors.ts
··· 1 + import { CID } from "multiformats/cid"; 2 + 3 + export const vectors = [ 4 + { 5 + name: "basic", 6 + json: { 7 + string: "abc", 8 + unicode: "a~öñ©⽘☎𓋓😀👨‍👩‍👧‍👧", 9 + integer: 123, 10 + bool: true, 11 + null: null, 12 + array: ["abc", "def", "ghi"], 13 + object: { 14 + string: "abc", 15 + number: 123, 16 + bool: true, 17 + arr: ["abc", "def", "ghi"], 18 + }, 19 + }, 20 + ipld: { 21 + string: "abc", 22 + unicode: "a~öñ©⽘☎𓋓😀👨‍👩‍👧‍👧", 23 + integer: 123, 24 + bool: true, 25 + null: null, 26 + array: ["abc", "def", "ghi"], 27 + object: { 28 + string: "abc", 29 + number: 123, 30 + bool: true, 31 + arr: ["abc", "def", "ghi"], 32 + }, 33 + }, 34 + cbor: new Uint8Array([ 35 + 167, 36 + 100, 37 + 98, 38 + 111, 39 + 111, 40 + 108, 41 + 245, 42 + 100, 43 + 110, 44 + 117, 45 + 108, 46 + 108, 47 + 246, 48 + 101, 49 + 97, 50 + 114, 51 + 114, 52 + 97, 53 + 121, 54 + 131, 55 + 99, 56 + 97, 57 + 98, 58 + 99, 59 + 99, 60 + 100, 61 + 101, 62 + 102, 63 + 99, 64 + 103, 65 + 104, 66 + 105, 67 + 102, 68 + 111, 69 + 98, 70 + 106, 71 + 101, 72 + 99, 73 + 116, 74 + 164, 75 + 99, 76 + 97, 77 + 114, 78 + 114, 79 + 131, 80 + 99, 81 + 97, 82 + 98, 83 + 99, 84 + 99, 85 + 100, 86 + 101, 87 + 102, 88 + 99, 89 + 103, 90 + 104, 91 + 105, 92 + 100, 93 + 98, 94 + 111, 95 + 111, 96 + 108, 97 + 245, 98 + 102, 99 + 110, 100 + 117, 101 + 109, 102 + 98, 103 + 101, 104 + 114, 105 + 24, 106 + 123, 107 + 102, 108 + 115, 109 + 116, 110 + 114, 111 + 105, 112 + 110, 113 + 103, 114 + 99, 115 + 97, 116 + 98, 117 + 99, 118 + 102, 119 + 115, 120 + 116, 121 + 114, 122 + 105, 123 + 110, 124 + 103, 125 + 99, 126 + 97, 127 + 98, 128 + 99, 129 + 103, 130 + 105, 131 + 110, 132 + 116, 133 + 101, 134 + 103, 135 + 101, 136 + 114, 137 + 24, 138 + 123, 139 + 103, 140 + 117, 141 + 110, 142 + 105, 143 + 99, 144 + 111, 145 + 100, 146 + 101, 147 + 120, 148 + 47, 149 + 97, 150 + 126, 151 + 195, 152 + 182, 153 + 195, 154 + 177, 155 + 194, 156 + 169, 157 + 226, 158 + 189, 159 + 152, 160 + 226, 161 + 152, 162 + 142, 163 + 240, 164 + 147, 165 + 139, 166 + 147, 167 + 240, 168 + 159, 169 + 152, 170 + 128, 171 + 240, 172 + 159, 173 + 145, 174 + 168, 175 + 226, 176 + 128, 177 + 141, 178 + 240, 179 + 159, 180 + 145, 181 + 169, 182 + 226, 183 + 128, 184 + 141, 185 + 240, 186 + 159, 187 + 145, 188 + 167, 189 + 226, 190 + 128, 191 + 141, 192 + 240, 193 + 159, 194 + 145, 195 + 167, 196 + ]), 197 + cid: "bafyreiclp443lavogvhj3d2ob2cxbfuscni2k5jk7bebjzg7khl3esabwq", 198 + }, 199 + { 200 + name: "ipld", 201 + json: { 202 + a: { 203 + $link: "bafyreidfayvfuwqa7qlnopdjiqrxzs6blmoeu4rujcjtnci5beludirz2a", 204 + }, 205 + b: { 206 + $bytes: "nFERjvLLiw9qm45JrqH9QTzyC2Lu1Xb4ne6+sBrCzI0", 207 + }, 208 + c: { 209 + $type: "blob", 210 + ref: { 211 + $link: "bafkreiccldh766hwcnuxnf2wh6jgzepf2nlu2lvcllt63eww5p6chi4ity", 212 + }, 213 + mimeType: "image/jpeg", 214 + size: 10000, 215 + }, 216 + }, 217 + ipld: { 218 + a: CID.parse( 219 + "bafyreidfayvfuwqa7qlnopdjiqrxzs6blmoeu4rujcjtnci5beludirz2a", 220 + ), 221 + b: new Uint8Array([ 222 + 156, 223 + 81, 224 + 17, 225 + 142, 226 + 242, 227 + 203, 228 + 139, 229 + 15, 230 + 106, 231 + 155, 232 + 142, 233 + 73, 234 + 174, 235 + 161, 236 + 253, 237 + 65, 238 + 60, 239 + 242, 240 + 11, 241 + 98, 242 + 238, 243 + 213, 244 + 118, 245 + 248, 246 + 157, 247 + 238, 248 + 190, 249 + 176, 250 + 26, 251 + 194, 252 + 204, 253 + 141, 254 + ]), 255 + c: { 256 + $type: "blob", 257 + ref: CID.parse( 258 + "bafkreiccldh766hwcnuxnf2wh6jgzepf2nlu2lvcllt63eww5p6chi4ity", 259 + ), 260 + mimeType: "image/jpeg", 261 + size: 10000, 262 + }, 263 + }, 264 + cbor: new Uint8Array([ 265 + 163, 266 + 97, 267 + 97, 268 + 216, 269 + 42, 270 + 88, 271 + 37, 272 + 0, 273 + 1, 274 + 113, 275 + 18, 276 + 32, 277 + 101, 278 + 6, 279 + 42, 280 + 90, 281 + 90, 282 + 0, 283 + 252, 284 + 22, 285 + 215, 286 + 60, 287 + 105, 288 + 68, 289 + 35, 290 + 124, 291 + 203, 292 + 193, 293 + 91, 294 + 28, 295 + 74, 296 + 114, 297 + 52, 298 + 72, 299 + 147, 300 + 54, 301 + 137, 302 + 29, 303 + 9, 304 + 23, 305 + 65, 306 + 162, 307 + 57, 308 + 208, 309 + 97, 310 + 98, 311 + 88, 312 + 32, 313 + 156, 314 + 81, 315 + 17, 316 + 142, 317 + 242, 318 + 203, 319 + 139, 320 + 15, 321 + 106, 322 + 155, 323 + 142, 324 + 73, 325 + 174, 326 + 161, 327 + 253, 328 + 65, 329 + 60, 330 + 242, 331 + 11, 332 + 98, 333 + 238, 334 + 213, 335 + 118, 336 + 248, 337 + 157, 338 + 238, 339 + 190, 340 + 176, 341 + 26, 342 + 194, 343 + 204, 344 + 141, 345 + 97, 346 + 99, 347 + 164, 348 + 99, 349 + 114, 350 + 101, 351 + 102, 352 + 216, 353 + 42, 354 + 88, 355 + 37, 356 + 0, 357 + 1, 358 + 85, 359 + 18, 360 + 32, 361 + 66, 362 + 88, 363 + 207, 364 + 255, 365 + 120, 366 + 246, 367 + 19, 368 + 105, 369 + 118, 370 + 151, 371 + 86, 372 + 63, 373 + 146, 374 + 108, 375 + 145, 376 + 229, 377 + 211, 378 + 87, 379 + 77, 380 + 46, 381 + 162, 382 + 90, 383 + 231, 384 + 237, 385 + 146, 386 + 214, 387 + 235, 388 + 252, 389 + 35, 390 + 163, 391 + 136, 392 + 158, 393 + 100, 394 + 115, 395 + 105, 396 + 122, 397 + 101, 398 + 25, 399 + 39, 400 + 16, 401 + 101, 402 + 36, 403 + 116, 404 + 121, 405 + 112, 406 + 101, 407 + 100, 408 + 98, 409 + 108, 410 + 111, 411 + 98, 412 + 104, 413 + 109, 414 + 105, 415 + 109, 416 + 101, 417 + 84, 418 + 121, 419 + 112, 420 + 101, 421 + 106, 422 + 105, 423 + 109, 424 + 97, 425 + 103, 426 + 101, 427 + 47, 428 + 106, 429 + 112, 430 + 101, 431 + 103, 432 + ]), 433 + cid: "bafyreihldkhcwijkde7gx4rpkkuw7pl6lbyu5gieunyc7ihactn5bkd2nm", 434 + }, 435 + { 436 + name: "ipldArray", 437 + json: [ 438 + { 439 + $link: "bafyreidfayvfuwqa7qlnopdjiqrxzs6blmoeu4rujcjtnci5beludirz2a", 440 + }, 441 + { 442 + $link: "bafyreigoxt64qghytzkr6ik7qvtzc7lyytiq5xbbrokbxjows2wp7vmo6q", 443 + }, 444 + { 445 + $link: "bafyreiaizynclnqiolq7byfpjjtgqzn4sfrsgn7z2hhf6bo4utdwkin7ke", 446 + }, 447 + { 448 + $link: "bafyreifd4w4tcr5tluxz7osjtnofffvtsmgdqcfrfi6evjde4pl27lrjpy", 449 + }, 450 + ], 451 + ipld: [ 452 + CID.parse("bafyreidfayvfuwqa7qlnopdjiqrxzs6blmoeu4rujcjtnci5beludirz2a"), 453 + CID.parse("bafyreigoxt64qghytzkr6ik7qvtzc7lyytiq5xbbrokbxjows2wp7vmo6q"), 454 + CID.parse("bafyreiaizynclnqiolq7byfpjjtgqzn4sfrsgn7z2hhf6bo4utdwkin7ke"), 455 + CID.parse("bafyreifd4w4tcr5tluxz7osjtnofffvtsmgdqcfrfi6evjde4pl27lrjpy"), 456 + ], 457 + cbor: new Uint8Array([ 458 + 132, 459 + 216, 460 + 42, 461 + 88, 462 + 37, 463 + 0, 464 + 1, 465 + 113, 466 + 18, 467 + 32, 468 + 101, 469 + 6, 470 + 42, 471 + 90, 472 + 90, 473 + 0, 474 + 252, 475 + 22, 476 + 215, 477 + 60, 478 + 105, 479 + 68, 480 + 35, 481 + 124, 482 + 203, 483 + 193, 484 + 91, 485 + 28, 486 + 74, 487 + 114, 488 + 52, 489 + 72, 490 + 147, 491 + 54, 492 + 137, 493 + 29, 494 + 9, 495 + 23, 496 + 65, 497 + 162, 498 + 57, 499 + 208, 500 + 216, 501 + 42, 502 + 88, 503 + 37, 504 + 0, 505 + 1, 506 + 113, 507 + 18, 508 + 32, 509 + 206, 510 + 188, 511 + 253, 512 + 200, 513 + 24, 514 + 248, 515 + 158, 516 + 85, 517 + 31, 518 + 33, 519 + 95, 520 + 133, 521 + 103, 522 + 145, 523 + 125, 524 + 120, 525 + 196, 526 + 209, 527 + 14, 528 + 220, 529 + 33, 530 + 139, 531 + 148, 532 + 27, 533 + 165, 534 + 214, 535 + 150, 536 + 172, 537 + 255, 538 + 213, 539 + 142, 540 + 244, 541 + 216, 542 + 42, 543 + 88, 544 + 37, 545 + 0, 546 + 1, 547 + 113, 548 + 18, 549 + 32, 550 + 8, 551 + 206, 552 + 26, 553 + 37, 554 + 182, 555 + 8, 556 + 114, 557 + 225, 558 + 240, 559 + 224, 560 + 175, 561 + 74, 562 + 102, 563 + 104, 564 + 101, 565 + 188, 566 + 145, 567 + 99, 568 + 35, 569 + 55, 570 + 249, 571 + 209, 572 + 206, 573 + 95, 574 + 5, 575 + 220, 576 + 164, 577 + 199, 578 + 101, 579 + 33, 580 + 191, 581 + 81, 582 + 216, 583 + 42, 584 + 88, 585 + 37, 586 + 0, 587 + 1, 588 + 113, 589 + 18, 590 + 32, 591 + 163, 592 + 229, 593 + 185, 594 + 49, 595 + 71, 596 + 179, 597 + 93, 598 + 47, 599 + 159, 600 + 186, 601 + 73, 602 + 155, 603 + 92, 604 + 82, 605 + 150, 606 + 179, 607 + 147, 608 + 12, 609 + 56, 610 + 8, 611 + 177, 612 + 42, 613 + 60, 614 + 74, 615 + 164, 616 + 100, 617 + 227, 618 + 215, 619 + 175, 620 + 174, 621 + 41, 622 + 126, 623 + ]), 624 + cid: "bafyreiaj3udmqlqrcbjxjayzuxwp64gt64olcbjfrkldzoqponpru6gq4m", 625 + }, 626 + { 627 + name: "ipldNested", 628 + json: { 629 + a: { 630 + b: [ 631 + { 632 + d: [ 633 + { 634 + $link: 635 + "bafyreidfayvfuwqa7qlnopdjiqrxzs6blmoeu4rujcjtnci5beludirz2a", 636 + }, 637 + { 638 + $link: 639 + "bafyreidfayvfuwqa7qlnopdjiqrxzs6blmoeu4rujcjtnci5beludirz2a", 640 + }, 641 + ], 642 + e: [ 643 + { 644 + $bytes: "nFERjvLLiw9qm45JrqH9QTzyC2Lu1Xb4ne6+sBrCzI0", 645 + }, 646 + { 647 + $bytes: "iE+sPoHobU9tSIqGI+309LLCcWQIRmEXwxcoDt19tas", 648 + }, 649 + ], 650 + }, 651 + ], 652 + }, 653 + }, 654 + ipld: { 655 + a: { 656 + b: [ 657 + { 658 + d: [ 659 + CID.parse( 660 + "bafyreidfayvfuwqa7qlnopdjiqrxzs6blmoeu4rujcjtnci5beludirz2a", 661 + ), 662 + CID.parse( 663 + "bafyreidfayvfuwqa7qlnopdjiqrxzs6blmoeu4rujcjtnci5beludirz2a", 664 + ), 665 + ], 666 + e: [ 667 + new Uint8Array([ 668 + 156, 669 + 81, 670 + 17, 671 + 142, 672 + 242, 673 + 203, 674 + 139, 675 + 15, 676 + 106, 677 + 155, 678 + 142, 679 + 73, 680 + 174, 681 + 161, 682 + 253, 683 + 65, 684 + 60, 685 + 242, 686 + 11, 687 + 98, 688 + 238, 689 + 213, 690 + 118, 691 + 248, 692 + 157, 693 + 238, 694 + 190, 695 + 176, 696 + 26, 697 + 194, 698 + 204, 699 + 141, 700 + ]), 701 + new Uint8Array([ 702 + 136, 703 + 79, 704 + 172, 705 + 62, 706 + 129, 707 + 232, 708 + 109, 709 + 79, 710 + 109, 711 + 72, 712 + 138, 713 + 134, 714 + 35, 715 + 237, 716 + 244, 717 + 244, 718 + 178, 719 + 194, 720 + 113, 721 + 100, 722 + 8, 723 + 70, 724 + 97, 725 + 23, 726 + 195, 727 + 23, 728 + 40, 729 + 14, 730 + 221, 731 + 125, 732 + 181, 733 + 171, 734 + ]), 735 + ], 736 + }, 737 + ], 738 + }, 739 + }, 740 + cbor: new Uint8Array([ 741 + 161, 742 + 97, 743 + 97, 744 + 161, 745 + 97, 746 + 98, 747 + 129, 748 + 162, 749 + 97, 750 + 100, 751 + 130, 752 + 216, 753 + 42, 754 + 88, 755 + 37, 756 + 0, 757 + 1, 758 + 113, 759 + 18, 760 + 32, 761 + 101, 762 + 6, 763 + 42, 764 + 90, 765 + 90, 766 + 0, 767 + 252, 768 + 22, 769 + 215, 770 + 60, 771 + 105, 772 + 68, 773 + 35, 774 + 124, 775 + 203, 776 + 193, 777 + 91, 778 + 28, 779 + 74, 780 + 114, 781 + 52, 782 + 72, 783 + 147, 784 + 54, 785 + 137, 786 + 29, 787 + 9, 788 + 23, 789 + 65, 790 + 162, 791 + 57, 792 + 208, 793 + 216, 794 + 42, 795 + 88, 796 + 37, 797 + 0, 798 + 1, 799 + 113, 800 + 18, 801 + 32, 802 + 101, 803 + 6, 804 + 42, 805 + 90, 806 + 90, 807 + 0, 808 + 252, 809 + 22, 810 + 215, 811 + 60, 812 + 105, 813 + 68, 814 + 35, 815 + 124, 816 + 203, 817 + 193, 818 + 91, 819 + 28, 820 + 74, 821 + 114, 822 + 52, 823 + 72, 824 + 147, 825 + 54, 826 + 137, 827 + 29, 828 + 9, 829 + 23, 830 + 65, 831 + 162, 832 + 57, 833 + 208, 834 + 97, 835 + 101, 836 + 130, 837 + 88, 838 + 32, 839 + 156, 840 + 81, 841 + 17, 842 + 142, 843 + 242, 844 + 203, 845 + 139, 846 + 15, 847 + 106, 848 + 155, 849 + 142, 850 + 73, 851 + 174, 852 + 161, 853 + 253, 854 + 65, 855 + 60, 856 + 242, 857 + 11, 858 + 98, 859 + 238, 860 + 213, 861 + 118, 862 + 248, 863 + 157, 864 + 238, 865 + 190, 866 + 176, 867 + 26, 868 + 194, 869 + 204, 870 + 141, 871 + 88, 872 + 32, 873 + 136, 874 + 79, 875 + 172, 876 + 62, 877 + 129, 878 + 232, 879 + 109, 880 + 79, 881 + 109, 882 + 72, 883 + 138, 884 + 134, 885 + 35, 886 + 237, 887 + 244, 888 + 244, 889 + 178, 890 + 194, 891 + 113, 892 + 100, 893 + 8, 894 + 70, 895 + 97, 896 + 23, 897 + 195, 898 + 23, 899 + 40, 900 + 14, 901 + 221, 902 + 125, 903 + 181, 904 + 171, 905 + ]), 906 + cid: "bafyreid3imdulnhgeytpf6uk7zahjvrsqlofkmm5b5ub2maw4kqus6jp4i", 907 + }, 908 + { 909 + name: "poorlyFormatted", 910 + json: { 911 + a: "bafyreidfayvfuwqa7qlnopdjiqrxzs6blmoeu4rujcjtnci5beludirz2a", 912 + b: "nFERjvLLiw9qm45JrqH9QTzyC2Lu1Xb4ne6+sBrCzI0", 913 + c: { 914 + $link: "bafyreigoxt64qghytzkr6ik7qvtzc7lyytiq5xbbrokbxjows2wp7vmo6q", 915 + another: "bad value", 916 + }, 917 + d: { 918 + $bytes: "nFERjvLLiw9qm45JrqH9QTzyC2Lu1Xb4ne6+sBrCzI0", 919 + another: "bad value", 920 + }, 921 + e: { 922 + "/": "bafyreigoxt64qghytzkr6ik7qvtzc7lyytiq5xbbrokbxjows2wp7vmo6q", 923 + }, 924 + f: { 925 + "/": { 926 + bytes: "nFERjvLLiw9qm45JrqH9QTzyC2Lu1Xb4ne6+sBrCzI0", 927 + }, 928 + }, 929 + }, 930 + ipld: { 931 + a: "bafyreidfayvfuwqa7qlnopdjiqrxzs6blmoeu4rujcjtnci5beludirz2a", 932 + b: "nFERjvLLiw9qm45JrqH9QTzyC2Lu1Xb4ne6+sBrCzI0", 933 + c: { 934 + $link: "bafyreigoxt64qghytzkr6ik7qvtzc7lyytiq5xbbrokbxjows2wp7vmo6q", 935 + another: "bad value", 936 + }, 937 + d: { 938 + $bytes: "nFERjvLLiw9qm45JrqH9QTzyC2Lu1Xb4ne6+sBrCzI0", 939 + another: "bad value", 940 + }, 941 + e: { 942 + "/": "bafyreigoxt64qghytzkr6ik7qvtzc7lyytiq5xbbrokbxjows2wp7vmo6q", 943 + }, 944 + f: { 945 + "/": { 946 + bytes: "nFERjvLLiw9qm45JrqH9QTzyC2Lu1Xb4ne6+sBrCzI0", 947 + }, 948 + }, 949 + }, 950 + cbor: new Uint8Array([ 951 + 166, 952 + 97, 953 + 97, 954 + 120, 955 + 59, 956 + 98, 957 + 97, 958 + 102, 959 + 121, 960 + 114, 961 + 101, 962 + 105, 963 + 100, 964 + 102, 965 + 97, 966 + 121, 967 + 118, 968 + 102, 969 + 117, 970 + 119, 971 + 113, 972 + 97, 973 + 55, 974 + 113, 975 + 108, 976 + 110, 977 + 111, 978 + 112, 979 + 100, 980 + 106, 981 + 105, 982 + 113, 983 + 114, 984 + 120, 985 + 122, 986 + 115, 987 + 54, 988 + 98, 989 + 108, 990 + 109, 991 + 111, 992 + 101, 993 + 117, 994 + 52, 995 + 114, 996 + 117, 997 + 106, 998 + 99, 999 + 106, 1000 + 116, 1001 + 110, 1002 + 99, 1003 + 105, 1004 + 53, 1005 + 98, 1006 + 101, 1007 + 108, 1008 + 117, 1009 + 100, 1010 + 105, 1011 + 114, 1012 + 122, 1013 + 50, 1014 + 97, 1015 + 97, 1016 + 98, 1017 + 120, 1018 + 43, 1019 + 110, 1020 + 70, 1021 + 69, 1022 + 82, 1023 + 106, 1024 + 118, 1025 + 76, 1026 + 76, 1027 + 105, 1028 + 119, 1029 + 57, 1030 + 113, 1031 + 109, 1032 + 52, 1033 + 53, 1034 + 74, 1035 + 114, 1036 + 113, 1037 + 72, 1038 + 57, 1039 + 81, 1040 + 84, 1041 + 122, 1042 + 121, 1043 + 67, 1044 + 50, 1045 + 76, 1046 + 117, 1047 + 49, 1048 + 88, 1049 + 98, 1050 + 52, 1051 + 110, 1052 + 101, 1053 + 54, 1054 + 43, 1055 + 115, 1056 + 66, 1057 + 114, 1058 + 67, 1059 + 122, 1060 + 73, 1061 + 48, 1062 + 97, 1063 + 99, 1064 + 162, 1065 + 101, 1066 + 36, 1067 + 108, 1068 + 105, 1069 + 110, 1070 + 107, 1071 + 120, 1072 + 59, 1073 + 98, 1074 + 97, 1075 + 102, 1076 + 121, 1077 + 114, 1078 + 101, 1079 + 105, 1080 + 103, 1081 + 111, 1082 + 120, 1083 + 116, 1084 + 54, 1085 + 52, 1086 + 113, 1087 + 103, 1088 + 104, 1089 + 121, 1090 + 116, 1091 + 122, 1092 + 107, 1093 + 114, 1094 + 54, 1095 + 105, 1096 + 107, 1097 + 55, 1098 + 113, 1099 + 118, 1100 + 116, 1101 + 122, 1102 + 99, 1103 + 55, 1104 + 108, 1105 + 121, 1106 + 121, 1107 + 116, 1108 + 105, 1109 + 113, 1110 + 53, 1111 + 120, 1112 + 98, 1113 + 98, 1114 + 114, 1115 + 111, 1116 + 107, 1117 + 98, 1118 + 120, 1119 + 106, 1120 + 111, 1121 + 119, 1122 + 115, 1123 + 50, 1124 + 119, 1125 + 112, 1126 + 55, 1127 + 118, 1128 + 109, 1129 + 111, 1130 + 54, 1131 + 113, 1132 + 103, 1133 + 97, 1134 + 110, 1135 + 111, 1136 + 116, 1137 + 104, 1138 + 101, 1139 + 114, 1140 + 105, 1141 + 98, 1142 + 97, 1143 + 100, 1144 + 32, 1145 + 118, 1146 + 97, 1147 + 108, 1148 + 117, 1149 + 101, 1150 + 97, 1151 + 100, 1152 + 162, 1153 + 102, 1154 + 36, 1155 + 98, 1156 + 121, 1157 + 116, 1158 + 101, 1159 + 115, 1160 + 120, 1161 + 43, 1162 + 110, 1163 + 70, 1164 + 69, 1165 + 82, 1166 + 106, 1167 + 118, 1168 + 76, 1169 + 76, 1170 + 105, 1171 + 119, 1172 + 57, 1173 + 113, 1174 + 109, 1175 + 52, 1176 + 53, 1177 + 74, 1178 + 114, 1179 + 113, 1180 + 72, 1181 + 57, 1182 + 81, 1183 + 84, 1184 + 122, 1185 + 121, 1186 + 67, 1187 + 50, 1188 + 76, 1189 + 117, 1190 + 49, 1191 + 88, 1192 + 98, 1193 + 52, 1194 + 110, 1195 + 101, 1196 + 54, 1197 + 43, 1198 + 115, 1199 + 66, 1200 + 114, 1201 + 67, 1202 + 122, 1203 + 73, 1204 + 48, 1205 + 103, 1206 + 97, 1207 + 110, 1208 + 111, 1209 + 116, 1210 + 104, 1211 + 101, 1212 + 114, 1213 + 105, 1214 + 98, 1215 + 97, 1216 + 100, 1217 + 32, 1218 + 118, 1219 + 97, 1220 + 108, 1221 + 117, 1222 + 101, 1223 + 97, 1224 + 101, 1225 + 161, 1226 + 97, 1227 + 47, 1228 + 120, 1229 + 59, 1230 + 98, 1231 + 97, 1232 + 102, 1233 + 121, 1234 + 114, 1235 + 101, 1236 + 105, 1237 + 103, 1238 + 111, 1239 + 120, 1240 + 116, 1241 + 54, 1242 + 52, 1243 + 113, 1244 + 103, 1245 + 104, 1246 + 121, 1247 + 116, 1248 + 122, 1249 + 107, 1250 + 114, 1251 + 54, 1252 + 105, 1253 + 107, 1254 + 55, 1255 + 113, 1256 + 118, 1257 + 116, 1258 + 122, 1259 + 99, 1260 + 55, 1261 + 108, 1262 + 121, 1263 + 121, 1264 + 116, 1265 + 105, 1266 + 113, 1267 + 53, 1268 + 120, 1269 + 98, 1270 + 98, 1271 + 114, 1272 + 111, 1273 + 107, 1274 + 98, 1275 + 120, 1276 + 106, 1277 + 111, 1278 + 119, 1279 + 115, 1280 + 50, 1281 + 119, 1282 + 112, 1283 + 55, 1284 + 118, 1285 + 109, 1286 + 111, 1287 + 54, 1288 + 113, 1289 + 97, 1290 + 102, 1291 + 161, 1292 + 97, 1293 + 47, 1294 + 161, 1295 + 101, 1296 + 98, 1297 + 121, 1298 + 116, 1299 + 101, 1300 + 115, 1301 + 120, 1302 + 43, 1303 + 110, 1304 + 70, 1305 + 69, 1306 + 82, 1307 + 106, 1308 + 118, 1309 + 76, 1310 + 76, 1311 + 105, 1312 + 119, 1313 + 57, 1314 + 113, 1315 + 109, 1316 + 52, 1317 + 53, 1318 + 74, 1319 + 114, 1320 + 113, 1321 + 72, 1322 + 57, 1323 + 81, 1324 + 84, 1325 + 122, 1326 + 121, 1327 + 67, 1328 + 50, 1329 + 76, 1330 + 117, 1331 + 49, 1332 + 88, 1333 + 98, 1334 + 52, 1335 + 110, 1336 + 101, 1337 + 54, 1338 + 43, 1339 + 115, 1340 + 66, 1341 + 114, 1342 + 67, 1343 + 122, 1344 + 73, 1345 + 48, 1346 + ]), 1347 + cid: "bafyreico7wgbbfe6dpfsuednrtrlh6t2yjl6xq5rf32gl3pgwhwxk77cn4", 1348 + }, 1349 + ];
+34
common/tests/ipld-multi_test.ts
··· 1 + import { CID } from "npm:multiformats/cid"; 2 + import * as ui8 from "npm:uint8arrays"; 3 + import { cborDecodeMulti, cborEncode, type CborObject } from "../mod.ts"; 4 + import { assert, assertEquals } from "jsr:@std/assert"; 5 + 6 + Deno.test("decodes concatenated dag-cbor messages", () => { 7 + const one = { 8 + a: 123, 9 + b: CID.parse( 10 + "bafyreidfayvfuwqa7qlnopdjiqrxzs6blmoeu4rujcjtnci5beludirz2a", 11 + ), 12 + }; 13 + const two = { 14 + c: new Uint8Array([1, 2, 3]), 15 + d: CID.parse( 16 + "bafyreidfayvfuwqa7qlnopdjiqrxzs6blmoeu4rujcjtnci5beludirz2a", 17 + ), 18 + }; 19 + const encoded = ui8.concat([cborEncode(one), cborEncode(two)]); 20 + const decoded = cborDecodeMulti(encoded); 21 + assertEquals(decoded.length, 2); 22 + assertEquals(decoded[0], one); 23 + assertEquals(decoded[1], two); 24 + }); 25 + 26 + Deno.test("parses safe ints as number", () => { 27 + const one = { 28 + test: Number.MAX_SAFE_INTEGER, 29 + }; 30 + const encoded = cborEncode(one); 31 + const decoded = cborDecodeMulti(encoded); 32 + const first = decoded[0] as CborObject; 33 + assert(Number.isInteger(first?.["test"])); 34 + });
+28
common/tests/ipld_test.ts
··· 1 + import * as ui8 from "npm:uint8arrays"; 2 + import { 3 + cborDecode, 4 + cborEncode, 5 + cidForCbor, 6 + ipldEquals, 7 + ipldToJson, 8 + jsonToIpld, 9 + } from "../mod.ts"; 10 + import { vectors } from "./interop/ipld-vectors.ts"; 11 + import { assert, assertEquals } from "jsr:@std/assert"; 12 + 13 + for (const vector of vectors) { 14 + Deno.test(`passes test vector: ${vector.name}`, async () => { 15 + const ipld = jsonToIpld(vector.json); 16 + const json = ipldToJson(ipld); 17 + const cbor = cborEncode(ipld); 18 + const ipldAgain = cborDecode(cbor); 19 + const jsonAgain = ipldToJson(ipldAgain); 20 + const cid = await cidForCbor(ipld); 21 + assertEquals(json, vector.json); 22 + assertEquals(jsonAgain, vector.json); 23 + assert(ipldEquals(ipld, vector.ipld)); 24 + assert(ipldEquals(ipldAgain, vector.ipld)); 25 + assert(ui8.equals(cbor, vector.cbor)); 26 + assertEquals(cid.toString(), vector.cid); 27 + }); 28 + }
+96
common/tests/retry_test.ts
··· 1 + import { assertEquals, assertRejects } from "jsr:@std/assert"; 2 + import { retry } from "../mod.ts"; 3 + 4 + Deno.test("retries until max retries", async () => { 5 + let fnCalls = 0; 6 + let waitMsCalls = 0; 7 + const fn = () => { 8 + fnCalls++; 9 + throw new Error(`Oops ${fnCalls}!`); 10 + }; 11 + const getWaitMs = (retries: number) => { 12 + waitMsCalls++; 13 + assertEquals(retries, waitMsCalls - 1); 14 + return 0; 15 + }; 16 + await assertRejects( 17 + () => retry(fn, { maxRetries: 13, getWaitMs }), 18 + Error, 19 + "Oops 14!", 20 + ); 21 + assertEquals(fnCalls, 14); 22 + assertEquals(waitMsCalls, 14); 23 + }); 24 + 25 + Deno.test("retries until max wait", async () => { 26 + let fnCalls = 0; 27 + let waitMsCalls = 0; 28 + const fn = () => { 29 + fnCalls++; 30 + throw new Error(`Oops ${fnCalls}!`); 31 + }; 32 + const getWaitMs = (retries: number) => { 33 + waitMsCalls++; 34 + assertEquals(retries, waitMsCalls - 1); 35 + if (retries === 13) { 36 + return null; 37 + } 38 + return 0; 39 + }; 40 + await assertRejects( 41 + () => retry(fn, { maxRetries: Infinity, getWaitMs }), 42 + Error, 43 + "Oops 14!", 44 + ); 45 + assertEquals(fnCalls, 14); 46 + assertEquals(waitMsCalls, 14); 47 + }); 48 + 49 + Deno.test("retries until non-retryable error", async () => { 50 + let fnCalls = 0; 51 + let waitMsCalls = 0; 52 + const fn = () => { 53 + fnCalls++; 54 + throw new Error(`Oops ${fnCalls}!`); 55 + }; 56 + const getWaitMs = (retries: number) => { 57 + waitMsCalls++; 58 + assertEquals(retries, waitMsCalls - 1); 59 + return 0; 60 + }; 61 + const retryable = (err: unknown) => (err as Error)?.message !== "Oops 14!"; 62 + await assertRejects( 63 + () => retry(fn, { maxRetries: Infinity, getWaitMs, retryable }), 64 + Error, 65 + "Oops 14!", 66 + ); 67 + assertEquals(fnCalls, 14); 68 + assertEquals(waitMsCalls, 14); 69 + }); 70 + 71 + Deno.test("returns latest result after retries", async () => { 72 + let fnCalls = 0; 73 + const fn = () => { 74 + fnCalls++; 75 + if (fnCalls < 14) { 76 + throw new Error(`Oops ${fnCalls}!`); 77 + } 78 + return "ok"; 79 + }; 80 + const getWaitMs = () => 0; 81 + const result = await retry(fn, { maxRetries: Infinity, getWaitMs }); 82 + assertEquals(result, "ok"); 83 + assertEquals(fnCalls, 14); 84 + }); 85 + 86 + Deno.test("returns result immediately on success", async () => { 87 + let fnCalls = 0; 88 + const fn = () => { 89 + fnCalls++; 90 + return "ok"; 91 + }; 92 + const getWaitMs = () => 0; 93 + const result = await retry(fn, { maxRetries: Infinity, getWaitMs }); 94 + assertEquals(result, "ok"); 95 + assertEquals(fnCalls, 1); 96 + });
+186
common/tests/streams_test.ts
··· 1 + import { assert, assertEquals, assertRejects } from "jsr:@std/assert"; 2 + import * as streams from "../streams.ts"; 3 + 4 + Deno.test("forwardStreamErrors - is a no-op in Web Streams", () => { 5 + const streamA = new ReadableStream(); 6 + const streamB = new ReadableStream(); 7 + 8 + // forwardStreamErrors is a no-op in Web Streams, so we just test it doesn't throw 9 + streams.forwardStreamErrors(streamA, streamB); 10 + 11 + // No assertion needed - just testing it doesn't throw 12 + assert(true); 13 + }); 14 + 15 + Deno.test("cloneStream - should clone stream", async () => { 16 + const data = new Uint8Array([102, 111, 111]); // "foo" as bytes 17 + const stream = new ReadableStream({ 18 + start(controller) { 19 + controller.enqueue(data); 20 + controller.close(); 21 + }, 22 + }); 23 + 24 + const cloned = streams.cloneStream(stream); 25 + 26 + const chunks: Uint8Array[] = []; 27 + const reader = cloned.getReader(); 28 + 29 + try { 30 + while (true) { 31 + const { done, value } = await reader.read(); 32 + if (done) break; 33 + chunks.push(value); 34 + } 35 + } finally { 36 + reader.releaseLock(); 37 + } 38 + 39 + assertEquals(chunks.length, 1); 40 + assertEquals(chunks[0], data); 41 + }); 42 + 43 + Deno.test("streamSize - reads entire stream and computes size", async () => { 44 + const stream = new ReadableStream({ 45 + start(controller) { 46 + controller.enqueue(new Uint8Array([102])); // "f" 47 + controller.enqueue(new Uint8Array([111])); // "o" 48 + controller.enqueue(new Uint8Array([111])); // "o" 49 + controller.close(); 50 + }, 51 + }); 52 + 53 + const size = await streams.streamSize(stream); 54 + assertEquals(size, 3); 55 + }); 56 + 57 + Deno.test("streamSize - returns 0 for empty streams", async () => { 58 + const stream = new ReadableStream({ 59 + start(controller) { 60 + controller.close(); 61 + }, 62 + }); 63 + 64 + const size = await streams.streamSize(stream); 65 + assertEquals(size, 0); 66 + }); 67 + 68 + Deno.test("streamToBuffer - converts stream to buffer", async () => { 69 + const stream = new ReadableStream({ 70 + start(controller) { 71 + controller.enqueue(new Uint8Array([102, 111, 111])); // "foo" 72 + controller.close(); 73 + }, 74 + }); 75 + 76 + const buffer = await streams.streamToBuffer(stream); 77 + const bytes = new Uint8Array(buffer.bytes()); 78 + 79 + assertEquals(bytes[0], "f".charCodeAt(0)); 80 + assertEquals(bytes[1], "o".charCodeAt(0)); 81 + assertEquals(bytes[2], "o".charCodeAt(0)); 82 + assertEquals(bytes.length, 3); 83 + }); 84 + 85 + Deno.test("streamToBuffer - converts async iterable to buffer", async () => { 86 + const iterable = (async function* () { 87 + yield new Uint8Array([98]); // "b" 88 + yield new Uint8Array([97]); // "a" 89 + yield new Uint8Array([114]); // "r" 90 + })(); 91 + 92 + const buffer = await streams.streamToBuffer(iterable); 93 + const bytes = new Uint8Array(buffer.bytes()); 94 + 95 + assertEquals(bytes[0], "b".charCodeAt(0)); 96 + assertEquals(bytes[1], "a".charCodeAt(0)); 97 + assertEquals(bytes[2], "r".charCodeAt(0)); 98 + assertEquals(bytes.length, 3); 99 + }); 100 + 101 + Deno.test("streamToBuffer - throws error for non Uint8Array chunks", async () => { 102 + const iterable = (async function* () { 103 + yield new Uint8Array([98]); // "b" 104 + yield new Uint8Array([97]); // "a" 105 + yield "r"; // This should cause an error 106 + })(); 107 + 108 + await assertRejects( 109 + () => streams.streamToBuffer(iterable as AsyncIterable<Uint8Array>), 110 + TypeError, 111 + "expected Uint8Array", 112 + ); 113 + }); 114 + 115 + Deno.test("byteIterableToStream - converts byte iterable to stream", async () => { 116 + const iterable: AsyncIterable<Uint8Array> = { 117 + async *[Symbol.asyncIterator]() { 118 + yield new Uint8Array([0xa, 0xb]); 119 + }, 120 + }; 121 + 122 + const stream = streams.byteIterableToStream(iterable); 123 + const reader = stream.getReader(); 124 + 125 + try { 126 + const { done, value } = await reader.read(); 127 + assertEquals(done, false); 128 + assertEquals(value![0], 0xa); 129 + assertEquals(value![1], 0xb); 130 + 131 + const { done: done2 } = await reader.read(); 132 + assertEquals(done2, true); 133 + } finally { 134 + reader.releaseLock(); 135 + } 136 + }); 137 + 138 + Deno.test("bytesToStream - converts byte array to readable stream", async () => { 139 + const bytes = new Uint8Array([0xa, 0xb]); 140 + const stream = streams.bytesToStream(bytes); 141 + const reader = stream.getReader(); 142 + 143 + try { 144 + const { done, value } = await reader.read(); 145 + assertEquals(done, false); 146 + assertEquals(value![0], 0xa); 147 + assertEquals(value![1], 0xb); 148 + 149 + const { done: done2 } = await reader.read(); 150 + assertEquals(done2, true); 151 + } finally { 152 + reader.releaseLock(); 153 + } 154 + }); 155 + 156 + Deno.test("MaxSizeChecker - destroys once max size is met", async () => { 157 + const err = new Error("foo"); 158 + const checker = new streams.MaxSizeChecker(1, () => err); 159 + let lastError: Error | undefined; 160 + 161 + const sourceStream = new ReadableStream({ 162 + start(controller) { 163 + controller.enqueue(new Uint8Array([0xa])); 164 + controller.enqueue(new Uint8Array([0xb])); 165 + controller.close(); 166 + }, 167 + }); 168 + 169 + const outStream = sourceStream.pipeThrough(checker); 170 + const reader = outStream.getReader(); 171 + 172 + assertEquals(checker.totalSize, 0); 173 + 174 + try { 175 + // Try to read chunks - should error on second chunk due to size limit 176 + await reader.read(); // First chunk (size 1) - should be ok 177 + await reader.read(); // Second chunk (total size 2) - should error 178 + } catch (error) { 179 + lastError = error as Error; 180 + } finally { 181 + reader.releaseLock(); 182 + } 183 + 184 + assertEquals(checker.totalSize, 2); 185 + assertEquals(lastError, err); 186 + });
+117
common/tests/strings_test.ts
··· 1 + import { assert, assertEquals, assertFalse } from "jsr:@std/assert"; 2 + import { 3 + graphemeLen, 4 + parseLanguage, 5 + utf8Len, 6 + validateLanguage, 7 + } from "../mod.ts"; 8 + 9 + Deno.test("calculates utf8 string length", () => { 10 + assertEquals(utf8Len("a"), 1); 11 + assertEquals(utf8Len("~"), 1); 12 + assertEquals(utf8Len("ö"), 2); 13 + assertEquals(utf8Len("ñ"), 2); 14 + assertEquals(utf8Len("©"), 2); 15 + assertEquals(utf8Len("⽘"), 3); 16 + assertEquals(utf8Len("☎"), 3); 17 + assertEquals(utf8Len("𓋓"), 4); 18 + assertEquals(utf8Len("😀"), 4); 19 + assertEquals(utf8Len("👨‍👩‍👧‍👧"), 25); 20 + }); 21 + 22 + Deno.test("calculates grapheme length", () => { 23 + assertEquals(graphemeLen("a"), 1); 24 + assertEquals(graphemeLen("~"), 1); 25 + assertEquals(graphemeLen("ö"), 1); 26 + assertEquals(graphemeLen("ñ"), 1); 27 + assertEquals(graphemeLen("©"), 1); 28 + assertEquals(graphemeLen("⽘"), 1); 29 + assertEquals(graphemeLen("☎"), 1); 30 + assertEquals(graphemeLen("𓋓"), 1); 31 + assertEquals(graphemeLen("😀"), 1); 32 + assertEquals(graphemeLen("👨‍👩‍👧‍👧"), 1); 33 + assertEquals(graphemeLen("a~öñ©⽘☎𓋓😀👨‍👩‍👧‍👧"), 10); 34 + }); 35 + 36 + Deno.test("validates BCP 47", () => { 37 + // valid 38 + assert(validateLanguage("de")); 39 + assert(validateLanguage("de-CH")); 40 + assert(validateLanguage("de-DE-1901")); 41 + assert(validateLanguage("es-419")); 42 + assert(validateLanguage("sl-IT-nedis")); 43 + assert(validateLanguage("mn-Cyrl-MN")); 44 + assert(validateLanguage("x-fr-CH")); 45 + assert( 46 + validateLanguage("en-GB-boont-r-extended-sequence-x-private"), 47 + ); 48 + assert(validateLanguage("sr-Cyrl")); 49 + assert(validateLanguage("hy-Latn-IT-arevela")); 50 + assert(validateLanguage("i-klingon")); 51 + // invalid 52 + assertFalse(validateLanguage("")); 53 + assertFalse(validateLanguage("x")); 54 + assertFalse(validateLanguage("de-CH-")); 55 + assertFalse(validateLanguage("i-bad-grandfathered")); 56 + }); 57 + 58 + Deno.test("parses BCP 47", () => { 59 + // valid 60 + assertEquals(parseLanguage("de"), { 61 + language: "de", 62 + }); 63 + assertEquals(parseLanguage("de-CH"), { 64 + language: "de", 65 + region: "CH", 66 + }); 67 + assertEquals(parseLanguage("de-DE-1901"), { 68 + language: "de", 69 + region: "DE", 70 + variant: "1901", 71 + }); 72 + assertEquals(parseLanguage("es-419"), { 73 + language: "es", 74 + region: "419", 75 + }); 76 + assertEquals(parseLanguage("sl-IT-nedis"), { 77 + language: "sl", 78 + region: "IT", 79 + variant: "nedis", 80 + }); 81 + assertEquals(parseLanguage("mn-Cyrl-MN"), { 82 + language: "mn", 83 + script: "Cyrl", 84 + region: "MN", 85 + }); 86 + assertEquals(parseLanguage("x-fr-CH"), { 87 + privateUse: "x-fr-CH", 88 + }); 89 + assertEquals( 90 + parseLanguage("en-GB-boont-r-extended-sequence-x-private"), 91 + { 92 + language: "en", 93 + region: "GB", 94 + variant: "boont", 95 + extension: "r-extended-sequence", 96 + privateUse: "x-private", 97 + }, 98 + ); 99 + assertEquals(parseLanguage("sr-Cyrl"), { 100 + language: "sr", 101 + script: "Cyrl", 102 + }); 103 + assertEquals(parseLanguage("hy-Latn-IT-arevela"), { 104 + language: "hy", 105 + script: "Latn", 106 + region: "IT", 107 + variant: "arevela", 108 + }); 109 + assertEquals(parseLanguage("i-klingon"), { 110 + grandfathered: "i-klingon", 111 + }); 112 + // invalid 113 + assertFalse(parseLanguage("")); 114 + assertFalse(parseLanguage("x")); 115 + assertFalse(parseLanguage("de-CH-")); 116 + assertFalse(parseLanguage("i-bad-grandfathered")); 117 + });
+132
common/tests/tid_test.ts
··· 1 + import { 2 + assert, 3 + assertEquals, 4 + assertFalse, 5 + assertThrows, 6 + } from "jsr:@std/assert"; 7 + import { TID } from "../tid.ts"; 8 + 9 + Deno.test("creates a new TID", () => { 10 + const tid = TID.next(); 11 + const str = tid.toString(); 12 + assertEquals(typeof str, "string"); 13 + assertEquals(str.length, 13); 14 + }); 15 + 16 + Deno.test("parses a TID", () => { 17 + const tid = TID.next(); 18 + const str = tid.toString(); 19 + const parsed = TID.fromStr(str); 20 + assertEquals(parsed.timestamp(), tid.timestamp()); 21 + assertEquals(parsed.clockid(), tid.clockid()); 22 + }); 23 + 24 + Deno.test("throws if invalid tid passed", () => { 25 + assertThrows(() => new TID(""), "Poorly formatted TID: 0 length"); 26 + }); 27 + 28 + Deno.test("nextStr returns next tid as a string", () => { 29 + const str = TID.nextStr(); 30 + assertEquals(typeof str, "string"); 31 + assertEquals(str.length, 13); 32 + }); 33 + 34 + Deno.test("nextStr returns a next tid larger than a provided prev", () => { 35 + const prev = TID.fromTime((Date.now() + 5000) * 1000, 0).toString(); 36 + const str = TID.nextStr(prev); 37 + assert(str > prev); 38 + }); 39 + 40 + Deno.test("newestFirst sorts tids newest first", () => { 41 + const oldest = TID.next(); 42 + const newest = TID.next(); 43 + 44 + const tids = [oldest, newest]; 45 + 46 + tids.sort(TID.newestFirst); 47 + 48 + assertEquals(tids, [newest, oldest]); 49 + }); 50 + 51 + Deno.test("oldestFirst sorts tids oldest first", () => { 52 + const oldest = TID.next(); 53 + const newest = TID.next(); 54 + 55 + const tids = [newest, oldest]; 56 + 57 + tids.sort(TID.oldestFirst); 58 + 59 + assertEquals(tids, [oldest, newest]); 60 + }); 61 + 62 + Deno.test("is true for valid tids", () => { 63 + const tid = TID.next(); 64 + const asStr = tid.toString(); 65 + 66 + assert(TID.is(asStr)); 67 + }); 68 + 69 + Deno.test("is false for invalid tids", () => { 70 + assertFalse(TID.is("")); 71 + }); 72 + 73 + Deno.test("equals true when same tid", () => { 74 + const tid = TID.next(); 75 + assert(tid.equals(tid)); 76 + }); 77 + 78 + Deno.test("equals true when different instance, same tid", () => { 79 + const tid0 = TID.next(); 80 + const tid1 = new TID(tid0.toString()); 81 + 82 + assert(tid0.equals(tid1)); 83 + }); 84 + 85 + Deno.test("equals false when different tid", () => { 86 + const tid0 = TID.next(); 87 + const tid1 = TID.next(); 88 + 89 + assertFalse(tid0.equals(tid1)); 90 + }); 91 + 92 + Deno.test("newerThan true for newer tid", () => { 93 + const tid0 = TID.next(); 94 + const tid1 = TID.next(); 95 + 96 + assert(tid1.newerThan(tid0)); 97 + }); 98 + 99 + Deno.test("newerThan false for older tid", () => { 100 + const tid0 = TID.next(); 101 + const tid1 = TID.next(); 102 + 103 + assertFalse(tid0.newerThan(tid1)); 104 + }); 105 + 106 + Deno.test("newerThan false for identical tids", () => { 107 + const tid0 = TID.next(); 108 + const tid1 = new TID(tid0.toString()); 109 + 110 + assertFalse(tid0.newerThan(tid1)); 111 + }); 112 + 113 + Deno.test("olderThan true for older tid", () => { 114 + const tid0 = TID.next(); 115 + const tid1 = TID.next(); 116 + 117 + assert(tid0.olderThan(tid1)); 118 + }); 119 + 120 + Deno.test("olderThan false for newer tid", () => { 121 + const tid0 = TID.next(); 122 + const tid1 = TID.next(); 123 + 124 + assertFalse(tid1.olderThan(tid0)); 125 + }); 126 + 127 + Deno.test("olderThan false for identical tids", () => { 128 + const tid0 = TID.next(); 129 + const tid1 = new TID(tid0.toString()); 130 + 131 + assertFalse(tid0.olderThan(tid1)); 132 + });
+93
common/tests/util_test.ts
··· 1 + import { assertEquals, assertStrictEquals } from "jsr:@std/assert"; 2 + import * as util from "../util.ts"; 3 + 4 + Deno.test("noUndefinedVals - removes undefined top-level keys", () => { 5 + const obj: Record<string, unknown> = { 6 + foo: 123, 7 + bar: undefined, 8 + }; 9 + 10 + const result = util.noUndefinedVals(obj); 11 + 12 + assertStrictEquals(result, obj); 13 + assertEquals(result, { 14 + foo: 123, 15 + }); 16 + }); 17 + 18 + Deno.test("noUndefinedVals - handles empty objects", () => { 19 + assertEquals(util.noUndefinedVals({}), {}); 20 + }); 21 + 22 + Deno.test("noUndefinedVals - leaves deep values intact", () => { 23 + const obj: Record<string, unknown> = { 24 + foo: 123, 25 + bar: { 26 + baz: undefined, 27 + }, 28 + }; 29 + const result = util.noUndefinedVals(obj); 30 + 31 + assertEquals(result, { 32 + foo: 123, 33 + bar: { 34 + baz: undefined, 35 + }, 36 + }); 37 + }); 38 + 39 + Deno.test("flattenUint8Arrays - flattens to single array of values", () => { 40 + const arr = [new Uint8Array([0xa, 0xb]), new Uint8Array([0xc, 0xd])]; 41 + 42 + const flat = util.flattenUint8Arrays(arr); 43 + 44 + assertEquals([...flat], [0xa, 0xb, 0xc, 0xd]); 45 + }); 46 + 47 + Deno.test("flattenUint8Arrays - flattens empty arrays", () => { 48 + const arr = [new Uint8Array(0), new Uint8Array(0)]; 49 + const flat = util.flattenUint8Arrays(arr); 50 + 51 + assertEquals(flat.length, 0); 52 + }); 53 + 54 + Deno.test("streamToUI8Array - reads iterable into array", async () => { 55 + const iterable: AsyncIterable<Uint8Array> = { 56 + async *[Symbol.asyncIterator]() { 57 + yield new Uint8Array([0xa, 0xb]); 58 + yield new Uint8Array([0xc, 0xd]); 59 + }, 60 + }; 61 + const buffer = await util.streamToUI8Array(iterable); 62 + 63 + assertEquals([...buffer], [0xa, 0xb, 0xc, 0xd]); 64 + }); 65 + 66 + Deno.test("asyncFilter - filters array values", async () => { 67 + const result = await util.asyncFilter( 68 + [0, 1, 2], 69 + (n) => Promise.resolve(n === 0), 70 + ); 71 + 72 + assertEquals(result, [0]); 73 + }); 74 + 75 + Deno.test("range - generates numeric range", () => { 76 + assertEquals(util.range(4), [0, 1, 2, 3]); 77 + }); 78 + 79 + Deno.test("dedupeStrs - removes duplicates", () => { 80 + assertEquals(util.dedupeStrs(["a", "a", "b"]), ["a", "b"]); 81 + }); 82 + 83 + Deno.test("parseIntWithFallback - accepts undefined", () => { 84 + assertEquals(util.parseIntWithFallback(undefined, -10), -10); 85 + }); 86 + 87 + Deno.test("parseIntWithFallback - parses numbers", () => { 88 + assertEquals(util.parseIntWithFallback("100", -10), 100); 89 + }); 90 + 91 + Deno.test("parseIntWithFallback - supports non-numeric fallbacks", () => { 92 + assertEquals(util.parseIntWithFallback(undefined, "foo"), "foo"); 93 + });
-1
common/tid.ts
··· 91 91 return this.str; 92 92 } 93 93 94 - // newer > older 95 94 compareTo(other: TID): number { 96 95 if (this.str > other.str) return 1; 97 96 if (this.str < other.str) return -1;
+1 -48
common/util.ts
··· 77 77 return flattened; 78 78 }; 79 79 80 - export const streamToBuffer = async ( 80 + export const streamToUI8Array = async ( 81 81 stream: AsyncIterable<Uint8Array>, 82 82 ): Promise<Uint8Array> => { 83 83 const arrays: Uint8Array[] = []; ··· 156 156 bytes.byteOffset, 157 157 bytes.byteLength + bytes.byteOffset, 158 158 ) as ArrayBuffer; 159 - } 160 - 161 - export type RetryOptions = { 162 - maxRetries?: number; 163 - getWaitMs?: (n: number) => number | null; 164 - }; 165 - 166 - export async function retry<T>( 167 - fn: () => Promise<T>, 168 - opts: RetryOptions & { 169 - retryable?: (err: unknown) => boolean; 170 - } = {}, 171 - ): Promise<T> { 172 - const { maxRetries = 3, retryable = () => true, getWaitMs = backoffMs } = 173 - opts; 174 - let retries = 0; 175 - let doneError: unknown; 176 - while (!doneError) { 177 - try { 178 - return await fn(); 179 - } catch (err) { 180 - const waitMs = getWaitMs(retries); 181 - const willRetry = retries < maxRetries && waitMs !== null && 182 - retryable(err); 183 - if (willRetry) { 184 - retries += 1; 185 - if (waitMs !== 0) { 186 - await wait(waitMs); 187 - } 188 - } else { 189 - doneError = err; 190 - } 191 - } 192 - } 193 - throw doneError; 194 - } 195 - 196 - export function createRetryable(retryable: (err: unknown) => boolean) { 197 - return <T>(fn: () => Promise<T>, opts?: RetryOptions) => 198 - retry(fn, { ...opts, retryable }); 199 - } 200 - 201 - // Waits exponential backoff with max and jitter: ~100, ~200, ~400, ~800, ~1000, ~1000, ... 202 - export function backoffMs(n: number, multiplier = 100, max = 1000) { 203 - const exponentialMs = Math.pow(2, n) * multiplier; 204 - const ms = Math.min(exponentialMs, max); 205 - return jitter(ms); 206 159 } 207 160 208 161 export function keyBy<T, K extends keyof T>(
+1
deno.lock
··· 50 50 "npm:multiformats@*": "13.4.0", 51 51 "npm:multiformats@^13.4.0": "13.4.0", 52 52 "npm:rate-limiter-flexible@^2.4.1": "2.4.2", 53 + "npm:uint8arrays@*": "3.0.0", 53 54 "npm:uint8arrays@3.0.0": "3.0.0", 54 55 "npm:ws@^8.12.0": "8.18.3" 55 56 },
+12 -12
syntax/tests/aturi_test.ts
··· 1 1 import { assertEquals, assertThrows } from "jsr:@std/assert"; 2 2 import { AtUri, ensureValidAtUri, ensureValidAtUriRegex } from "../mod.ts"; 3 3 4 - Deno.test("At Uris - parses valid at uris", () => { 4 + Deno.test("parses valid at uris", () => { 5 5 // input host path query hash 6 6 type AtUriTest = [string, string, string, string, string]; 7 7 const TESTS: AtUriTest[] = [ ··· 255 255 } 256 256 }); 257 257 258 - Deno.test("At Uris - handles ATP-specific parsing", () => { 258 + Deno.test("handles ATP-specific parsing", () => { 259 259 { 260 260 const urip = new AtUri("at://foo.com"); 261 261 assertEquals(urip.collection, ""); ··· 273 273 } 274 274 }); 275 275 276 - Deno.test("At Uris - supports modifications", () => { 276 + Deno.test("supports modifications", () => { 277 277 const urip = new AtUri("at://foo.com"); 278 278 assertEquals(urip.toString(), "at://foo.com/"); 279 279 ··· 313 313 assertEquals(urip.toString(), "at://foo.com/foo?foo=bar&baz=buux#hash"); 314 314 }); 315 315 316 - Deno.test("At Uris - supports relative URIs", () => { 316 + Deno.test("supports relative URIs", () => { 317 317 // input path query hash 318 318 type AtUriTest = [string, string, string, string]; 319 319 const TESTS: AtUriTest[] = [ ··· 358 358 } 359 359 }); 360 360 361 - Deno.test("AtUri validation - enforces spec basics", () => { 361 + Deno.test("validation enforces spec basics", () => { 362 362 const expectValid = (h: string) => { 363 363 ensureValidAtUri(h); 364 364 ensureValidAtUriRegex(h); ··· 426 426 ); 427 427 }); 428 428 429 - Deno.test("AtUri validation - has specified behavior on edge cases", () => { 429 + Deno.test("validation has specified behavior on edge cases", () => { 430 430 const expectInvalid = (h: string) => { 431 431 assertThrows(() => ensureValidAtUri(h)); 432 432 assertThrows(() => ensureValidAtUriRegex(h)); ··· 442 442 expectInvalid("at://did:plc:asdf123/12345"); 443 443 }); 444 444 445 - Deno.test("AtUri validation - enforces no trailing slashes", () => { 445 + Deno.test("validation enforces no trailing slashes", () => { 446 446 const expectValid = (h: string) => { 447 447 ensureValidAtUri(h); 448 448 ensureValidAtUriRegex(h); ··· 466 466 expectInvalid("at://did:plc:asdf123/com.atproto.feed.post/record/#/frag"); 467 467 }); 468 468 469 - Deno.test("AtUri validation - enforces strict paths", () => { 469 + Deno.test("validation enforces strict paths", () => { 470 470 const expectValid = (h: string) => { 471 471 ensureValidAtUri(h); 472 472 ensureValidAtUriRegex(h); ··· 480 480 expectInvalid("at://did:plc:asdf123/com.atproto.feed.post/asdf123/asdf"); 481 481 }); 482 482 483 - Deno.test("AtUri validation - is restrictive about record keys", () => { 483 + Deno.test("validation is restrictive about record keys", () => { 484 484 const expectValid = (h: string) => { 485 485 ensureValidAtUri(h); 486 486 ensureValidAtUriRegex(h); ··· 517 517 expectInvalid("at://did:plc:asdf123/com.atproto.feed.post/.."); 518 518 }); 519 519 520 - Deno.test("AtUri validation - properly validates URL encoding in record keys", () => { 520 + Deno.test("properly validates URL encoding in record keys", () => { 521 521 const expectInvalid = (h: string) => { 522 522 assertThrows(() => ensureValidAtUri(h)); 523 523 assertThrows(() => ensureValidAtUriRegex(h)); ··· 563 563 expectValid("at://did:plc:asdf123#/,"); 564 564 }); 565 565 566 - Deno.test("AtUri validation - conforms to interop valid ATURIs", async () => { 566 + Deno.test("validation conforms to interop valid ATURIs", async () => { 567 567 const expectValid = (h: string) => { 568 568 ensureValidAtUri(h); 569 569 ensureValidAtUriRegex(h); ··· 582 582 } 583 583 }); 584 584 585 - Deno.test("AtUri validation - conforms to interop invalid ATURIs", async () => { 585 + Deno.test("validation conforms to interop invalid ATURIs", async () => { 586 586 const expectInvalid = (h: string) => { 587 587 assertThrows(() => ensureValidAtUri(h)); 588 588 assertThrows(() => ensureValidAtUriRegex(h));
+3 -3
syntax/tests/datetime_test.ts
··· 7 7 normalizeDatetimeAlways, 8 8 } from "../mod.ts"; 9 9 10 - Deno.test("datetime validation - conforms to interop valid datetimes", async () => { 10 + Deno.test("conforms to interop valid datetimes", async () => { 11 11 const expectValid = (h: string) => { 12 12 ensureValidDatetime(h); 13 13 normalizeDatetime(h); ··· 30 30 } 31 31 }); 32 32 33 - Deno.test("datetime validation - conforms to interop invalid datetimes", async () => { 33 + Deno.test("conforms to interop invalid datetimes", async () => { 34 34 const expectInvalid = (h: string) => { 35 35 assertThrows(() => ensureValidDatetime(h), InvalidDatetimeError); 36 36 }; ··· 48 48 } 49 49 }); 50 50 51 - Deno.test("datetime validation - conforms to interop invalid parse (semantics) datetimes", async () => { 51 + Deno.test("conforms to interop invalid parse (semantics) datetimes", async () => { 52 52 const expectInvalid = (h: string) => { 53 53 assertThrows(() => ensureValidDatetime(h), InvalidDatetimeError); 54 54 };
+4 -4
syntax/tests/did_test.ts
··· 5 5 InvalidDidError, 6 6 } from "../mod.ts"; 7 7 8 - Deno.test("DID permissive validation - enforces spec details", () => { 8 + Deno.test("validation enforces spec details", () => { 9 9 const expectValid = (h: string) => { 10 10 ensureValidDid(h); 11 11 ensureValidDidRegex(h); ··· 60 60 ); 61 61 }); 62 62 63 - Deno.test("DID permissive validation - allows some real DID values", () => { 63 + Deno.test("validation allows some real DID values", () => { 64 64 const expectValid = (h: string) => { 65 65 ensureValidDid(h); 66 66 ensureValidDidRegex(h); ··· 74 74 expectValid("did:ethr:0xb9c5714089478a327f09197987f16f9e5d936e8a"); 75 75 }); 76 76 77 - Deno.test("DID permissive validation - conforms to interop valid DIDs", async () => { 77 + Deno.test("validation conforms to interop valid DIDs", async () => { 78 78 const expectValid = (h: string) => { 79 79 ensureValidDid(h); 80 80 ensureValidDidRegex(h); ··· 93 93 } 94 94 }); 95 95 96 - Deno.test("DID permissive validation - conforms to interop invalid DIDs", async () => { 96 + Deno.test("validation conforms to interop invalid DIDs", async () => { 97 97 const expectInvalid = (h: string) => { 98 98 assertThrows(() => ensureValidDid(h), InvalidDidError); 99 99 assertThrows(() => ensureValidDidRegex(h), InvalidDidError);
+31 -12
syntax/tests/handle_test.ts
··· 6 6 normalizeAndEnsureValidHandle, 7 7 } from "../mod.ts"; 8 8 9 - Deno.test("handle validation - allows valid handles", () => { 9 + Deno.test("validation allows valid handles", () => { 10 10 const expectValid = (h: string) => { 11 11 ensureValidHandle(h); 12 12 ensureValidHandleRegex(h); ··· 42 42 43 43 // NOTE: we may change this at the proto level; currently only disallowed at 44 44 // the registration level 45 - Deno.test("handle validation - allows .local and .arpa handles (proto-level)", () => { 45 + Deno.test("validation allows .local and .arpa handles (proto-level)", () => { 46 46 const expectValid = (h: string) => { 47 47 ensureValidHandle(h); 48 48 ensureValidHandleRegex(h); ··· 52 52 expectValid("laptop.arpa"); 53 53 }); 54 54 55 - Deno.test("handle validation - allows punycode handles", () => { 55 + Deno.test("validation allows punycode handles", () => { 56 56 const expectValid = (h: string) => { 57 57 ensureValidHandle(h); 58 58 ensureValidHandleRegex(h); ··· 71 71 expectValid("xn--2lb.com"); 72 72 }); 73 73 74 - Deno.test("handle validation - allows onion (Tor) handles", () => { 74 + Deno.test("validation allows onion (Tor) handles", () => { 75 75 const expectValid = (h: string) => { 76 76 ensureValidHandle(h); 77 77 ensureValidHandleRegex(h); ··· 96 96 ); 97 97 }); 98 98 99 - Deno.test("handle validation - throws on invalid handles", () => { 99 + Deno.test("validation throws on invalid handles", () => { 100 100 const expectInvalid = (h: string) => { 101 101 assertThrows(() => ensureValidHandle(h), InvalidHandleError); 102 102 assertThrows(() => ensureValidHandleRegex(h), InvalidHandleError); ··· 142 142 expectInvalid("john.tes-"); 143 143 }); 144 144 145 - Deno.test('handle validation - throws on "dotless" TLD handles', () => { 145 + Deno.test("validation throws on 'dotless' TLD handles", () => { 146 146 const expectInvalid = (h: string) => { 147 147 assertThrows(() => ensureValidHandle(h), InvalidHandleError); 148 148 assertThrows(() => ensureValidHandleRegex(h), InvalidHandleError); ··· 154 154 expectInvalid("io"); 155 155 }); 156 156 157 - Deno.test("handle validation - correctly validates corner cases (modern vs. old RFCs)", () => { 157 + Deno.test("validation correctly validates corner cases (modern vs. old RFCs)", () => { 158 158 const expectValid = (h: string) => { 159 159 ensureValidHandle(h); 160 160 ensureValidHandleRegex(h); ··· 183 183 expectInvalid("thing.0aa"); 184 184 }); 185 185 186 - Deno.test("handle validation - does not allow IP addresses as handles", () => { 186 + Deno.test("validation does not allow IP addresses as handles", () => { 187 187 const expectInvalid = (h: string) => { 188 188 assertThrows(() => ensureValidHandle(h), InvalidHandleError); 189 189 assertThrows(() => ensureValidHandleRegex(h), InvalidHandleError); ··· 195 195 expectInvalid("2600:3c03::f03c:9100:feb0:af1f"); 196 196 }); 197 197 198 - Deno.test("handle validation - is consistent with examples from stackoverflow", () => { 198 + Deno.test("validation is consistent with examples from stackoverflow", () => { 199 199 const expectValid = (h: string) => { 200 200 ensureValidHandle(h); 201 201 ensureValidHandleRegex(h); ··· 235 235 badStackoverflow.forEach(expectInvalid); 236 236 }); 237 237 238 - Deno.test("handle validation - conforms to interop valid handles", async () => { 238 + Deno.test("validation conforms to interop valid handles", async () => { 239 239 const expectValid = (h: string) => { 240 240 ensureValidHandle(h); 241 241 ensureValidHandleRegex(h); ··· 254 254 } 255 255 }); 256 256 257 - Deno.test("handle validation - conforms to interop invalid handles", async () => { 257 + Deno.test("validation conforms to interop invalid handles", async () => { 258 + const expectInvalid = (h: string) => { 259 + assertThrows(() => ensureValidHandle(h), InvalidHandleError); 260 + assertThrows(() => ensureValidHandleRegex(h), InvalidHandleError); 261 + }; 262 + 263 + const filePath = 264 + new URL("./interop/handle_syntax_invalid.txt", import.meta.url).pathname; 265 + const fileContent = await Deno.readTextFile(filePath); 266 + const lines = fileContent.split("\n"); 267 + 268 + for (const line of lines) { 269 + if (line.startsWith("#") || line.length === 0) { 270 + continue; 271 + } 272 + expectInvalid(line); 273 + } 274 + }); 275 + 276 + Deno.test("validation conforms to interop invalid handles", async () => { 258 277 const expectInvalid = (h: string) => { 259 278 assertThrows(() => ensureValidHandle(h), InvalidHandleError); 260 279 assertThrows(() => ensureValidHandleRegex(h), InvalidHandleError); ··· 278 297 assertEquals(normalized, "john.test"); 279 298 }); 280 299 281 - Deno.test("normalization - throws on invalid normalized handles", () => { 300 + Deno.test("normalization throws on invalid normalized handles", () => { 282 301 assertThrows( 283 302 () => normalizeAndEnsureValidHandle("JoH!n.TeST"), 284 303 InvalidHandleError,