diff --git a/packages/effect-fc/src/Async.ts b/packages/effect-fc/src/Async.ts index e69f570..5ae3b91 100644 --- a/packages/effect-fc/src/Async.ts +++ b/packages/effect-fc/src/Async.ts @@ -1,5 +1,5 @@ -/** biome-ignore-all lint/complexity/noBannedTypes: {} is the default type for React props */ -import { Effect, Predicate, Runtime, Scope } from "effect" +/** biome-ignore-all lint/complexity/useArrowFunction: necessary for class prototypes */ +import { Effect, Function, Predicate, Runtime, Scope } from "effect" import * as React from "react" import * as Component from "./Component.js" @@ -7,11 +7,19 @@ import * as Component from "./Component.js" export const AsyncTypeId: unique symbol = Symbol.for("@effect-fc/Async/Async") export type AsyncTypeId = typeof AsyncTypeId -export interface Async extends AsyncPrototype {} +export interface Async extends AsyncPrototype, AsyncOptions {} + export interface AsyncPrototype { readonly [AsyncTypeId]: AsyncTypeId } +export interface AsyncOptions { + readonly defaultFallback?: React.ReactNode +} + +export type AsyncProps = Omit + + export const AsyncPrototype: AsyncPrototype = Object.freeze({ [AsyncTypeId]: AsyncTypeId, @@ -20,9 +28,8 @@ export const AsyncPrototype: AsyncPrototype = Object.freeze({ runtimeRef: React.RefObject>>, ) { const Inner = (props: { readonly promise: Promise }) => React.use(props.promise) - Inner.displayName = `${ this.displayName }Inner` - return (props: P) => { + return ({ fallback, name, ...props }: AsyncProps) => { const promise = Runtime.runPromise(runtimeRef.current)( Effect.andThen( Component.useScope([], this), @@ -30,7 +37,11 @@ export const AsyncPrototype: AsyncPrototype = Object.freeze({ ) ) - return React.createElement(Inner, { promise }) + return React.createElement( + React.Suspense, + { fallback: fallback ?? this.defaultFallback, name }, + React.createElement(Inner, { promise }), + ) } }, } as const) @@ -40,10 +51,35 @@ export const isAsync = (u: unknown): u is Async => Predicate.hasProperty(u, Asyn export const async = >( self: T -): T & Async => Object.setPrototypeOf( - Object.assign(() => {}, self), +): ( + & Omit> + & Component.Component< + Component.Component.Props & AsyncProps, + Component.Component.Success, + Component.Component.Error, + Component.Component.Context + > + & Async +) => Object.setPrototypeOf( + Object.assign(function() {}, self), Object.freeze(Object.setPrototypeOf( Object.assign({}, AsyncPrototype), Object.getPrototypeOf(self), )), ) + +export const withOptions: { + & Async>( + options: Partial + ): (self: T) => T + & Async>( + self: T, + options: Partial, + ): T +} = Function.dual(2, & Async>( + self: T, + options: Partial, +): T => Object.setPrototypeOf( + Object.assign(function() {}, self, options), + Object.getPrototypeOf(self), +))