@@ -581,5 +581,85 @@ export const createConsoleArrayOutput = (
581581 } ,
582582} ) ;
583583
584- // TODO: multiOutput - routes entries to different outputs by method
585- // TODO: asyncOutput - buffers entries for better performance
584+ /**
585+ * Creates a {@link ConsoleOutput} that routes entries to multiple outputs.
586+ *
587+ * Useful for logging to both console and file simultaneously.
588+ *
589+ * ### Example
590+ *
591+ * ```ts
592+ * const output = createConsoleMultiOutput([
593+ * createNativeConsoleOutput(),
594+ * createConsoleArrayOutput(entries),
595+ * ]);
596+ * ```
597+ */
598+ export const createConsoleMultiOutput = (
599+ outputs : ReadonlyArray < ConsoleOutput > ,
600+ ) : ConsoleOutput => ( {
601+ write : ( entry , formatEntry ) => {
602+ for ( const output of outputs ) output . write ( entry , formatEntry ) ;
603+ } ,
604+ flush : async ( ) => {
605+ await Promise . all ( outputs . map ( ( o ) => o . flush ?.( ) ) ) ;
606+ } ,
607+ } ) ;
608+
609+ /**
610+ * Creates a {@link ConsoleOutput} that buffers entries for async writing.
611+ *
612+ * Useful for high-throughput scenarios where synchronous logging would be a
613+ * bottleneck. Call `flush()` to ensure all buffered entries are written.
614+ *
615+ * ### Example
616+ *
617+ * ```ts
618+ * const entries: Array<ConsoleEntry> = [];
619+ * const output = createConsoleAsyncOutput({
620+ * write: async (entry) => {
621+ * entries.push(entry);
622+ * },
623+ * });
624+ *
625+ * // Usage with Bun file writer:
626+ * // const writer = Bun.file("app.log").writer();
627+ * // const output = createConsoleAsyncOutput({
628+ * // write: async (entry) => {
629+ * // writer.write(JSON.stringify(entry) + "\\n");
630+ * // },
631+ * // flush: () => writer.flush(),
632+ * // });
633+ * ```
634+ */
635+ export const createConsoleAsyncOutput = ( config : {
636+ readonly write : ( entry : ConsoleEntry ) => void | Promise < void > ;
637+ readonly flush ?: ( ) => void | Promise < void > ;
638+ } ) : ConsoleOutput => {
639+ const buffer : Array < ConsoleEntry > = [ ] ;
640+ let flushPromise : Promise < void > | null = null ;
641+
642+ const processBuffer = async ( ) : Promise < void > => {
643+ while ( buffer . length > 0 ) {
644+ const entry = buffer . shift ( ) ;
645+ if ( entry ) await config . write ( entry ) ;
646+ }
647+ await config . flush ?.( ) ;
648+ } ;
649+
650+ return {
651+ write : ( entry ) => {
652+ buffer . push ( entry ) ;
653+ // Auto-flush when buffer grows (debounced)
654+ if ( ! flushPromise && buffer . length >= 10 ) {
655+ flushPromise = processBuffer ( ) . finally ( ( ) => {
656+ flushPromise = null ;
657+ } ) ;
658+ }
659+ } ,
660+ flush : async ( ) => {
661+ if ( flushPromise ) await flushPromise ;
662+ await processBuffer ( ) ;
663+ } ,
664+ } ;
665+ } ;
0 commit comments