@@ -95,8 +95,16 @@ export type PerformanceObserverOptions<T> = {
9595
9696 /**
9797 * Whether to enable buffered observation mode.
98- * When true, captures all performance entries that occurred before observation started.
99- * When false, only captures entries after subscription begins.
98+ *
99+ * When true, captures all performance marks and measures that exist in the Node.js
100+ * performance buffer at the time `subscribe()` is called. This allows you to capture
101+ * performance entries that were created before the observer was created.
102+ *
103+ * When false, only captures entries created after `subscribe()` is called.
104+ *
105+ * **Important:** The implementation uses a manual approach via `performance.getEntriesByType()`
106+ * rather than the native PerformanceObserver `buffered` option. See the `subscribe()` method
107+ * documentation for details on guarantees and limitations.
100108 *
101109 * @default true
102110 */
@@ -312,6 +320,34 @@ export class PerformanceObserverSink<T> {
312320 * When buffered mode is enabled, any existing buffered entries are immediately flushed.
313321 * If the sink is closed, items stay in the queue until reopened.
314322 *
323+ * ## Buffered Mode Implementation
324+ *
325+ * When `captureBufferedEntries` is true, this method captures all performance entries
326+ * that exist in the Node.js performance buffer at the time of subscription.
327+ *
328+ * **Why Manual Approach:**
329+ * The standard `buffered: true` option in PerformanceObserver.observe() is not used
330+ * because it has proven unreliable in Node.js environments. Instead, we use
331+ * `performance.getEntriesByType()` to manually retrieve buffered entries.
332+ *
333+ * **Guarantees:**
334+ * - All marks and measures in the performance buffer at subscription time will be captured
335+ * - Entries are processed synchronously before the observer begins watching for new entries
336+ * - No entries created after subscription will be missed (observer handles them)
337+ *
338+ * **Limitations:**
339+ * - Potential for duplicate processing if an entry exists both in the buffer and is
340+ * delivered by the observer callback (though Node.js typically avoids this)
341+ * - Performance buffer has a limited size; very old entries may have been evicted by Node.js
342+ * - The manual approach captures a snapshot at subscription time; there's a small race
343+ * condition window where entries created during getEntriesByType() execution might
344+ * be captured by both the manual call and the observer
345+ *
346+ * **Memory Management:**
347+ * Applications should call `performance.clearMarks()` and `performance.clearMeasures()`
348+ * periodically to prevent the Node.js performance buffer from growing unbounded.
349+ * This is especially important when using buffered mode, as the entire buffer is
350+ * processed on subscription.
315351 */
316352 subscribe ( ) : void {
317353 if ( this . #observer) {
@@ -322,11 +358,8 @@ export class PerformanceObserverSink<T> {
322358 this . processPerformanceEntries ( list . getEntries ( ) ) ;
323359 } ) ;
324360
325- // When buffered mode is enabled, Node.js PerformanceObserver invokes
326- // the callback synchronously with all buffered entries before observe() returns.
327- // However, entries created before any observer existed may not be buffered by Node.js.
328- // We manually retrieve entries from the performance buffer using getEntriesByType()
329- // to capture entries that were created before the observer was created.
361+ // Manually capture buffered entries instead of using the native buffered option.
362+ // See method documentation above for rationale and guarantees.
330363 if ( this . #buffered) {
331364 const existingMarks = performance . getEntriesByType ( 'mark' ) ;
332365 const existingMeasures = performance . getEntriesByType ( 'measure' ) ;
@@ -336,8 +369,7 @@ export class PerformanceObserverSink<T> {
336369
337370 this . #observer. observe ( {
338371 entryTypes : OBSERVED_TYPES ,
339- // @NOTE : This is for unknown reasons not working, and we manually do it above
340- // buffered: this.#buffered,
372+ // Note: buffered option intentionally omitted. See method documentation above.
341373 } ) ;
342374 }
343375
0 commit comments