Add Effect v4 support, Fast Refresh tooling, and revamped docs #56

Merged
Thilawyn merged 133 commits from next into master 2026-07-26 02:32:59 +02:00
Showing only changes of commit c6eeb7b8bc - Show all commits
+22 -25
View File
@@ -1,4 +1,4 @@
import { type Cause, Context, DateTime, type Duration, Effect, Equal, Exit, HashMap, Layer, Option, Order, Scope, Semaphore, Stream, SubscriptionRef } from "effect" import { type Cause, Context, DateTime, type Duration, Effect, Equal, Exit, flow, HashMap, Layer, Option, Order, Scope, Semaphore, Stream, SubscriptionRef } from "effect"
export interface ScopeRegistryService { export interface ScopeRegistryService {
@@ -39,14 +39,13 @@ export class ScopeRegistryServiceImpl implements ScopeRegistryService {
readonly runSemaphore: Semaphore.Semaphore, readonly runSemaphore: Semaphore.Semaphore,
) {} ) {}
register( register(
key: ScopeRegistryService.Key, key: ScopeRegistryService.Key,
options: ScopeRegistryService.RegisterOptions, options: ScopeRegistryService.RegisterOptions,
): Effect.Effect<ScopeRegistryService.Entry> { ): Effect.Effect<ScopeRegistryService.Entry> {
return Effect.gen({ self: this }, function*() { return Effect.gen({ self: this }, function*() {
const now = yield* DateTime.now const now = yield* DateTime.now
const entry = Equal.byReference<ScopeRegistryService.Entry>({ const entry = Equal.byReference({
scope: yield* Scope.make(options.finalizerExecutionStrategy), scope: yield* Scope.make(options.finalizerExecutionStrategy),
expiresAt: Option.some(DateTime.addDuration(now, options.finalizerExecutionDebounce)), expiresAt: Option.some(DateTime.addDuration(now, options.finalizerExecutionDebounce)),
finalizerExecutionDebounce: options.finalizerExecutionDebounce, finalizerExecutionDebounce: options.finalizerExecutionDebounce,
@@ -58,21 +57,34 @@ export class ScopeRegistryServiceImpl implements ScopeRegistryService {
} }
commit(key: ScopeRegistryService.Key): Effect.Effect<ScopeRegistryService.Entry, Cause.NoSuchElementError> { commit(key: ScopeRegistryService.Key): Effect.Effect<ScopeRegistryService.Entry, Cause.NoSuchElementError> {
return this.updateEntry(key, entry => Equal.byReference({ return SubscriptionRef.get(this.ref).pipe(
Effect.map(HashMap.get(key)),
Effect.flatMap(Effect.fromOption),
Effect.map(entry => Equal.byReference<ScopeRegistryService.Entry>({
...entry, ...entry,
expiresAt: Option.none(), expiresAt: Option.none(),
})) })),
Effect.tap(entry => SubscriptionRef.update(this.ref, HashMap.set(key, entry))),
)
} }
release(key: ScopeRegistryService.Key): Effect.Effect<ScopeRegistryService.Entry, Cause.NoSuchElementError> { release(key: ScopeRegistryService.Key): Effect.Effect<ScopeRegistryService.Entry, Cause.NoSuchElementError> {
return Effect.flatMap(DateTime.now, now => this.updateEntry(key, entry => Equal.byReference({ return SubscriptionRef.get(this.ref).pipe(
Effect.map(HashMap.get(key)),
Effect.flatMap(option => Effect.all([
DateTime.now,
Effect.fromOption(option),
])),
Effect.map(([now, entry]) => Equal.byReference<ScopeRegistryService.Entry>({
...entry, ...entry,
expiresAt: Option.some(DateTime.addDuration(now, entry.finalizerExecutionDebounce)), expiresAt: Option.some(DateTime.addDuration(now, entry.finalizerExecutionDebounce)),
}))) })),
Effect.tap(entry => SubscriptionRef.update(this.ref, HashMap.set(key, entry))),
)
} }
get run(): Effect.Effect<void> { get run(): Effect.Effect<void> {
return this.runSemaphore.withPermits(1)(SubscriptionRef.changes(this.ref).pipe( return SubscriptionRef.changes(this.ref).pipe(
Stream.switchMap(entries => Option.match(this.getNextExpiration(entries), { Stream.switchMap(entries => Option.match(this.getNextExpiration(entries), {
onNone: () => Stream.never, onNone: () => Stream.never,
onSome: expiresAt => Stream.fromEffect(DateTime.now.pipe( onSome: expiresAt => Stream.fromEffect(DateTime.now.pipe(
@@ -83,7 +95,8 @@ export class ScopeRegistryServiceImpl implements ScopeRegistryService {
)), )),
})), })),
Stream.runDrain, Stream.runDrain,
)) this.runSemaphore.withPermit,
)
} }
get dispose(): Effect.Effect<void> { get dispose(): Effect.Effect<void> {
@@ -99,22 +112,6 @@ export class ScopeRegistryServiceImpl implements ScopeRegistryService {
) )
} }
updateEntry(
key: ScopeRegistryService.Key,
f: (entry: ScopeRegistryService.Entry) => ScopeRegistryService.Entry,
): Effect.Effect<ScopeRegistryService.Entry, Cause.NoSuchElementError> {
return SubscriptionRef.modify(this.ref, entries => {
const current = HashMap.get(entries, key)
if (current._tag === "None")
return [Option.none(), entries]
const entry = f(current.value)
return [Option.some(entry), HashMap.set(entries, key, entry)]
}).pipe(Effect.flatMap(Effect.fromOption))
}
get closeExpired(): Effect.Effect<void> { get closeExpired(): Effect.Effect<void> {
return Effect.flatMap(DateTime.now, now => SubscriptionRef.modify( return Effect.flatMap(DateTime.now, now => SubscriptionRef.modify(
this.ref, this.ref,