1/**
2 * Wraps console operations in Effect.
3 *
4 * The `Console` service exposes common console methods such as logging,
5 * warnings, errors, groups, counters, tables, and timers. Because console access
6 * goes through a service, programs can use custom console implementations in
7 * tests or other environments. This module also includes scoped helpers that
8 * close console groups or timers automatically.
9 *
10 * @since 2.0.0
11 */
12import type * as Context from "./Context.ts"
13import type * as Effect from "./Effect.ts"
14import { dual } from "./Function.ts"
15import * as core from "./internal/core.ts"
16import * as effect from "./internal/effect.ts"
17import type { Scope } from "./Scope.ts"
18
19/**
20 * Represents a console interface for logging, debugging, timing, and grouping output.
21 *
22 * @category services
23 * @since 2.0.0
24 */
25export interface Console {
26 assert(condition: boolean, ...args: ReadonlyArray<any>): void
27 clear(): void
28 count(label?: string): void
29 countReset(label?: string): void
30 debug(...args: ReadonlyArray<any>): void
31 dir(item: any, options?: any): void
32 dirxml(...args: ReadonlyArray<any>): void
33 error(...args: ReadonlyArray<any>): void
34 group(...args: ReadonlyArray<any>): void
35 groupCollapsed(...args: ReadonlyArray<any>): void
36 groupEnd(): void
37 info(...args: ReadonlyArray<any>): void
38 log(...args: ReadonlyArray<any>): void
39 table(tabularData: any, properties?: ReadonlyArray<string>): void
40 time(label?: string): void
41 timeEnd(label?: string): void
42 timeLog(label?: string, ...args: ReadonlyArray<any>): void
43 trace(...args: ReadonlyArray<any>): void
44 warn(...args: ReadonlyArray<any>): void
45}
46
47/**
48 * Context reference for the current console service in the Effect system, allowing access to the active console implementation from within the Effect context.
49 *
50 * **When to use**
51 *
52 * Use when you need an effect to run against a provided console implementation,
53 * such as tests or alternate runtimes, rather than the default console.
54 *
55 * **Details**
56 *
57 * When no override is provided, the reference resolves to `globalThis.console`.
58 *
59 * **Example** (Accessing the current console)
60 *
61 * ```ts import.meta.vitest
62 * import { Console, Effect } from "effect"
63 *
64 * const messages: Array<unknown> = []
65 * const testConsole: Console.Console = Object.assign(Object.create(console), {
66 * log: (...args: ReadonlyArray<unknown>) => messages.push(...args)
67 * })
68 * const program = Console.consoleWith((console) =>
69 * Effect.sync(() => {
70 * console.log("Hello from current console!")
71 * })
72 * )
73 *
74 * Effect.runSync(Effect.provideService(program, Console.Console, testConsole))
75 * messages // => ["Hello from current console!"]
76 * ```
77 *
78 * @see {@link consoleWith} for using the current console service inside an effect
79 *
80 * @category services
81 * @since 2.0.0
82 */
83export const Console: Context.Reference<Console> = effect.ConsoleRef
84
85/**
86 * Creates an Effect that provides access to the current console service and lets you perform operations with it within an Effect context.
87 *
88 * **Example** (Accessing the current console service)
89 *
90 * ```ts import.meta.vitest
91 * import { Console, Effect } from "effect"
92 *
93 * const messages: Array<unknown> = []
94 * const testConsole: Console.Console = Object.assign(Object.create(console), {
95 * log: (...args: ReadonlyArray<unknown>) => messages.push(...args),
96 * error: (...args: ReadonlyArray<unknown>) => messages.push(...args)
97 * })
98 * const program = Console.consoleWith((console) =>
99 * Effect.sync(() => {
100 * console.log("Hello, world!")
101 * console.error("This is an error message")
102 * })
103 * )
104 *
105 * Effect.runSync(Effect.provideService(program, Console.Console, testConsole))
106 * messages // => ["Hello, world!", "This is an error message"]
107 * ```
108 *
109 * @category constructors
110 * @since 2.0.0
111 */
112export const consoleWith = <A, E, R>(f: (console: Console) => Effect.Effect<A, E, R>): Effect.Effect<A, E, R> =>
113 core.withFiber((fiber) => f(fiber.getRef(Console)))
114
115/**
116 * Writes the supplied assertion message to the console as an error when `condition` is false; when `condition` is true, no console output is produced.
117 *
118 * **Example** (Logging failed assertions)
119 *
120 * ```ts import.meta.vitest
121 * import { Console, Effect } from "effect"
122 *
123 * const errors: Array<unknown> = []
124 * const testConsole: Console.Console = Object.assign(Object.create(console), {
125 * assert: (condition: boolean, ...args: ReadonlyArray<unknown>) => {
126 * if (!condition) errors.push(...args)
127 * }
128 * })
129 * const program = Effect.gen(function*() {
130 * yield* Console.assert(2 + 2 === 4, "Math is working correctly")
131 * yield* Console.assert(2 + 2 === 5, "This will be logged as an error")
132 * })
133 *
134 * Effect.runSync(Effect.provideService(program, Console.Console, testConsole))
135 * errors // => ["This will be logged as an error"]
136 * ```
137 *
138 * @category accessors
139 * @since 2.0.0
140 */
141export const assert = (condition: boolean, ...args: ReadonlyArray<any>): Effect.Effect<void> =>
142 consoleWith((console) =>
143 effect.sync(() => {
144 console.assert(condition, ...args)
145 })
146 )
147
148/**
149 * Runs the current console service's clear operation.
150 *
151 * **When to use**
152 *
153 * Use to request that the active console implementation clear its visible
154 * output.
155 *
156 * **Gotchas**
157 *
158 * The clearing behavior depends on the active console implementation and host
159 * environment.
160 *
161 * **Example** (Clearing console output)
162 *
163 * ```ts import.meta.vitest
164 * import { Console, Effect } from "effect"
165 *
166 * const operations: Array<string> = []
167 * const testConsole: Console.Console = Object.assign(Object.create(console), {
168 * log: (message: string) => operations.push(`log:${message}`),
169 * clear: () => operations.push("clear")
170 * })
171 * const program = Effect.gen(function*() {
172 * yield* Console.log("This will be cleared")
173 * yield* Console.clear
174 * yield* Console.log("This appears after clearing")
175 * })
176 *
177 * Effect.runSync(Effect.provideService(program, Console.Console, testConsole))
178 * operations // => ["log:This will be cleared", "clear", "log:This appears after clearing"]
179 * ```
180 *
181 * @category accessors
182 * @since 2.0.0
183 */
184export const clear: Effect.Effect<void> = consoleWith((console) =>
185 effect.sync(() => {
186 console.clear()
187 })
188)
189
190/**
191 * Logs and increments the counter associated with `label`, using the console's default counter when no label is provided.
192 *
193 * **Example** (Counting repeated calls)
194 *
195 * ```ts import.meta.vitest
196 * import { Console, Effect } from "effect"
197 *
198 * const counters = new Map<string, number>()
199 * const messages: Array<string> = []
200 * const testConsole: Console.Console = Object.assign(Object.create(console), {
201 * count: (label = "default") => {
202 * const count = (counters.get(label) ?? 0) + 1
203 * counters.set(label, count)
204 * messages.push(`${label}: ${count}`)
205 * }
206 * })
207 * const program = Effect.gen(function*() {
208 * yield* Console.count("my-counter")
209 * yield* Console.count("my-counter")
210 * yield* Console.count()
211 * })
212 *
213 * Effect.runSync(Effect.provideService(program, Console.Console, testConsole))
214 * messages // => ["my-counter: 1", "my-counter: 2", "default: 1"]
215 * ```
216 *
217 * @category accessors
218 * @since 2.0.0
219 */
220export const count = (label?: string): Effect.Effect<void> =>
221 consoleWith((console) =>
222 effect.sync(() => {
223 console.count(label)
224 })
225 )
226
227/**
228 * Resets the counter associated with the specified label back to zero.
229 *
230 * **Example** (Resetting a counter)
231 *
232 * ```ts import.meta.vitest
233 * import { Console, Effect } from "effect"
234 *
235 * const counters = new Map<string, number>()
236 * const messages: Array<string> = []
237 * const testConsole: Console.Console = Object.assign(Object.create(console), {
238 * count: (label = "default") => {
239 * const count = (counters.get(label) ?? 0) + 1
240 * counters.set(label, count)
241 * messages.push(`${label}: ${count}`)
242 * },
243 * countReset: (label = "default") => counters.set(label, 0)
244 * })
245 * const program = Effect.gen(function*() {
246 * yield* Console.count("my-counter")
247 * yield* Console.count("my-counter")
248 * yield* Console.countReset("my-counter")
249 * yield* Console.count("my-counter")
250 * })
251 *
252 * Effect.runSync(Effect.provideService(program, Console.Console, testConsole))
253 * messages // => ["my-counter: 1", "my-counter: 2", "my-counter: 1"]
254 * ```
255 *
256 * @category accessors
257 * @since 2.0.0
258 */
259export const countReset = (label?: string): Effect.Effect<void> =>
260 consoleWith((console) =>
261 effect.sync(() => {
262 console.countReset(label)
263 })
264 )
265
266/**
267 * Writes a debug message through the current `Console` service.
268 *
269 * **Details**
270 *
271 * The arguments are passed to the service's `debug` method when the returned
272 * Effect is executed. Any filtering behavior depends on the active console
273 * implementation.
274 *
275 * **Example** (Writing debug messages)
276 *
277 * ```ts import.meta.vitest
278 * import { Console, Effect } from "effect"
279 *
280 * const messages: Array<ReadonlyArray<unknown>> = []
281 * const testConsole: Console.Console = Object.assign(Object.create(console), {
282 * debug: (...args: ReadonlyArray<unknown>) => messages.push(args)
283 * })
284 * const program = Effect.gen(function*() {
285 * yield* Console.debug("Debug info:", { userId: 123, action: "login" })
286 * yield* Console.debug("Processing step", 1, "of", 5)
287 * })
288 *
289 * Effect.runSync(Effect.provideService(program, Console.Console, testConsole))
290 * messages // => [["Debug info:", { userId: 123, action: "login" }], ["Processing step", 1, "of", 5]]
291 * ```
292 *
293 * @category accessors
294 * @since 2.0.0
295 */
296export const debug = (...args: ReadonlyArray<any>): Effect.Effect<void> =>
297 consoleWith((console) =>
298 effect.sync(() => {
299 console.debug(...args)
300 })
301 )
302
303/**
304 * Displays an interactive list of the properties of the specified object, optionally using console-specific inspection options for debugging complex data structures.
305 *
306 * **Example** (Inspecting an object)
307 *
308 * ```ts import.meta.vitest
309 * import { Console, Effect } from "effect"
310 *
311 * const inspected: Array<unknown> = []
312 * const testConsole: Console.Console = Object.assign(Object.create(console), {
313 * dir: (item: unknown, options?: unknown) => inspected.push([item, options])
314 * })
315 * const program = Effect.gen(function*() {
316 * const obj = { name: "John", age: 30, nested: { city: "New York" } }
317 * yield* Console.dir(obj)
318 * yield* Console.dir(obj, { depth: 2 })
319 * })
320 *
321 * Effect.runSync(Effect.provideService(program, Console.Console, testConsole))
322 * const expected = [
323 * [{ name: "John", age: 30, nested: { city: "New York" } }, undefined],
324 * [{ name: "John", age: 30, nested: { city: "New York" } }, { depth: 2 }]
325 * ]
326 * inspected // => expected
327 * ```
328 *
329 * @category accessors
330 * @since 2.0.0
331 */
332export const dir = (item: any, options?: any): Effect.Effect<void> =>
333 consoleWith((console) =>
334 effect.sync(() => {
335 console.dir(item, options)
336 })
337 )
338
339/**
340 * Displays an interactive tree of descendant XML or HTML elements, which is particularly useful for inspecting DOM elements in browser environments.
341 *
342 * **Example** (Inspecting XML-like data)
343 *
344 * ```ts import.meta.vitest
345 * import { Console, Effect } from "effect"
346 *
347 * const messages: Array<unknown> = []
348 * const testConsole: Console.Console = Object.assign(Object.create(console), {
349 * dirxml: (...args: ReadonlyArray<unknown>) => messages.push(...args)
350 * })
351 * const program = Effect.gen(function*() {
352 * yield* Console.dirxml("<user id=\"1\">Ada</user>")
353 * })
354 *
355 * Effect.runSync(Effect.provideService(program, Console.Console, testConsole))
356 * messages // => ["<user id=\"1\">Ada</user>"]
357 * ```
358 *
359 * @category accessors
360 * @since 2.0.0
361 */
362export const dirxml = (...args: ReadonlyArray<any>): Effect.Effect<void> =>
363 consoleWith((console) =>
364 effect.sync(() => {
365 console.dirxml(...args)
366 })
367 )
368
369/**
370 * Writes an error-level message to the console, typically displayed with error
371 * styling by the active console implementation.
372 *
373 * **Example** (Writing error messages)
374 *
375 * ```ts import.meta.vitest
376 * import { Console, Effect } from "effect"
377 *
378 * const messages: Array<ReadonlyArray<unknown>> = []
379 * const testConsole: Console.Console = Object.assign(Object.create(console), {
380 * error: (...args: ReadonlyArray<unknown>) => messages.push(args)
381 * })
382 * const program = Effect.gen(function*() {
383 * yield* Console.error("Something went wrong!")
384 * yield* Console.error("Error details:", {
385 * code: 500,
386 * message: "Internal Server Error"
387 * })
388 * })
389 *
390 * Effect.runSync(Effect.provideService(program, Console.Console, testConsole))
391 * const expected = [
392 * ["Something went wrong!"],
393 * ["Error details:", { code: 500, message: "Internal Server Error" }]
394 * ]
395 * messages // => expected
396 * ```
397 *
398 * @category accessors
399 * @since 2.0.0
400 */
401export const error = (...args: ReadonlyArray<any>): Effect.Effect<void> =>
402 consoleWith((console) =>
403 effect.sync(() => {
404 console.error(...args)
405 })
406 )
407
408/**
409 * Creates a scoped console group, optionally collapsed and labeled, and closes it automatically when the Effect scope is finalized.
410 *
411 * **Example** (Grouping scoped output)
412 *
413 * ```ts import.meta.vitest
414 * import { Console, Effect } from "effect"
415 *
416 * const operations: Array<string> = []
417 * const testConsole: Console.Console = Object.assign(Object.create(console), {
418 * group: (label?: string) => operations.push(`group:${label}`),
419 * groupEnd: () => operations.push("groupEnd"),
420 * log: (message: string) => operations.push(`log:${message}`)
421 * })
422 * const program = Effect.gen(function*() {
423 * yield* Effect.scoped(
424 * Effect.gen(function*() {
425 * yield* Console.group({ label: "User Processing" })
426 * yield* Console.log("Loading user data...")
427 * yield* Console.log("Validating user...")
428 * yield* Console.log("User processed successfully")
429 * })
430 * )
431 * })
432 *
433 * Effect.runSync(Effect.provideService(program, Console.Console, testConsole))
434 * const expected = [
435 * "group:User Processing",
436 * "log:Loading user data...",
437 * "log:Validating user...",
438 * "log:User processed successfully",
439 * "groupEnd"
440 * ]
441 * operations // => expected
442 * ```
443 *
444 * @category accessors
445 * @since 2.0.0
446 */
447export const group = (
448 options?: { label?: string | undefined; collapsed?: boolean | undefined } | undefined
449): Effect.Effect<void, never, Scope> =>
450 consoleWith((console) =>
451 effect.acquireRelease(
452 effect.sync(() => {
453 if (options?.collapsed) {
454 console.groupCollapsed(options.label)
455 } else {
456 console.group(options?.label)
457 }
458 }),
459 () =>
460 effect.sync(() => {
461 console.groupEnd()
462 })
463 )
464 )
465
466/**
467 * Writes an informational message to the console, typically displayed with info
468 * styling by the active console implementation.
469 *
470 * **Example** (Writing informational messages)
471 *
472 * ```ts import.meta.vitest
473 * import { Console, Effect } from "effect"
474 *
475 * const messages: Array<ReadonlyArray<unknown>> = []
476 * const testConsole: Console.Console = Object.assign(Object.create(console), {
477 * info: (...args: ReadonlyArray<unknown>) => messages.push(args)
478 * })
479 * const program = Effect.gen(function*() {
480 * yield* Console.info("Application started successfully")
481 * yield* Console.info("Server configuration:", {
482 * port: 3000,
483 * env: "development"
484 * })
485 * })
486 *
487 * Effect.runSync(Effect.provideService(program, Console.Console, testConsole))
488 * const expected = [
489 * ["Application started successfully"],
490 * ["Server configuration:", { port: 3000, env: "development" }]
491 * ]
492 * messages // => expected
493 * ```
494 *
495 * @category accessors
496 * @since 2.0.0
497 */
498export const info = (...args: ReadonlyArray<any>): Effect.Effect<void> =>
499 consoleWith((console) =>
500 effect.sync(() => {
501 console.info(...args)
502 })
503 )
504
505/**
506 * Logs a general-purpose message to the console.
507 *
508 * **Example** (Writing log messages)
509 *
510 * ```ts import.meta.vitest
511 * import { Console, Effect } from "effect"
512 *
513 * const messages: Array<ReadonlyArray<unknown>> = []
514 * const testConsole: Console.Console = Object.assign(Object.create(console), {
515 * log: (...args: ReadonlyArray<unknown>) => messages.push(args)
516 * })
517 * const program = Effect.gen(function*() {
518 * yield* Console.log("Hello, world!")
519 * yield* Console.log("User data:", { name: "John", age: 30 })
520 * yield* Console.log("Processing", 42, "items")
521 * })
522 *
523 * Effect.runSync(Effect.provideService(program, Console.Console, testConsole))
524 * const expected = [
525 * ["Hello, world!"],
526 * ["User data:", { name: "John", age: 30 }],
527 * ["Processing", 42, "items"]
528 * ]
529 * messages // => expected
530 * ```
531 *
532 * @category accessors
533 * @since 2.0.0
534 */
535export const log = (...args: ReadonlyArray<any>): Effect.Effect<void> =>
536 consoleWith((console) =>
537 effect.sync(() => {
538 console.log(...args)
539 })
540 )
541
542/**
543 * Displays tabular data as a formatted table in the console, optionally limited to selected properties.
544 *
545 * **Example** (Displaying tabular data)
546 *
547 * ```ts import.meta.vitest
548 * import { Console, Effect } from "effect"
549 *
550 * const calls: Array<unknown> = []
551 * const testConsole: Console.Console = Object.assign(Object.create(console), {
552 * table: (data: ReadonlyArray<unknown>, properties?: ReadonlyArray<string>) => {
553 * calls.push({ rows: data.length, properties })
554 * }
555 * })
556 *
557 * const program = Effect.gen(function*() {
558 * const users = [
559 * { name: "John", age: 30, city: "New York" },
560 * { name: "Jane", age: 25, city: "London" },
561 * { name: "Bob", age: 35, city: "Paris" }
562 * ]
563 * yield* Console.table(users)
564 * yield* Console.table(users, ["name", "age"]) // Only show specific columns
565 * })
566 *
567 * Effect.runSync(Effect.provideService(program, Console.Console, testConsole))
568 * calls // => [{ rows: 3, properties: undefined }, { rows: 3, properties: ["name", "age"] }]
569 * ```
570 *
571 * @category accessors
572 * @since 2.0.0
573 */
574export const table = (tabularData: any, properties?: ReadonlyArray<string>): Effect.Effect<void> =>
575 consoleWith((console) =>
576 effect.sync(() => {
577 console.table(tabularData, properties)
578 })
579 )
580
581/**
582 * Starts a scoped timer for `label` and automatically ends it when the Effect scope is finalized.
583 *
584 * **Example** (Timing scoped work)
585 *
586 * ```ts import.meta.vitest
587 * import { Console, Effect } from "effect"
588 *
589 * const operations: Array<string> = []
590 * const testConsole: Console.Console = Object.assign(Object.create(console), {
591 * time: (label?: string) => operations.push(`start:${label}`),
592 * timeEnd: (label?: string) => operations.push(`end:${label}`),
593 * log: (message: string) => operations.push(`log:${message}`)
594 * })
595 *
596 * const program = Effect.gen(function*() {
597 * yield* Effect.scoped(
598 * Effect.gen(function*() {
599 * yield* Console.time("operation-timer")
600 * yield* Console.log("Operation completed")
601 * // Timer ends automatically when scope closes
602 * })
603 * )
604 * })
605 *
606 * Effect.runSync(Effect.provideService(program, Console.Console, testConsole))
607 * operations // => ["start:operation-timer", "log:Operation completed", "end:operation-timer"]
608 * ```
609 *
610 * @category accessors
611 * @since 2.0.0
612 */
613export const time = (label?: string | undefined): Effect.Effect<void, never, Scope> =>
614 consoleWith((console) =>
615 effect.acquireRelease(
616 effect.sync(() => {
617 console.time(label)
618 }),
619 () =>
620 effect.sync(() => {
621 console.timeEnd(label)
622 })
623 )
624 )
625
626/**
627 * Logs the elapsed time for an existing timer without stopping it, allowing progress reports for long-running operations.
628 *
629 * **Example** (Logging timer progress)
630 *
631 * ```ts import.meta.vitest
632 * import { Console, Effect } from "effect"
633 *
634 * const operations: Array<unknown> = []
635 * const testConsole: Console.Console = Object.assign(Object.create(console), {
636 * time: (label?: string) => operations.push(["start", label]),
637 * timeLog: (label?: string, ...args: ReadonlyArray<unknown>) => operations.push(["log", label, ...args]),
638 * timeEnd: (label?: string) => operations.push(["end", label])
639 * })
640 *
641 * const program = Effect.gen(function*() {
642 * yield* Effect.scoped(
643 * Effect.gen(function*() {
644 * yield* Console.time("long-operation")
645 * yield* Console.timeLog("long-operation", "Halfway done")
646 * // Timer ends when scope closes
647 * })
648 * )
649 * })
650 *
651 * Effect.runSync(Effect.provideService(program, Console.Console, testConsole))
652 * operations // => [["start", "long-operation"], ["log", "long-operation", "Halfway done"], ["end", "long-operation"]]
653 * ```
654 *
655 * @category accessors
656 * @since 2.0.0
657 */
658export const timeLog = (label?: string, ...args: ReadonlyArray<any>): Effect.Effect<void> =>
659 consoleWith((console) =>
660 effect.sync(() => {
661 console.timeLog(label, ...args)
662 })
663 )
664
665/**
666 * Writes the current stack trace to the console to show how the current point in
667 * the code was reached.
668 *
669 * **Example** (Writing stack traces)
670 *
671 * ```ts import.meta.vitest
672 * import { Console, Effect } from "effect"
673 *
674 * const traces: Array<ReadonlyArray<unknown>> = []
675 * const testConsole: Console.Console = Object.assign(Object.create(console), {
676 * trace: (...args: ReadonlyArray<unknown>) => traces.push(args)
677 * })
678 *
679 * const program = Effect.gen(function*() {
680 * yield* Console.trace("Debug trace point")
681 * yield* Console.trace("Function call:", { functionName: "processData" })
682 * })
683 *
684 * Effect.runSync(Effect.provideService(program, Console.Console, testConsole))
685 * traces // => [["Debug trace point"], ["Function call:", { functionName: "processData" }]]
686 * ```
687 *
688 * @category accessors
689 * @since 2.0.0
690 */
691export const trace = (...args: ReadonlyArray<any>): Effect.Effect<void> =>
692 consoleWith((console) =>
693 effect.sync(() => {
694 console.trace(...args)
695 })
696 )
697
698/**
699 * Writes a warning-level message to the console, typically displayed with
700 * warning styling by the active console implementation.
701 *
702 * **Example** (Writing warning messages)
703 *
704 * ```ts import.meta.vitest
705 * import { Console, Effect } from "effect"
706 *
707 * const messages: Array<ReadonlyArray<unknown>> = []
708 * const testConsole: Console.Console = Object.assign(Object.create(console), {
709 * warn: (...args: ReadonlyArray<unknown>) => messages.push(args)
710 * })
711 * const program = Effect.gen(function*() {
712 * yield* Console.warn("This feature is deprecated")
713 * yield* Console.warn("Performance warning:", {
714 * slowQuery: "SELECT * FROM large_table"
715 * })
716 * })
717 *
718 * Effect.runSync(Effect.provideService(program, Console.Console, testConsole))
719 * const expected = [
720 * ["This feature is deprecated"],
721 * ["Performance warning:", { slowQuery: "SELECT * FROM large_table" }]
722 * ]
723 * messages // => expected
724 * ```
725 *
726 * @category accessors
727 * @since 2.0.0
728 */
729export const warn = (...args: ReadonlyArray<any>): Effect.Effect<void> =>
730 consoleWith((console) =>
731 effect.sync(() => {
732 console.warn(...args)
733 })
734 )
735
736/**
737 * Runs an Effect inside an optionally labeled or collapsed console group, starting the group before execution and ending it after the Effect completes.
738 *
739 * **Example** (Wrapping an effect in a group)
740 *
741 * ```ts import.meta.vitest
742 * import { Console, Effect } from "effect"
743 *
744 * const operations: Array<string> = []
745 * const testConsole: Console.Console = Object.assign(Object.create(console), {
746 * group: (label?: string) => operations.push(`group:${label}`),
747 * groupEnd: () => operations.push("groupEnd"),
748 * log: (message: string) => operations.push(`log:${message}`)
749 * })
750 * const program = Effect.gen(function*() {
751 * yield* Console.withGroup(
752 * Effect.gen(function*() {
753 * yield* Console.log("Step 1: Initialize")
754 * yield* Console.log("Step 2: Process")
755 * yield* Console.log("Step 3: Complete")
756 * }),
757 * { label: "Processing Steps", collapsed: false }
758 * )
759 * })
760 *
761 * Effect.runSync(Effect.provideService(program, Console.Console, testConsole))
762 * const expected = [
763 * "group:Processing Steps",
764 * "log:Step 1: Initialize",
765 * "log:Step 2: Process",
766 * "log:Step 3: Complete",
767 * "groupEnd"
768 * ]
769 * operations // => expected
770 * ```
771 *
772 * @category accessors
773 * @since 2.0.0
774 */
775export const withGroup = dual<
776 (
777 options?: {
778 readonly label?: string | undefined
779 readonly collapsed?: boolean | undefined
780 }
781 ) => <A, E, R>(self: Effect.Effect<A, E, R>) => Effect.Effect<A, E, R>,
782 <A, E, R>(
783 self: Effect.Effect<A, E, R>,
784 options?: {
785 readonly label?: string | undefined
786 readonly collapsed?: boolean | undefined
787 }
788 ) => Effect.Effect<A, E, R>
789>((args) => core.isEffect(args[0]), (self, options) =>
790 consoleWith((console) =>
791 effect.acquireUseRelease(
792 effect.sync(() => {
793 if (options?.collapsed) {
794 console.groupCollapsed(options.label)
795 } else {
796 console.group(options?.label)
797 }
798 }),
799 () => self,
800 () =>
801 effect.sync(() => {
802 console.groupEnd()
803 })
804 )
805 ))
806
807/**
808 * Runs an Effect with a console timer, starting the timer before execution and ending it after the Effect completes.
809 *
810 * **Example** (Timing an effect)
811 *
812 * ```ts import.meta.vitest
813 * import { Console, Effect } from "effect"
814 *
815 * const operations: Array<string> = []
816 * const testConsole: Console.Console = Object.assign(Object.create(console), {
817 * time: (label?: string) => operations.push(`start:${label}`),
818 * timeEnd: (label?: string) => operations.push(`end:${label}`),
819 * log: (message: string) => operations.push(`log:${message}`)
820 * })
821 *
822 * const program = Effect.gen(function*() {
823 * yield* Console.withTime(
824 * Effect.gen(function*() {
825 * yield* Console.log("Operation completed")
826 * }),
827 * "my-operation"
828 * )
829 * })
830 *
831 * Effect.runSync(Effect.provideService(program, Console.Console, testConsole))
832 * operations // => ["start:my-operation", "log:Operation completed", "end:my-operation"]
833 * ```
834 *
835 * @category accessors
836 * @since 2.0.0
837 */
838export const withTime = dual<
839 (label?: string) => <A, E, R>(self: Effect.Effect<A, E, R>) => Effect.Effect<A, E, R>,
840 <A, E, R>(self: Effect.Effect<A, E, R>, label?: string) => Effect.Effect<A, E, R>
841>((args) => core.isEffect(args[0]), (self, label) =>
842 consoleWith((console) =>
843 effect.acquireUseRelease(
844 effect.sync(() => {
845 console.time(label)
846 }),
847 () => self,
848 () =>
849 effect.sync(() => {
850 console.timeEnd(label)
851 })
852 )
853 ))