PLC Bundle V1 Example Implementations

fist commit

tree.fail 6f4d1ee4

+643
+3
.gitignore
··· 1 + node_modules 2 + .DS_Store 3 + plc_bundles
+17
package.json
··· 1 + { 2 + "name": "plcbundle-js", 3 + "version": "1.0.0", 4 + "description": "", 5 + "main": "index.js", 6 + "type": "module", 7 + "scripts": { 8 + "test": "echo \"Error: no test specified\" && exit 1" 9 + }, 10 + "keywords": [], 11 + "author": "", 12 + "license": "ISC", 13 + "dependencies": { 14 + "@bokuweb/zstd-wasm": "^0.0.27", 15 + "axios": "^1.13.0" 16 + } 17 + }
+411
plcbundle.ts
··· 1 + #!/usr/bin/env node 2 + 3 + /** 4 + * plcbundle.ts - Fetch from PLC Directory and create verifiable bundles 5 + */ 6 + 7 + import fs from 'fs/promises'; 8 + import path from 'path'; 9 + import crypto from 'crypto'; 10 + import { fileURLToPath } from 'url'; 11 + import { init, compress, decompress } from '@bokuweb/zstd-wasm'; 12 + import axios from 'axios'; 13 + 14 + const __dirname = path.dirname(fileURLToPath(import.meta.url)); 15 + 16 + const BUNDLE_SIZE = 10000; 17 + const INDEX_FILE = 'plc_bundles.json'; 18 + const PLC_URL = 'https://plc.directory'; 19 + 20 + // ============================================================================ 21 + // Types 22 + // ============================================================================ 23 + 24 + interface PLCOperation { 25 + did: string; 26 + cid: string; 27 + createdAt: string; 28 + operation: Record<string, any>; 29 + nullified?: boolean | string; 30 + _raw?: string; 31 + } 32 + 33 + interface BundleMetadata { 34 + bundle_number: number; 35 + start_time: string; 36 + end_time: string; 37 + operation_count: number; 38 + did_count: number; 39 + hash: string; 40 + content_hash: string; 41 + parent: string; 42 + compressed_hash: string; 43 + compressed_size: number; 44 + uncompressed_size: number; 45 + created_at: string; 46 + } 47 + 48 + interface Index { 49 + version: string; 50 + last_bundle: number; 51 + updated_at: string; 52 + total_size_bytes: number; 53 + bundles: BundleMetadata[]; 54 + } 55 + 56 + // Initialize zstd 57 + await init(); 58 + 59 + // ============================================================================ 60 + // Index Management 61 + // ============================================================================ 62 + 63 + const loadIndex = async (dir: string): Promise<Index> => { 64 + try { 65 + const data = await fs.readFile(path.join(dir, INDEX_FILE), 'utf8'); 66 + return JSON.parse(data); 67 + } catch (err) { 68 + return { 69 + version: '1.0', 70 + last_bundle: 0, 71 + updated_at: new Date().toISOString(), 72 + total_size_bytes: 0, 73 + bundles: [] 74 + }; 75 + } 76 + }; 77 + 78 + const saveIndex = async (dir: string, index: Index): Promise<void> => { 79 + index.updated_at = new Date().toISOString(); 80 + const indexPath = path.join(dir, INDEX_FILE); 81 + const tempPath = indexPath + '.tmp'; 82 + await fs.writeFile(tempPath, JSON.stringify(index, null, 2)); 83 + await fs.rename(tempPath, indexPath); 84 + }; 85 + 86 + // ============================================================================ 87 + // Bundle Loading 88 + // ============================================================================ 89 + 90 + const loadBundle = async (dir: string, bundleNumber: number): Promise<PLCOperation[]> => { 91 + const filename = `${String(bundleNumber).padStart(6, '0')}.jsonl.zst`; 92 + const filepath = path.join(dir, filename); 93 + 94 + const compressed = await fs.readFile(filepath); 95 + const decompressed = decompress(compressed); 96 + const jsonl = Buffer.from(decompressed).toString('utf8'); 97 + 98 + const lines = jsonl.trim().split('\n').filter(l => l); 99 + return lines.map(line => { 100 + const op = JSON.parse(line) as PLCOperation; 101 + op._raw = line; 102 + return op; 103 + }); 104 + }; 105 + 106 + // ============================================================================ 107 + // Boundary Handling 108 + // ============================================================================ 109 + 110 + const getBoundaryCIDs = (operations: PLCOperation[]): Set<string> => { 111 + if (operations.length === 0) return new Set(); 112 + 113 + const lastOp = operations[operations.length - 1]; 114 + const boundaryTime = lastOp.createdAt; 115 + const cidSet = new Set<string>(); 116 + 117 + // Walk backwards from the end to find all operations with the same timestamp 118 + for (let i = operations.length - 1; i >= 0; i--) { 119 + if (operations[i].createdAt === boundaryTime) { 120 + cidSet.add(operations[i].cid); 121 + } else { 122 + break; 123 + } 124 + } 125 + 126 + return cidSet; 127 + }; 128 + 129 + const stripBoundaryDuplicates = ( 130 + operations: PLCOperation[], 131 + prevBoundaryCIDs: Set<string> 132 + ): PLCOperation[] => { 133 + if (prevBoundaryCIDs.size === 0) return operations; 134 + if (operations.length === 0) return operations; 135 + 136 + const boundaryTime = operations[0].createdAt; 137 + let startIdx = 0; 138 + 139 + // Skip operations that are in the previous bundle's boundary 140 + for (let i = 0; i < operations.length; i++) { 141 + const op = operations[i]; 142 + 143 + // Stop if we've moved past the boundary timestamp 144 + if (op.createdAt > boundaryTime) { 145 + break; 146 + } 147 + 148 + // Skip if this CID was in the previous boundary 149 + if (op.createdAt === boundaryTime && prevBoundaryCIDs.has(op.cid)) { 150 + startIdx = i + 1; 151 + continue; 152 + } 153 + 154 + break; 155 + } 156 + 157 + const stripped = operations.slice(startIdx); 158 + if (startIdx > 0) { 159 + console.log(` Stripped ${startIdx} boundary duplicates`); 160 + } 161 + return stripped; 162 + }; 163 + 164 + // ============================================================================ 165 + // PLC Directory Client 166 + // ============================================================================ 167 + 168 + const fetchOperations = async (after: string | null, count: number = 1000): Promise<PLCOperation[]> => { 169 + const params: Record<string, any> = { count }; 170 + if (after) { 171 + params.after = after; 172 + } 173 + 174 + const response = await axios.get<string>(`${PLC_URL}/export`, { 175 + params, 176 + responseType: 'text' 177 + }); 178 + 179 + const lines = response.data.trim().split('\n').filter(l => l); 180 + 181 + return lines.map(line => { 182 + const op = JSON.parse(line) as PLCOperation; 183 + op._raw = line; // Preserve exact JSON 184 + return op; 185 + }); 186 + }; 187 + 188 + // ============================================================================ 189 + // Bundle Operations 190 + // ============================================================================ 191 + 192 + const serializeJSONL = (operations: PLCOperation[]): string => { 193 + const lines = operations.map(op => { 194 + const json = op._raw || JSON.stringify(op); 195 + return json + '\n'; 196 + }); 197 + return lines.join(''); 198 + }; 199 + 200 + const sha256 = (data: Buffer | string): string => { 201 + return crypto.createHash('sha256').update(data).digest('hex'); 202 + }; 203 + 204 + const calculateChainHash = (parent: string, contentHash: string): string => { 205 + let data: string; 206 + if (!parent || parent === '') { 207 + data = `plcbundle:genesis:${contentHash}`; 208 + } else { 209 + data = `${parent}:${contentHash}`; 210 + } 211 + return sha256(data); 212 + }; 213 + 214 + const extractUniqueDIDs = (operations: PLCOperation[]): number => { 215 + const dids = new Set<string>(); 216 + operations.forEach(op => dids.add(op.did)); 217 + return dids.size; 218 + }; 219 + 220 + const saveBundle = async ( 221 + dir: string, 222 + bundleNumber: number, 223 + operations: PLCOperation[], 224 + parentHash: string 225 + ): Promise<BundleMetadata> => { 226 + const filename = `${String(bundleNumber).padStart(6, '0')}.jsonl.zst`; 227 + const filepath = path.join(dir, filename); 228 + 229 + const jsonl = serializeJSONL(operations); 230 + const uncompressedBuffer = Buffer.from(jsonl, 'utf8'); 231 + 232 + const contentHash = sha256(uncompressedBuffer); 233 + const uncompressedSize = uncompressedBuffer.length; 234 + 235 + const chainHash = calculateChainHash(parentHash, contentHash); 236 + 237 + const compressed = compress(uncompressedBuffer, 3); 238 + const compressedBuffer = Buffer.from(compressed); 239 + const compressedHash = sha256(compressedBuffer); 240 + const compressedSize = compressedBuffer.length; 241 + 242 + await fs.writeFile(filepath, compressedBuffer); 243 + 244 + const startTime = operations[0].createdAt; 245 + const endTime = operations[operations.length - 1].createdAt; 246 + const didCount = extractUniqueDIDs(operations); 247 + 248 + return { 249 + bundle_number: bundleNumber, 250 + start_time: startTime, 251 + end_time: endTime, 252 + operation_count: operations.length, 253 + did_count: didCount, 254 + hash: chainHash, 255 + content_hash: contentHash, 256 + parent: parentHash || '', 257 + compressed_hash: compressedHash, 258 + compressed_size: compressedSize, 259 + uncompressed_size: uncompressedSize, 260 + created_at: new Date().toISOString() 261 + }; 262 + }; 263 + 264 + // ============================================================================ 265 + // Main Logic 266 + // ============================================================================ 267 + 268 + const run = async (): Promise<void> => { 269 + const dir = process.argv[2] || './plc_bundles'; 270 + 271 + console.log('PLC Bundle Fetcher'); 272 + console.log('=================='); 273 + console.log(); 274 + console.log(`Directory: ${dir}`); 275 + console.log(`Source: ${PLC_URL}`); 276 + console.log(); 277 + 278 + await fs.mkdir(dir, { recursive: true }); 279 + 280 + const index = await loadIndex(dir); 281 + 282 + let currentBundle = index.last_bundle + 1; 283 + let cursor: string | null = null; 284 + let parentHash = ''; 285 + let prevBoundaryCIDs = new Set<string>(); 286 + 287 + if (index.bundles.length > 0) { 288 + const lastBundle = index.bundles[index.bundles.length - 1]; 289 + cursor = lastBundle.end_time; 290 + parentHash = lastBundle.hash; 291 + 292 + try { 293 + const prevOps = await loadBundle(dir, lastBundle.bundle_number); 294 + prevBoundaryCIDs = getBoundaryCIDs(prevOps); 295 + console.log(`Loaded previous bundle boundary: ${prevBoundaryCIDs.size} CIDs`); 296 + } catch (err) { 297 + console.log(`Could not load previous bundle for boundary detection`); 298 + } 299 + 300 + console.log(`Resuming from bundle ${currentBundle}`); 301 + console.log(`Last operation: ${cursor}`); 302 + } else { 303 + console.log('Starting from the beginning (genesis)'); 304 + } 305 + 306 + console.log(); 307 + 308 + let mempool: PLCOperation[] = []; 309 + const seenCIDs = new Set<string>(prevBoundaryCIDs); 310 + let totalFetched = 0; 311 + let totalBundles = 0; 312 + 313 + while (true) { 314 + try { 315 + console.log(`Fetching operations (cursor: ${cursor || 'start'})...`); 316 + const operations = await fetchOperations(cursor, 1000); 317 + 318 + if (operations.length === 0) { 319 + console.log('No more operations available'); 320 + break; 321 + } 322 + 323 + // Deduplicate 324 + const uniqueOps = operations.filter(op => { 325 + if (seenCIDs.has(op.cid)) { 326 + return false; 327 + } 328 + seenCIDs.add(op.cid); 329 + return true; 330 + }); 331 + 332 + console.log(` Fetched ${operations.length} operations (${uniqueOps.length} unique)`); 333 + totalFetched += uniqueOps.length; 334 + 335 + mempool.push(...uniqueOps); 336 + cursor = operations[operations.length - 1].createdAt; 337 + 338 + while (mempool.length >= BUNDLE_SIZE) { 339 + const bundleOps = mempool.splice(0, BUNDLE_SIZE); 340 + 341 + console.log(`\nCreating bundle ${String(currentBundle).padStart(6, '0')}...`); 342 + 343 + const metadata = await saveBundle(dir, currentBundle, bundleOps, parentHash); 344 + 345 + index.bundles.push(metadata); 346 + index.last_bundle = currentBundle; 347 + index.total_size_bytes += metadata.compressed_size; 348 + 349 + await saveIndex(dir, index); 350 + 351 + console.log(` ✓ Bundle ${String(currentBundle).padStart(6, '0')}: ${metadata.operation_count} ops, ${metadata.did_count} DIDs`); 352 + console.log(` Chain Hash: ${metadata.hash}`); 353 + console.log(` Content Hash: ${metadata.content_hash}`); 354 + console.log(` Size: ${(metadata.compressed_size / 1024).toFixed(1)} KB`); 355 + 356 + // Get boundary CIDs for next bundle 357 + prevBoundaryCIDs = getBoundaryCIDs(bundleOps); 358 + console.log(` Boundary CIDs: ${prevBoundaryCIDs.size}`); 359 + console.log(); 360 + 361 + parentHash = metadata.hash; 362 + currentBundle++; 363 + totalBundles++; 364 + } 365 + 366 + await new Promise(resolve => setTimeout(resolve, 100)); 367 + 368 + } catch (err: any) { 369 + console.error(`Error: ${err.message}`); 370 + 371 + if (err.response) { 372 + console.error(`HTTP Status: ${err.response.status}`); 373 + } 374 + 375 + if (err.code === 'ECONNRESET' || err.code === 'ECONNABORTED') { 376 + console.log('Connection error, retrying in 5 seconds...'); 377 + await new Promise(resolve => setTimeout(resolve, 5000)); 378 + continue; 379 + } 380 + 381 + break; 382 + } 383 + } 384 + 385 + await saveIndex(dir, index); 386 + 387 + console.log(); 388 + console.log('================'); 389 + console.log('Complete!'); 390 + console.log('================'); 391 + console.log(`Total operations fetched: ${totalFetched}`); 392 + console.log(`Bundles created: ${totalBundles}`); 393 + console.log(`Total bundles: ${index.bundles.length}`); 394 + console.log(`Mempool: ${mempool.length} operations`); 395 + console.log(`Total size: ${(index.total_size_bytes / 1024 / 1024).toFixed(1)} MB`); 396 + 397 + if (mempool.length > 0) { 398 + console.log(); 399 + console.log(`Note: ${mempool.length} operations in mempool`); 400 + } 401 + }; 402 + 403 + // ============================================================================ 404 + // Entry Point 405 + // ============================================================================ 406 + 407 + run().catch(err => { 408 + console.error('Fatal error:', err.message); 409 + console.error(err.stack); 410 + process.exit(1); 411 + });
+212
pnpm-lock.yaml
··· 1 + lockfileVersion: '9.0' 2 + 3 + settings: 4 + autoInstallPeers: true 5 + excludeLinksFromLockfile: false 6 + 7 + importers: 8 + 9 + .: 10 + dependencies: 11 + '@bokuweb/zstd-wasm': 12 + specifier: ^0.0.27 13 + version: 0.0.27 14 + axios: 15 + specifier: ^1.13.0 16 + version: 1.13.0 17 + 18 + packages: 19 + 20 + '@bokuweb/zstd-wasm@0.0.27': 21 + resolution: {integrity: sha512-GDm2uOTK3ESjnYmSeLQifJnBsRCWajKLvN32D2ZcQaaCIJI/Hse9s74f7APXjHit95S10UImsRGkTsbwHmrtmg==} 22 + 23 + asynckit@0.4.0: 24 + resolution: {integrity: sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==} 25 + 26 + axios@1.13.0: 27 + resolution: {integrity: sha512-zt40Pz4zcRXra9CVV31KeyofwiNvAbJ5B6YPz9pMJ+yOSLikvPT4Yi5LjfgjRa9CawVYBaD1JQzIVcIvBejKeA==} 28 + 29 + call-bind-apply-helpers@1.0.2: 30 + resolution: {integrity: sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==} 31 + engines: {node: '>= 0.4'} 32 + 33 + combined-stream@1.0.8: 34 + resolution: {integrity: sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==} 35 + engines: {node: '>= 0.8'} 36 + 37 + delayed-stream@1.0.0: 38 + resolution: {integrity: sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==} 39 + engines: {node: '>=0.4.0'} 40 + 41 + dunder-proto@1.0.1: 42 + resolution: {integrity: sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==} 43 + engines: {node: '>= 0.4'} 44 + 45 + es-define-property@1.0.1: 46 + resolution: {integrity: sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==} 47 + engines: {node: '>= 0.4'} 48 + 49 + es-errors@1.3.0: 50 + resolution: {integrity: sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==} 51 + engines: {node: '>= 0.4'} 52 + 53 + es-object-atoms@1.1.1: 54 + resolution: {integrity: sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==} 55 + engines: {node: '>= 0.4'} 56 + 57 + es-set-tostringtag@2.1.0: 58 + resolution: {integrity: sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==} 59 + engines: {node: '>= 0.4'} 60 + 61 + follow-redirects@1.15.11: 62 + resolution: {integrity: sha512-deG2P0JfjrTxl50XGCDyfI97ZGVCxIpfKYmfyrQ54n5FO/0gfIES8C/Psl6kWVDolizcaaxZJnTS0QSMxvnsBQ==} 63 + engines: {node: '>=4.0'} 64 + peerDependencies: 65 + debug: '*' 66 + peerDependenciesMeta: 67 + debug: 68 + optional: true 69 + 70 + form-data@4.0.4: 71 + resolution: {integrity: sha512-KrGhL9Q4zjj0kiUt5OO4Mr/A/jlI2jDYs5eHBpYHPcBEVSiipAvn2Ko2HnPe20rmcuuvMHNdZFp+4IlGTMF0Ow==} 72 + engines: {node: '>= 6'} 73 + 74 + function-bind@1.1.2: 75 + resolution: {integrity: sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==} 76 + 77 + get-intrinsic@1.3.0: 78 + resolution: {integrity: sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==} 79 + engines: {node: '>= 0.4'} 80 + 81 + get-proto@1.0.1: 82 + resolution: {integrity: sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==} 83 + engines: {node: '>= 0.4'} 84 + 85 + gopd@1.2.0: 86 + resolution: {integrity: sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==} 87 + engines: {node: '>= 0.4'} 88 + 89 + has-symbols@1.1.0: 90 + resolution: {integrity: sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==} 91 + engines: {node: '>= 0.4'} 92 + 93 + has-tostringtag@1.0.2: 94 + resolution: {integrity: sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==} 95 + engines: {node: '>= 0.4'} 96 + 97 + hasown@2.0.2: 98 + resolution: {integrity: sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==} 99 + engines: {node: '>= 0.4'} 100 + 101 + math-intrinsics@1.1.0: 102 + resolution: {integrity: sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==} 103 + engines: {node: '>= 0.4'} 104 + 105 + mime-db@1.52.0: 106 + resolution: {integrity: sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==} 107 + engines: {node: '>= 0.6'} 108 + 109 + mime-types@2.1.35: 110 + resolution: {integrity: sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==} 111 + engines: {node: '>= 0.6'} 112 + 113 + proxy-from-env@1.1.0: 114 + resolution: {integrity: sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==} 115 + 116 + snapshots: 117 + 118 + '@bokuweb/zstd-wasm@0.0.27': {} 119 + 120 + asynckit@0.4.0: {} 121 + 122 + axios@1.13.0: 123 + dependencies: 124 + follow-redirects: 1.15.11 125 + form-data: 4.0.4 126 + proxy-from-env: 1.1.0 127 + transitivePeerDependencies: 128 + - debug 129 + 130 + call-bind-apply-helpers@1.0.2: 131 + dependencies: 132 + es-errors: 1.3.0 133 + function-bind: 1.1.2 134 + 135 + combined-stream@1.0.8: 136 + dependencies: 137 + delayed-stream: 1.0.0 138 + 139 + delayed-stream@1.0.0: {} 140 + 141 + dunder-proto@1.0.1: 142 + dependencies: 143 + call-bind-apply-helpers: 1.0.2 144 + es-errors: 1.3.0 145 + gopd: 1.2.0 146 + 147 + es-define-property@1.0.1: {} 148 + 149 + es-errors@1.3.0: {} 150 + 151 + es-object-atoms@1.1.1: 152 + dependencies: 153 + es-errors: 1.3.0 154 + 155 + es-set-tostringtag@2.1.0: 156 + dependencies: 157 + es-errors: 1.3.0 158 + get-intrinsic: 1.3.0 159 + has-tostringtag: 1.0.2 160 + hasown: 2.0.2 161 + 162 + follow-redirects@1.15.11: {} 163 + 164 + form-data@4.0.4: 165 + dependencies: 166 + asynckit: 0.4.0 167 + combined-stream: 1.0.8 168 + es-set-tostringtag: 2.1.0 169 + hasown: 2.0.2 170 + mime-types: 2.1.35 171 + 172 + function-bind@1.1.2: {} 173 + 174 + get-intrinsic@1.3.0: 175 + dependencies: 176 + call-bind-apply-helpers: 1.0.2 177 + es-define-property: 1.0.1 178 + es-errors: 1.3.0 179 + es-object-atoms: 1.1.1 180 + function-bind: 1.1.2 181 + get-proto: 1.0.1 182 + gopd: 1.2.0 183 + has-symbols: 1.1.0 184 + hasown: 2.0.2 185 + math-intrinsics: 1.1.0 186 + 187 + get-proto@1.0.1: 188 + dependencies: 189 + dunder-proto: 1.0.1 190 + es-object-atoms: 1.1.1 191 + 192 + gopd@1.2.0: {} 193 + 194 + has-symbols@1.1.0: {} 195 + 196 + has-tostringtag@1.0.2: 197 + dependencies: 198 + has-symbols: 1.1.0 199 + 200 + hasown@2.0.2: 201 + dependencies: 202 + function-bind: 1.1.2 203 + 204 + math-intrinsics@1.1.0: {} 205 + 206 + mime-db@1.52.0: {} 207 + 208 + mime-types@2.1.35: 209 + dependencies: 210 + mime-db: 1.52.0 211 + 212 + proxy-from-env@1.1.0: {}