effect 4.0.0-rc.118 → 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/README.md +17 -4
- package/dist/Array.d.ts +25 -25
- package/dist/Array.d.ts.map +1 -1
- package/dist/Array.js +16 -16
- package/dist/Array.js.map +1 -1
- 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/Chunk.d.ts +26 -26
- package/dist/Chunk.d.ts.map +1 -1
- package/dist/Chunk.js +15 -15
- package/dist/Chunk.js.map +1 -1
- package/dist/Effect.d.ts +167 -39
- package/dist/Effect.d.ts.map +1 -1
- package/dist/Effect.js +16 -10
- 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/MutableHashMap.d.ts.map +1 -1
- package/dist/MutableHashMap.js +4 -22
- package/dist/MutableHashMap.js.map +1 -1
- package/dist/Option.d.ts +17 -17
- package/dist/Option.d.ts.map +1 -1
- package/dist/Option.js +6 -6
- package/dist/Option.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/Queue.d.ts +19 -2
- package/dist/Queue.d.ts.map +1 -1
- package/dist/Queue.js +9 -4
- package/dist/Queue.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/Record.d.ts +14 -14
- package/dist/Record.d.ts.map +1 -1
- package/dist/Record.js +10 -10
- package/dist/Record.js.map +1 -1
- package/dist/Schema.d.ts +37 -21
- package/dist/Schema.d.ts.map +1 -1
- package/dist/Schema.js +29 -10
- package/dist/Schema.js.map +1 -1
- package/dist/SchemaAST.d.ts.map +1 -1
- package/dist/SchemaAST.js +116 -97
- package/dist/SchemaAST.js.map +1 -1
- package/dist/SchemaRepresentation.d.ts +3 -1
- package/dist/SchemaRepresentation.d.ts.map +1 -1
- package/dist/SchemaRepresentation.js +3 -1
- package/dist/SchemaRepresentation.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 +402 -402
- 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 +16 -3
- package/dist/cluster/ClusterWorkflowEngine.js.map +1 -1
- package/dist/cluster/MessageStorage.d.ts +32 -8
- package/dist/cluster/MessageStorage.d.ts.map +1 -1
- package/dist/cluster/MessageStorage.js +2 -1
- package/dist/cluster/MessageStorage.js.map +1 -1
- package/dist/cluster/Sharding.d.ts +5 -2
- package/dist/cluster/Sharding.d.ts.map +1 -1
- package/dist/cluster/Sharding.js +15 -8
- package/dist/cluster/Sharding.js.map +1 -1
- package/dist/cluster/SqlMessageStorage.d.ts.map +1 -1
- package/dist/cluster/SqlMessageStorage.js +36 -16
- 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/HttpEffect.d.ts +1 -0
- package/dist/http/HttpEffect.d.ts.map +1 -1
- package/dist/http/HttpEffect.js +29 -22
- package/dist/http/HttpEffect.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 +138 -165
- 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/annotations.js +1 -7
- package/dist/internal/schema/annotations.js.map +1 -1
- package/dist/internal/schema/codegen.js +56 -28
- package/dist/internal/schema/codegen.js.map +1 -1
- package/dist/internal/schema/fromJsonSchemaDocument.js +11 -8
- package/dist/internal/schema/fromJsonSchemaDocument.js.map +1 -1
- package/dist/internal/schema/toCodeDocument.js +10 -30
- package/dist/internal/schema/toCodeDocument.js.map +1 -1
- package/dist/internal/schema/toCodec.js +1 -15
- package/dist/internal/schema/toCodec.js.map +1 -1
- package/dist/internal/schema/toEquivalence.js +4 -3
- package/dist/internal/schema/toEquivalence.js.map +1 -1
- package/dist/internal/schema/toFormatter.js +4 -4
- package/dist/internal/schema/toFormatter.js.map +1 -1
- package/dist/reactivity/Atom.d.ts.map +1 -1
- package/dist/reactivity/Atom.js +6 -2
- package/dist/reactivity/Atom.js.map +1 -1
- package/dist/reactivity/AtomRpc.d.ts +3 -3
- package/dist/reactivity/AtomRpc.d.ts.map +1 -1
- package/dist/rpc/Rpc.d.ts +2 -2
- package/dist/rpc/Rpc.d.ts.map +1 -1
- package/dist/rpc/RpcClient.d.ts +7 -7
- package/dist/rpc/RpcClient.d.ts.map +1 -1
- package/dist/rpc/RpcClient.js +21 -16
- package/dist/rpc/RpcClient.js.map +1 -1
- package/dist/rpc/RpcMessage.d.ts +1 -1
- package/dist/rpc/RpcMessage.d.ts.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/schema/SchemaCompiler/runtime.js +6 -1
- package/dist/schema/SchemaCompiler/runtime.js.map +1 -1
- package/dist/testing/TestSchema.d.ts +234 -6
- package/dist/testing/TestSchema.d.ts.map +1 -1
- package/dist/testing/TestSchema.js +244 -25
- package/dist/testing/TestSchema.js.map +1 -1
- package/dist/testing/index.d.ts +1 -0
- package/dist/testing/index.d.ts.map +1 -1
- package/dist/testing/index.js +1 -0
- package/dist/testing/index.js.map +1 -1
- package/dist/workflow/DurableDeferred.js +1 -1
- package/dist/workflow/DurableDeferred.js.map +1 -1
- package/dist/workflow/Workflow.js +1 -1
- package/dist/workflow/Workflow.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/Array.ts +31 -31
- package/src/Cache.ts +22 -16
- package/src/Cause.ts +1 -1
- package/src/Chunk.ts +29 -29
- package/src/Effect.ts +170 -39
- package/src/HashRing.ts +14 -5
- package/src/MutableHashMap.ts +4 -24
- package/src/Option.ts +19 -19
- package/src/Pool.ts +28 -36
- package/src/Queue.ts +26 -6
- package/src/RcMap.ts +26 -2
- package/src/Record.ts +20 -20
- package/src/Schema.ts +48 -24
- package/src/SchemaAST.ts +135 -115
- package/src/SchemaRepresentation.ts +3 -1
- 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 +26 -5
- package/src/cluster/MessageStorage.ts +22 -5
- package/src/cluster/Sharding.ts +32 -28
- package/src/cluster/SqlMessageStorage.ts +45 -21
- 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/HttpEffect.ts +15 -9
- 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 +146 -173
- package/src/internal/rcRef.ts +83 -33
- package/src/internal/schema/annotations.ts +0 -9
- package/src/internal/schema/codegen.ts +90 -43
- package/src/internal/schema/fromJsonSchemaDocument.ts +5 -3
- package/src/internal/schema/toCodeDocument.ts +10 -47
- package/src/internal/schema/toCodec.ts +3 -16
- package/src/internal/schema/toEquivalence.ts +4 -5
- package/src/internal/schema/toFormatter.ts +4 -6
- package/src/reactivity/Atom.ts +6 -2
- package/src/reactivity/AtomRpc.ts +5 -5
- package/src/rpc/Rpc.ts +2 -2
- package/src/rpc/RpcClient.ts +35 -32
- package/src/rpc/RpcMessage.ts +1 -1
- package/src/rpc/RpcServer.ts +2 -1
- package/src/rpc/Utils.ts +4 -10
- package/src/schema/SchemaCompiler/runtime.ts +17 -1
- package/src/testing/TestSchema.ts +315 -63
- package/src/testing/index.ts +1 -0
- package/src/workflow/DurableDeferred.ts +1 -1
- package/src/workflow/Workflow.ts +1 -1
- package/src/workflow/WorkflowEngine.ts +4 -0
|
@@ -1,133 +1,226 @@
|
|
|
1
|
-
import
|
|
1
|
+
import * as Deferred from "../../Deferred.ts"
|
|
2
2
|
import * as Effect from "../../Effect.ts"
|
|
3
3
|
import * as Exit from "../../Exit.ts"
|
|
4
|
-
import * as
|
|
5
|
-
import * as MutableRef from "../../MutableRef.ts"
|
|
6
|
-
import * as Option from "../../Option.ts"
|
|
4
|
+
import * as Fiber from "../../Fiber.ts"
|
|
7
5
|
import * as Scope from "../../Scope.ts"
|
|
8
6
|
import type { EntityAddress } from "../EntityAddress.ts"
|
|
9
|
-
import {
|
|
7
|
+
import { aroundEntity } from "./interruptors.ts"
|
|
10
8
|
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
*/
|
|
14
|
-
export type State<A, E> = {
|
|
15
|
-
readonly _tag: "Closed"
|
|
16
|
-
} | {
|
|
17
|
-
readonly _tag: "Acquiring"
|
|
18
|
-
readonly scope: Scope.Closeable
|
|
19
|
-
} | {
|
|
20
|
-
readonly _tag: "Acquired"
|
|
21
|
-
readonly scope: Scope.Closeable
|
|
22
|
-
readonly value: A
|
|
23
|
-
} | {
|
|
24
|
-
readonly _tag: "Failed"
|
|
9
|
+
type Pending<A> = {
|
|
10
|
+
readonly _tag: "Pending"
|
|
25
11
|
readonly scope: Scope.Closeable
|
|
26
|
-
|
|
12
|
+
/** Predecessor scope or ongoing release to await before acquisition. */
|
|
13
|
+
readonly releases: Scope.Closeable | Fiber.Fiber<void> | undefined
|
|
14
|
+
releasing: boolean
|
|
15
|
+
/** Shared with waiters across superseded rebuilds. */
|
|
16
|
+
readonly ready: Deferred.Deferred<A>
|
|
17
|
+
value: A | undefined
|
|
18
|
+
work: Fiber.Fiber<void> | undefined
|
|
27
19
|
}
|
|
28
20
|
|
|
21
|
+
type State<A> =
|
|
22
|
+
| { readonly _tag: "Closed" }
|
|
23
|
+
| { readonly _tag: "Ready"; readonly scope: Scope.Closeable; readonly value: A }
|
|
24
|
+
| Pending<A>
|
|
25
|
+
| {
|
|
26
|
+
readonly _tag: "Failed"
|
|
27
|
+
readonly ready: Deferred.Deferred<A>
|
|
28
|
+
readonly releasing: Fiber.Fiber<void> | undefined
|
|
29
|
+
}
|
|
30
|
+
|
|
29
31
|
/**
|
|
32
|
+
* A replaceable resource whose acquired value identifies its generation.
|
|
33
|
+
*
|
|
30
34
|
* @internal
|
|
31
35
|
*/
|
|
32
|
-
export class ResourceRef<A
|
|
33
|
-
static from = Effect.fnUntraced(function*<A
|
|
36
|
+
export class ResourceRef<A> {
|
|
37
|
+
static from = Effect.fnUntraced(function*<A>(
|
|
34
38
|
parentScope: Scope.Scope,
|
|
35
|
-
acquire: (scope: Scope.Scope) => Effect.Effect<A
|
|
39
|
+
acquire: (scope: Scope.Scope) => Effect.Effect<A>,
|
|
36
40
|
teardownAddress?: EntityAddress
|
|
37
41
|
) {
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
MutableRef.set(state, { _tag: "Closed" })
|
|
47
|
-
return Scope.close(scope, exit)
|
|
48
|
-
})
|
|
49
|
-
|
|
50
|
-
const scope = yield* Scope.make()
|
|
51
|
-
MutableRef.set(state, { _tag: "Acquiring", scope })
|
|
52
|
-
const value = yield* acquire(scope)
|
|
53
|
-
MutableRef.set(state, { _tag: "Acquired", scope, value })
|
|
54
|
-
|
|
55
|
-
return new ResourceRef(state, acquire, teardownAddress)
|
|
42
|
+
// Close the ref before joining releases, so waiters wake even if release stalls.
|
|
43
|
+
const releaseScope = Scope.forkUnsafe(parentScope)
|
|
44
|
+
const ref = new ResourceRef(releaseScope, acquire, teardownAddress)
|
|
45
|
+
yield* Scope.addFinalizerExit(parentScope, (exit) => ref.close(exit))
|
|
46
|
+
const pending = ref.pending(undefined, Deferred.makeUnsafe())
|
|
47
|
+
const value = yield* acquire(pending.scope)
|
|
48
|
+
if (ref.state === pending) ref.publish(pending, value)
|
|
49
|
+
return ref
|
|
56
50
|
})
|
|
57
51
|
|
|
58
|
-
|
|
59
|
-
readonly
|
|
60
|
-
readonly
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
52
|
+
private state: State<A> = { _tag: "Closed" }
|
|
53
|
+
private readonly releaseScope: Scope.Scope
|
|
54
|
+
private readonly acquire: (scope: Scope.Scope) => Effect.Effect<A>
|
|
55
|
+
private readonly teardownAddress: EntityAddress | undefined
|
|
56
|
+
private constructor(
|
|
57
|
+
releaseScope: Scope.Scope,
|
|
58
|
+
acquire: (scope: Scope.Scope) => Effect.Effect<A>,
|
|
59
|
+
teardownAddress: EntityAddress | undefined
|
|
65
60
|
) {
|
|
66
|
-
this.
|
|
61
|
+
this.releaseScope = releaseScope
|
|
67
62
|
this.acquire = acquire
|
|
68
63
|
this.teardownAddress = teardownAddress
|
|
69
64
|
}
|
|
70
65
|
|
|
71
|
-
|
|
66
|
+
/** Returns the value only after acquisition and preparation complete. */
|
|
67
|
+
getUnsafe(): A | undefined {
|
|
68
|
+
return this.state._tag === "Ready" ? this.state.value : undefined
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/** Waits for a ready value; interrupts when the ref closes. */
|
|
72
|
+
readonly await: Effect.Effect<A> = Effect.suspend(() => {
|
|
73
|
+
const s = this.state
|
|
74
|
+
switch (s._tag) {
|
|
75
|
+
case "Closed":
|
|
76
|
+
return Effect.interrupt
|
|
77
|
+
case "Ready":
|
|
78
|
+
return Effect.succeed(s.value)
|
|
79
|
+
case "Pending":
|
|
80
|
+
case "Failed":
|
|
81
|
+
return Deferred.await(s.ready)
|
|
82
|
+
}
|
|
83
|
+
})
|
|
72
84
|
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
85
|
+
/**
|
|
86
|
+
* Replaces the generation, holding waiters until acquisition and `prepare` finish.
|
|
87
|
+
* Returns `undefined` if closed, if `from` differs from the ready or preparing
|
|
88
|
+
* value, or if `from` is omitted while a rebuild is pending.
|
|
89
|
+
*
|
|
90
|
+
* Acceptance immediately blocks waiters; run the returned effect to release
|
|
91
|
+
* the predecessor and build its replacement. Closing or superseding the ref
|
|
92
|
+
* interrupts the rebuild. Failure wakes waiters with its cause; the next
|
|
93
|
+
* rebuild must omit `from` and await cleanup. Interruption during predecessor
|
|
94
|
+
* release instead carries waiters forward without awaiting that release again.
|
|
95
|
+
*/
|
|
96
|
+
rebuildUnsafe(options?: {
|
|
97
|
+
readonly from?: A | undefined
|
|
98
|
+
readonly prepare?: ((value: A) => Effect.Effect<void>) | undefined
|
|
99
|
+
}): Effect.Effect<void> | undefined {
|
|
100
|
+
const s = this.state
|
|
101
|
+
const from = options?.from
|
|
102
|
+
let pending: Pending<A>
|
|
103
|
+
switch (s._tag) {
|
|
104
|
+
case "Closed":
|
|
105
|
+
return undefined
|
|
106
|
+
case "Ready": {
|
|
107
|
+
if (from !== undefined && from !== s.value) return undefined
|
|
108
|
+
pending = this.pending(s.scope, Deferred.makeUnsafe())
|
|
109
|
+
break
|
|
110
|
+
}
|
|
111
|
+
case "Failed": {
|
|
112
|
+
if (from !== undefined) return undefined
|
|
113
|
+
pending = this.pending(s.releasing, s.ready)
|
|
114
|
+
break
|
|
115
|
+
}
|
|
116
|
+
case "Pending": {
|
|
117
|
+
if (from === undefined || s.value !== from) return undefined
|
|
118
|
+
s.work?.interruptUnsafe()
|
|
119
|
+
pending = this.pending(s.scope, s.ready)
|
|
120
|
+
break
|
|
121
|
+
}
|
|
76
122
|
}
|
|
77
|
-
return
|
|
123
|
+
return this.run(pending, options?.prepare)
|
|
78
124
|
}
|
|
79
125
|
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
126
|
+
private pending(
|
|
127
|
+
releases: Scope.Closeable | Fiber.Fiber<void> | undefined,
|
|
128
|
+
ready: Deferred.Deferred<A>
|
|
129
|
+
): Pending<A> {
|
|
130
|
+
const pending: Pending<A> = {
|
|
131
|
+
_tag: "Pending",
|
|
132
|
+
scope: Scope.makeUnsafe(),
|
|
133
|
+
releases,
|
|
134
|
+
releasing: false,
|
|
135
|
+
ready,
|
|
136
|
+
value: undefined,
|
|
137
|
+
work: undefined
|
|
84
138
|
}
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
139
|
+
this.state = pending
|
|
140
|
+
return pending
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
private publish(pending: Pending<A>, value: A): void {
|
|
144
|
+
this.state = { _tag: "Ready", scope: pending.scope, value }
|
|
145
|
+
Deferred.doneUnsafe(pending.ready, Exit.succeed(value))
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
private run(pending: Pending<A>, prepare: ((value: A) => Effect.Effect<void>) | undefined): Effect.Effect<void> {
|
|
149
|
+
const work = Effect.suspend(() => {
|
|
150
|
+
if (this.state !== pending) return Effect.interrupt
|
|
151
|
+
const releases = pending.releases
|
|
152
|
+
if (!releases) return Effect.void
|
|
153
|
+
pending.releasing = true
|
|
154
|
+
return Effect.andThen(
|
|
155
|
+
Fiber.isFiber(releases) ? Fiber.join(releases) : Effect.flatMap(this.release(releases), Fiber.join),
|
|
156
|
+
Effect.sync(() => {
|
|
157
|
+
pending.releasing = false
|
|
158
|
+
})
|
|
159
|
+
)
|
|
95
160
|
}).pipe(
|
|
96
|
-
Effect.andThen(this.acquire(scope)),
|
|
161
|
+
Effect.andThen(Effect.suspend(() => this.acquire(pending.scope))),
|
|
162
|
+
Effect.tap((value) => {
|
|
163
|
+
if (this.state !== pending) return Effect.interrupt
|
|
164
|
+
pending.value = value
|
|
165
|
+
return prepare ? prepare(value) : Effect.void
|
|
166
|
+
}),
|
|
97
167
|
Effect.flatMap((value) => {
|
|
98
|
-
if (this.state
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
return this.latch.open
|
|
103
|
-
})
|
|
104
|
-
).pipe(
|
|
168
|
+
if (this.state !== pending) return Effect.interrupt
|
|
169
|
+
this.publish(pending, value)
|
|
170
|
+
return Effect.void
|
|
171
|
+
}),
|
|
105
172
|
Effect.onExit((exit) => {
|
|
106
|
-
if (Exit.isSuccess(exit)) {
|
|
173
|
+
if (Exit.isSuccess(exit) || this.state !== pending) {
|
|
174
|
+
return Effect.void
|
|
175
|
+
}
|
|
176
|
+
if (pending.releasing) {
|
|
177
|
+
// No resource was acquired. Carry waiters forward without rejoining the stalled release.
|
|
178
|
+
this.state = { _tag: "Failed", ready: pending.ready, releasing: undefined }
|
|
107
179
|
return Effect.void
|
|
108
180
|
}
|
|
109
|
-
return
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
this.latch.openUnsafe()
|
|
115
|
-
}
|
|
116
|
-
}))
|
|
117
|
-
)
|
|
181
|
+
return Effect.map(this.release(pending.scope), (releasing) => {
|
|
182
|
+
if (this.state !== pending) return
|
|
183
|
+
this.state = { _tag: "Failed", ready: Deferred.makeUnsafe(), releasing }
|
|
184
|
+
Deferred.doneUnsafe(pending.ready, Exit.failCause(exit.cause))
|
|
185
|
+
})
|
|
118
186
|
})
|
|
119
187
|
)
|
|
188
|
+
return Effect.flatMap(Effect.forkChild(work, { startImmediately: true }), (fiber) => {
|
|
189
|
+
pending.work = fiber
|
|
190
|
+
// Replaced or closed before the fiber handle existed.
|
|
191
|
+
if (this.state !== pending) fiber.interruptUnsafe()
|
|
192
|
+
return Fiber.join(fiber)
|
|
193
|
+
})
|
|
120
194
|
}
|
|
121
195
|
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
196
|
+
// A stalled release must not prevent rebuild interruption.
|
|
197
|
+
private release(scope: Scope.Closeable): Effect.Effect<Fiber.Fiber<void>> {
|
|
198
|
+
const close = Scope.close(scope, Exit.void)
|
|
199
|
+
return Effect.forkIn(
|
|
200
|
+
this.teardownAddress ? aroundEntity(this.teardownAddress, close) : close,
|
|
201
|
+
this.releaseScope,
|
|
202
|
+
{ startImmediately: true }
|
|
203
|
+
)
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
private close(exit: Exit.Exit<unknown, unknown>): Effect.Effect<void> {
|
|
207
|
+
const s = this.state
|
|
208
|
+
this.state = { _tag: "Closed" }
|
|
209
|
+
switch (s._tag) {
|
|
210
|
+
case "Closed":
|
|
211
|
+
return Effect.void
|
|
212
|
+
case "Ready":
|
|
213
|
+
return Scope.close(s.scope, exit)
|
|
214
|
+
case "Failed": {
|
|
215
|
+
Deferred.doneUnsafe(s.ready, Effect.interrupt)
|
|
216
|
+
return Effect.void
|
|
217
|
+
}
|
|
218
|
+
case "Pending": {
|
|
219
|
+
Deferred.doneUnsafe(s.ready, Effect.interrupt)
|
|
220
|
+
s.work?.interruptUnsafe()
|
|
221
|
+
const close = Scope.close(s.scope, exit)
|
|
222
|
+
return s.releases && !Fiber.isFiber(s.releases) ? Effect.andThen(Scope.close(s.releases, exit), close) : close
|
|
223
|
+
}
|
|
130
224
|
}
|
|
131
|
-
|
|
132
|
-
})
|
|
225
|
+
}
|
|
133
226
|
}
|
package/src/http/HttpEffect.ts
CHANGED
|
@@ -32,6 +32,7 @@ import * as preResponseHandler from "./internal/preResponseHandler.ts"
|
|
|
32
32
|
|
|
33
33
|
/**
|
|
34
34
|
* Runs an HTTP server effect, sends the produced response with the supplied handler, and converts failures into HTTP responses.
|
|
35
|
+
* On handler failure, the request scope closes with the original cause.
|
|
35
36
|
*
|
|
36
37
|
* @stability unstable
|
|
37
38
|
* @category combinators
|
|
@@ -45,8 +46,10 @@ export const toHandled = <E, R, EH, RH>(
|
|
|
45
46
|
) => Effect.Effect<unknown, EH, RH>,
|
|
46
47
|
middleware?: HttpMiddleware | undefined
|
|
47
48
|
): Effect.Effect<void, never, Exclude<R | RH | HttpServerRequest, Scope.Scope>> => {
|
|
48
|
-
const handleCause = (
|
|
49
|
-
|
|
49
|
+
const handleCause = (frame: RequestFrame, cause: Cause.Cause<E | EH | HttpServerError>) => {
|
|
50
|
+
frame.cause = cause
|
|
51
|
+
const request = frame.request
|
|
52
|
+
return Effect.flatMapEager(causeResponse(cause), ([response, cause]) => {
|
|
50
53
|
const fiber = Fiber.getCurrent()!
|
|
51
54
|
reportCauseUnsafe(fiber, cause)
|
|
52
55
|
const handler = preResponseHandler.requestPreResponseHandlers.get(request.source)
|
|
@@ -67,6 +70,7 @@ export const toHandled = <E, R, EH, RH>(
|
|
|
67
70
|
() => cont
|
|
68
71
|
)
|
|
69
72
|
})
|
|
73
|
+
}
|
|
70
74
|
|
|
71
75
|
// Writes the response, applying any registered pre-response handler first.
|
|
72
76
|
// Returns an `Exit` when the write completes synchronously.
|
|
@@ -85,14 +89,15 @@ export const toHandled = <E, R, EH, RH>(
|
|
|
85
89
|
})
|
|
86
90
|
}
|
|
87
91
|
|
|
88
|
-
const withMiddleware = (
|
|
92
|
+
const withMiddleware = (frame: RequestFrame): Effect.Effect<
|
|
89
93
|
unknown,
|
|
90
94
|
E | EH | HttpServerError,
|
|
91
95
|
HttpServerRequest | R | RH
|
|
92
96
|
> => {
|
|
97
|
+
const request = frame.request
|
|
93
98
|
const responded = Effect.matchCauseEffect(self, {
|
|
94
99
|
onSuccess: (response) => sendResponse(request, response),
|
|
95
|
-
onFailure: (cause) => handleCause(
|
|
100
|
+
onFailure: (cause) => handleCause(frame, cause)
|
|
96
101
|
})
|
|
97
102
|
return middleware === undefined ?
|
|
98
103
|
responded :
|
|
@@ -116,9 +121,9 @@ export const toHandled = <E, R, EH, RH>(
|
|
|
116
121
|
}
|
|
117
122
|
|
|
118
123
|
// Apply tracing lazily so disabled requests can use the single-frame path.
|
|
119
|
-
const traced = (
|
|
124
|
+
const traced = (frame: RequestFrame) =>
|
|
120
125
|
tracer(
|
|
121
|
-
withMiddleware(
|
|
126
|
+
withMiddleware(frame) as Effect.Effect<
|
|
122
127
|
HttpServerResponse,
|
|
123
128
|
E | EH | HttpServerError,
|
|
124
129
|
HttpServerRequest | R | RH
|
|
@@ -135,13 +140,14 @@ export const toHandled = <E, R, EH, RH>(
|
|
|
135
140
|
Effect.onExitPrimitive(effect, (exit) => {
|
|
136
141
|
fiber.setContext(frame.prev)
|
|
137
142
|
if (scopeEjected in frame.scope) return undefined
|
|
138
|
-
return Scope.closeUnsafe(frame.scope, exit)
|
|
143
|
+
return Scope.closeUnsafe(frame.scope, frame.cause === undefined ? exit : Exit.failCause(frame.cause))
|
|
139
144
|
}, true)
|
|
140
145
|
|
|
141
146
|
class RequestFrame {
|
|
142
147
|
readonly scope: Scope.Closeable
|
|
143
148
|
readonly prev: Context.Context<never>
|
|
144
149
|
readonly request: HttpServerRequest
|
|
150
|
+
cause: Cause.Cause<E | EH | HttpServerError> | undefined
|
|
145
151
|
constructor(scope: Scope.Closeable, prev: Context.Context<never>, request: HttpServerRequest) {
|
|
146
152
|
this.scope = scope
|
|
147
153
|
this.prev = prev
|
|
@@ -159,7 +165,7 @@ export const toHandled = <E, R, EH, RH>(
|
|
|
159
165
|
return finishAfter(this, fiber, sent)
|
|
160
166
|
}
|
|
161
167
|
[contE](cause: Cause.Cause<E | EH | HttpServerError>, fiber: Fiber.Fiber<unknown, unknown>) {
|
|
162
|
-
return finishAfter(this, fiber, handleCause(this
|
|
168
|
+
return finishAfter(this, fiber, handleCause(this, cause))
|
|
163
169
|
}
|
|
164
170
|
}
|
|
165
171
|
|
|
@@ -177,7 +183,7 @@ export const toHandled = <E, R, EH, RH>(
|
|
|
177
183
|
;(fiber as any)._stack.push(frame)
|
|
178
184
|
return self
|
|
179
185
|
}
|
|
180
|
-
return finishAfter(frame, fiber, middleware === undefined ? traced(frame
|
|
186
|
+
return finishAfter(frame, fiber, middleware === undefined ? traced(frame) : withMiddleware(frame))
|
|
181
187
|
}) as any
|
|
182
188
|
}
|
|
183
189
|
|
|
@@ -26,7 +26,7 @@ import { nativeTracer, ParentSpan, Tracer } from "../Tracer.ts"
|
|
|
26
26
|
import * as Headers from "./Headers.ts"
|
|
27
27
|
import type { CompressionAlgorithm } from "./HttpPlatform.ts"
|
|
28
28
|
import { HttpPlatform } from "./HttpPlatform.ts"
|
|
29
|
-
import { causeResponseStripped } from "./HttpServerError.ts"
|
|
29
|
+
import { causeResponseStripped, ClientAbort } from "./HttpServerError.ts"
|
|
30
30
|
import { HttpServerRequest } from "./HttpServerRequest.ts"
|
|
31
31
|
import * as Request from "./HttpServerRequest.ts"
|
|
32
32
|
import * as Response from "./HttpServerResponse.ts"
|
|
@@ -158,8 +158,14 @@ export const logger: <E, R>(
|
|
|
158
158
|
return exit
|
|
159
159
|
} else if (exit._tag === "Failure") {
|
|
160
160
|
const [response, cause] = causeResponseStripped(exit.cause)
|
|
161
|
+
const message = Option.isSome(cause) &&
|
|
162
|
+
!cause.value.reasons.every((reason) =>
|
|
163
|
+
reason._tag === "Interrupt" && reason.annotations.has(ClientAbort.key)
|
|
164
|
+
)
|
|
165
|
+
? cause.value
|
|
166
|
+
: "Sent HTTP response"
|
|
161
167
|
return Effect.andThen(
|
|
162
|
-
Effect.annotateLogs(Effect.log(
|
|
168
|
+
Effect.annotateLogs(Effect.log(message), {
|
|
163
169
|
"http.method": request.method,
|
|
164
170
|
"http.url": path,
|
|
165
171
|
"http.status": response.status
|
|
@@ -140,6 +140,14 @@ export const make: (options: {
|
|
|
140
140
|
return yield* getFullResponse()
|
|
141
141
|
}
|
|
142
142
|
|
|
143
|
+
const ifRange = request.headers["if-range"]
|
|
144
|
+
if (ifRange !== undefined) {
|
|
145
|
+
fullResponse = yield* getFullResponse()
|
|
146
|
+
if (!matchesIfRange(ifRange, fullResponse.headers["etag"])) {
|
|
147
|
+
return fullResponse
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
|
|
143
151
|
const resolvedFileSize = fileSize ?? (yield* handlePlatformError(request, fileSystem.stat(filePath))).size
|
|
144
152
|
const parsedRange = parseRange(rangeHeader, resolvedFileSize)
|
|
145
153
|
|
|
@@ -459,6 +467,14 @@ const isNotModifiedSince = (ifModifiedSince: string, lastModified: string | unde
|
|
|
459
467
|
return lastModifiedMs <= ifModifiedSinceMs
|
|
460
468
|
}
|
|
461
469
|
|
|
470
|
+
// Only a strong entity-tag can match (RFC 9110 §13.1.5). Weak tags start
|
|
471
|
+
// with `W/`, and Last-Modified dates never match: filesystem metadata cannot
|
|
472
|
+
// prove the file did not change twice within the same second.
|
|
473
|
+
const matchesIfRange = (ifRange: string, etag: string | undefined): boolean => {
|
|
474
|
+
const value = ifRange.trim()
|
|
475
|
+
return value.startsWith("\"") && value === etag
|
|
476
|
+
}
|
|
477
|
+
|
|
462
478
|
const notModifiedResponse = (
|
|
463
479
|
response: HttpServerResponse.HttpServerResponse
|
|
464
480
|
): HttpServerResponse.HttpServerResponse => {
|
|
@@ -99,7 +99,7 @@ export const b3: FromHeaders = (headers) => {
|
|
|
99
99
|
return Option.some(Tracer.externalSpan({
|
|
100
100
|
traceId: parts[0],
|
|
101
101
|
spanId: parts[1],
|
|
102
|
-
sampled: parts[2] ? parts[2] === "1" : true
|
|
102
|
+
sampled: parts[2] ? parts[2] === "1" || parts[2] === "d" : true
|
|
103
103
|
}))
|
|
104
104
|
}
|
|
105
105
|
|
|
@@ -109,7 +109,7 @@ export const b3: FromHeaders = (headers) => {
|
|
|
109
109
|
* **Details**
|
|
110
110
|
*
|
|
111
111
|
* The decoder reads `x-b3-traceid`, `x-b3-spanid`, and optional `x-b3-sampled`
|
|
112
|
-
* headers.
|
|
112
|
+
* and `x-b3-flags` headers. Debug flags override the sampling decision.
|
|
113
113
|
*
|
|
114
114
|
* @stability unstable
|
|
115
115
|
* @category decoding
|
|
@@ -122,7 +122,8 @@ export const xb3: FromHeaders = (headers) => {
|
|
|
122
122
|
return Option.some(Tracer.externalSpan({
|
|
123
123
|
traceId: headers["x-b3-traceid"],
|
|
124
124
|
spanId: headers["x-b3-spanid"],
|
|
125
|
-
sampled: headers["x-b3-
|
|
125
|
+
sampled: headers["x-b3-flags"] === "1" ||
|
|
126
|
+
(headers["x-b3-sampled"] ? headers["x-b3-sampled"] === "1" || headers["x-b3-sampled"] === "true" : true)
|
|
126
127
|
}))
|
|
127
128
|
}
|
|
128
129
|
|
|
@@ -151,7 +152,10 @@ export const w3c: FromHeaders = (headers) => {
|
|
|
151
152
|
const [version, traceId, spanId, flags] = parts
|
|
152
153
|
switch (version) {
|
|
153
154
|
case "00": {
|
|
154
|
-
if (
|
|
155
|
+
if (
|
|
156
|
+
w3cTraceId.test(traceId) === false || w3cSpanId.test(spanId) === false ||
|
|
157
|
+
traceId === "00000000000000000000000000000000" || spanId === "0000000000000000"
|
|
158
|
+
) {
|
|
155
159
|
return Option.none()
|
|
156
160
|
}
|
|
157
161
|
return Option.some(Tracer.externalSpan({
|
|
@@ -479,6 +479,7 @@ export class HttpApiSchemaError extends Data.TaggedClass("HttpApiSchemaError")<{
|
|
|
479
479
|
readonly cause: Schema.SchemaError
|
|
480
480
|
}> {
|
|
481
481
|
readonly [HttpApiSchemaErrorTypeId]: HttpApiSchemaErrorTypeId = HttpApiSchemaErrorTypeId
|
|
482
|
+
readonly [ErrorReporter.ignore] = true
|
|
482
483
|
|
|
483
484
|
static is(u: unknown): u is HttpApiSchemaError {
|
|
484
485
|
return hasProperty(u, HttpApiSchemaErrorTypeId)
|
package/src/internal/core.ts
CHANGED
|
@@ -279,6 +279,48 @@ export const causeFromReasons = <E>(
|
|
|
279
279
|
reasons: ReadonlyArray<Cause.Reason<E>>
|
|
280
280
|
): Cause.Cause<E> => new CauseImpl(reasons)
|
|
281
281
|
|
|
282
|
+
const dedupeReasons = <E>(
|
|
283
|
+
self: ReadonlyArray<Cause.Reason<E>>,
|
|
284
|
+
that: ReadonlyArray<Cause.Reason<E>>
|
|
285
|
+
): Array<Cause.Reason<E>> => {
|
|
286
|
+
// Avoid importing Array.ts into the core bundle.
|
|
287
|
+
// Snapshot both arrays before invoking user-defined hash or equality methods.
|
|
288
|
+
const buckets = new Map<number, Array<Cause.Reason<E>>>()
|
|
289
|
+
const out: Array<Cause.Reason<E>> = []
|
|
290
|
+
for (const reason of self.concat(that)) {
|
|
291
|
+
const hash = Hash.hash(reason)
|
|
292
|
+
const bucket = buckets.get(hash)
|
|
293
|
+
if (bucket === undefined) {
|
|
294
|
+
buckets.set(hash, [reason])
|
|
295
|
+
} else if (bucket.some((previous) => Equal.equals(previous, reason))) {
|
|
296
|
+
continue
|
|
297
|
+
} else {
|
|
298
|
+
bucket.push(reason)
|
|
299
|
+
}
|
|
300
|
+
out.push(reason)
|
|
301
|
+
}
|
|
302
|
+
return out
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
/** @internal */
|
|
306
|
+
export const causeCombine: {
|
|
307
|
+
<E2>(that: Cause.Cause<E2>): <E>(self: Cause.Cause<E>) => Cause.Cause<E | E2>
|
|
308
|
+
<E, E2>(self: Cause.Cause<E>, that: Cause.Cause<E2>): Cause.Cause<E | E2>
|
|
309
|
+
} = dual(
|
|
310
|
+
2,
|
|
311
|
+
<E, E2>(self: Cause.Cause<E>, that: Cause.Cause<E2>): Cause.Cause<E | E2> => {
|
|
312
|
+
if (self.reasons.length === 0) {
|
|
313
|
+
return that as Cause.Cause<E | E2>
|
|
314
|
+
} else if (that.reasons.length === 0) {
|
|
315
|
+
return self as Cause.Cause<E | E2>
|
|
316
|
+
}
|
|
317
|
+
const newCause = new CauseImpl<E | E2>(
|
|
318
|
+
dedupeReasons<E | E2>(self.reasons, that.reasons)
|
|
319
|
+
)
|
|
320
|
+
return Equal.equals(self, newCause) ? self : newCause
|
|
321
|
+
}
|
|
322
|
+
)
|
|
323
|
+
|
|
282
324
|
/** @internal */
|
|
283
325
|
export const causeEmpty: Cause.Cause<never> = new CauseImpl([])
|
|
284
326
|
|
|
@@ -546,8 +588,20 @@ export const exitFailCause: <E>(cause: Cause.Cause<E>) => Exit.Exit<never, E> =
|
|
|
546
588
|
annotated = true
|
|
547
589
|
}
|
|
548
590
|
let cont = fiber.getCont(contE)
|
|
549
|
-
|
|
550
|
-
|
|
591
|
+
const interruptedCause = fiber._interruptedCause
|
|
592
|
+
if (interruptedCause && fiber.interruptible) {
|
|
593
|
+
// Drop typed failures only when interruption skips a recovery handler.
|
|
594
|
+
// Interruptibility-restoration continuations have no identifier.
|
|
595
|
+
let skippedHandler = false
|
|
596
|
+
while (cont && fiber.interruptible) {
|
|
597
|
+
skippedHandler ||= identifier in cont
|
|
598
|
+
cont = fiber.getCont(contE)
|
|
599
|
+
}
|
|
600
|
+
if (skippedHandler) {
|
|
601
|
+
cause = causeFromReasons(cause.reasons.filter((reason) => reason._tag !== "Fail"))
|
|
602
|
+
}
|
|
603
|
+
cause = causeCombine(cause, interruptedCause)
|
|
604
|
+
annotated = true
|
|
551
605
|
}
|
|
552
606
|
return cont
|
|
553
607
|
? cont[contE](cause, fiber, annotated ? undefined : this)
|
package/src/internal/dateTime.ts
CHANGED
|
@@ -575,7 +575,9 @@ export const toDate = (self: DateTime.DateTime): Date => {
|
|
|
575
575
|
Number(parts[3].value),
|
|
576
576
|
Number(parts[4].value),
|
|
577
577
|
Number(parts[5].value),
|
|
578
|
-
|
|
578
|
+
parts[6]?.type === "fractionalSecond"
|
|
579
|
+
? Number(parts[6].value)
|
|
580
|
+
: ((self.epochMilliseconds % 1000) + 1000) % 1000
|
|
579
581
|
)
|
|
580
582
|
self.adjustedEpochMilliseconds = date.getTime()
|
|
581
583
|
return date
|