Hyperlinkv0.8.0-beta.28

Polling

Polling.dynamicconstsrc/Polling.ts:532
(
  field:
    | FixedField<Duration.Duration>
    | SwappableField<Duration.Duration>,
  options?: { readonly fallback?: Duration.Input }
): Layer.Layer<PollingTag>

Cadence read from a DynamicConfig field on EVERY tick — swap the field's value (locally or over a ProcessManager control verb) and the new interval applies from the next wait. When the field is swappable AND a DynamicConfigStore is in context, the current wait also WAKES on swap, so a shorter interval takes effect immediately instead of after the old one elapses (presence-driven, like durability: no store → no subscription, and R stays never).

Read failures fall back to options.fallback when given; otherwise they are defects — a misconfigured cadence should be loud, not a silent default.

presets
Source src/Polling.ts:53256 lines
export const dynamic = (
  field: FixedField<Duration.Duration> | SwappableField<Duration.Duration>,
  options?: { readonly fallback?: Duration.Input }
): Layer.Layer<PollingTag> => {
  const fallback =
    options?.fallback === undefined
      ? undefined
      : Duration.fromInputUnsafe(options.fallback);
  const read: Effect.Effect<Duration.Duration> = Effect.catch(field, (error) =>
    fallback === undefined ? Effect.die(error) : Effect.succeed(fallback)
  );
  return registerPollingLayer(
    Layer.effect(
      PollingTag,
      Effect.gen(function* () {
        const wakeRef = yield* Ref.make<Deferred.Deferred<void, never>>(
          Deferred.makeUnsafe()
        );
        const requestWake = Effect.flatMap(Ref.get(wakeRef), (d) =>
          Deferred.succeed(d, undefined)
        ).pipe(Effect.asVoid);
        const awaitNextTick = Effect.gen(function* () {
          const d = Deferred.makeUnsafe<void, never>();
          yield* Ref.set(wakeRef, d);
          const dur = yield* read;
          yield* Effect.race(Effect.sleep(dur), Deferred.await(d)).pipe(
            Effect.asVoid
          );
        });
        // wake-on-swap: only when the field is swappable and the store is around
        const store = yield* Effect.serviceOption(DynamicConfigStore);
        if ("changes" in field && Option.isSome(store)) {
          yield* Effect.forkScoped(
            Stream.runForEach(field.changes, () => requestWake).pipe(
              Effect.provideService(DynamicConfigStore, store.value)
            )
          );
        }
        return {
          awaitNextTick,
          requestWake,
          resetCadence: requestWake,
          afterTick: Effect.void,
          peekCadence: field.pipe(
            Effect.map(Option.some),
            Effect.catch(() =>
              Effect.succeed(
                fallback === undefined ? Option.none() : Option.some(fallback)
              )
            )
          ),
        } satisfies PollingService;
      })
    )
  );
};