Press n or j to go to the next uncovered block, b, p or k for the previous block.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 | 732x 732x 732x 732x 732x 732x 732x 732x 83476653x 83476653x 83476653x 83476653x 83476653x 83476653x 2387668085x 2387668085x 2387668085x 107243390x 107243390x 107243390x 725459367x 89629037x 217338270x 217338270x 213917462x 75546062x 138371400x 25566116x 25566112x 25566112x 25566112x 25566112x 25566112x 25566112x 4x 112805284x 2451033383x 2451033383x 89629037x 89629037x 89629037x 89629037x 23176247x 23176247x 173121204x 173121204x 173121204x 173121204x 57926524x 115194680x 16x 19288102x 53510x 4649182x 20x 4x 22791763x 4259743x 12x 12x 12x 8x 8x 8x 8x 8x 4x 4x 59502964x 4649356x 4x 48290032x 9697168x 9697168x 9659820x 9659820x 207678450x 207678450x 21587052x 241x 21586811x 186091398x 173121204x 173121200x 57926540x 8x 57926532x 115194660x 128164854x 21139558x 21139558x 21139558x 21139558x 21139558x 21139558x 8x 21139550x 57852478x 152798x 152798x | import { Binary, type BSONElement, BSONError, BSONType, deserialize, type DeserializeOptions, getBigInt64LE, getFloat64LE, getInt32LE, ObjectId, parseToElementsToArray, Timestamp, toUTF8 } from '../../../bson'; // eslint-disable-next-line no-restricted-syntax const enum BSONElementOffset { type = 0, nameOffset = 1, nameLength = 2, offset = 3, length = 4 } /** @internal */ export type JSTypeOf = { [BSONType.null]: null; [BSONType.undefined]: null; [BSONType.double]: number; [BSONType.int]: number; [BSONType.long]: bigint; [BSONType.timestamp]: Timestamp; [BSONType.binData]: Binary; [BSONType.bool]: boolean; [BSONType.objectId]: ObjectId; [BSONType.string]: string; [BSONType.date]: Date; [BSONType.object]: OnDemandDocument; [BSONType.array]: OnDemandDocument; }; /** @internal */ type CachedBSONElement = { element: BSONElement; value: any | undefined }; /** * @internal * * Options for `OnDemandDocument.toObject()`. Validation is required to ensure * that callers provide utf8 validation options. */ export type OnDemandDocumentDeserializeOptions = Omit<DeserializeOptions, 'validation'> & Required<Pick<DeserializeOptions, 'validation'>>; /** @internal */ export class OnDemandDocument { /** * Maps JS strings to elements and jsValues for speeding up subsequent lookups. * - If `false` then name does not exist in the BSON document * - If `CachedBSONElement` instance name exists * - If `cache[name].value == null` jsValue has not yet been parsed * - Null/Undefined values do not get cached because they are zero-length values. */ private readonly cache: Record<string, CachedBSONElement | false | undefined> = Object.create(null); /** Caches the index of elements that have been named */ private readonly indexFound: Record<number, boolean> = Object.create(null); /** All bson elements in this document */ private readonly elements: ReadonlyArray<BSONElement>; constructor( /** BSON bytes, this document begins at offset */ protected readonly bson: Uint8Array, /** The start of the document */ private readonly offset = 0, /** If this is an embedded document, indicates if this was a BSON array */ public readonly isArray = false, /** If elements was already calculated */ elements?: BSONElement[] ) { this.elements = elements ?? parseToElementsToArray(this.bson, offset); } /** Only supports basic latin strings */ private isElementName(name: string, element: BSONElement): boolean { const nameLength = element[BSONElementOffset.nameLength]; const nameOffset = element[BSONElementOffset.nameOffset]; if (name.length !== nameLength) return false; const nameEnd = nameOffset + nameLength; for ( let byteIndex = nameOffset, charIndex = 0; charIndex < name.length && byteIndex < nameEnd; charIndex++, byteIndex++ ) { if (this.bson[byteIndex] !== name.charCodeAt(charIndex)) return false; } return true; } /** * Seeks into the elements array for an element matching the given name. * * @remarks * Caching: * - Caches the existence of a property making subsequent look ups for non-existent properties return immediately * - Caches names mapped to elements to avoid reiterating the array and comparing the name again * - Caches the index at which an element has been found to prevent rechecking against elements already determined to belong to another name * * @param name - a basic latin string name of a BSON element * @returns */ private getElement(name: string | number): CachedBSONElement | null { const cachedElement = this.cache[name]; if (cachedElement === false) return null; if (cachedElement != null) { return cachedElement; } if (typeof name === 'number') { if (this.isArray) { Eif (name < this.elements.length) { const element = this.elements[name]; const cachedElement = { element, value: undefined }; this.cache[name] = cachedElement; this.indexFound[name] = true; return cachedElement; } else { return null; } } else { return null; } } for (let index = 0; index < this.elements.length; index++) { const element = this.elements[index]; // skip this element if it has already been associated with a name if (!(index in this.indexFound) && this.isElementName(name, element)) { const cachedElement = { element, value: undefined }; this.cache[name] = cachedElement; this.indexFound[index] = true; return cachedElement; } } this.cache[name] = false; return null; } /** * Translates BSON bytes into a javascript value. Checking `as` against the BSON element's type * this methods returns the small subset of BSON types that the driver needs to function. * * @remarks * - BSONType.null and BSONType.undefined always return null * - If the type requested does not match this returns null * * @param element - The element to revive to a javascript value * @param as - A type byte expected to be returned */ private toJSValue<T extends keyof JSTypeOf>(element: BSONElement, as: T): JSTypeOf[T]; private toJSValue(element: BSONElement, as: keyof JSTypeOf): any { const type = element[BSONElementOffset.type]; const offset = element[BSONElementOffset.offset]; const length = element[BSONElementOffset.length]; if (as !== type) { return null; } switch (as) { case BSONType.null: case BSONType.undefined: return null; case BSONType.double: return getFloat64LE(this.bson, offset); case BSONType.int: return getInt32LE(this.bson, offset); case BSONType.long: return getBigInt64LE(this.bson, offset); case BSONType.bool: return Boolean(this.bson[offset]); case BSONType.objectId: return new ObjectId(this.bson.subarray(offset, offset + 12)); case BSONType.timestamp: return new Timestamp(getBigInt64LE(this.bson, offset)); case BSONType.string: return toUTF8(this.bson, offset + 4, offset + length - 1, false); case BSONType.binData: { const totalBinarySize = getInt32LE(this.bson, offset); const subType = this.bson[offset + 4]; if (subType === 2) { const subType2BinarySize = getInt32LE(this.bson, offset + 1 + 4); Iif (subType2BinarySize < 0) throw new BSONError('Negative binary type element size found for subtype 0x02'); Iif (subType2BinarySize > totalBinarySize - 4) throw new BSONError('Binary type with subtype 0x02 contains too long binary size'); Iif (subType2BinarySize < totalBinarySize - 4) throw new BSONError('Binary type with subtype 0x02 contains too short binary size'); return new Binary( this.bson.subarray(offset + 1 + 4 + 4, offset + 1 + 4 + 4 + subType2BinarySize), 2 ); } return new Binary( this.bson.subarray(offset + 1 + 4, offset + 1 + 4 + totalBinarySize), subType ); } case BSONType.date: // Pretend this is correct. return new Date(Number(getBigInt64LE(this.bson, offset))); case BSONType.object: return new OnDemandDocument(this.bson, offset); case BSONType.array: return new OnDemandDocument(this.bson, offset, true); default: throw new BSONError(`Unsupported BSON type: ${as}`); } } /** * Returns the number of elements in this BSON document */ public size() { return this.elements.length; } /** * Checks for the existence of an element by name. * * @remarks * Uses `getElement` with the expectation that will populate caches such that a `has` call * followed by a `getElement` call will not repeat the cost paid by the first look up. * * @param name - element name */ public has(name: string): boolean { const cachedElement = this.cache[name]; if (cachedElement === false) return false; Iif (cachedElement != null) return true; return this.getElement(name) != null; } /** * Turns BSON element with `name` into a javascript value. * * @typeParam T - must be one of the supported BSON types determined by `JSTypeOf` this will determine the return type of this function. * @param name - the element name * @param as - the bson type expected * @param required - whether or not the element is expected to exist, if true this function will throw if it is not present */ public get<const T extends keyof JSTypeOf>( name: string | number, as: T, required?: boolean | undefined ): JSTypeOf[T] | null; /** `required` will make `get` throw if name does not exist or is null/undefined */ public get<const T extends keyof JSTypeOf>( name: string | number, as: T, required: true ): JSTypeOf[T]; public get<const T extends keyof JSTypeOf>( name: string | number, as: T, required?: boolean ): JSTypeOf[T] | null { const element = this.getElement(name); if (element == null) { if (required === true) { throw new BSONError(`BSON element "${name}" is missing`); } else { return null; } } if (element.value == null) { const value = this.toJSValue(element.element, as); if (value == null) { if (required === true) { throw new BSONError(`BSON element "${name}" is missing`); } else { return null; } } // It is important to never store null element.value = value; } return element.value; } /** * Supports returning int, double, long, and bool as javascript numbers * * @remarks * **NOTE:** * - Use this _only_ when you believe the potential precision loss of an int64 is acceptable * - This method does not cache the result as Longs or booleans would be stored incorrectly * * @param name - element name * @param required - throws if name does not exist */ public getNumber<const Req extends boolean = false>( name: string, required?: Req ): Req extends true ? number : number | null; public getNumber(name: string, required: boolean): number | null { const maybeBool = this.get(name, BSONType.bool); const bool = maybeBool == null ? null : maybeBool ? 1 : 0; const maybeLong = this.get(name, BSONType.long); const long = maybeLong == null ? null : Number(maybeLong); const result = bool ?? long ?? this.get(name, BSONType.int) ?? this.get(name, BSONType.double); if (required === true && result == null) { throw new BSONError(`BSON element "${name}" is missing`); } return result; } /** * Deserialize this object, DOES NOT cache result so avoid multiple invocations * @param options - BSON deserialization options */ public toObject(options?: OnDemandDocumentDeserializeOptions): Record<string, any> { return deserialize(this.bson, { ...options, index: this.offset, allowObjectSmallerThanBufferSize: true }); } /** Returns this document's bytes only */ toBytes() { const size = getInt32LE(this.bson, this.offset); return this.bson.subarray(this.offset, this.offset + size); } } |