1/**
2 * Describes policies for retrying, repeating, and pacing Effect programs.
3 *
4 * A `Schedule<Output, Input, Error, Env>` is stepped with an input value. Each
5 * step either stops or produces an output together with the delay before the
6 * next step. Schedules are used by retry, repeat, stream, and channel APIs to
7 * decide when work should continue, how long to wait, and when to stop.
8 *
9 * @since 2.0.0
10 */
11import type { NonEmptyReadonlyArray } from "./Array.ts"
12import * as Cause from "./Cause.ts"
13import * as Context from "./Context.ts"
14import * as Cron from "./Cron.ts"
15import type * as DateTime from "./DateTime.ts"
16import * as Duration from "./Duration.ts"
17import type { Effect } from "./Effect.ts"
18import { constant, dual, identity } from "./Function.ts"
19import { isEffect } from "./internal/core.ts"
20import * as effect from "./internal/effect.ts"
21import * as random from "./internal/random.ts"
22import { type Pipeable, pipeArguments } from "./Pipeable.ts"
23import { hasProperty } from "./Predicate.ts"
24import * as Pull from "./Pull.ts"
25import * as Result from "./Result.ts"
26import type { Contravariant, Covariant, Mutable, UnionToIntersection } from "./Types.ts"
27
28const TypeId = "~effect/Schedule"
29
30const randomNext: Effect<number> = random.Random.useSync((random) => random.nextDoubleUnsafe())
31
32/**
33 * A Schedule defines a strategy for repeating or retrying effects based on some policy.
34 *
35 * **Example** (Defining retry and repeat schedules)
36 *
37 * ```ts import.meta.vitest
38 * import { Effect, Schedule } from "effect"
39 * import { TestClock } from "effect/testing"
40 *
41 * const executions: Array<number> = []
42 * const program = Effect.sync(() => executions.push(executions.length + 1)).pipe(
43 * Effect.repeat(Schedule.recurs(2)),
44 * Effect.as(executions)
45 * )
46 *
47 * await Effect.runPromise(Effect.provide(program, TestClock.layer())) // => [1, 2, 3]
48 * ```
49 *
50 * @category models
51 * @since 2.0.0
52 */
53export interface Schedule<out Output, in Input = unknown, out Error = never, out Env = never>
54 extends Schedule.Variance<Output, Input, Error, Env>, Pipeable
55{}
56
57/**
58 * Metadata provided to schedule functions containing timing and input information.
59 *
60 * @category metadata
61 * @since 4.0.0
62 */
63export interface InputMetadata<Input> {
64 readonly input: Input
65 readonly attempt: number
66 readonly start: number
67 readonly now: number
68 readonly elapsed: number
69 readonly elapsedSincePrevious: number
70}
71
72/**
73 * Extended metadata that includes both input metadata and the output value from the schedule.
74 *
75 * @category metadata
76 * @since 4.0.0
77 */
78export interface Metadata<Output = unknown, Input = unknown> extends InputMetadata<Input> {
79 readonly output: Output
80 readonly duration: Duration.Duration
81}
82
83/**
84 * Context reference containing metadata for the currently running schedule step.
85 *
86 * **Details**
87 *
88 * Repeat, retry, stream, and channel scheduling operations provide this service
89 * to effects run between schedule steps. The default value contains undefined
90 * input and output values, zero duration, and zeroed timing fields before any
91 * schedule step has produced metadata.
92 *
93 * @category services
94 * @since 4.0.0
95 */
96export const CurrentMetadata = Context.Reference<Metadata>("effect/Schedule/CurrentMetadata", {
97 defaultValue: constant({
98 input: undefined,
99 output: undefined,
100 duration: Duration.zero,
101 attempt: 0,
102 start: 0,
103 now: 0,
104 elapsed: 0,
105 elapsedSincePrevious: 0
106 })
107})
108
109/**
110 * The Schedule namespace contains types and utilities for working with schedules.
111 *
112 * @since 2.0.0
113 */
114export declare namespace Schedule {
115 /**
116 * Variance interface that defines the type parameter relationships for Schedule.
117 *
118 * **Example** (Understanding schedule variance)
119 *
120 * ```ts import.meta.vitest
121 * import { Schedule } from "effect"
122 *
123 * const schedule: Schedule.Schedule<number, unknown, never, never> = Schedule.recurs(2)
124 * Schedule.isSchedule(schedule) // => true
125 * ```
126 *
127 * @category models
128 * @since 2.0.0
129 */
130 export interface Variance<out Output, in Input, out Error, out Env> {
131 readonly [TypeId]: VarianceStruct<Output, Input, Error, Env>
132 }
133
134 /**
135 * Type-level marker used by `Schedule.Variance` to record the variance of
136 * `Schedule` type parameters.
137 *
138 * **Details**
139 *
140 * This interface exists for TypeScript inference and assignability. Users
141 * normally do not construct or inspect it directly.
142 *
143 * @category models
144 * @since 4.0.0
145 */
146 export interface VarianceStruct<out Output, in Input, out Error, out Env> {
147 readonly _Out: Covariant<Output>
148 readonly _In: Contravariant<Input>
149 readonly _Error: Covariant<Error>
150 readonly _Env: Covariant<Env>
151 }
152}
153
154/**
155 * Extracts the output type from a `Schedule`.
156 *
157 * @category utility types
158 * @since 4.0.0
159 */
160export type Output<S> = S extends Schedule<infer Output, any, any, any> ? Output : never
161
162/**
163 * Extracts the input type from a `Schedule`.
164 *
165 * @category utility types
166 * @since 4.0.0
167 */
168export type Input<S> = S extends Schedule<any, infer Input, any, any> ? Input : never
169
170/**
171 * Extracts the error type from a `Schedule`.
172 *
173 * @category utility types
174 * @since 4.0.0
175 */
176export type Error<S> = S extends Schedule<any, any, infer Error, any> ? Error : never
177
178/**
179 * Extracts the service requirements from a `Schedule`.
180 *
181 * @category utility types
182 * @since 4.0.0
183 */
184export type Env<S> = S extends Schedule<any, any, any, infer Env> ? Env : never
185
186const ScheduleProto = {
187 [TypeId]: {
188 _Out: identity,
189 _In: identity,
190 _Env: identity
191 },
192 pipe() {
193 return pipeArguments(this, arguments)
194 }
195}
196
197/**
198 * Type guard that checks if a value is a Schedule.
199 *
200 * **Example** (Checking for schedules)
201 *
202 * ```ts import.meta.vitest
203 * import { Schedule } from "effect"
204 *
205 * const schedule = Schedule.exponential("100 millis")
206 * const notSchedule = { foo: "bar" }
207 *
208 * Schedule.isSchedule(schedule) // => true
209 * Schedule.isSchedule(notSchedule) // => false
210 * Schedule.isSchedule(null) // => false
211 * Schedule.isSchedule(undefined) // => false
212 * ```
213 *
214 * @category guards
215 * @since 2.0.0
216 */
217export const isSchedule = (u: unknown): u is Schedule<unknown, never, unknown, unknown> => hasProperty(u, TypeId)
218
219/**
220 * Creates a Schedule from a step function that returns a Pull.
221 *
222 * **Example** (Creating a custom schedule from a step function)
223 *
224 * ```ts import.meta.vitest
225 * import { Cause, Duration, Effect, Schedule } from "effect"
226 *
227 * const schedule = Schedule.fromStep(Effect.sync(() => {
228 * let count = 0
229 *
230 * return (_now: number, _input: string) => {
231 * if (count >= 3) {
232 * return Cause.done(count)
233 * }
234 * return Effect.succeed([count++, Duration.millis(100)] as [number, Duration.Duration])
235 * }
236 * }))
237 *
238 * const program = Effect.gen(function*() {
239 * const step = yield* Schedule.toStep(schedule)
240 * const [output] = yield* step(0, "input")
241 * return output
242 * })
243 *
244 * await Effect.runPromise(program) // => 0
245 * ```
246 *
247 * @category constructors
248 * @since 4.0.0
249 */
250export const fromStep = <Input, Output, EnvX, Error, ErrorX, Env>(
251 step: Effect<
252 (now: number, input: Input) => Pull.Pull<[Output, Duration.Duration], ErrorX, Output, EnvX>,
253 Error,
254 Env
255 >
256): Schedule<Output, Input, Error | Pull.ExcludeDone<ErrorX>, Env | EnvX> => {
257 const self = Object.create(ScheduleProto)
258 self.step = step
259 return self
260}
261
262const metadataFn = () => {
263 let n = 0
264 let previous: number | undefined
265 let start: number | undefined
266 return <In>(now: number, input: In): InputMetadata<In> => {
267 if (start === undefined) start = now
268 const elapsed = now - start
269 const elapsedSincePrevious = previous === undefined ? 0 : now - previous
270 previous = now
271 return { input, attempt: ++n, start, now, elapsed, elapsedSincePrevious }
272 }
273}
274
275/**
276 * Creates a Schedule from a step function that receives metadata about the schedule's execution.
277 *
278 * **Example** (Creating a metadata-aware schedule)
279 *
280 * ```ts import.meta.vitest
281 * import { Cause, Duration, Effect, Schedule } from "effect"
282 *
283 * const firstThreeInputs = Schedule.fromStepWithMetadata(Effect.succeed((metadata: Schedule.InputMetadata<string>) => {
284 * if (metadata.attempt > 3) {
285 * return Cause.done("finished")
286 * }
287 *
288 * return Effect.succeed([
289 * `attempt ${metadata.attempt}: ${metadata.input}`,
290 * Duration.millis(250)
291 * ] as [string, Duration.Duration])
292 * }))
293 *
294 * const program = Effect.gen(function*() {
295 * const step = yield* Schedule.toStep(firstThreeInputs)
296 * const [output] = yield* step(0, "input")
297 * return output
298 * })
299 *
300 * await Effect.runPromise(program) // => "attempt 1: input"
301 * ```
302 *
303 * @category constructors
304 * @since 4.0.0
305 */
306export const fromStepWithMetadata = <Input, Output, EnvX, ErrorX, Error, Env>(
307 step: Effect<
308 (options: InputMetadata<Input>) => Pull.Pull<[Output, Duration.Duration], ErrorX, Output, EnvX>,
309 Error,
310 Env
311 >
312): Schedule<Output, Input, Error | Pull.ExcludeDone<ErrorX>, Env | EnvX> =>
313 fromStep(effect.map(step, (f) => {
314 const meta = metadataFn()
315 return (now, input) => f(meta(now, input))
316 }))
317
318/**
319 * Extracts the step function from a Schedule.
320 *
321 * **Example** (Extracting a schedule step function)
322 *
323 * ```ts import.meta.vitest
324 * import { Duration, Effect, Schedule } from "effect"
325 *
326 * // Extract step function from an existing schedule
327 * const schedule = Schedule.exponential("100 millis").pipe(Schedule.upTo({ times: 3 }))
328 *
329 * const program = Effect.gen(function*() {
330 * const stepFn = yield* Schedule.toStep(schedule)
331 *
332 * // Use the step function directly for custom logic. The timestamp is
333 * // supplied by the caller, so tests can pass a deterministic value.
334 * const now = 0
335 * return yield* stepFn(now, "input")
336 * })
337 *
338 * await Effect.runPromise(program) // => [Duration.millis(100), Duration.millis(100)]
339 * ```
340 *
341 * @category destructors
342 * @since 4.0.0
343 */
344export const toStep = <Output, Input, Error, Env>(
345 schedule: Schedule<Output, Input, Error, Env>
346): Effect<
347 (now: number, input: Input) => Pull.Pull<[Output, Duration.Duration], Error, Output, Env>,
348 never,
349 Env
350> =>
351 effect.catchCause(
352 (schedule as any).step,
353 (cause) => effect.succeed(() => effect.failCause(cause) as any)
354 )
355
356/**
357 * Extracts a step function from a `Schedule` that sleeps for each computed
358 * delay and returns metadata for the completed step.
359 *
360 * **When to use**
361 *
362 * Use to drive a schedule manually while preserving the computed output,
363 * delay, input, attempt, and elapsed timing metadata for each step.
364 *
365 * **Details**
366 *
367 * The returned step reads the current time from `Clock` when invoked, calls the
368 * schedule step with that timestamp and input, sleeps for the returned
369 * duration, and then yields `Metadata`.
370 *
371 * @see {@link toStep} for manually supplying the timestamp and handling the returned delay yourself
372 * @see {@link toStepWithSleep} for the same automatic sleeping behavior when only the schedule output is needed
373 *
374 * @category destructors
375 * @since 4.0.0
376 */
377export const toStepWithMetadata = <Output, Input, Error, Env>(
378 schedule: Schedule<Output, Input, Error, Env>
379): Effect<
380 (input: Input) => Pull.Pull<Metadata<Output, Input>, Error, Output, Env>,
381 never,
382 Env
383> =>
384 effect.clockWith((clock) =>
385 effect.map(
386 toStep(schedule),
387 (step) => {
388 const metaFn = metadataFn()
389 return (input) =>
390 effect.suspend(() => {
391 const now = clock.currentTimeMillisUnsafe()
392 return effect.flatMap(
393 step(now, input),
394 ([output, duration]) => {
395 const meta = metaFn(now, input) as Mutable<Metadata<Output, Input>>
396 meta.output = output
397 meta.duration = duration
398 return effect.as(effect.sleep(duration), meta)
399 }
400 )
401 })
402 }
403 )
404 )
405
406/**
407 * Extracts a step function from a Schedule that automatically handles sleep delays.
408 *
409 * **Example** (Extracting a sleeping step function)
410 *
411 * ```ts import.meta.vitest
412 * import { Effect, Schedule } from "effect"
413 * import { TestClock } from "effect/testing"
414 *
415 * const schedule = Schedule.recurs(3)
416 *
417 * const program = Effect.gen(function*() {
418 * const stepWithSleep = yield* Schedule.toStepWithSleep(schedule)
419 *
420 * return [yield* stepWithSleep("first"), yield* stepWithSleep("second")]
421 * })
422 *
423 * await Effect.runPromise(Effect.provide(program, TestClock.layer())) // => [0, 1]
424 * ```
425 *
426 * @category destructors
427 * @since 4.0.0
428 */
429export const toStepWithSleep = <Output, Input, Error, Env>(
430 schedule: Schedule<Output, Input, Error, Env>
431): Effect<
432 (input: Input) => Pull.Pull<Output, Error, Output, Env>,
433 never,
434 Env
435> =>
436 effect.map(
437 toStepWithMetadata(schedule),
438 (step) => (input) => effect.map(step(input), (meta) => meta.output)
439 )
440
441/**
442 * Returns a new `Schedule` that adds the delay computed by the specified
443 * effectful function to the next recurrence of the schedule.
444 *
445 * **Example** (Adding extra delay to a schedule)
446 *
447 * ```ts import.meta.vitest
448 * import { Duration, Effect, Schedule } from "effect"
449 *
450 * const schedule = Schedule.recurs(1).pipe(
451 * Schedule.addDelay(() => Effect.succeed("25 millis"))
452 * )
453 * const program = Effect.gen(function*() {
454 * const step = yield* Schedule.toStep(schedule)
455 * const [, delay] = yield* step(0, undefined)
456 * return delay
457 * })
458 *
459 * await Effect.runPromise(program) // => Duration.millis(25)
460 * ```
461 *
462 * @category delays & timeouts
463 * @since 2.0.0
464 */
465export const addDelay: {
466 <Output, Input, Error2 = never, Env2 = never>(
467 f: (metadata: Metadata<Output, Input>) => Effect<Duration.Input, Error2, Env2>
468 ): <Error, Env>(
469 self: Schedule<Output, Input, Error, Env>
470 ) => Schedule<Output, Input, Error | Error2, Env | Env2>
471 <Output, Input, Error, Env, Error2 = never, Env2 = never>(
472 self: Schedule<Output, Input, Error, Env>,
473 f: (metadata: Metadata<Output, Input>) => Effect<Duration.Input, Error2, Env2>
474 ): Schedule<Output, Input, Error | Error2, Env | Env2>
475} = dual(2, <Output, Input, Error, Env, Error2 = never, Env2 = never>(
476 self: Schedule<Output, Input, Error, Env>,
477 f: (metadata: Metadata<Output, Input>) => Effect<Duration.Input, Error2, Env2>
478): Schedule<Output, Input, Error | Error2, Env | Env2> =>
479 modifyDelay(
480 self,
481 (metadata) => effect.map(f(metadata), (d) => Duration.sum(Duration.fromInputUnsafe(d), metadata.duration))
482 ))
483
484/**
485 * Returns a schedule that runs `self` to completion, then runs `other`, and
486 * merges their outputs.
487 *
488 * **Example** (Sequencing quick and slow retries)
489 *
490 * ```ts import.meta.vitest
491 * import { Schedule } from "effect"
492 *
493 * const schedule = Schedule.concat(Schedule.recurs(1), Schedule.recurs(2))
494 * Schedule.isSchedule(schedule) // => true
495 * ```
496 *
497 * @category sequencing
498 * @since 2.0.0
499 */
500export const concat: {
501 <Output2, Input2, Error2, Env2>(
502 other: Schedule<Output2, Input2, Error2, Env2>
503 ): <Output, Input, Error, Env>(
504 self: Schedule<Output, Input, Error, Env>
505 ) => Schedule<Output | Output2, Input & Input2, Error | Error2, Env | Env2>
506 <Output, Input, Error, Env, Output2, Input2, Error2, Env2>(
507 self: Schedule<Output, Input, Error, Env>,
508 other: Schedule<Output2, Input2, Error2, Env2>
509 ): Schedule<Output | Output2, Input & Input2, Error | Error2, Env | Env2>
510} = dual(2, <Output, Input, Error, Env, Output2, Input2, Error2, Env2>(
511 self: Schedule<Output, Input, Error, Env>,
512 other: Schedule<Output2, Input2, Error2, Env2>
513): Schedule<Output | Output2, Input & Input2, Error | Error2, Env | Env2> =>
514 map(concatResult(self, other), ({ output }) => effect.succeed(Result.merge(output))))
515
516/**
517 * Returns a schedule that runs `self` to completion, then runs `other`, and
518 * preserves which schedule produced each output.
519 *
520 * **Details**
521 *
522 * The resulting schedule emits a `Result` to indicate which phase produced
523 * each output: outputs from `self` are emitted as `Failure`, and outputs from
524 * `other` are emitted as `Success`.
525 *
526 * **Example** (Tracking sequential schedule phases)
527 *
528 * ```ts import.meta.vitest
529 * import { Schedule } from "effect"
530 *
531 * const schedule = Schedule.concatResult(Schedule.recurs(1), Schedule.recurs(2))
532 * Schedule.isSchedule(schedule) // => true
533 * ```
534 *
535 * @category sequencing
536 * @since 4.0.0
537 */
538export const concatResult: {
539 <Output2, Input2, Error2, Env2>(
540 other: Schedule<Output2, Input2, Error2, Env2>
541 ): <Output, Input, Error, Env>(
542 self: Schedule<Output, Input, Error, Env>
543 ) => Schedule<Result.Result<Output2, Output>, Input & Input2, Error | Error2, Env | Env2>
544 <Output, Input, Error, Env, Output2, Input2, Error2, Env2>(
545 self: Schedule<Output, Input, Error, Env>,
546 other: Schedule<Output2, Input2, Error2, Env2>
547 ): Schedule<Result.Result<Output2, Output>, Input & Input2, Error | Error2, Env | Env2>
548} = dual(2, <Output, Input, Error, Env, Output2, Input2, Error2, Env2>(
549 self: Schedule<Output, Input, Error, Env>,
550 other: Schedule<Output2, Input2, Error2, Env2>
551): Schedule<Result.Result<Output2, Output>, Input & Input2, Error | Error2, Env | Env2> =>
552 fromStep(effect.sync(() => {
553 let currentSide = 0
554 let currentStep:
555 | undefined
556 | ((now: number, input: Input & Input2) => Pull.Pull<
557 [Result.Result<Output2, Output>, Duration.Duration],
558 Error | Error2,
559 Result.Result<Output2, Output>,
560 Env | Env2
561 >)
562 const left = map(self, ({ output }) => Result.fail(output))
563 const right = map(other, ({ output }) => Result.succeed(output))
564 return function recur(
565 now,
566 input
567 ): Pull.Pull<
568 [Result.Result<Output2, Output>, Duration.Duration],
569 Error | Error2,
570 Result.Result<Output2, Output>,
571 Env | Env2
572 > {
573 if (currentStep) return currentStep(now, input)
574 return toStep<
575 Result.Result<Output2, Output>,
576 Input & Input2,
577 Error | Error2,
578 Env | Env2
579 >(currentSide === 0 ? left : right).pipe(
580 effect.flatMap((step) => {
581 currentSide++
582 if (currentSide === 1) {
583 currentStep = (now, input) =>
584 Pull.catchDone(step(now, input), (_) => {
585 currentStep = undefined
586 return recur(now, input)
587 })
588 return currentStep(now, input)
589 }
590 currentStep = step
591 return currentStep(now, input)
592 })
593 )
594 }
595 })))
596
597/**
598 * Combines schedules by recurring while all schedules want to recur, using the
599 * maximum delay between recurrences and outputting that maximum delay.
600 *
601 * **When to use**
602 *
603 * Use when a combined policy should continue only while every schedule still
604 * recurs, and should wait for the slowest schedule between recurrences.
605 *
606 * **Example** (Combining retry schedules by their maximum delay)
607 *
608 * ```ts import.meta.vitest
609 * import { Schedule } from "effect"
610 *
611 * const schedule = Schedule.max([Schedule.fixed("5 seconds"), Schedule.spaced("10 seconds")])
612 * Schedule.isSchedule(schedule) // => true
613 * ```
614 *
615 * @category combining
616 * @since 4.0.0
617 */
618export const max = <
619 const Schedules extends NonEmptyReadonlyArray<
620 Schedule<any, any, any, any>
621 >
622>(
623 schedules: Schedules
624): Schedule<
625 Duration.Duration,
626 UnionToIntersection<
627 Input<Schedules[number]>
628 >,
629 Error<Schedules[number]>,
630 Env<Schedules[number]>
631> =>
632 fromStep(effect.map(
633 effect.all(schedules.map(toStep)),
634 (steps) => (now, input) =>
635 effect.flatMap(
636 effect.forEach(steps, (step) =>
637 Pull.matchEffect(step(now, input as never), {
638 onSuccess: (result) => effect.succeed(result[1]),
639 onDone: () => effect.undefined,
640 onFailure: effect.failCause
641 })),
642 (results) => {
643 const duration = maxDuration(results)
644 if (duration === undefined) {
645 return Cause.done(Duration.zero)
646 }
647 return effect.succeed([duration, duration] as [Duration.Duration, Duration.Duration])
648 }
649 )
650 ))
651
652const maxDuration = (results: ReadonlyArray<Duration.Duration | undefined>): Duration.Duration | undefined => {
653 let max = results[0]
654 for (let i = 1; i < results.length; i++) {
655 max = results[i] && max && Duration.max(max, results[i]!)
656 if (max === undefined) break
657 }
658
659 return max
660}
661
662/**
663 * Returns a new `Schedule` that recurs on the specified `Cron` schedule and
664 * outputs the duration between recurrences.
665 *
666 * **Example** (Scheduling work with cron expressions)
667 *
668 * ```ts import.meta.vitest
669 * import { Schedule } from "effect"
670 *
671 * const everyMinute = Schedule.cron("* * * * *")
672 * Schedule.isSchedule(everyMinute) // => true
673 * ```
674 *
675 * @category constructors
676 * @since 2.0.0
677 */
678export const cron: {
679 (expression: Cron.Cron): Schedule<Duration.Duration, unknown, Cron.CronParseError>
680 (expression: string, tz?: string | DateTime.TimeZone): Schedule<Duration.Duration, unknown, Cron.CronParseError>
681} = (expression: string | Cron.Cron, tz?: string | DateTime.TimeZone) => {
682 const parsed = Cron.isCron(expression) ? Result.succeed(expression) : Cron.parse(expression, tz)
683 return fromStep(effect.map(effect.fromResult(parsed), (cron) => (now, _) => {
684 if (now === Number.POSITIVE_INFINITY) {
685 return Cause.done(Duration.zero)
686 }
687 return effect.sync(() => {
688 const next = Cron.next(cron, now).getTime()
689 const duration = Duration.millis(next - now)
690 return [duration, duration]
691 })
692 }))
693}
694
695/**
696 * Returns a schedule that recurs once after the specified duration.
697 *
698 * **When to use**
699 *
700 * Use when you need a schedule that recurs once after a fixed delay.
701 *
702 * **Details**
703 *
704 * The schedule outputs the configured duration for its first recurrence and
705 * then completes.
706 *
707 * **Example** (Recurring once after a duration)
708 *
709 * ```ts import.meta.vitest
710 * import { Schedule } from "effect"
711 *
712 * Schedule.isSchedule(Schedule.duration("1 second")) // => true
713 * ```
714 *
715 * @see {@link during} for recurring until a duration has elapsed
716 *
717 * @category constructors
718 * @since 2.0.0
719 */
720export const duration = (durationInput: Duration.Input): Schedule<Duration.Duration> => {
721 const duration = Duration.fromInputUnsafe(durationInput)
722 return fromStepWithMetadata(effect.succeed((meta) =>
723 meta.attempt === 1
724 ? effect.succeed([duration, duration])
725 : Cause.done(Duration.zero)
726 ))
727}
728
729/**
730 * Returns a new `Schedule` that will always recur, but only during the
731 * specified `duration` of time.
732 *
733 * **When to use**
734 *
735 * Use to bound a repeating or retrying schedule by elapsed time.
736 *
737 * **Example** (Repeating work during a duration)
738 *
739 * ```ts import.meta.vitest
740 * import { Schedule } from "effect"
741 *
742 * Schedule.isSchedule(Schedule.during("5 seconds")) // => true
743 * ```
744 *
745 * @see {@link duration} for one delayed recurrence
746 *
747 * @category constructors
748 * @since 4.0.0
749 */
750export const during = (duration: Duration.Input): Schedule<Duration.Duration> => {
751 const durationMillis = Duration.toMillis(duration)
752 return fromStepWithMetadata(
753 effect.succeed((meta) => {
754 const elapsed = Duration.millis(meta.elapsed)
755 return meta.elapsed > durationMillis
756 ? Cause.done(elapsed)
757 : effect.succeed([elapsed, Duration.zero])
758 })
759 )
760}
761
762/**
763 * Combines schedules by recurring while at least one schedule wants to recur,
764 * using the minimum delay between recurrences and outputting that minimum delay.
765 *
766 * **When to use**
767 *
768 * Use when a combined policy should continue while any schedule still recurs,
769 * and should wait for the fastest schedule between recurrences.
770 *
771 * **Example** (Combining retry schedules by their minimum delay)
772 *
773 * ```ts import.meta.vitest
774 * import { Schedule } from "effect"
775 *
776 * const schedule = Schedule.min([Schedule.fixed("5 seconds"), Schedule.spaced("10 seconds")])
777 * Schedule.isSchedule(schedule) // => true
778 * ```
779 *
780 * @category combining
781 * @since 4.0.0
782 */
783export const min = <
784 const Schedules extends NonEmptyReadonlyArray<
785 Schedule<any, any, any, any>
786 >
787>(
788 schedules: Schedules
789): Schedule<
790 Duration.Duration,
791 UnionToIntersection<
792 Input<Schedules[number]>
793 >,
794 Error<Schedules[number]>,
795 Env<Schedules[number]>
796> =>
797 fromStep(effect.map(
798 effect.all(schedules.map(toStep)),
799 (steps) => (now, input) =>
800 effect.flatMap(
801 effect.forEach(steps, (step) =>
802 Pull.matchEffect(step(now, input as never), {
803 onSuccess: (result) => effect.succeed(result[1]),
804 onDone: () => effect.undefined,
805 onFailure: effect.failCause
806 })),
807 (results) => {
808 const duration = minDuration(results)
809 if (duration === undefined) {
810 return Cause.done(Duration.zero)
811 }
812 return effect.succeed([duration, duration] as [Duration.Duration, Duration.Duration])
813 }
814 )
815 ))
816
817const minDuration = (results: ReadonlyArray<Duration.Duration | undefined>): Duration.Duration | undefined => {
818 let min: Duration.Duration | undefined = undefined
819 for (let i = 0; i < results.length; i++) {
820 const duration = results[i]
821 if (duration !== undefined) {
822 min = min === undefined ? duration : Duration.min(min, duration)
823 }
824 }
825
826 return min
827}
828
829/**
830 * Schedule that always recurs, but will wait a certain amount between
831 * repetitions, given by `base * factor.pow(n)`, where `n` is the number of
832 * repetitions so far. Returns the current duration between recurrences.
833 *
834 * **Example** (Retrying with exponential backoff)
835 *
836 * ```ts import.meta.vitest
837 * import { Duration, Effect, Schedule } from "effect"
838 *
839 * const program = Effect.gen(function*() {
840 * const step = yield* Schedule.toStep(Schedule.exponential("100 millis"))
841 * return yield* step(0, undefined)
842 * })
843 *
844 * await Effect.runPromise(program) // => [Duration.millis(100), Duration.millis(100)]
845 * ```
846 *
847 * @category constructors
848 * @since 2.0.0
849 */
850export const exponential = (
851 base: Duration.Input,
852 factor: number = 2
853): Schedule<Duration.Duration> => {
854 const baseMillis = Duration.toMillis(Duration.fromInputUnsafe(base))
855 return fromStepWithMetadata(effect.succeed((meta) => {
856 const duration = Duration.millis(baseMillis * Math.pow(factor, meta.attempt - 1))
857 return effect.succeed([duration, duration])
858 }))
859}
860
861/**
862 * Schedule that always recurs, increasing delays by summing the preceding
863 * two delays (similar to the Fibonacci sequence). Returns the current
864 * duration between recurrences.
865 *
866 * **Example** (Retrying with Fibonacci backoff)
867 *
868 * ```ts import.meta.vitest
869 * import { Duration, Effect, Schedule } from "effect"
870 *
871 * const program = Effect.gen(function*() {
872 * const step = yield* Schedule.toStep(Schedule.fibonacci("100 millis"))
873 * return yield* step(0, undefined)
874 * })
875 *
876 * await Effect.runPromise(program) // => [Duration.millis(100), Duration.millis(100)]
877 * ```
878 *
879 * @category constructors
880 * @since 2.0.0
881 */
882export const fibonacci = (one: Duration.Input): Schedule<Duration.Duration> => {
883 const oneMillis = Duration.toMillis(Duration.fromInputUnsafe(one))
884 return fromStep(effect.sync(() => {
885 let a = 0
886 let b = oneMillis
887 return constant(effect.sync(() => {
888 const next = a + b
889 a = b
890 b = next
891 const duration = Duration.millis(next)
892 return [duration, duration]
893 }))
894 }))
895}
896
897/**
898 * Returns a `Schedule` that recurs on the specified fixed `interval` and
899 * outputs the number of repetitions of the schedule so far.
900 *
901 * **When to use**
902 *
903 * Use when recurrences should stay aligned to a regular cadence.
904 *
905 * **Gotchas**
906 *
907 * If the action run between recurrences takes longer than the interval, the
908 * next recurrence happens immediately, but missed intervals are not replayed.
909 *
910 * ```text
911 * |-----interval-----|-----interval-----|-----interval-----|
912 * |---------action--------||action|-----|action|-----------|
913 * ```
914 *
915 * **Example** (Repeating on fixed intervals)
916 *
917 * ```ts import.meta.vitest
918 * import { Duration, Effect, Schedule } from "effect"
919 *
920 * const program = Effect.gen(function*() {
921 * const step = yield* Schedule.toStep(Schedule.fixed("1 second"))
922 * return yield* step(0, undefined)
923 * })
924 *
925 * await Effect.runPromise(program) // => [0, Duration.seconds(1)]
926 * ```
927 *
928 * @see {@link spaced} for delaying after each action completes
929 *
930 * @category constructors
931 * @since 2.0.0
932 */
933export const fixed = (interval: Duration.Input): Schedule<number> => {
934 const window = Duration.toMillis(Duration.fromInputUnsafe(interval))
935 return fromStepWithMetadata(effect.sync(() => {
936 let start = 0
937 let lastRun = 0
938 return (meta) =>
939 effect.sync(() => {
940 if (window === 0) {
941 return [meta.attempt - 1, Duration.zero] as const
942 }
943 if (meta.attempt === 1) {
944 start = meta.now
945 lastRun = meta.now + window
946 return [0, Duration.millis(window)] as const
947 }
948 const runningBehind = meta.now > (lastRun + window)
949 const boundary = window - ((meta.now - start) % window)
950 const delay = runningBehind ? 0 : boundary === 0 ? window : boundary
951 lastRun = runningBehind ? meta.now : meta.now + delay
952 return [meta.attempt - 1, Duration.millis(delay)] as const
953 })
954 }))
955}
956
957/**
958 * Returns a new `Schedule` that maps each schedule decision to a new output
959 * using the full schedule metadata.
960 *
961 * **Details**
962 *
963 * The callback receives the schedule input, output, selected delay duration,
964 * current attempt, and elapsed timing information. Return either a plain value
965 * or an `Effect` that produces the new output.
966 *
967 * **Example** (Mapping schedule outputs)
968 *
969 * ```ts import.meta.vitest
970 * import { Effect, Schedule } from "effect"
971 *
972 * const countSchedule = Schedule.recurs(5).pipe(
973 * Schedule.map(({ output: count }) => Effect.succeed(`Execution #${count + 1}`))
974 * )
975 * const program = Effect.gen(function*() {
976 * const step = yield* Schedule.toStep(countSchedule)
977 * const [output] = yield* step(0, undefined)
978 * return output
979 * })
980 *
981 * await Effect.runPromise(program) // => "Execution #1"
982 * ```
983 *
984 * @category mapping
985 * @since 2.0.0
986 */
987export const map: {
988 <Input, Output, Output2, Error2 = never, Env2 = never>(
989 f: (metadata: Metadata<Output, Input>) => Output2 | Effect<Output2, Error2, Env2>
990 ): <Error, Env>(
991 self: Schedule<Output, Input, Error, Env>
992 ) => Schedule<Output2, Input, Error | Error2, Env | Env2>
993 <Output, Input, Error, Env, Output2, Error2 = never, Env2 = never>(
994 self: Schedule<Output, Input, Error, Env>,
995 f: (metadata: Metadata<Output, Input>) => Output2 | Effect<Output2, Error2, Env2>
996 ): Schedule<Output2, Input, Error | Error2, Env | Env2>
997} = dual(2, <Output, Input, Error, Env, Output2, Error2 = never, Env2 = never>(
998 self: Schedule<Output, Input, Error, Env>,
999 f: (metadata: Metadata<Output, Input>) => Output2 | Effect<Output2, Error2, Env2>
1000): Schedule<Output2, Input, Error | Error2, Env | Env2> =>
1001 fromStep(effect.map(toStep(self), (step) => {
1002 const meta = metadataFn()
1003 return (now, input) =>
1004 Pull.matchEffect(step(now, input), {
1005 onSuccess: ([output, duration]) => {
1006 const result = f({ ...meta(now, input), output, duration })
1007 if (!isEffect(result)) return effect.succeed([result, duration] as [Output2, Duration.Duration])
1008 return effect.map(result, (output) => [output, duration] as [Output2, Duration.Duration])
1009 },
1010 onFailure: effect.failCause<Error>,
1011 onDone: (output) => {
1012 const result = f({ ...meta(now, input), output, duration: Duration.zero })
1013 if (!isEffect(result)) return Cause.done(result as Output2)
1014 return effect.flatMap(result, Cause.done)
1015 }
1016 })
1017 })))
1018
1019/**
1020 * Returns a new `Schedule` that modifies the delay of the next recurrence
1021 * of the schedule using the specified effectful function.
1022 *
1023 * **Example** (Modifying delays from schedule metadata)
1024 *
1025 * ```ts import.meta.vitest
1026 * import { Duration, Effect, Schedule } from "effect"
1027 *
1028 * const schedule = Schedule.spaced("10 millis").pipe(
1029 * Schedule.modifyDelay(({ duration }) => Effect.succeed(Duration.times(duration, 2)))
1030 * )
1031 * const program = Effect.gen(function*() {
1032 * const step = yield* Schedule.toStep(schedule)
1033 * const [, delay] = yield* step(0, undefined)
1034 * return delay
1035 * })
1036 *
1037 * await Effect.runPromise(program) // => Duration.millis(20)
1038 * ```
1039 *
1040 * @category delays & timeouts
1041 * @since 2.0.0
1042 */
1043export const modifyDelay: {
1044 <Output, Input, Error2 = never, Env2 = never>(
1045 f: (
1046 metadata: Metadata<Output, Input>
1047 ) => Effect<Duration.Input, Error2, Env2>
1048 ): <Error, Env>(
1049 self: Schedule<Output, Input, Error, Env>
1050 ) => Schedule<Output, Input, Error | Error2, Env | Env2>
1051 <Output, Input, Error, Env, Error2 = never, Env2 = never>(
1052 self: Schedule<Output, Input, Error, Env>,
1053 f: (
1054 metadata: Metadata<Output, Input>
1055 ) => Effect<Duration.Input, Error2, Env2>
1056 ): Schedule<Output, Input, Error | Error2, Env | Env2>
1057} = dual(2, <Output, Input, Error, Env, Error2 = never, Env2 = never>(
1058 self: Schedule<Output, Input, Error, Env>,
1059 f: (
1060 metadata: Metadata<Output, Input>
1061 ) => Effect<Duration.Input, Error2, Env2>
1062): Schedule<Output, Input, Error | Error2, Env | Env2> =>
1063 fromStep(effect.map(toStep(self), (step) => {
1064 const meta = metadataFn()
1065 return (now, input) =>
1066 effect.flatMap(
1067 step(now, input),
1068 ([output, duration]) =>
1069 effect.map(f({ ...meta(now, input), output, duration }), (replacement) => [
1070 output,
1071 Duration.fromInputUnsafe(replacement)
1072 ])
1073 )
1074 })))
1075
1076/**
1077 * Returns a new `Schedule` that randomly adjusts each recurrence delay.
1078 *
1079 * **When to use**
1080 *
1081 * Use to add random variation to an existing schedule's recurrence delays while
1082 * preserving its output and completion behavior.
1083 *
1084 * **Details**
1085 *
1086 * Each recurrence delay is scaled by a random factor between `0.8` and `1.2`.
1087 *
1088 * @see {@link modifyDelay} for replacing recurrence delays with a custom effectful transformation
1089 *
1090 * @category delays & timeouts
1091 * @since 2.0.0
1092 */
1093export const jittered = <Output, Input, Error, Env>(
1094 self: Schedule<Output, Input, Error, Env>
1095): Schedule<Output, Input, Error, Env> =>
1096 modifyDelay(self, ({ duration }) =>
1097 effect.map(randomNext, (random) => {
1098 const millis = Duration.toMillis(duration)
1099 return Duration.millis(millis * 0.8 * (1 - random) + millis * 1.2 * random)
1100 }))
1101
1102/**
1103 * Returns a new `Schedule` that outputs the inputs of the specified schedule.
1104 *
1105 * **Example** (Passing inputs through as outputs)
1106 *
1107 * ```ts import.meta.vitest
1108 * import { Effect, Schedule } from "effect"
1109 *
1110 * const inputSchedule = Schedule.passthrough(
1111 * Schedule.exponential("100 millis").pipe(Schedule.upTo({ times: 3 }))
1112 * )
1113 * const program = Effect.gen(function*() {
1114 * const step = yield* Schedule.toStep(inputSchedule)
1115 * const [output] = yield* step(0, "input")
1116 * return output
1117 * })
1118 *
1119 * await Effect.runPromise(program) // => "input"
1120 * ```
1121 *
1122 * @category mapping
1123 * @since 2.0.0
1124 */
1125export const passthrough = <Output, Input, Error, Env>(
1126 self: Schedule<Output, Input, Error, Env>
1127): Schedule<Input, Input, Error, Env> =>
1128 fromStep(effect.map(toStep(self), (step) => (now, input) =>
1129 Pull.matchEffect(step(now, input), {
1130 onSuccess: (result) => effect.succeed([input, result[1]]),
1131 onFailure: effect.failCause,
1132 onDone: () => Cause.done(input)
1133 })))
1134
1135/**
1136 * Returns a `Schedule` which can only be stepped the specified number of
1137 * `times` before it terminates.
1138 *
1139 * **When to use**
1140 *
1141 * Use when you need a counter schedule with no additional delay.
1142 *
1143 * **Gotchas**
1144 *
1145 * `recurs(n)` counts schedule recurrences, not the first evaluation of the
1146 * effect being repeated or retried. For retrying, this means one initial
1147 * attempt plus at most `n` retries.
1148 *
1149 * **Example** (Limiting recurrences)
1150 *
1151 * ```ts import.meta.vitest
1152 * import { Effect, Schedule } from "effect"
1153 * import { TestClock } from "effect/testing"
1154 *
1155 * const executions: Array<number> = []
1156 * const program = Effect.sync(() => executions.push(executions.length + 1)).pipe(
1157 * Effect.repeat(Schedule.recurs(3)),
1158 * Effect.as(executions)
1159 * )
1160 *
1161 * await Effect.runPromise(Effect.provide(program, TestClock.layer())) // => [1, 2, 3, 4]
1162 * ```
1163 *
1164 * @see {@link upTo} for limiting an existing schedule
1165 *
1166 * @category constructors
1167 * @since 2.0.0
1168 */
1169export const recurs = (times: number): Schedule<number> =>
1170 while_(forever, ({ attempt }) => effect.succeed(attempt <= times))
1171
1172/**
1173 * Returns a schedule that recurs continuously, each repetition spaced the
1174 * specified duration from the last run.
1175 *
1176 * **When to use**
1177 *
1178 * Use when each delay should start after the previous action completes.
1179 *
1180 * **Example** (Repeating with fixed spacing)
1181 *
1182 * ```ts import.meta.vitest
1183 * import { Duration, Effect, Schedule } from "effect"
1184 *
1185 * const program = Effect.gen(function*() {
1186 * const step = yield* Schedule.toStep(Schedule.spaced("2 seconds"))
1187 * return yield* step(0, undefined)
1188 * })
1189 *
1190 * await Effect.runPromise(program) // => [0, Duration.seconds(2)]
1191 * ```
1192 *
1193 * @see {@link fixed} for recurrence aligned to a regular cadence
1194 *
1195 * @category constructors
1196 * @since 2.0.0
1197 */
1198export const spaced = (duration: Duration.Input): Schedule<number> => {
1199 const decoded = Duration.fromInputUnsafe(duration)
1200 return fromStepWithMetadata(effect.succeed((meta) => effect.succeed([meta.attempt - 1, decoded])))
1201}
1202
1203/**
1204 * Returns a new `Schedule` that allows execution of an effectful function for
1205 * every decision of the schedule, but does not alter the inputs and outputs of
1206 * the schedule.
1207 *
1208 * **Details**
1209 *
1210 * The callback receives the full schedule metadata, including the input, output,
1211 * computed delay duration, current attempt, and elapsed timing information.
1212 *
1213 * **Example** (Tapping schedule metadata)
1214 *
1215 * ```ts import.meta.vitest
1216 * import { Effect, Schedule } from "effect"
1217 *
1218 * const attempts: Array<number> = []
1219 * const monitoredSchedule = Schedule.recurs(2).pipe(
1220 * Schedule.tap((metadata) => Effect.sync(() => attempts.push(metadata.attempt)))
1221 * )
1222 * const program = Effect.gen(function*() {
1223 * const step = yield* Schedule.toStep(monitoredSchedule)
1224 * const [output] = yield* step(0, undefined)
1225 * return { attempts, output }
1226 * })
1227 *
1228 * await Effect.runPromise(program) // => { attempts: [1], output: 0 }
1229 * ```
1230 *
1231 * @category sequencing
1232 * @since 4.0.0
1233 */
1234export const tap: {
1235 <Output, Input, X, Error2, Env2>(
1236 f: (metadata: Metadata<Output, Input>) => Effect<X, Error2, Env2>
1237 ): <Error, Env>(
1238 self: Schedule<Output, Input, Error, Env>
1239 ) => Schedule<Output, Input, Error | Error2, Env | Env2>
1240 <Output, Input, Error, Env, X, Error2, Env2>(
1241 self: Schedule<Output, Input, Error, Env>,
1242 f: (metadata: Metadata<Output, Input>) => Effect<X, Error2, Env2>
1243 ): Schedule<Output, Input, Error | Error2, Env | Env2>
1244} = dual(2, <Output, Input, Error, Env, X, Error2, Env2>(
1245 self: Schedule<Output, Input, Error, Env>,
1246 f: (metadata: Metadata<Output, Input>) => Effect<X, Error2, Env2>
1247): Schedule<Output, Input, Error | Error2, Env | Env2> =>
1248 fromStep(effect.map(toStep(self), (step) => {
1249 const meta = metadataFn()
1250 return (now, input) =>
1251 effect.tap(step(now, input), ([output, duration]) => f({ ...meta(now, input), output, duration }))
1252 })))
1253
1254/**
1255 * Returns a new `Schedule` that limits an existing schedule by elapsed
1256 * duration, number of outputs, or both.
1257 *
1258 * **When to use**
1259 *
1260 * Use to bound an existing schedule while preserving its output and delay
1261 * behavior. When both `duration` and `times` are specified, the schedule
1262 * stops as soon as either limit is reached.
1263 *
1264 * **Gotchas**
1265 *
1266 * The `times` option limits schedule outputs. When used with repeat or retry,
1267 * the effect is evaluated once before the schedule is stepped, so the total
1268 * number of evaluations can be one greater than the configured number of
1269 * outputs.
1270 *
1271 * The `duration` option is based on the elapsed time observed by the schedule
1272 * step. Long-running effects can cause the duration limit to be detected on the
1273 * following schedule step.
1274 *
1275 * **Example** (Limiting by duration and recurrence count)
1276 *
1277 * ```ts import.meta.vitest
1278 * import { Effect, Schedule } from "effect"
1279 * import { TestClock } from "effect/testing"
1280 *
1281 * const executions: Array<number> = []
1282 * const schedule = Schedule.forever.pipe(Schedule.upTo({ times: 2 }))
1283 * const program = Effect.sync(() => executions.push(executions.length + 1)).pipe(
1284 * Effect.repeat(schedule),
1285 * Effect.as(executions)
1286 * )
1287 *
1288 * await Effect.runPromise(Effect.provide(program, TestClock.layer())) // => [1, 2, 3]
1289 * ```
1290 *
1291 * @category filtering
1292 * @since 4.0.0
1293 */
1294export const upTo: {
1295 (options: {
1296 readonly duration?: Duration.Input | undefined
1297 readonly times?: number | undefined
1298 }): <Output, Input, Error, Env>(
1299 self: Schedule<Output, Input, Error, Env>
1300 ) => Schedule<Output, Input, Error, Env>
1301 <Output, Input, Error, Env>(
1302 self: Schedule<Output, Input, Error, Env>,
1303 options: {
1304 readonly duration?: Duration.Input | undefined
1305 readonly times?: number | undefined
1306 }
1307 ): Schedule<Output, Input, Error, Env>
1308} = dual(2, <Output, Input, Error, Env>(
1309 self: Schedule<Output, Input, Error, Env>,
1310 options: {
1311 readonly duration?: Duration.Input | undefined
1312 readonly times?: number | undefined
1313 }
1314): Schedule<Output, Input, Error, Env> => {
1315 const duration = options.duration === undefined ? undefined : Duration.fromInputUnsafe(options.duration)
1316 return while_(self, ({ attempt, elapsed }) =>
1317 effect.succeed(
1318 (options.times === undefined || attempt <= options.times) &&
1319 (duration === undefined || Duration.isLessThanOrEqualTo(Duration.millis(elapsed), duration))
1320 ))
1321})
1322
1323const while_: {
1324 <Input, Output, Meta extends Metadata<Output, Input>>(
1325 predicate: (
1326 metadata: Metadata<Output, Input>
1327 ) => metadata is Meta
1328 ): <Error, Env>(
1329 self: Schedule<Output, Input, Error, Env>
1330 ) => Schedule<Meta["output"], Meta["input"], Error, Env>
1331 <Input, Output, Error2 = never, Env2 = never>(
1332 predicate: (
1333 metadata: Metadata<Output, Input>
1334 ) => boolean | Effect<boolean, Error2, Env2>
1335 ): <Error, Env>(
1336 self: Schedule<Output, Input, Error, Env>
1337 ) => Schedule<Output, Input, Error | Error2, Env | Env2>
1338 <Output, Input, Error, Env, Meta extends Metadata<Output, Input>>(
1339 self: Schedule<Output, Input, Error, Env>,
1340 predicate: (
1341 metadata: Metadata<Output, Input>
1342 ) => metadata is Meta
1343 ): Schedule<Meta["output"], Meta["input"], Error, Env>
1344 <Output, Input, Error, Env, Error2 = never, Env2 = never>(
1345 self: Schedule<Output, Input, Error, Env>,
1346 predicate: (
1347 metadata: Metadata<Output, Input>
1348 ) => boolean | Effect<boolean, Error2, Env2>
1349 ): Schedule<Output, Input, Error | Error2, Env | Env2>
1350} = dual(2, <Output, Input, Error, Env, Error2 = never, Env2 = never>(
1351 self: Schedule<Output, Input, Error, Env>,
1352 predicate: (
1353 metadata: Metadata<Output, Input>
1354 ) => boolean | Effect<boolean, Error2, Env2>
1355): Schedule<Output, Input, Error | Error2, Env | Env2> =>
1356 fromStep(effect.map(toStep(self), (step) => {
1357 const meta = metadataFn()
1358 return (now, input) =>
1359 effect.flatMap(step(now, input), (result) => {
1360 const [output, duration] = result
1361 const eff = predicate({ ...meta(now, input), output, duration })
1362 return effect.flatMap(
1363 isEffect(eff) ? eff : effect.succeed(eff),
1364 (check) => (check ? effect.succeed(result) : Cause.done(output))
1365 )
1366 })
1367 })))
1368
1369export {
1370 /**
1371 * Returns a new schedule that continues while the predicate returns `true`.
1372 *
1373 * **When to use**
1374 *
1375 * Use to stop an existing schedule based on its full metadata, such as the
1376 * current input, output, attempt, delay, or elapsed time.
1377 *
1378 * **Details**
1379 *
1380 * The predicate receives `Metadata`, may return `boolean` or an
1381 * `Effect<boolean, ...>`, preserves the output and delay when it returns
1382 * `true`, and stops the schedule when it returns `false`.
1383 *
1384 * @see {@link upTo} for stopping after a fixed number of schedule outputs
1385 *
1386 * @category filtering
1387 * @since 4.0.0
1388 */
1389 while_ as while
1390}
1391
1392/**
1393 * Schedule that divides the timeline to `interval`-long windows, and sleeps
1394 * until the nearest window boundary every time it recurs.
1395 *
1396 * **Details**
1397 *
1398 * For example, `Schedule.windowed("10 seconds")` would produce a schedule as
1399 * follows:
1400 *
1401 * ```text
1402 * 10s 10s 10s 10s
1403 * |----------|----------|----------|----------|
1404 * |action------|sleep---|act|-sleep|action----|
1405 * ```
1406 *
1407 * **Example** (Repeating on aligned windows)
1408 *
1409 * ```ts import.meta.vitest
1410 * import { Duration, Effect, Schedule } from "effect"
1411 *
1412 * const program = Effect.gen(function*() {
1413 * const step = yield* Schedule.toStep(Schedule.windowed("5 seconds"))
1414 * return yield* step(0, undefined)
1415 * })
1416 *
1417 * await Effect.runPromise(program) // => [0, Duration.seconds(5)]
1418 * ```
1419 *
1420 * @category constructors
1421 * @since 2.0.0
1422 */
1423export const windowed = (interval: Duration.Input): Schedule<number> => {
1424 const window = Duration.toMillis(Duration.fromInputUnsafe(interval))
1425 return fromStepWithMetadata(effect.succeed((meta) =>
1426 effect.sync(() => [
1427 meta.attempt - 1,
1428 window === 0 ? Duration.zero : Duration.millis(window - (meta.elapsed % window))
1429 ])
1430 ))
1431}
1432
1433/**
1434 * Returns a new `Schedule` that will recur forever.
1435 *
1436 * **Details**
1437 *
1438 * The output of the schedule is the current count of its repetitions thus far
1439 * (i.e. `0, 1, 2, ...`).
1440 *
1441 * **Example** (Repeating forever)
1442 *
1443 * ```ts import.meta.vitest
1444 * import { Effect, Schedule } from "effect"
1445 * import { TestClock } from "effect/testing"
1446 *
1447 * const executions: Array<number> = []
1448 * const schedule = Schedule.forever.pipe(Schedule.upTo({ times: 2 }))
1449 * const program = Effect.sync(() => executions.push(executions.length + 1)).pipe(
1450 * Effect.repeat(schedule),
1451 * Effect.as(executions)
1452 * )
1453 *
1454 * await Effect.runPromise(Effect.provide(program, TestClock.layer())) // => [1, 2, 3]
1455 * ```
1456 *
1457 * @category constructors
1458 * @since 2.0.0
1459 */
1460export const forever: Schedule<number> = spaced(Duration.zero)
1461
1462/**
1463 * Returns a schedule that recurs immediately once, then completes.
1464 *
1465 * **Details**
1466 *
1467 * The output of both the recurrence and completion is `void`. When used with
1468 * `Effect.repeat`, the effect runs twice in total: once initially and once
1469 * after the schedule recurs.
1470 *
1471 * @category constructors
1472 * @since 4.0.0
1473 */
1474export const once: Schedule<void> = map(recurs(1), () => void 0)
1475
1476const constIdentity = fromStep(
1477 effect.succeed((_now, input: unknown) => effect.succeed([input, Duration.zero] as [unknown, Duration.Duration]))
1478)
1479
1480const identity_ = <A>(): Schedule<A, A> => constIdentity as Schedule<A, A>
1481
1482export {
1483 /**
1484 * Creates a schedule that always recurs, passing inputs directly as outputs.
1485 *
1486 * **When to use**
1487 *
1488 * Use when you need an infinite schedule that preserves input values as
1489 * outputs.
1490 *
1491 * **Details**
1492 *
1493 * This schedule runs indefinitely, returning each input value as its output
1494 * without modification. It effectively acts as a pass-through that simply
1495 * echoes its input values at each step.
1496 *
1497 * @see {@link forever} for an infinite schedule that returns incrementing step counts
1498 * @category constructors
1499 * @since 2.0.0
1500 */
1501 identity_ as identity
1502}
1503
1504/**
1505 * Sets the input type of the provided schedule without altering its behavior.
1506 *
1507 * **When to use**
1508 *
1509 * Use to adapt a schedule that does not depend on its input values.
1510 *
1511 * **Details**
1512 *
1513 * This helper is checked at compile time and does not change the schedule's
1514 * runtime behavior.
1515 *
1516 * **Example** (Setting a schedule input type)
1517 *
1518 * ```ts import.meta.vitest
1519 * import { Schedule } from "effect"
1520 *
1521 * const schedule = Schedule.recurs(3).pipe(
1522 * Schedule.setInputType<string>()
1523 * )
1524 * Schedule.isSchedule(schedule) // => true
1525 * ```
1526 *
1527 * @category utility types
1528 * @since 4.0.0
1529 */
1530export const setInputType =
1531 <T>() => <Output, Error, Env>(self: Schedule<Output, T, Error, Env>): Schedule<Output, T, Error, Env> => self