offline-first, p2p synced, atproto enabled, feed reader
1/**
2 * A Breaker, which allows creating wrapped functions which will only be executed before
3 * the breaker is tripped.
4 *
5 * @example
6 * ```
7 * const breaker = makeBreaker()
8 *
9 * state.addEventHandler('finish', breaker.tripThen((e) => {
10 * // this will only be allowed to run once
11 * // the second time the event fired, the handler is a no-op
12 * })
13 *
14 * state.addEventHandler('error', breaker.tripThen((e) => {
15 * // all wrapped functions created by the same breaker share state
16 * // so if the above fired, this can never be called
17 * })
18 *
19 * state.addEventHandler('message', breaker.untilTripped((e) => {
20 * // this will only be allowed to run many times
21 * // but not *after* any of the _once_ wrappers has been called
22 * })
23 * ```
24 */
25export class Breaker {
26 #tripped: boolean
27 #onTripped?: () => void
28
29 /**
30 * @param onTripped -
31 * an optional callback, called when the breaker is tripped, /before/ any wrapped functions.
32 */
33 constructor(onTripped?: () => void) {
34 this.#tripped = false
35 this.#onTripped = onTripped
36 }
37
38 /** @returns true if the breaker has already tripped */
39 tripped(): boolean {
40 return this.#tripped
41 }
42
43 /**
44 * wrap the given callback in a function that will trip the breaker before it's called.
45 * any subsequent calls to the wrapped function will be no-ops.
46 *
47 * @param fn - the function to be wrapped in the breaker
48 * @returns a wrapped function, controlled by the breaker
49 */
50 tripThen<T extends unknown[]>(fn: (...args: T) => void): (...args: T) => void {
51 return (...args: T): void => {
52 if (!this.#tripped) {
53 this.#tripped = true
54
55 // TODO: if these throw, what to do?
56 this.#onTripped?.()
57 fn(...args)
58 }
59 }
60 }
61
62 /**
63 * wrap the given callback in a function that check the breaker before it's called.
64 * once the breaker has been tripped, calls to the wrapped function will be no-ops.
65 *
66 * @param fn - the function to be wrapped in the breaker
67 * @returns a wrapped function, controlled by the breaker
68 */
69 untilTripped<T extends unknown[]>(fn: (...args: T) => void): (...args: T) => void {
70 return (...args: T): void => {
71 if (!this.#tripped) {
72 // TODO: if these throw, what to do?
73 fn(...args)
74 }
75 }
76 }
77}