effect 4.0.0 → 4.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/Cache.d.ts.map +1 -1
- package/dist/Cache.js +20 -16
- package/dist/Cache.js.map +1 -1
- package/dist/Cause.d.ts.map +1 -1
- package/dist/Cause.js +1 -1
- package/dist/Cause.js.map +1 -1
- package/dist/Effect.d.ts +150 -22
- package/dist/Effect.d.ts.map +1 -1
- package/dist/Effect.js +11 -5
- package/dist/Effect.js.map +1 -1
- package/dist/HashRing.d.ts.map +1 -1
- package/dist/HashRing.js +13 -5
- package/dist/HashRing.js.map +1 -1
- package/dist/Pool.d.ts +9 -5
- package/dist/Pool.d.ts.map +1 -1
- package/dist/Pool.js +34 -41
- package/dist/Pool.js.map +1 -1
- package/dist/RcMap.d.ts.map +1 -1
- package/dist/RcMap.js +16 -1
- package/dist/RcMap.js.map +1 -1
- package/dist/ScopedCache.d.ts.map +1 -1
- package/dist/ScopedCache.js +24 -16
- package/dist/ScopedCache.js.map +1 -1
- package/dist/Stream.d.ts +45 -6
- package/dist/Stream.d.ts.map +1 -1
- package/dist/Stream.js +55 -44
- package/dist/Stream.js.map +1 -1
- package/dist/ai/LanguageModel.d.ts +8 -0
- package/dist/ai/LanguageModel.d.ts.map +1 -1
- package/dist/ai/LanguageModel.js +13 -2
- package/dist/ai/LanguageModel.js.map +1 -1
- package/dist/ai/McpSchema.d.ts +508 -508
- package/dist/ai/McpSchema.d.ts.map +1 -1
- package/dist/ai/McpSchema.js +1 -1
- package/dist/ai/McpSchema.js.map +1 -1
- package/dist/ai/McpServer.d.ts.map +1 -1
- package/dist/ai/McpServer.js +11 -3
- package/dist/ai/McpServer.js.map +1 -1
- package/dist/ai/Response.d.ts.map +1 -1
- package/dist/ai/Response.js +22 -12
- package/dist/ai/Response.js.map +1 -1
- package/dist/ai/internal/mcpProtocol/v2024_11_05.js +1 -0
- package/dist/ai/internal/mcpProtocol/v2024_11_05.js.map +1 -1
- package/dist/ai/internal/mcpProtocol/v2025_03_26.js +1 -0
- package/dist/ai/internal/mcpProtocol/v2025_03_26.js.map +1 -1
- package/dist/ai/internal/mcpProtocol/v2025_06_18.js +1 -0
- package/dist/ai/internal/mcpProtocol/v2025_06_18.js.map +1 -1
- package/dist/ai/internal/mcpProtocol/v2025_11_25.js +1 -0
- package/dist/ai/internal/mcpProtocol/v2025_11_25.js.map +1 -1
- package/dist/ai/internal/mcpSchema/v2024_11_05.js +3 -1
- package/dist/ai/internal/mcpSchema/v2024_11_05.js.map +1 -1
- package/dist/cluster/ClusterWorkflowEngine.d.ts.map +1 -1
- package/dist/cluster/ClusterWorkflowEngine.js +11 -2
- package/dist/cluster/ClusterWorkflowEngine.js.map +1 -1
- package/dist/cluster/Sharding.d.ts.map +1 -1
- package/dist/cluster/Sharding.js +14 -7
- package/dist/cluster/Sharding.js.map +1 -1
- package/dist/cluster/SqlMessageStorage.d.ts.map +1 -1
- package/dist/cluster/SqlMessageStorage.js +21 -12
- package/dist/cluster/SqlMessageStorage.js.map +1 -1
- package/dist/cluster/SqlRunnerStorage.d.ts.map +1 -1
- package/dist/cluster/SqlRunnerStorage.js +30 -44
- package/dist/cluster/SqlRunnerStorage.js.map +1 -1
- package/dist/cluster/internal/entityManager.js +63 -62
- package/dist/cluster/internal/entityManager.js.map +1 -1
- package/dist/cluster/internal/interruptors.js +8 -0
- package/dist/cluster/internal/interruptors.js.map +1 -1
- package/dist/cluster/internal/resourceMap.js +39 -14
- package/dist/cluster/internal/resourceMap.js.map +1 -1
- package/dist/cluster/internal/resourceRef.js +168 -87
- package/dist/cluster/internal/resourceRef.js.map +1 -1
- package/dist/http/HttpMiddleware.d.ts.map +1 -1
- package/dist/http/HttpMiddleware.js +3 -2
- package/dist/http/HttpMiddleware.js.map +1 -1
- package/dist/http/HttpStaticServer.d.ts.map +1 -1
- package/dist/http/HttpStaticServer.js +14 -0
- package/dist/http/HttpStaticServer.js.map +1 -1
- package/dist/http/HttpTraceContext.d.ts +1 -1
- package/dist/http/HttpTraceContext.d.ts.map +1 -1
- package/dist/http/HttpTraceContext.js +4 -4
- package/dist/http/HttpTraceContext.js.map +1 -1
- package/dist/http-api/HttpApiError.d.ts +1 -0
- package/dist/http-api/HttpApiError.d.ts.map +1 -1
- package/dist/http-api/HttpApiError.js +1 -0
- package/dist/http-api/HttpApiError.js.map +1 -1
- package/dist/internal/core.js +43 -2
- package/dist/internal/core.js.map +1 -1
- package/dist/internal/dateTime.js +1 -1
- package/dist/internal/dateTime.js.map +1 -1
- package/dist/internal/effect.js +63 -81
- package/dist/internal/effect.js.map +1 -1
- package/dist/internal/rcRef.js +59 -23
- package/dist/internal/rcRef.js.map +1 -1
- package/dist/internal/schema/fromJsonSchemaDocument.js +11 -8
- package/dist/internal/schema/fromJsonSchemaDocument.js.map +1 -1
- package/dist/rpc/RpcClient.d.ts.map +1 -1
- package/dist/rpc/RpcClient.js +20 -14
- package/dist/rpc/RpcClient.js.map +1 -1
- package/dist/rpc/RpcServer.d.ts.map +1 -1
- package/dist/rpc/RpcServer.js +2 -2
- package/dist/rpc/RpcServer.js.map +1 -1
- package/dist/rpc/Utils.d.ts +2 -3
- package/dist/rpc/Utils.d.ts.map +1 -1
- package/dist/rpc/Utils.js +3 -4
- package/dist/rpc/Utils.js.map +1 -1
- package/dist/workflow/WorkflowEngine.d.ts +2 -0
- package/dist/workflow/WorkflowEngine.d.ts.map +1 -1
- package/dist/workflow/WorkflowEngine.js +1 -0
- package/dist/workflow/WorkflowEngine.js.map +1 -1
- package/package.json +1 -1
- package/src/Cache.ts +22 -16
- package/src/Cause.ts +1 -1
- package/src/Effect.ts +153 -22
- package/src/HashRing.ts +14 -5
- package/src/Pool.ts +28 -36
- package/src/RcMap.ts +26 -2
- package/src/ScopedCache.ts +27 -16
- package/src/Stream.ts +158 -88
- package/src/ai/LanguageModel.ts +27 -2
- package/src/ai/McpSchema.ts +3 -3
- package/src/ai/McpServer.ts +12 -3
- package/src/ai/Response.ts +27 -12
- package/src/ai/internal/mcpProtocol/v2024_11_05.ts +1 -0
- package/src/ai/internal/mcpProtocol/v2025_03_26.ts +1 -0
- package/src/ai/internal/mcpProtocol/v2025_06_18.ts +1 -0
- package/src/ai/internal/mcpProtocol/v2025_11_25.ts +1 -0
- package/src/ai/internal/mcpSchema/v2024_11_05.ts +4 -2
- package/src/cluster/ClusterWorkflowEngine.ts +18 -4
- package/src/cluster/Sharding.ts +24 -24
- package/src/cluster/SqlMessageStorage.ts +26 -13
- package/src/cluster/SqlRunnerStorage.ts +85 -113
- package/src/cluster/internal/entityManager.ts +68 -80
- package/src/cluster/internal/interruptors.ts +13 -0
- package/src/cluster/internal/resourceMap.ts +55 -17
- package/src/cluster/internal/resourceRef.ts +189 -96
- package/src/http/HttpMiddleware.ts +8 -2
- package/src/http/HttpStaticServer.ts +16 -0
- package/src/http/HttpTraceContext.ts +8 -4
- package/src/http-api/HttpApiError.ts +1 -0
- package/src/internal/core.ts +56 -2
- package/src/internal/dateTime.ts +3 -1
- package/src/internal/effect.ts +109 -111
- package/src/internal/rcRef.ts +83 -33
- package/src/internal/schema/fromJsonSchemaDocument.ts +5 -3
- package/src/rpc/RpcClient.ts +26 -22
- package/src/rpc/RpcServer.ts +2 -1
- package/src/rpc/Utils.ts +4 -10
- package/src/workflow/WorkflowEngine.ts +4 -0
package/src/Effect.ts
CHANGED
|
@@ -13909,6 +13909,11 @@ export const onExitFilter: {
|
|
|
13909
13909
|
* evaluations of the same effect will return the cached result without
|
|
13910
13910
|
* re-executing the logic.
|
|
13911
13911
|
*
|
|
13912
|
+
* Concurrent callers share the pending computation, which is interrupted only
|
|
13913
|
+
* once every caller waiting on it has been interrupted. Interrupted
|
|
13914
|
+
* computations are never cached, so the next evaluation starts a fresh
|
|
13915
|
+
* computation.
|
|
13916
|
+
*
|
|
13912
13917
|
* **Example** (Memoizing an effect until invalidated)
|
|
13913
13918
|
*
|
|
13914
13919
|
* ```ts import.meta.vitest
|
|
@@ -13970,11 +13975,12 @@ export const cached: <A, E, R>(self: Effect<A, E, R>) => Effect<Effect<A, E, R>>
|
|
|
13970
13975
|
* `Duration.Input`. The function runs once after each fresh computation,
|
|
13971
13976
|
* including failures, so successes and failures can have different TTLs. It
|
|
13972
13977
|
* does not run when the cache is created or when a cached result is reused.
|
|
13973
|
-
*
|
|
13974
|
-
* returned duration.
|
|
13978
|
+
* Interrupted computations are never cached and do not call the function.
|
|
13975
13979
|
*
|
|
13976
13980
|
* The TTL starts when the computation completes. Concurrent callers share the
|
|
13977
|
-
* pending computation
|
|
13981
|
+
* pending computation, which is interrupted only once every caller waiting on
|
|
13982
|
+
* it has been interrupted. The next evaluation then starts a fresh
|
|
13983
|
+
* computation. A zero TTL expires immediately, and an infinite TTL
|
|
13978
13984
|
* keeps the result indefinitely.
|
|
13979
13985
|
*
|
|
13980
13986
|
* **Example** (Memoizing an effect with TTL)
|
|
@@ -14054,11 +14060,12 @@ export const cachedWithTTL: {
|
|
|
14054
14060
|
* `Duration.Input`. The function runs once after each fresh computation,
|
|
14055
14061
|
* including failures, so successes and failures can have different TTLs. It
|
|
14056
14062
|
* does not run when the cache is created or when a cached result is reused.
|
|
14057
|
-
*
|
|
14058
|
-
* returned duration.
|
|
14063
|
+
* Interrupted computations are never cached and do not call the function.
|
|
14059
14064
|
*
|
|
14060
14065
|
* The TTL starts when the computation completes. Concurrent callers share the
|
|
14061
|
-
* pending computation
|
|
14066
|
+
* pending computation, which is interrupted only once every caller waiting on
|
|
14067
|
+
* it has been interrupted. The next evaluation then starts a fresh
|
|
14068
|
+
* computation. A zero TTL expires immediately, and an infinite TTL
|
|
14062
14069
|
* keeps the result indefinitely.
|
|
14063
14070
|
*
|
|
14064
14071
|
* **Example** (Memoizing an effect with TTL)
|
|
@@ -14138,11 +14145,12 @@ export const cachedWithTTL: {
|
|
|
14138
14145
|
* `Duration.Input`. The function runs once after each fresh computation,
|
|
14139
14146
|
* including failures, so successes and failures can have different TTLs. It
|
|
14140
14147
|
* does not run when the cache is created or when a cached result is reused.
|
|
14141
|
-
*
|
|
14142
|
-
* returned duration.
|
|
14148
|
+
* Interrupted computations are never cached and do not call the function.
|
|
14143
14149
|
*
|
|
14144
14150
|
* The TTL starts when the computation completes. Concurrent callers share the
|
|
14145
|
-
* pending computation
|
|
14151
|
+
* pending computation, which is interrupted only once every caller waiting on
|
|
14152
|
+
* it has been interrupted. The next evaluation then starts a fresh
|
|
14153
|
+
* computation. A zero TTL expires immediately, and an infinite TTL
|
|
14146
14154
|
* keeps the result indefinitely.
|
|
14147
14155
|
*
|
|
14148
14156
|
* **Example** (Memoizing an effect with TTL)
|
|
@@ -14222,11 +14230,12 @@ export const cachedWithTTL: {
|
|
|
14222
14230
|
* `Duration.Input`. The function runs once after each fresh computation,
|
|
14223
14231
|
* including failures, so successes and failures can have different TTLs. It
|
|
14224
14232
|
* does not run when the cache is created or when a cached result is reused.
|
|
14225
|
-
*
|
|
14226
|
-
* returned duration.
|
|
14233
|
+
* Interrupted computations are never cached and do not call the function.
|
|
14227
14234
|
*
|
|
14228
14235
|
* The TTL starts when the computation completes. Concurrent callers share the
|
|
14229
|
-
* pending computation
|
|
14236
|
+
* pending computation, which is interrupted only once every caller waiting on
|
|
14237
|
+
* it has been interrupted. The next evaluation then starts a fresh
|
|
14238
|
+
* computation. A zero TTL expires immediately, and an infinite TTL
|
|
14230
14239
|
* keeps the result indefinitely.
|
|
14231
14240
|
*
|
|
14232
14241
|
* **Example** (Memoizing an effect with TTL)
|
|
@@ -14306,11 +14315,12 @@ export const cachedWithTTL: {
|
|
|
14306
14315
|
* `Duration.Input`. The function runs once after each fresh computation,
|
|
14307
14316
|
* including failures, so successes and failures can have different TTLs. It
|
|
14308
14317
|
* does not run when the cache is created or when a cached result is reused.
|
|
14309
|
-
*
|
|
14310
|
-
* returned duration.
|
|
14318
|
+
* Interrupted computations are never cached and do not call the function.
|
|
14311
14319
|
*
|
|
14312
14320
|
* The TTL starts when the computation completes. Concurrent callers share the
|
|
14313
|
-
* pending computation
|
|
14321
|
+
* pending computation, which is interrupted only once every caller waiting on
|
|
14322
|
+
* it has been interrupted. The next evaluation then starts a fresh
|
|
14323
|
+
* computation. A zero TTL expires immediately, and an infinite TTL
|
|
14314
14324
|
* keeps the result indefinitely.
|
|
14315
14325
|
*
|
|
14316
14326
|
* **Example** (Memoizing an effect with TTL)
|
|
@@ -14371,8 +14381,8 @@ export const cachedWithTTL: {
|
|
|
14371
14381
|
} = internal.cachedWithTTL
|
|
14372
14382
|
|
|
14373
14383
|
/**
|
|
14374
|
-
* Creates a cached effect result for a
|
|
14375
|
-
* invalidation before expiration.
|
|
14384
|
+
* Creates a cached effect result for a fixed duration or a duration computed
|
|
14385
|
+
* from its `Exit` and allows manual invalidation before expiration.
|
|
14376
14386
|
*
|
|
14377
14387
|
* **When to use**
|
|
14378
14388
|
*
|
|
@@ -14430,8 +14440,67 @@ export const cachedWithTTL: {
|
|
|
14430
14440
|
*/
|
|
14431
14441
|
export const cachedInvalidateWithTTL: {
|
|
14432
14442
|
/**
|
|
14433
|
-
* Creates a cached effect result for a
|
|
14434
|
-
* invalidation before expiration.
|
|
14443
|
+
* Creates a cached effect result for a fixed duration or a duration computed
|
|
14444
|
+
* from its `Exit` and allows manual invalidation before expiration.
|
|
14445
|
+
*
|
|
14446
|
+
* **When to use**
|
|
14447
|
+
*
|
|
14448
|
+
* Use when an effect result should be cached for a bounded time and callers
|
|
14449
|
+
* also need a manual invalidation effect to force recomputation before
|
|
14450
|
+
* expiration.
|
|
14451
|
+
*
|
|
14452
|
+
* **Details**
|
|
14453
|
+
*
|
|
14454
|
+
* This function behaves similarly to {@link cachedWithTTL} by caching the
|
|
14455
|
+
* result of an effect for a specified period of time. However, it introduces an
|
|
14456
|
+
* additional feature: it provides an effect that allows you to manually
|
|
14457
|
+
* invalidate the cached result before it naturally expires.
|
|
14458
|
+
*
|
|
14459
|
+
* This gives you more control over the cache, allowing you to refresh the
|
|
14460
|
+
* result when needed, even if the original cache has not yet expired.
|
|
14461
|
+
*
|
|
14462
|
+
* Once the cache is invalidated, the next time the effect is evaluated, the
|
|
14463
|
+
* result will be recomputed, and the cache will be refreshed.
|
|
14464
|
+
*
|
|
14465
|
+
* **Example** (Memoizing with TTL and invalidation)
|
|
14466
|
+
*
|
|
14467
|
+
* ```ts import.meta.vitest
|
|
14468
|
+
* import { Effect } from "effect"
|
|
14469
|
+
* const output: Array<unknown> = []
|
|
14470
|
+
* const record = (value: unknown) => Effect.sync(() => { output.push(value) })
|
|
14471
|
+
*
|
|
14472
|
+
* let i = 1
|
|
14473
|
+
* const expensiveTask = Effect.sync(() => {
|
|
14474
|
+
* void output.push("expensive task...")
|
|
14475
|
+
* return `result ${i++}`
|
|
14476
|
+
* })
|
|
14477
|
+
*
|
|
14478
|
+
* const program = Effect.gen(function*() {
|
|
14479
|
+
* const [cached, invalidate] = yield* Effect.cachedInvalidateWithTTL(
|
|
14480
|
+
* expensiveTask,
|
|
14481
|
+
* "1 hour"
|
|
14482
|
+
* )
|
|
14483
|
+
* yield* cached.pipe(Effect.andThen(record))
|
|
14484
|
+
* yield* cached.pipe(Effect.andThen(record))
|
|
14485
|
+
* yield* invalidate
|
|
14486
|
+
* yield* cached.pipe(Effect.andThen(record))
|
|
14487
|
+
* })
|
|
14488
|
+
*
|
|
14489
|
+
* Effect.runSync(program)
|
|
14490
|
+
* output // => ["expensive task...", "result 1", "result 1", "expensive task...", "result 2"]
|
|
14491
|
+
* ```
|
|
14492
|
+
*
|
|
14493
|
+
* @see {@link cached} for a similar function that caches the result
|
|
14494
|
+
* indefinitely.
|
|
14495
|
+
* @see {@link cachedWithTTL} for a similar function that caches the result for
|
|
14496
|
+
* a specified duration but does not include an effect for manual invalidation.
|
|
14497
|
+
* @category caching
|
|
14498
|
+
* @since 2.0.0
|
|
14499
|
+
*/
|
|
14500
|
+
<A, E>(timeToLive: (exit: Exit.Exit<A, E>) => Duration.Input): <R>(self: Effect<A, E, R>) => Effect<[Effect<A, E, R>, Effect<void>]>
|
|
14501
|
+
/**
|
|
14502
|
+
* Creates a cached effect result for a fixed duration or a duration computed
|
|
14503
|
+
* from its `Exit` and allows manual invalidation before expiration.
|
|
14435
14504
|
*
|
|
14436
14505
|
* **When to use**
|
|
14437
14506
|
*
|
|
@@ -14489,8 +14558,67 @@ export const cachedInvalidateWithTTL: {
|
|
|
14489
14558
|
*/
|
|
14490
14559
|
(timeToLive: Duration.Input): <A, E, R>(self: Effect<A, E, R>) => Effect<[Effect<A, E, R>, Effect<void>]>
|
|
14491
14560
|
/**
|
|
14492
|
-
* Creates a cached effect result for a
|
|
14493
|
-
* invalidation before expiration.
|
|
14561
|
+
* Creates a cached effect result for a fixed duration or a duration computed
|
|
14562
|
+
* from its `Exit` and allows manual invalidation before expiration.
|
|
14563
|
+
*
|
|
14564
|
+
* **When to use**
|
|
14565
|
+
*
|
|
14566
|
+
* Use when an effect result should be cached for a bounded time and callers
|
|
14567
|
+
* also need a manual invalidation effect to force recomputation before
|
|
14568
|
+
* expiration.
|
|
14569
|
+
*
|
|
14570
|
+
* **Details**
|
|
14571
|
+
*
|
|
14572
|
+
* This function behaves similarly to {@link cachedWithTTL} by caching the
|
|
14573
|
+
* result of an effect for a specified period of time. However, it introduces an
|
|
14574
|
+
* additional feature: it provides an effect that allows you to manually
|
|
14575
|
+
* invalidate the cached result before it naturally expires.
|
|
14576
|
+
*
|
|
14577
|
+
* This gives you more control over the cache, allowing you to refresh the
|
|
14578
|
+
* result when needed, even if the original cache has not yet expired.
|
|
14579
|
+
*
|
|
14580
|
+
* Once the cache is invalidated, the next time the effect is evaluated, the
|
|
14581
|
+
* result will be recomputed, and the cache will be refreshed.
|
|
14582
|
+
*
|
|
14583
|
+
* **Example** (Memoizing with TTL and invalidation)
|
|
14584
|
+
*
|
|
14585
|
+
* ```ts import.meta.vitest
|
|
14586
|
+
* import { Effect } from "effect"
|
|
14587
|
+
* const output: Array<unknown> = []
|
|
14588
|
+
* const record = (value: unknown) => Effect.sync(() => { output.push(value) })
|
|
14589
|
+
*
|
|
14590
|
+
* let i = 1
|
|
14591
|
+
* const expensiveTask = Effect.sync(() => {
|
|
14592
|
+
* void output.push("expensive task...")
|
|
14593
|
+
* return `result ${i++}`
|
|
14594
|
+
* })
|
|
14595
|
+
*
|
|
14596
|
+
* const program = Effect.gen(function*() {
|
|
14597
|
+
* const [cached, invalidate] = yield* Effect.cachedInvalidateWithTTL(
|
|
14598
|
+
* expensiveTask,
|
|
14599
|
+
* "1 hour"
|
|
14600
|
+
* )
|
|
14601
|
+
* yield* cached.pipe(Effect.andThen(record))
|
|
14602
|
+
* yield* cached.pipe(Effect.andThen(record))
|
|
14603
|
+
* yield* invalidate
|
|
14604
|
+
* yield* cached.pipe(Effect.andThen(record))
|
|
14605
|
+
* })
|
|
14606
|
+
*
|
|
14607
|
+
* Effect.runSync(program)
|
|
14608
|
+
* output // => ["expensive task...", "result 1", "result 1", "expensive task...", "result 2"]
|
|
14609
|
+
* ```
|
|
14610
|
+
*
|
|
14611
|
+
* @see {@link cached} for a similar function that caches the result
|
|
14612
|
+
* indefinitely.
|
|
14613
|
+
* @see {@link cachedWithTTL} for a similar function that caches the result for
|
|
14614
|
+
* a specified duration but does not include an effect for manual invalidation.
|
|
14615
|
+
* @category caching
|
|
14616
|
+
* @since 2.0.0
|
|
14617
|
+
*/
|
|
14618
|
+
<A, E>(timeToLive: Duration.Input | ((exit: Exit.Exit<A, E>) => Duration.Input)): <R>(self: Effect<A, E, R>) => Effect<[Effect<A, E, R>, Effect<void>]>
|
|
14619
|
+
/**
|
|
14620
|
+
* Creates a cached effect result for a fixed duration or a duration computed
|
|
14621
|
+
* from its `Exit` and allows manual invalidation before expiration.
|
|
14494
14622
|
*
|
|
14495
14623
|
* **When to use**
|
|
14496
14624
|
*
|
|
@@ -14546,7 +14674,10 @@ export const cachedInvalidateWithTTL: {
|
|
|
14546
14674
|
* @category caching
|
|
14547
14675
|
* @since 2.0.0
|
|
14548
14676
|
*/
|
|
14549
|
-
<A, E, R>(
|
|
14677
|
+
<A, E, R>(
|
|
14678
|
+
self: Effect<A, E, R>,
|
|
14679
|
+
timeToLive: Duration.Input | ((exit: Exit.Exit<A, E>) => Duration.Input)
|
|
14680
|
+
): Effect<[Effect<A, E, R>, Effect<void>]>
|
|
14550
14681
|
} = internal.cachedInvalidateWithTTL
|
|
14551
14682
|
|
|
14552
14683
|
// -----------------------------------------------------------------------------
|
package/src/HashRing.ts
CHANGED
|
@@ -179,12 +179,9 @@ export const addMany: {
|
|
|
179
179
|
if (entry[1] === weight) continue
|
|
180
180
|
toRemove ??= new Set()
|
|
181
181
|
toRemove.add(key)
|
|
182
|
-
self.totalWeightCache -= entry[1]
|
|
183
|
-
self.totalWeightCache += weight
|
|
184
182
|
entry[1] = weight
|
|
185
183
|
} else {
|
|
186
184
|
self.nodes.set(key, [node, weight])
|
|
187
|
-
self.totalWeightCache += weight
|
|
188
185
|
}
|
|
189
186
|
keys.push(key)
|
|
190
187
|
}
|
|
@@ -192,6 +189,7 @@ export const addMany: {
|
|
|
192
189
|
self.ring = self.ring.filter(([, n]) => !toRemove.has(n))
|
|
193
190
|
}
|
|
194
191
|
addNodesToRing(self, keys, Math.round(weight * self.baseWeight))
|
|
192
|
+
updateTotalWeight(self)
|
|
195
193
|
return self
|
|
196
194
|
}
|
|
197
195
|
)
|
|
@@ -206,7 +204,18 @@ function addNodesToRing<A extends PrimaryKey.PrimaryKey>(self: HashRing<A>, keys
|
|
|
206
204
|
])
|
|
207
205
|
}
|
|
208
206
|
}
|
|
209
|
-
|
|
207
|
+
// Break hash ties by node key to avoid insertion-order dependence.
|
|
208
|
+
self.ring.sort((a, b) => a[0] - b[0] || (a[1] < b[1] ? -1 : a[1] > b[1] ? 1 : 0))
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
// Sum in key order to avoid history-dependent floating-point rounding.
|
|
212
|
+
function updateTotalWeight<A extends PrimaryKey.PrimaryKey>(self: HashRing<A>) {
|
|
213
|
+
const keys = Array.from(self.nodes.keys()).sort()
|
|
214
|
+
let total = 0
|
|
215
|
+
for (let i = 0; i < keys.length; i++) {
|
|
216
|
+
total += self.nodes.get(keys[i])![1]
|
|
217
|
+
}
|
|
218
|
+
self.totalWeightCache = total
|
|
210
219
|
}
|
|
211
220
|
|
|
212
221
|
/**
|
|
@@ -380,7 +389,7 @@ export const remove: {
|
|
|
380
389
|
if (entry) {
|
|
381
390
|
self.nodes.delete(key)
|
|
382
391
|
self.ring = self.ring.filter(([, n]) => n !== key)
|
|
383
|
-
self
|
|
392
|
+
updateTotalWeight(self)
|
|
384
393
|
}
|
|
385
394
|
return self
|
|
386
395
|
})
|
package/src/Pool.ts
CHANGED
|
@@ -19,7 +19,6 @@ import * as Fiber from "./Fiber.ts"
|
|
|
19
19
|
import { constant, dual, identity } from "./Function.ts"
|
|
20
20
|
import * as core from "./internal/core.ts"
|
|
21
21
|
import * as internal from "./internal/effect.ts"
|
|
22
|
-
import * as Iterable from "./Iterable.ts"
|
|
23
22
|
import { type Pipeable, pipeArguments } from "./Pipeable.ts"
|
|
24
23
|
import { hasProperty } from "./Predicate.ts"
|
|
25
24
|
import * as Queue from "./Queue.ts"
|
|
@@ -31,10 +30,12 @@ const TypeId = "~effect/Pool"
|
|
|
31
30
|
|
|
32
31
|
const Acquire = Symbol()
|
|
33
32
|
const AcquireContext = Symbol()
|
|
33
|
+
const PoolClock = Symbol()
|
|
34
34
|
|
|
35
35
|
interface PoolImpl<A, E> extends Pool<A, E> {
|
|
36
36
|
readonly [Acquire]: Effect.Effect<A, E, Scope.Scope>
|
|
37
37
|
readonly [AcquireContext]: Context.Context<Scope.Scope>
|
|
38
|
+
readonly [PoolClock]: Clock
|
|
38
39
|
}
|
|
39
40
|
|
|
40
41
|
/**
|
|
@@ -119,7 +120,6 @@ export interface State<A, E> {
|
|
|
119
120
|
isShuttingDown: boolean
|
|
120
121
|
usage: number
|
|
121
122
|
readonly resizeSemaphore: Semaphore.Semaphore
|
|
122
|
-
// Insertion order determines usage-TTL retirement order; reclaimed items move to the back.
|
|
123
123
|
readonly items: Set<PoolItem<A, E>>
|
|
124
124
|
availableHead: PoolItem<A, E> | undefined
|
|
125
125
|
availableTail: PoolItem<A, E> | undefined
|
|
@@ -139,8 +139,8 @@ export interface State<A, E> {
|
|
|
139
139
|
* **Details**
|
|
140
140
|
*
|
|
141
141
|
* Each item stores the acquisition `Exit`, its finalizer, the current
|
|
142
|
-
* reference count, and whether automatic reclaiming
|
|
143
|
-
* the item was invalidated.
|
|
142
|
+
* reference count, when it last became idle, and whether automatic reclaiming
|
|
143
|
+
* has been disabled because the item was invalidated.
|
|
144
144
|
*
|
|
145
145
|
* @see {@link Strategy} for the custom strategy callbacks that receive and return pool items
|
|
146
146
|
* @see {@link State} for the runtime sets that store active, available, and invalidated pool items
|
|
@@ -152,6 +152,8 @@ export interface PoolItem<A, E> {
|
|
|
152
152
|
readonly exit: Exit.Exit<A, E>
|
|
153
153
|
finalizer: Effect.Effect<void>
|
|
154
154
|
refCount: number
|
|
155
|
+
/** Clock time at which the item was acquired or its last borrower released it. */
|
|
156
|
+
idleSince: number
|
|
155
157
|
disableReclaim: boolean
|
|
156
158
|
isAvailable: boolean
|
|
157
159
|
availablePrevious: PoolItem<A, E> | undefined
|
|
@@ -257,9 +259,11 @@ export const make = <A, E, R>(options: {
|
|
|
257
259
|
* pool implementation. A value of `1` waits until existing items are fully
|
|
258
260
|
* utilized before creating more items.
|
|
259
261
|
*
|
|
260
|
-
* `timeToLiveStrategy`
|
|
261
|
-
*
|
|
262
|
-
* `
|
|
262
|
+
* `timeToLiveStrategy` defaults to `"usage"`, which expires idle, unreserved
|
|
263
|
+
* excess items after `timeToLive` since their last borrower released them
|
|
264
|
+
* (or acquisition if never borrowed). It checks once per `timeToLive`, so
|
|
265
|
+
* retirement can take up to twice that duration. `"creation"` measures from
|
|
266
|
+
* item creation instead.
|
|
263
267
|
*
|
|
264
268
|
* **Example** (Creating a connection pool)
|
|
265
269
|
*
|
|
@@ -374,6 +378,7 @@ export const makeWithStrategy = <A, E, R>(options: {
|
|
|
374
378
|
[TypeId]: TypeId,
|
|
375
379
|
[Acquire]: options.acquire as Effect.Effect<A, E, Scope.Scope>,
|
|
376
380
|
[AcquireContext]: services as Context.Context<Scope.Scope>,
|
|
381
|
+
[PoolClock]: yield* Clock,
|
|
377
382
|
config,
|
|
378
383
|
state,
|
|
379
384
|
pipe() {
|
|
@@ -676,6 +681,9 @@ const releaseItem = <A, E>(self: Pool<A, E>, item: PoolItem<A, E>): Effect.Effec
|
|
|
676
681
|
const state = self.state
|
|
677
682
|
item.refCount--
|
|
678
683
|
state.usage--
|
|
684
|
+
if (item.refCount === 0) {
|
|
685
|
+
item.idleSince = (self as PoolImpl<A, E>)[PoolClock].currentTimeMillisUnsafe()
|
|
686
|
+
}
|
|
679
687
|
if (state.invalidated.has(item)) {
|
|
680
688
|
return invalidatePoolItem(self, item)
|
|
681
689
|
}
|
|
@@ -1014,6 +1022,7 @@ const allocate = <A, E>(self: Pool<A, E>): Effect.Effect<PoolItem<A, E>> =>
|
|
|
1014
1022
|
exit,
|
|
1015
1023
|
finalizer: Effect.catchCause(Scope.close(scope, exit), reportUnhandledError),
|
|
1016
1024
|
refCount: 0,
|
|
1025
|
+
idleSince: impl[PoolClock].currentTimeMillisUnsafe(),
|
|
1017
1026
|
disableReclaim: false,
|
|
1018
1027
|
isAvailable: false,
|
|
1019
1028
|
availablePrevious: undefined,
|
|
@@ -1110,19 +1119,21 @@ const strategyCreationTTL = Effect.fnUntraced(function*<A, E>(ttl: Duration.Inpu
|
|
|
1110
1119
|
})
|
|
1111
1120
|
})
|
|
1112
1121
|
|
|
1113
|
-
const strategyUsageTTL =
|
|
1114
|
-
|
|
1122
|
+
const strategyUsageTTL = Effect.fnUntraced(function*<A, E>(ttl: Duration.Input) {
|
|
1123
|
+
const clock = yield* Clock
|
|
1124
|
+
const ttlMillis = Duration.toMillis(Duration.fromInputUnsafe(ttl))
|
|
1125
|
+
return identity<Strategy<A, E>>({
|
|
1115
1126
|
run: (pool) => {
|
|
1116
|
-
// `state.items` iterates in insertion order, so its oldest live entry is
|
|
1117
|
-
// the next to retire. Using it directly means an item stops being
|
|
1118
|
-
// referenced by this strategy as soon as it leaves the pool.
|
|
1119
1127
|
const process: Effect.Effect<void> = Effect.suspend(() => {
|
|
1120
1128
|
if (activeSize(pool) <= targetSize(pool)) return Effect.void
|
|
1129
|
+
const expired = clock.currentTimeMillisUnsafe() - ttlMillis
|
|
1130
|
+
let oldest: PoolItem<A, E> | undefined
|
|
1121
1131
|
for (const item of pool.state.items) {
|
|
1122
|
-
if (
|
|
1123
|
-
|
|
1132
|
+
if (item.refCount > 0 || item.idleSince > expired) continue
|
|
1133
|
+
if (pool.state.invalidated.has(item) || reservations.has(item)) continue
|
|
1134
|
+
if (oldest === undefined || item.idleSince < oldest.idleSince) oldest = item
|
|
1124
1135
|
}
|
|
1125
|
-
return Effect.void
|
|
1136
|
+
return oldest === undefined ? Effect.void : Effect.flatMap(invalidatePoolItem(pool, oldest), () => process)
|
|
1126
1137
|
})
|
|
1127
1138
|
return process.pipe(
|
|
1128
1139
|
Effect.delay(ttl),
|
|
@@ -1130,28 +1141,9 @@ const strategyUsageTTL = <A, E>(ttl: Duration.Input): Effect.Effect<Strategy<A,
|
|
|
1130
1141
|
)
|
|
1131
1142
|
},
|
|
1132
1143
|
onAcquire: (_) => Effect.void,
|
|
1133
|
-
reclaim(
|
|
1134
|
-
return Effect.suspend((): Effect.Effect<PoolItem<A, E> | undefined> => {
|
|
1135
|
-
if (pool.state.invalidated.size === 0) {
|
|
1136
|
-
return Effect.undefined
|
|
1137
|
-
}
|
|
1138
|
-
const item = Iterable.head(
|
|
1139
|
-
Iterable.filter(pool.state.invalidated, (item) => !item.disableReclaim && !reservations.has(item))
|
|
1140
|
-
)
|
|
1141
|
-
if (item._tag === "None") {
|
|
1142
|
-
return Effect.undefined
|
|
1143
|
-
}
|
|
1144
|
-
pool.state.invalidated.delete(item.value)
|
|
1145
|
-
// Re-adding moves the reclaimed item to the back of the retirement order.
|
|
1146
|
-
pool.state.items.delete(item.value)
|
|
1147
|
-
pool.state.items.add(item.value)
|
|
1148
|
-
if (item.value.refCount < pool.config.concurrency) {
|
|
1149
|
-
addAvailable(pool, item.value)
|
|
1150
|
-
}
|
|
1151
|
-
return Effect.succeed(item.value)
|
|
1152
|
-
})
|
|
1153
|
-
}
|
|
1144
|
+
reclaim: (_) => Effect.undefined
|
|
1154
1145
|
})
|
|
1146
|
+
})
|
|
1155
1147
|
|
|
1156
1148
|
const reportUnhandledError = <E>(cause: Cause.Cause<E>) =>
|
|
1157
1149
|
Effect.withFiber<void>((fiber) => {
|
package/src/RcMap.ts
CHANGED
|
@@ -566,10 +566,16 @@ export const get: {
|
|
|
566
566
|
context.set(key, value)
|
|
567
567
|
})
|
|
568
568
|
context.set(Scope.Scope.key, entry.scope)
|
|
569
|
+
const lookupContext: Context.Context<Scope.Scope> = Context.makeUnsafe(context)
|
|
569
570
|
Effect.suspend(() => self.lookup(key)).pipe(
|
|
570
|
-
Effect.runForkWith(
|
|
571
|
+
Effect.runForkWith(lookupContext),
|
|
571
572
|
Fiber.runIn(entry.scope)
|
|
572
|
-
).addObserver((exit) =>
|
|
573
|
+
).addObserver((exit) => {
|
|
574
|
+
// Interruption is abandonment, so the entry is dropped and the next
|
|
575
|
+
// get starts a fresh lookup.
|
|
576
|
+
if (Exit.hasInterrupts(exit)) removeInterrupted(self, key, entry, lookupContext)
|
|
577
|
+
Deferred.doneUnsafe(entry.deferred, exit)
|
|
578
|
+
})
|
|
573
579
|
}
|
|
574
580
|
const scope = Context.getUnsafe(parent.context, Scope.Scope)
|
|
575
581
|
return Scope.addFinalizer(scope, entry.finalizer).pipe(
|
|
@@ -733,6 +739,24 @@ export const getOption: {
|
|
|
733
739
|
})
|
|
734
740
|
)
|
|
735
741
|
|
|
742
|
+
const removeInterrupted = <K, A, E>(
|
|
743
|
+
self: RcMap<K, A, E>,
|
|
744
|
+
key: K,
|
|
745
|
+
entry: State.Entry<A, E>,
|
|
746
|
+
context: Context.Context<Scope.Scope>
|
|
747
|
+
) => {
|
|
748
|
+
if (self.state._tag === "Closed") return
|
|
749
|
+
const o = MutableHashMap.get(self.state.map, key)
|
|
750
|
+
if (o._tag === "None" || o.value !== entry) return
|
|
751
|
+
MutableHashMap.remove(self.state.map, key)
|
|
752
|
+
// Borrowers close the entry on release; an idle entry has none left.
|
|
753
|
+
if (entry.refCount > 0) return
|
|
754
|
+
closeEntry(entry).pipe(
|
|
755
|
+
Effect.runForkWith(context),
|
|
756
|
+
Fiber.runIn(self.scope)
|
|
757
|
+
)
|
|
758
|
+
}
|
|
759
|
+
|
|
736
760
|
const closeEntry = <A, E>(entry: State.Entry<A, E>) =>
|
|
737
761
|
entry.fiber
|
|
738
762
|
? Fiber.interrupt(entry.fiber).pipe(Effect.andThen(Scope.close(entry.scope, Exit.void)))
|
package/src/ScopedCache.ts
CHANGED
|
@@ -317,7 +317,7 @@ export const get: {
|
|
|
317
317
|
// Move the entry to the end of the map to keep it fresh
|
|
318
318
|
MutableHashMap.remove(state.map, key)
|
|
319
319
|
MutableHashMap.set(state.map, key, oentry.value)
|
|
320
|
-
return awaitEntry(oentry.value, restore)
|
|
320
|
+
return awaitEntry(self, key, oentry.value, restore)
|
|
321
321
|
}
|
|
322
322
|
const scope = Scope.makeUnsafe()
|
|
323
323
|
const deferred = Deferred.makeUnsafe</**
|
|
@@ -380,41 +380,46 @@ export const get: {
|
|
|
380
380
|
entry.fiber = effect.forkUnsafe(
|
|
381
381
|
fiber,
|
|
382
382
|
effect.onExit(effect.suspend(() => Scope.provide(self.lookup(key), scope)), (exit) => {
|
|
383
|
-
Deferred.doneUnsafe(deferred, exit)
|
|
384
383
|
if (effect.exitHasInterrupts(exit)) {
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
if (Option.isSome(current) && current.value === entry) MutableHashMap.remove(self.state.map, key)
|
|
388
|
-
}
|
|
384
|
+
removeEntry(self, key, entry)
|
|
385
|
+
Deferred.doneUnsafe(deferred, exit)
|
|
389
386
|
return Scope.close(scope, exit)
|
|
390
387
|
}
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
388
|
+
try {
|
|
389
|
+
const ttl = self.timeToLive(exit, key)
|
|
390
|
+
if (Duration.isFinite(ttl)) {
|
|
391
|
+
entry.expiresAt = fiber.getRef(effect.ClockRef).currentTimeMillisUnsafe() + Duration.toMillis(ttl)
|
|
392
|
+
}
|
|
393
|
+
} finally {
|
|
394
|
+
Deferred.doneUnsafe(deferred, exit)
|
|
394
395
|
}
|
|
395
396
|
return effect.void
|
|
396
397
|
}),
|
|
397
398
|
true,
|
|
398
399
|
true
|
|
399
400
|
)
|
|
400
|
-
return awaitEntry(entry, restore)
|
|
401
|
+
return awaitEntry(self, key, entry, restore)
|
|
401
402
|
})
|
|
402
403
|
)
|
|
403
404
|
})
|
|
404
405
|
)
|
|
405
406
|
)
|
|
406
407
|
|
|
407
|
-
const awaitEntry = <A, E>(
|
|
408
|
+
const awaitEntry = <Key, A, E, R>(
|
|
409
|
+
self: ScopedCache<Key, A, E, R>,
|
|
410
|
+
key: Key,
|
|
408
411
|
entry: Entry<A, E>,
|
|
409
412
|
restore: <X, Y, Z>(effect: Effect.Effect<X, Y, Z>) => Effect.Effect<X, Y, Z>
|
|
410
413
|
): Effect.Effect<A, E> => {
|
|
411
|
-
|
|
412
|
-
if (fiber === undefined || Deferred.isDoneUnsafe(entry.deferred)) return restore(Deferred.await(entry.deferred))
|
|
414
|
+
if (Deferred.isDoneUnsafe(entry.deferred)) return restore(Deferred.await(entry.deferred))
|
|
413
415
|
entry.awaiters++
|
|
414
416
|
// Install cleanup before restore so a pending interrupt cannot skip the decrement.
|
|
415
417
|
return effect.onExit(restore(Deferred.await(entry.deferred)), () => {
|
|
416
418
|
entry.awaiters--
|
|
417
|
-
|
|
419
|
+
const fiber = entry.fiber
|
|
420
|
+
if (entry.awaiters > 0 || fiber === undefined || Deferred.isDoneUnsafe(entry.deferred)) return effect.void
|
|
421
|
+
// Detach before interruption so callers arriving during finalization start fresh.
|
|
422
|
+
removeEntry(self, key, entry)
|
|
418
423
|
return effect.flatMap(effect.fiberInterrupt(fiber), () => {
|
|
419
424
|
const exit = fiber.pollUnsafe()!
|
|
420
425
|
return Exit.isFailure(exit) && Cause.hasDies(exit.cause) ? effect.failCause(exit.cause) : effect.void
|
|
@@ -422,6 +427,12 @@ const awaitEntry = <A, E>(
|
|
|
422
427
|
})
|
|
423
428
|
}
|
|
424
429
|
|
|
430
|
+
const removeEntry = <Key, A, E, R>(self: ScopedCache<Key, A, E, R>, key: Key, entry: Entry<A, E>): void => {
|
|
431
|
+
if (self.state._tag !== "Open") return
|
|
432
|
+
const current = MutableHashMap.get(self.state.map, key)
|
|
433
|
+
if (Option.isSome(current) && current.value === entry) MutableHashMap.remove(self.state.map, key)
|
|
434
|
+
}
|
|
435
|
+
|
|
425
436
|
const hasExpired = <A, E>(entry: Entry<A, E>, fiber: Fiber.Fiber<unknown, unknown>): boolean => {
|
|
426
437
|
if (entry.expiresAt === undefined) {
|
|
427
438
|
return false
|
|
@@ -518,7 +529,7 @@ export const getOption: {
|
|
|
518
529
|
core.withFiber((fiber) =>
|
|
519
530
|
effect.flatMap(
|
|
520
531
|
getImpl(self, key, fiber),
|
|
521
|
-
(entry) => entry ? effect.asSome(awaitEntry(entry, restore)) : effect.succeedNone
|
|
532
|
+
(entry) => entry ? effect.asSome(awaitEntry(self, key, entry, restore)) : effect.succeedNone
|
|
522
533
|
)
|
|
523
534
|
)
|
|
524
535
|
)
|
|
@@ -1004,7 +1015,7 @@ export const invalidateWhen: {
|
|
|
1004
1015
|
if (entry === undefined) {
|
|
1005
1016
|
return effect.succeed(false)
|
|
1006
1017
|
}
|
|
1007
|
-
return awaitEntry(entry, restore).pipe(
|
|
1018
|
+
return awaitEntry(self, key, entry, restore).pipe(
|
|
1008
1019
|
effect.flatMap((value) => {
|
|
1009
1020
|
if (self.state._tag === "Closed") {
|
|
1010
1021
|
return effect.succeed(false)
|