0.2.2 #31

Merged
Thilawyn merged 184 commits from next into master 2026-01-16 17:05:31 +01:00
Showing only changes of commit 0f79f12632 - Show all commits

View File

@@ -11,13 +11,11 @@ export type Result<A, E = never, P = never> = (
| Final<A, E, P> | Final<A, E, P>
) )
export type Final<A, E = never, P = never> = (
& (Success<A> | Failure<A, E>)
// biome-ignore lint/complexity/noBannedTypes: "{}" is relevant here // biome-ignore lint/complexity/noBannedTypes: "{}" is relevant here
& ({} | WillFetch | WillRefresh | Refreshing<P>) export type Final<A, E = never, P = never> = (Success<A> | Failure<A, E>) & ({} | Flags<P>)
) export type Flags<P = never> = WillFetch | WillRefresh | Refreshing<P>
export namespace Result { export declare namespace Result {
export interface Prototype extends Pipeable.Pipeable, Equal.Equal { export interface Prototype extends Pipeable.Pipeable, Equal.Equal {
readonly [ResultTypeId]: ResultTypeId readonly [ResultTypeId]: ResultTypeId
} }
@@ -27,6 +25,10 @@ export namespace Result {
export type Progress<R extends Result<any, any, any>> = [R] extends [Result<infer _A, infer _E, infer P>] ? P : never export type Progress<R extends Result<any, any, any>> = [R] extends [Result<infer _A, infer _E, infer P>] ? P : never
} }
export declare namespace Flags {
export type Keys = keyof WillFetch & WillRefresh & Refreshing<any>
}
export interface Initial extends Result.Prototype { export interface Initial extends Result.Prototype {
readonly _tag: "Initial" readonly _tag: "Initial"
} }
@@ -47,16 +49,16 @@ export interface Failure<A, E = never> extends Result.Prototype {
readonly previousSuccess: Option.Option<Success<A>> readonly previousSuccess: Option.Option<Success<A>>
} }
export interface WillFetch extends Result.Prototype { export interface WillFetch {
readonly willFetch: true readonly _flag: "WillFetch"
} }
export interface WillRefresh extends Result.Prototype { export interface WillRefresh {
readonly willRefresh: true readonly _flag: "WillRefresh"
} }
export interface Refreshing<P = never> extends Result.Prototype { export interface Refreshing<P = never> {
readonly refreshing: true readonly _flag: "Refreshing"
readonly progress: P readonly progress: P
} }
@@ -113,9 +115,9 @@ export const isInitial = (u: unknown): u is Initial => isResult(u) && u._tag ===
export const isRunning = (u: unknown): u is Running<unknown> => isResult(u) && u._tag === "Running" export const isRunning = (u: unknown): u is Running<unknown> => isResult(u) && u._tag === "Running"
export const isSuccess = (u: unknown): u is Success<unknown> => isResult(u) && u._tag === "Success" export const isSuccess = (u: unknown): u is Success<unknown> => isResult(u) && u._tag === "Success"
export const isFailure = (u: unknown): u is Failure<unknown, unknown> => isResult(u) && u._tag === "Failure" export const isFailure = (u: unknown): u is Failure<unknown, unknown> => isResult(u) && u._tag === "Failure"
export const isWillFetch = (u: unknown): u is WillFetch => isResult(u) && Predicate.hasProperty(u, "willFetch") export const hasWillFetchFlag = (u: unknown): u is WillFetch => isResult(u) && Predicate.hasProperty(u, "_flag") && u._flag === "WillFetch"
export const isWillRefresh = (u: unknown): u is WillRefresh => isResult(u) && Predicate.hasProperty(u, "willRefresh") export const hasWillRefreshFlag = (u: unknown): u is WillRefresh => isResult(u) && Predicate.hasProperty(u, "_flag") && u._flag === "WillRefresh"
export const isRefreshing = (u: unknown): u is Refreshing<unknown> => isResult(u) && Predicate.hasProperty(u, "refreshing") export const hasRefreshingFlag = (u: unknown): u is Refreshing<unknown> => isResult(u) && Predicate.hasProperty(u, "_flag") && u._flag === "Refreshing"
export const initial: { export const initial: {
(): Initial (): Initial
@@ -135,23 +137,23 @@ export const fail = <E, A = never>(
export const willFetch = <R extends Initial | Final<any, any, any>>( export const willFetch = <R extends Initial | Final<any, any, any>>(
result: R result: R
): Omit<R, keyof WillFetch> & WillFetch => Object.setPrototypeOf( ): Omit<R, keyof Flags.Keys> & WillFetch => Object.setPrototypeOf(
Object.assign({}, result, { willFetch: true }), Object.assign({}, result, { _flag: "WillFetch" }),
Object.getPrototypeOf(result), Object.getPrototypeOf(result),
) )
export const willRefresh = <R extends Final<any, any, any>>( export const willRefresh = <R extends Final<any, any, any>>(
result: R result: R
): Omit<R, keyof WillRefresh> & WillRefresh => Object.setPrototypeOf( ): Omit<R, keyof Flags.Keys> & WillRefresh => Object.setPrototypeOf(
Object.assign({}, result, { willRefresh: true }), Object.assign({}, result, { _flag: "WillRefresh" }),
Object.getPrototypeOf(result), Object.getPrototypeOf(result),
) )
export const refreshing = <R extends Final<any, any, any>, P = never>( export const refreshing = <R extends Final<any, any, any>, P = never>(
result: R, result: R,
progress?: P, progress?: P,
): Omit<R, keyof Refreshing<Result.Progress<R>>> & Refreshing<P> => Object.setPrototypeOf( ): Omit<R, keyof Flags.Keys> & Refreshing<P> => Object.setPrototypeOf(
Object.assign({}, result, { refreshing: true, progress }), Object.assign({}, result, { _flag: "Refreshing", progress }),
Object.getPrototypeOf(result), Object.getPrototypeOf(result),
) )
@@ -203,17 +205,17 @@ export const makeProgressLayer = <A, E, P = never>(): Layer.Layer<
const state = yield* State<A, E, P>() const state = yield* State<A, E, P>()
return { return {
update: <E, R>(f: (previous: P) => Effect.Effect<P, E, R>) => Effect.Do.pipe( update: <FE, FR>(f: (previous: P) => Effect.Effect<P, FE, FR>) => Effect.Do.pipe(
Effect.bind("previous", () => Effect.andThen(state.get, previous => Effect.bind("previous", () => Effect.andThen(state.get, previous =>
isRunning(previous) || isRefreshing(previous) (isRunning(previous) || hasRefreshingFlag(previous))
? Effect.succeed(previous) ? Effect.succeed(previous)
: Effect.fail(new PreviousResultNotRunningNorRefreshing({ previous })), : Effect.fail(new PreviousResultNotRunningNorRefreshing({ previous })),
)), )),
Effect.bind("progress", ({ previous }) => f(previous.progress)), Effect.bind("progress", ({ previous }) => f(previous.progress)),
Effect.let("next", ({ previous, progress }) => Object.setPrototypeOf( Effect.let("next", ({ previous, progress }) => isRunning(previous)
Object.assign({}, previous, { progress }), ? running(progress)
Object.getPrototypeOf(previous), : refreshing(previous, progress) as Final<A, E, P> & Refreshing<P>
)), ),
Effect.andThen(({ next }) => state.set(next)), Effect.andThen(({ next }) => state.set(next)),
), ),
} }
@@ -223,19 +225,11 @@ export const makeProgressLayer = <A, E, P = never>(): Layer.Layer<
export namespace unsafeForkEffect { export namespace unsafeForkEffect {
export type OutputContext<A, E, R, P> = Exclude<R, State<A, E, P> | Progress<P> | Progress<never>> export type OutputContext<A, E, R, P> = Exclude<R, State<A, E, P> | Progress<P> | Progress<never>>
export type Options<A, E, P> = { export interface Options<A, E, P> {
readonly initial?: Result<A, E, P> // biome-ignore lint/complexity/noBannedTypes: "{}" is relevant here
readonly initial?: (Initial | Success<A> | Failure<A, E>) & ({} | WillFetch | WillRefresh)
readonly initialProgress?: P readonly initialProgress?: P
readonly previous?: Final<A, E, P>
} & (
| {
readonly refresh: true
readonly previous: Final<A, E, P>
} }
| {
readonly refresh?: false
}
)
} }
export const unsafeForkEffect = <A, E, R, P = never>( export const unsafeForkEffect = <A, E, R, P = never>(
@@ -249,13 +243,17 @@ export const unsafeForkEffect = <A, E, R, P = never>(
Effect.bind("ref", () => Ref.make(options?.initial ?? initial<A, E, P>())), Effect.bind("ref", () => Ref.make(options?.initial ?? initial<A, E, P>())),
Effect.bind("pubsub", () => PubSub.unbounded<Result<A, E, P>>()), Effect.bind("pubsub", () => PubSub.unbounded<Result<A, E, P>>()),
Effect.bind("fiber", ({ ref, pubsub }) => Effect.forkScoped(State<A, E, P>().pipe( Effect.bind("fiber", ({ ref, pubsub }) => Effect.forkScoped(State<A, E, P>().pipe(
Effect.andThen(state => state.set(options?.refresh Effect.andThen(state => state.set(
? refreshing(options.previous, options?.initialProgress) as Result<A, E, P> (isFinal(options?.initial) && hasWillRefreshFlag(options?.initial))
? refreshing(options.initial, options?.initialProgress) as Result<A, E, P>
: running(options?.initialProgress) : running(options?.initialProgress)
).pipe( ).pipe(
Effect.andThen(effect), Effect.andThen(effect),
Effect.onExit(exit => Effect.andThen( Effect.onExit(exit => Effect.andThen(
state.set(fromExit(exit, (options?.previous && isSuccess(options.previous)) ? options.previous : undefined)), state.set(fromExit(
exit,
isSuccess(options?.initial) ? options.initial : undefined),
),
Effect.forkScoped(PubSub.shutdown(pubsub)), Effect.forkScoped(PubSub.shutdown(pubsub)),
)), )),
)), )),
@@ -286,7 +284,7 @@ export const unsafeForkEffect = <A, E, R, P = never>(
export namespace forkEffect { export namespace forkEffect {
export type InputContext<R, P> = R extends Progress<infer X> ? [X] extends [P] ? R : never : R export type InputContext<R, P> = R extends Progress<infer X> ? [X] extends [P] ? R : never : R
export type OutputContext<A, E, R, P> = unsafeForkEffect.OutputContext<A, E, R, P> export type OutputContext<A, E, R, P> = unsafeForkEffect.OutputContext<A, E, R, P>
export type Options<A, E, P> = unsafeForkEffect.Options<A, E, P> export interface Options<A, E, P> extends unsafeForkEffect.Options<A, E, P> {}
} }
export const forkEffect: { export const forkEffect: {