effect 4.0.3 → 4.0.4
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/AGENTS.md +24 -0
- package/CLAUDE.md +24 -0
- package/ai-docs/package.json +2 -2
- package/ai-docs/src/52_graphql/10_basics.ts +111 -0
- package/ai-docs/src/52_graphql/20_testing.ts +45 -0
- package/ai-docs/src/52_graphql/30_batching.ts +116 -0
- package/ai-docs/src/52_graphql/40_aliased-batching.ts +122 -0
- package/ai-docs/src/52_graphql/50_errors-and-retries.ts +74 -0
- package/ai-docs/src/52_graphql/index.md +6 -0
- package/dist/Context.d.ts.map +1 -1
- package/dist/Context.js +9 -0
- package/dist/Context.js.map +1 -1
- package/dist/RequestResolver.d.ts.map +1 -1
- package/dist/RequestResolver.js +4 -10
- package/dist/RequestResolver.js.map +1 -1
- package/dist/Scheduler.d.ts +9 -0
- package/dist/Scheduler.d.ts.map +1 -1
- package/dist/Scheduler.js +9 -0
- package/dist/Scheduler.js.map +1 -1
- package/dist/Schema.d.ts +8 -0
- package/dist/Schema.d.ts.map +1 -1
- package/dist/Schema.js +7 -0
- package/dist/Schema.js.map +1 -1
- package/dist/SchemaGetter.d.ts +4 -0
- package/dist/SchemaGetter.d.ts.map +1 -1
- package/dist/SchemaGetter.js +52 -33
- package/dist/SchemaGetter.js.map +1 -1
- package/dist/Sink.d.ts.map +1 -1
- package/dist/Sink.js +38 -17
- package/dist/Sink.js.map +1 -1
- package/dist/Stream.d.ts +11 -0
- package/dist/Stream.d.ts.map +1 -1
- package/dist/Stream.js +16 -1
- package/dist/Stream.js.map +1 -1
- package/dist/Utils.js +8 -3
- package/dist/Utils.js.map +1 -1
- package/dist/ai/AnthropicStructuredOutput.js +0 -2
- package/dist/ai/AnthropicStructuredOutput.js.map +1 -1
- package/dist/ai/Chat.d.ts +2 -2
- package/dist/ai/Chat.d.ts.map +1 -1
- package/dist/ai/LanguageModel.d.ts +38 -7
- package/dist/ai/LanguageModel.d.ts.map +1 -1
- package/dist/ai/LanguageModel.js +25 -7
- package/dist/ai/LanguageModel.js.map +1 -1
- package/dist/ai/McpServer.d.ts +55 -0
- package/dist/ai/McpServer.d.ts.map +1 -1
- package/dist/ai/McpServer.js +132 -22
- package/dist/ai/McpServer.js.map +1 -1
- package/dist/ai/Prompt.d.ts.map +1 -1
- package/dist/ai/Prompt.js +28 -5
- package/dist/ai/Prompt.js.map +1 -1
- package/dist/ai/Response.d.ts +4 -3
- package/dist/ai/Response.d.ts.map +1 -1
- package/dist/ai/Response.js.map +1 -1
- package/dist/cluster/ClusterWorkflowEngine.d.ts.map +1 -1
- package/dist/cluster/ClusterWorkflowEngine.js +46 -8
- package/dist/cluster/ClusterWorkflowEngine.js.map +1 -1
- package/dist/cluster/Sharding.d.ts.map +1 -1
- package/dist/cluster/Sharding.js +6 -4
- package/dist/cluster/Sharding.js.map +1 -1
- package/dist/encoding/Sse.d.ts +6 -0
- package/dist/encoding/Sse.d.ts.map +1 -1
- package/dist/encoding/Sse.js +62 -70
- package/dist/encoding/Sse.js.map +1 -1
- package/dist/graphql/GraphQL.d.ts +349 -0
- package/dist/graphql/GraphQL.d.ts.map +1 -0
- package/dist/graphql/GraphQL.js +207 -0
- package/dist/graphql/GraphQL.js.map +1 -0
- package/dist/graphql/GraphQLClient.d.ts +562 -0
- package/dist/graphql/GraphQLClient.d.ts.map +1 -0
- package/dist/graphql/GraphQLClient.js +498 -0
- package/dist/graphql/GraphQLClient.js.map +1 -0
- package/dist/graphql/GraphQLClientError.d.ts +445 -0
- package/dist/graphql/GraphQLClientError.d.ts.map +1 -0
- package/dist/graphql/GraphQLClientError.js +479 -0
- package/dist/graphql/GraphQLClientError.js.map +1 -0
- package/dist/graphql/GraphQLGroup.d.ts +118 -0
- package/dist/graphql/GraphQLGroup.d.ts.map +1 -0
- package/dist/graphql/GraphQLGroup.js +109 -0
- package/dist/graphql/GraphQLGroup.js.map +1 -0
- package/dist/graphql/GraphQLMiddleware.d.ts +249 -0
- package/dist/graphql/GraphQLMiddleware.d.ts.map +1 -0
- package/dist/graphql/GraphQLMiddleware.js +130 -0
- package/dist/graphql/GraphQLMiddleware.js.map +1 -0
- package/dist/graphql/GraphQLProtocol.d.ts +731 -0
- package/dist/graphql/GraphQLProtocol.d.ts.map +1 -0
- package/dist/graphql/GraphQLProtocol.js +719 -0
- package/dist/graphql/GraphQLProtocol.js.map +1 -0
- package/dist/graphql/GraphQLResolver.d.ts +453 -0
- package/dist/graphql/GraphQLResolver.d.ts.map +1 -0
- package/dist/graphql/GraphQLResolver.js +552 -0
- package/dist/graphql/GraphQLResolver.js.map +1 -0
- package/dist/graphql/index.d.ts +40 -0
- package/dist/graphql/index.d.ts.map +1 -0
- package/dist/graphql/index.js +41 -0
- package/dist/graphql/index.js.map +1 -0
- package/dist/http/HttpClient.d.ts +15 -0
- package/dist/http/HttpClient.d.ts.map +1 -1
- package/dist/http/HttpClient.js +24 -10
- package/dist/http/HttpClient.js.map +1 -1
- package/dist/http/HttpEffect.d.ts.map +1 -1
- package/dist/http/HttpEffect.js +14 -7
- package/dist/http/HttpEffect.js.map +1 -1
- package/dist/http/HttpMiddleware.d.ts.map +1 -1
- package/dist/http/HttpMiddleware.js +31 -32
- package/dist/http/HttpMiddleware.js.map +1 -1
- package/dist/http/HttpRouter.d.ts +35 -0
- package/dist/http/HttpRouter.d.ts.map +1 -1
- package/dist/http/HttpRouter.js +35 -0
- package/dist/http/HttpRouter.js.map +1 -1
- package/dist/http/HttpServerResponse.d.ts +8 -6
- package/dist/http/HttpServerResponse.d.ts.map +1 -1
- package/dist/http/HttpServerResponse.js +27 -16
- package/dist/http/HttpServerResponse.js.map +1 -1
- package/dist/http/Multipart.d.ts.map +1 -1
- package/dist/http/Multipart.js +15 -1
- package/dist/http/Multipart.js.map +1 -1
- package/dist/http-api/HttpApiScalar.d.ts +5 -0
- package/dist/http-api/HttpApiScalar.d.ts.map +1 -1
- package/dist/http-api/HttpApiScalar.js.map +1 -1
- package/dist/internal/dateTime.js +40 -35
- package/dist/internal/dateTime.js.map +1 -1
- package/dist/internal/effect.js +8 -3
- package/dist/internal/effect.js.map +1 -1
- package/dist/internal/graphql.d.ts +2 -0
- package/dist/internal/graphql.d.ts.map +1 -0
- package/dist/internal/graphql.js +195 -0
- package/dist/internal/graphql.js.map +1 -0
- package/dist/internal/graphqlHeaders.d.ts +4 -0
- package/dist/internal/graphqlHeaders.d.ts.map +1 -0
- package/dist/internal/graphqlHeaders.js +11 -0
- package/dist/internal/graphqlHeaders.js.map +1 -0
- package/dist/internal/request.js +20 -1
- package/dist/internal/request.js.map +1 -1
- package/dist/internal/schema/toCodeDocument.js +4 -1
- package/dist/internal/schema/toCodeDocument.js.map +1 -1
- package/dist/internal/schema/toJsonSchemaDocument.js +29 -5
- package/dist/internal/schema/toJsonSchemaDocument.js.map +1 -1
- package/dist/internal/version.js +1 -1
- package/dist/net/Host.d.ts +49 -0
- package/dist/net/Host.d.ts.map +1 -1
- package/dist/net/Host.js +55 -0
- package/dist/net/Host.js.map +1 -1
- package/dist/net/NetAddress.d.ts +8 -0
- package/dist/net/NetAddress.d.ts.map +1 -1
- package/dist/net/NetAddress.js +8 -0
- package/dist/net/NetAddress.js.map +1 -1
- package/dist/rpc/RpcServer.d.ts.map +1 -1
- package/dist/rpc/RpcServer.js +36 -9
- package/dist/rpc/RpcServer.js.map +1 -1
- package/dist/socket/Socket.d.ts +6 -0
- package/dist/socket/Socket.d.ts.map +1 -1
- package/dist/socket/Socket.js.map +1 -1
- package/dist/workflow/DurableDeferred.d.ts.map +1 -1
- package/dist/workflow/DurableDeferred.js +32 -3
- package/dist/workflow/DurableDeferred.js.map +1 -1
- package/dist/workflow/WorkflowEngine.d.ts +24 -2
- package/dist/workflow/WorkflowEngine.d.ts.map +1 -1
- package/dist/workflow/WorkflowEngine.js +11 -2
- package/dist/workflow/WorkflowEngine.js.map +1 -1
- package/package.json +6 -3
- package/src/Context.ts +9 -0
- package/src/RequestResolver.ts +4 -10
- package/src/Scheduler.ts +9 -0
- package/src/Schema.ts +8 -0
- package/src/SchemaGetter.ts +47 -35
- package/src/Sink.ts +45 -18
- package/src/Stream.ts +33 -4
- package/src/Utils.ts +8 -5
- package/src/ai/AnthropicStructuredOutput.ts +2 -2
- package/src/ai/Chat.ts +2 -2
- package/src/ai/LanguageModel.ts +96 -24
- package/src/ai/McpServer.ts +244 -90
- package/src/ai/Prompt.ts +24 -10
- package/src/ai/Response.ts +6 -3
- package/src/cluster/ClusterWorkflowEngine.ts +51 -12
- package/src/cluster/Sharding.ts +12 -9
- package/src/encoding/Sse.ts +69 -79
- package/src/graphql/GraphQL.ts +494 -0
- package/src/graphql/GraphQLClient.ts +912 -0
- package/src/graphql/GraphQLClientError.ts +542 -0
- package/src/graphql/GraphQLGroup.ts +167 -0
- package/src/graphql/GraphQLMiddleware.ts +253 -0
- package/src/graphql/GraphQLProtocol.ts +1286 -0
- package/src/graphql/GraphQLResolver.ts +1010 -0
- package/src/graphql/index.ts +48 -0
- package/src/http/HttpClient.ts +54 -10
- package/src/http/HttpEffect.ts +11 -5
- package/src/http/HttpMiddleware.ts +33 -34
- package/src/http/HttpRouter.ts +35 -0
- package/src/http/HttpServerResponse.ts +28 -16
- package/src/http/Multipart.ts +17 -1
- package/src/http-api/HttpApiScalar.ts +5 -0
- package/src/internal/dateTime.ts +13 -10
- package/src/internal/effect.ts +8 -3
- package/src/internal/graphql.ts +263 -0
- package/src/internal/graphqlHeaders.ts +11 -0
- package/src/internal/request.ts +31 -3
- package/src/internal/schema/toCodeDocument.ts +7 -1
- package/src/internal/schema/toJsonSchemaDocument.ts +34 -2
- package/src/internal/version.ts +1 -1
- package/src/net/Host.ts +72 -0
- package/src/net/NetAddress.ts +9 -0
- package/src/rpc/RpcServer.ts +55 -15
- package/src/socket/Socket.ts +6 -0
- package/src/workflow/DurableDeferred.ts +43 -5
- package/src/workflow/WorkflowEngine.ts +48 -4
|
@@ -0,0 +1,263 @@
|
|
|
1
|
+
import { isReadonlyArrayNonEmpty, type NonEmptyReadonlyArray } from "../Array.ts"
|
|
2
|
+
import * as Context from "../Context.ts"
|
|
3
|
+
import * as Effect from "../Effect.ts"
|
|
4
|
+
import type * as GraphQL from "../graphql/GraphQL.ts"
|
|
5
|
+
import type { DocumentTemplate } from "../graphql/GraphQL.ts"
|
|
6
|
+
import type { PartialResult } from "../graphql/GraphQLClient.ts"
|
|
7
|
+
import {
|
|
8
|
+
DecodeError,
|
|
9
|
+
EncodeError,
|
|
10
|
+
GraphQLClientError,
|
|
11
|
+
type GraphQLError,
|
|
12
|
+
type Reason,
|
|
13
|
+
ResponseError
|
|
14
|
+
} from "../graphql/GraphQLClientError.ts"
|
|
15
|
+
import type { ExecutionResult, GraphQLRequest } from "../graphql/GraphQLProtocol.ts"
|
|
16
|
+
import * as Schema from "../Schema.ts"
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Builds a response error with raw data and transport metadata.
|
|
20
|
+
*
|
|
21
|
+
* @internal
|
|
22
|
+
*/
|
|
23
|
+
export const responseError = (
|
|
24
|
+
result: ExecutionResult | undefined,
|
|
25
|
+
errors: NonEmptyReadonlyArray<GraphQLError>
|
|
26
|
+
): ResponseError =>
|
|
27
|
+
new ResponseError({
|
|
28
|
+
errors,
|
|
29
|
+
...(result?.data === undefined ? {} : { data: result.data as Schema.Json }),
|
|
30
|
+
...(result?.extensions === undefined ? {} : { extensions: result.extensions }),
|
|
31
|
+
...(result?.status === undefined ? {} : { status: result.status }),
|
|
32
|
+
...(result?.headers === undefined ? {} : { headers: result.headers })
|
|
33
|
+
})
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Raw responses for partial results, used by resolvers to preserve error
|
|
37
|
+
* metadata. Mock client results have no entry.
|
|
38
|
+
*
|
|
39
|
+
* @internal
|
|
40
|
+
*/
|
|
41
|
+
export const partialResponses = new WeakMap<object, ExecutionResult>()
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Joins two printed pieces, adding a space only where two non-punctuators
|
|
45
|
+
* would otherwise touch. This is the generator printer's rule, so joining a
|
|
46
|
+
* generated template reproduces the printed document exactly.
|
|
47
|
+
*/
|
|
48
|
+
const concat = (left: string, right: string): string =>
|
|
49
|
+
/[\w"-]$/.test(left) && /^[\w"-]/.test(right) ? `${left} ${right}` : left + right
|
|
50
|
+
|
|
51
|
+
const concatAll = (pieces: ReadonlyArray<string>): string => pieces.reduce(concat, "")
|
|
52
|
+
|
|
53
|
+
/** Renders template parts, prefixing every name (the odd entries). */
|
|
54
|
+
const renderParts = (parts: DocumentTemplate.Parts, prefix: string): string =>
|
|
55
|
+
parts.map((part, i) => i % 2 === 1 ? prefix + part : part).join("")
|
|
56
|
+
|
|
57
|
+
const isShared = (fragment: DocumentTemplate.Fragment): boolean => fragment.body.length <= 1
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Renders the template once per prefix. The empty prefix renders it as
|
|
61
|
+
* written; any other prefix renames variables and per-slice fragments and
|
|
62
|
+
* aliases root fields to the prefixed response key.
|
|
63
|
+
*/
|
|
64
|
+
const render = (
|
|
65
|
+
kind: string,
|
|
66
|
+
name: string,
|
|
67
|
+
template: DocumentTemplate,
|
|
68
|
+
prefixes: ReadonlyArray<string>
|
|
69
|
+
): string =>
|
|
70
|
+
concatAll([
|
|
71
|
+
kind,
|
|
72
|
+
name,
|
|
73
|
+
template.variables.length === 0 ? "" : `(${
|
|
74
|
+
concatAll(
|
|
75
|
+
prefixes.flatMap((prefix) =>
|
|
76
|
+
template.variables.map((variable) => `$${prefix}${variable.name}:${variable.body}`)
|
|
77
|
+
)
|
|
78
|
+
)
|
|
79
|
+
})`,
|
|
80
|
+
template.directives === undefined ? "" : renderParts(template.directives, ""),
|
|
81
|
+
`{${
|
|
82
|
+
concatAll(prefixes.flatMap((prefix) =>
|
|
83
|
+
template.selections.map((selection) => {
|
|
84
|
+
const key = prefix === "" ? selection.alias : `${prefix}${selection.alias ?? selection.name}`
|
|
85
|
+
return `${key === undefined ? "" : `${key}:`}${selection.name ?? ""}${renderParts(selection.body, prefix)}`
|
|
86
|
+
})
|
|
87
|
+
))
|
|
88
|
+
}}`,
|
|
89
|
+
...template.fragments.flatMap((fragment) =>
|
|
90
|
+
(isShared(fragment) ? [""] : prefixes).map((prefix) =>
|
|
91
|
+
concatAll(["fragment", prefix + fragment.name, renderParts(fragment.body, prefix)])
|
|
92
|
+
)
|
|
93
|
+
)
|
|
94
|
+
])
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* Joins a template into the document it was split from.
|
|
98
|
+
*
|
|
99
|
+
* @internal
|
|
100
|
+
*/
|
|
101
|
+
export const joinTemplate = (kind: string, name: string, template: DocumentTemplate): string =>
|
|
102
|
+
render(kind, name, template, [""])
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* Why the template can't be sliced for any prefixes, if it can't: an operation
|
|
106
|
+
* directive references a variable, or a root selection is a fragment spread or
|
|
107
|
+
* inline fragment.
|
|
108
|
+
*
|
|
109
|
+
* @internal
|
|
110
|
+
*/
|
|
111
|
+
export const templateUnsliceableReason = (template: DocumentTemplate): string | undefined => {
|
|
112
|
+
if (template.directives !== undefined && template.directives.length > 1) {
|
|
113
|
+
return "its operation directives reference variables"
|
|
114
|
+
}
|
|
115
|
+
if (template.selections.some((selection) => selection.name === undefined)) {
|
|
116
|
+
return "it has a fragment spread or inline fragment at the root"
|
|
117
|
+
}
|
|
118
|
+
return undefined
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
/**
|
|
122
|
+
* Renders one operation that repeats the template once per prefix. Each
|
|
123
|
+
* slice's variables, root response keys and per-slice fragments are renamed
|
|
124
|
+
* with its prefix; operation directives and shared fragments appear once.
|
|
125
|
+
* Prefixes must start a GraphQL name, and none may be a prefix of another.
|
|
126
|
+
*
|
|
127
|
+
* Throws rather than producing an invalid document when the template is
|
|
128
|
+
* unsliceable (see {@link templateUnsliceableReason}) or a renamed fragment
|
|
129
|
+
* would take a shared fragment's name.
|
|
130
|
+
*
|
|
131
|
+
* @internal
|
|
132
|
+
*/
|
|
133
|
+
export const renderSlices = (
|
|
134
|
+
kind: string,
|
|
135
|
+
name: string,
|
|
136
|
+
template: DocumentTemplate,
|
|
137
|
+
prefixes: NonEmptyReadonlyArray<string>
|
|
138
|
+
): string => {
|
|
139
|
+
const shared = new Set(template.fragments.filter(isShared).map((fragment) => fragment.name))
|
|
140
|
+
const collision = template.fragments
|
|
141
|
+
.flatMap((fragment) => isShared(fragment) ? [] : prefixes.map((prefix) => prefix + fragment.name))
|
|
142
|
+
.find((renamed) => shared.has(renamed))
|
|
143
|
+
const reason = templateUnsliceableReason(template) ??
|
|
144
|
+
(collision === undefined ? undefined : `fragment ${collision} would collide with a shared fragment`)
|
|
145
|
+
if (reason !== undefined) throw new Error(`Cannot render slices of operation ${name}: ${reason}`)
|
|
146
|
+
return render(kind, name, template, prefixes)
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
/**
|
|
150
|
+
* Forces POST for alias batches to avoid GET URL length limits.
|
|
151
|
+
*
|
|
152
|
+
* @internal
|
|
153
|
+
*/
|
|
154
|
+
export const ForcePost = Context.Reference<boolean>("effect/graphql/ForcePost", { defaultValue: () => false })
|
|
155
|
+
|
|
156
|
+
/**
|
|
157
|
+
* JSON with object keys sorted, so equal JSON values give equal strings.
|
|
158
|
+
*
|
|
159
|
+
* @internal
|
|
160
|
+
*/
|
|
161
|
+
export const canonicalJson = (value: unknown): string =>
|
|
162
|
+
JSON.stringify(value, (_, value) =>
|
|
163
|
+
value !== null && typeof value === "object" && !Array.isArray(value)
|
|
164
|
+
? Object.fromEntries(Object.keys(value).sort().map((key) => [key, value[key]]))
|
|
165
|
+
: value)
|
|
166
|
+
|
|
167
|
+
/**
|
|
168
|
+
* Shared variable encoding, result decoding and client error mapping.
|
|
169
|
+
*
|
|
170
|
+
* @internal
|
|
171
|
+
*/
|
|
172
|
+
export const makeCodec = <Op extends GraphQL.Any>(operation: Op) => {
|
|
173
|
+
const fail = (reason: Reason) => new GraphQLClientError({ operation: operation.name, reason })
|
|
174
|
+
const schemaFail = (make: (description: string) => Reason) => (error: Schema.SchemaError) =>
|
|
175
|
+
Effect.fail(fail(make(error.message)))
|
|
176
|
+
const encode = Schema.encodeEffect(operation.variables)
|
|
177
|
+
const decode = Schema.decodeUnknownEffect(operation.result)
|
|
178
|
+
|
|
179
|
+
const encodeVariables = (
|
|
180
|
+
variables: GraphQL.Variables<Op> | undefined
|
|
181
|
+
): Effect.Effect<unknown, GraphQLClientError, Op["variables"]["EncodingServices"]> =>
|
|
182
|
+
Effect.catch(encode(variables ?? {}), schemaFail((description) => new EncodeError({ description })))
|
|
183
|
+
|
|
184
|
+
const decodeData = (
|
|
185
|
+
data: unknown
|
|
186
|
+
): Effect.Effect<GraphQL.Result<Op>, GraphQLClientError, Op["result"]["DecodingServices"]> =>
|
|
187
|
+
Effect.catch(decode(data), schemaFail((description) => new DecodeError({ description })))
|
|
188
|
+
|
|
189
|
+
// GraphQL requires errors when data is null or missing. Check before decoding,
|
|
190
|
+
// since schemas such as Schema.Json accept null.
|
|
191
|
+
const failResponse = (result: ExecutionResult): Effect.Effect<never, GraphQLClientError> => {
|
|
192
|
+
const errors = result.errors ?? []
|
|
193
|
+
return Effect.fail(fail(
|
|
194
|
+
isReadonlyArrayNonEmpty(errors)
|
|
195
|
+
? responseError(result, errors)
|
|
196
|
+
: new DecodeError({ description: "data is null or missing without errors" })
|
|
197
|
+
))
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
const decodeStrict = (
|
|
201
|
+
result: ExecutionResult
|
|
202
|
+
): Effect.Effect<GraphQL.Result<Op>, GraphQLClientError, Op["result"]["DecodingServices"]> =>
|
|
203
|
+
isReadonlyArrayNonEmpty(result.errors ?? []) || result.data === null || result.data === undefined
|
|
204
|
+
? failResponse(result)
|
|
205
|
+
: decodeData(result.data)
|
|
206
|
+
|
|
207
|
+
const decodePartial = (
|
|
208
|
+
result: ExecutionResult
|
|
209
|
+
): Effect.Effect<PartialResult<GraphQL.Result<Op>>, GraphQLClientError, Op["result"]["DecodingServices"]> => {
|
|
210
|
+
if (result.data === null || result.data === undefined) return failResponse(result)
|
|
211
|
+
return Effect.map(decodeData(result.data), (data): PartialResult<GraphQL.Result<Op>> => {
|
|
212
|
+
const partial = { data, errors: result.errors ?? [] }
|
|
213
|
+
partialResponses.set(partial, result)
|
|
214
|
+
return partial
|
|
215
|
+
})
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
return { fail, schemaFail, encodeVariables, failResponse, decodeStrict, decodePartial } as const
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
/**
|
|
222
|
+
* Each real client method's runner for batches: its middleware and protocol,
|
|
223
|
+
* sending as POST. Mock and custom methods have none.
|
|
224
|
+
*
|
|
225
|
+
* @internal
|
|
226
|
+
*/
|
|
227
|
+
export const rawExecutors = new WeakMap<
|
|
228
|
+
object,
|
|
229
|
+
(request: GraphQLRequest) => Effect.Effect<ExecutionResult, unknown, unknown>
|
|
230
|
+
>()
|
|
231
|
+
|
|
232
|
+
/**
|
|
233
|
+
* Splits a batched response, removing prefixes from root keys and error paths.
|
|
234
|
+
* Returns undefined if an error matches no slice or data is null or missing.
|
|
235
|
+
*
|
|
236
|
+
* @internal
|
|
237
|
+
*/
|
|
238
|
+
export const splitSlices = (
|
|
239
|
+
result: ExecutionResult,
|
|
240
|
+
template: DocumentTemplate,
|
|
241
|
+
prefixes: ReadonlyArray<string>
|
|
242
|
+
): ReadonlyArray<ExecutionResult> | undefined => {
|
|
243
|
+
const sliceErrors = prefixes.map((): Array<GraphQLError> => [])
|
|
244
|
+
for (const error of result.errors ?? []) {
|
|
245
|
+
const key = error.path?.[0]
|
|
246
|
+
// No prefix is a prefix of another, so at most one matches.
|
|
247
|
+
const index = typeof key === "string" ? prefixes.findIndex((prefix) => key.startsWith(prefix)) : -1
|
|
248
|
+
if (index === -1) return undefined
|
|
249
|
+
sliceErrors[index].push(
|
|
250
|
+
{ ...error, path: [(key as string).slice(prefixes[index].length), ...error.path!.slice(1)] } as GraphQLError
|
|
251
|
+
)
|
|
252
|
+
}
|
|
253
|
+
if (result.data === null || result.data === undefined) return undefined
|
|
254
|
+
const data = result.data as Record<string, unknown>
|
|
255
|
+
return prefixes.map((prefix, i) => {
|
|
256
|
+
const sliceData: Record<string, unknown> = {}
|
|
257
|
+
for (const selection of template.selections) {
|
|
258
|
+
const key = (selection.alias ?? selection.name)!
|
|
259
|
+
if (Object.hasOwn(data, prefix + key)) sliceData[key] = data[prefix + key]
|
|
260
|
+
}
|
|
261
|
+
return { ...result, data: sliceData, errors: sliceErrors[i] }
|
|
262
|
+
})
|
|
263
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import * as Headers from "../http/Headers.ts"
|
|
2
|
+
import * as Schema from "../Schema.ts"
|
|
3
|
+
import * as SchemaGetter from "../SchemaGetter.ts"
|
|
4
|
+
|
|
5
|
+
// Keep headers branded at runtime while encoding only their string entries.
|
|
6
|
+
export const HeadersSchema = Schema.Record(Schema.String, Schema.String).pipe(
|
|
7
|
+
Schema.decodeTo(Schema.declare<Headers.Headers>(Headers.isHeaders), {
|
|
8
|
+
decode: SchemaGetter.transform(Headers.fromInput),
|
|
9
|
+
encode: SchemaGetter.transform((headers) => ({ ...headers }))
|
|
10
|
+
})
|
|
11
|
+
)
|
package/src/internal/request.ts
CHANGED
|
@@ -4,6 +4,8 @@ import type { Effect } from "../Effect.ts"
|
|
|
4
4
|
import type { Exit } from "../Exit.ts"
|
|
5
5
|
import type { Fiber } from "../Fiber.ts"
|
|
6
6
|
import { dual } from "../Function.ts"
|
|
7
|
+
import * as MutableHashMap from "../MutableHashMap.ts"
|
|
8
|
+
import * as Option from "../Option.ts"
|
|
7
9
|
import type * as Request from "../Request.ts"
|
|
8
10
|
import { makeEntry } from "../Request.ts"
|
|
9
11
|
import type { RequestResolver } from "../RequestResolver.ts"
|
|
@@ -67,10 +69,36 @@ export const requestUnsafe = <A extends Request.Any>(
|
|
|
67
69
|
return () => removeEntryUnsafe(options.resolver, entry)
|
|
68
70
|
}
|
|
69
71
|
|
|
72
|
+
/**
|
|
73
|
+
* Pending batches of resolvers whose `batchKey` is in this set match keys with
|
|
74
|
+
* `Equal` instead of by reference.
|
|
75
|
+
*
|
|
76
|
+
* @internal
|
|
77
|
+
*/
|
|
78
|
+
export const equalBatchKeys = new WeakSet<Function>()
|
|
79
|
+
|
|
80
|
+
interface BatchMap {
|
|
81
|
+
get(key: unknown): Batch | undefined
|
|
82
|
+
has(key: unknown): boolean
|
|
83
|
+
set(key: unknown, batch: Batch): void
|
|
84
|
+
delete(key: unknown): void
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
const makeBatchMap = (resolver: RequestResolver<any>): BatchMap => {
|
|
88
|
+
if (!equalBatchKeys.has(resolver.batchKey)) return new Map()
|
|
89
|
+
const map = MutableHashMap.empty<unknown, Batch>()
|
|
90
|
+
return {
|
|
91
|
+
get: (key) => Option.getOrUndefined(MutableHashMap.get(map, key)),
|
|
92
|
+
has: (key) => MutableHashMap.has(map, key),
|
|
93
|
+
set: (key, batch) => MutableHashMap.set(map, key, batch),
|
|
94
|
+
delete: (key) => MutableHashMap.remove(map, key)
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
70
98
|
interface Batch {
|
|
71
99
|
key: unknown
|
|
72
100
|
resolver: RequestResolver<any>
|
|
73
|
-
map:
|
|
101
|
+
map: BatchMap
|
|
74
102
|
readonly entrySet: Set<Request.Entry<any>>
|
|
75
103
|
readonly entries: Set<Request.Entry<any>>
|
|
76
104
|
readonly delayEffect: Effect<void>
|
|
@@ -79,7 +107,7 @@ interface Batch {
|
|
|
79
107
|
}
|
|
80
108
|
|
|
81
109
|
const batchPool: Array<Batch> = []
|
|
82
|
-
const pendingBatches = new WeakMap<RequestResolver<any>,
|
|
110
|
+
const pendingBatches = new WeakMap<RequestResolver<any>, BatchMap>()
|
|
83
111
|
|
|
84
112
|
const addEntry = <A extends Request.Any>(
|
|
85
113
|
resolver: RequestResolver<A>,
|
|
@@ -93,7 +121,7 @@ const addEntry = <A extends Request.Any>(
|
|
|
93
121
|
) => {
|
|
94
122
|
let batchMap = pendingBatches.get(resolver)
|
|
95
123
|
if (!batchMap) {
|
|
96
|
-
batchMap =
|
|
124
|
+
batchMap = makeBatchMap(resolver)
|
|
97
125
|
pendingBatches.set(resolver, batchMap)
|
|
98
126
|
}
|
|
99
127
|
let batch: Batch | undefined
|
|
@@ -48,6 +48,8 @@ function renderEmittableAnnotation(input: unknown): string | undefined {
|
|
|
48
48
|
return entries.length === 0 ? "{}" : `{ ${entries.join(", ")} }`
|
|
49
49
|
}
|
|
50
50
|
|
|
51
|
+
const canonicalFiniteCheck = `Schema.isFinite().annotate({ "expected": "a finite number" })`
|
|
52
|
+
|
|
51
53
|
function renderAnnotations(
|
|
52
54
|
annotations: Schema.Annotations.Annotations | undefined
|
|
53
55
|
): string | undefined {
|
|
@@ -341,7 +343,11 @@ export function toCodeDocument(
|
|
|
341
343
|
let runtime = base.runtime + runtimeAnnotate(representation.annotations)
|
|
342
344
|
for (let index = 0; index < representation.checks.length; index++) {
|
|
343
345
|
const check = representation.checks[index]
|
|
344
|
-
|
|
346
|
+
const compiledCheck = compileCheck(check, [...path, "checks", index])
|
|
347
|
+
// Only collapse a bare number's leading canonical check.
|
|
348
|
+
runtime = runtime === "Schema.Number" && compiledCheck === canonicalFiniteCheck
|
|
349
|
+
? "Schema.Finite"
|
|
350
|
+
: `${runtime}.check(${compiledCheck})`
|
|
345
351
|
}
|
|
346
352
|
return makeCode(runtime, base.Type)
|
|
347
353
|
}
|
|
@@ -121,6 +121,37 @@ function hasNoCollisions(left: JsonSchema.JsonSchema, rightKeys: ReadonlyArray<s
|
|
|
121
121
|
return typeof left.$ref !== "string" && rightKeys.every((key) => !Object.hasOwn(left, key))
|
|
122
122
|
}
|
|
123
123
|
|
|
124
|
+
// Every bound on one schema object applies, so a repeated bound reduces to its tighter value.
|
|
125
|
+
const lowerBoundKeywords = "|minimum|exclusiveMinimum|minLength|minItems|minProperties|"
|
|
126
|
+
const upperBoundKeywords = "|maximum|exclusiveMaximum|maxLength|maxItems|maxProperties|"
|
|
127
|
+
|
|
128
|
+
// Tightens repeated bounds and drops an inclusive or exclusive bound implied by the other on the same side. Returns
|
|
129
|
+
// `undefined` for a `$ref` schema or a non-bound collision, so the caller falls back to `allOf`.
|
|
130
|
+
function mergeInlineCheck(
|
|
131
|
+
left: JsonSchema.JsonSchema,
|
|
132
|
+
right: JsonSchema.JsonSchema,
|
|
133
|
+
rightKeys: ReadonlyArray<string>
|
|
134
|
+
): JsonSchema.JsonSchema | undefined {
|
|
135
|
+
if (typeof left.$ref === "string") return undefined
|
|
136
|
+
const out: JsonSchema.JsonSchema = { ...left, ...right }
|
|
137
|
+
for (const key of rightKeys) {
|
|
138
|
+
if (!Object.hasOwn(left, key)) continue
|
|
139
|
+
const a = left[key]
|
|
140
|
+
const b = right[key]
|
|
141
|
+
if (typeof a !== "number" || typeof b !== "number") return undefined
|
|
142
|
+
if (lowerBoundKeywords.includes(`|${key}|`)) out[key] = Math.max(a, b)
|
|
143
|
+
else if (upperBoundKeywords.includes(`|${key}|`)) out[key] = Math.min(a, b)
|
|
144
|
+
else return undefined
|
|
145
|
+
}
|
|
146
|
+
if (typeof out.minimum === "number" && typeof out.exclusiveMinimum === "number") {
|
|
147
|
+
delete out[out.exclusiveMinimum >= out.minimum ? "minimum" : "exclusiveMinimum"]
|
|
148
|
+
}
|
|
149
|
+
if (typeof out.maximum === "number" && typeof out.exclusiveMaximum === "number") {
|
|
150
|
+
delete out[out.exclusiveMaximum <= out.maximum ? "maximum" : "exclusiveMaximum"]
|
|
151
|
+
}
|
|
152
|
+
return out
|
|
153
|
+
}
|
|
154
|
+
|
|
124
155
|
// `format` and `content*` can affect validation, so they are not treated as pure annotations.
|
|
125
156
|
const promotableAnnotationKeywords = "|title|description|default|examples|readOnly|writeOnly|"
|
|
126
157
|
const inlineableAnnotatedCheckKeywords = inlineableCheckKeywords + promotableAnnotationKeywords
|
|
@@ -149,8 +180,9 @@ function appendJsonSchema(
|
|
|
149
180
|
: appendJsonSchema(base, extracted.schema, inlineCheck)
|
|
150
181
|
}
|
|
151
182
|
}
|
|
152
|
-
if (inlineCheck
|
|
153
|
-
|
|
183
|
+
if (inlineCheck) {
|
|
184
|
+
const merged = mergeInlineCheck(left, right, rightKeys)
|
|
185
|
+
if (merged !== undefined) return merged
|
|
154
186
|
}
|
|
155
187
|
const members = Array.isArray(right.allOf) && rightKeys.length === 1 ? right.allOf : [right]
|
|
156
188
|
if (Array.isArray(left.allOf)) {
|
package/src/internal/version.ts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
/** @internal */
|
|
2
|
-
export const version = "4.0.
|
|
2
|
+
export const version = "4.0.4"
|
package/src/net/Host.ts
CHANGED
|
@@ -9,6 +9,7 @@
|
|
|
9
9
|
* @stability experimental
|
|
10
10
|
* @since 4.0.0
|
|
11
11
|
*/
|
|
12
|
+
import type * as Arr from "../Array.ts"
|
|
12
13
|
import type * as Brand from "../Brand.ts"
|
|
13
14
|
import * as Equal from "../Equal.ts"
|
|
14
15
|
import * as Hash from "../Hash.ts"
|
|
@@ -321,6 +322,28 @@ export const formatHost = (self: Host): string => typeof self === "string" ? sel
|
|
|
321
322
|
*/
|
|
322
323
|
export const isFullyQualified = (self: DomainName): boolean => self.endsWith(".")
|
|
323
324
|
|
|
325
|
+
/**
|
|
326
|
+
* Appends the trailing dot to a domain name, unless it is already fully
|
|
327
|
+
* qualified.
|
|
328
|
+
*
|
|
329
|
+
* @stability experimental
|
|
330
|
+
* @category converting
|
|
331
|
+
* @since 4.0.0
|
|
332
|
+
*/
|
|
333
|
+
export const toFullyQualified = (self: DomainName): DomainName =>
|
|
334
|
+
isFullyQualified(self) ? self : `${self}.` as DomainName
|
|
335
|
+
|
|
336
|
+
/**
|
|
337
|
+
* Removes the trailing dot of a fully qualified domain name, except from the
|
|
338
|
+
* root name `.`.
|
|
339
|
+
*
|
|
340
|
+
* @stability experimental
|
|
341
|
+
* @category converting
|
|
342
|
+
* @since 4.0.0
|
|
343
|
+
*/
|
|
344
|
+
export const toRelative = (self: DomainName): DomainName =>
|
|
345
|
+
self.length > 1 && isFullyQualified(self) ? self.slice(0, -1) as DomainName : self
|
|
346
|
+
|
|
324
347
|
const HostPortProto = {
|
|
325
348
|
...Inspectable.BaseProto,
|
|
326
349
|
_tag: "HostPort",
|
|
@@ -470,3 +493,52 @@ export const formatHostPort = (self: HostPort): string => {
|
|
|
470
493
|
const host = formatHost(self.host)
|
|
471
494
|
return host.includes(":") ? `[${host}]:${self.port}` : `${host}:${self.port}`
|
|
472
495
|
}
|
|
496
|
+
|
|
497
|
+
/**
|
|
498
|
+
* The addresses of host names, as read from a hosts file.
|
|
499
|
+
*
|
|
500
|
+
* @see {@link parseHostsFile} for reading a hosts file
|
|
501
|
+
* @stability experimental
|
|
502
|
+
* @category models
|
|
503
|
+
* @since 4.0.0
|
|
504
|
+
*/
|
|
505
|
+
export type HostsTable = ReadonlyMap<DomainName, Arr.NonEmptyReadonlyArray<NetAddress.IpAddress>>
|
|
506
|
+
|
|
507
|
+
/**
|
|
508
|
+
* Parses a hosts file into the addresses of each host name. Invalid entries
|
|
509
|
+
* are skipped.
|
|
510
|
+
*
|
|
511
|
+
* **Example** (Parsing a hosts file)
|
|
512
|
+
*
|
|
513
|
+
* ```ts import.meta.vitest
|
|
514
|
+
* import { Host, NetAddress } from "effect/net"
|
|
515
|
+
*
|
|
516
|
+
* const hosts = Host.parseHostsFile(`
|
|
517
|
+
* 127.0.0.1 localhost
|
|
518
|
+
* ::1 localhost ip6-localhost # loopback
|
|
519
|
+
* `)
|
|
520
|
+
* hosts.get(Host.domainNameFromStringUnsafe("localhost"))?.map(NetAddress.formatIp) // => ["127.0.0.1", "::1"]
|
|
521
|
+
* ```
|
|
522
|
+
*
|
|
523
|
+
* @stability experimental
|
|
524
|
+
* @category decoding
|
|
525
|
+
* @since 4.0.0
|
|
526
|
+
*/
|
|
527
|
+
export const parseHostsFile = (text: string): HostsTable => {
|
|
528
|
+
const hosts = new Map<DomainName, Array<NetAddress.IpAddress>>()
|
|
529
|
+
for (const line of text.split(/\r?\n/)) {
|
|
530
|
+
const comment = line.indexOf("#")
|
|
531
|
+
const [first, ...names] = (comment === -1 ? line : line.slice(0, comment)).trim().split(/\s+/)
|
|
532
|
+
const address = NetAddress.ipFromString(first)
|
|
533
|
+
if (Result.isFailure(address)) continue
|
|
534
|
+
for (const name of names) {
|
|
535
|
+
const domain = domainNameFromString(name)
|
|
536
|
+
if (Result.isFailure(domain)) continue
|
|
537
|
+
const key = toRelative(domain.success)
|
|
538
|
+
const addresses = hosts.get(key)
|
|
539
|
+
if (addresses === undefined) hosts.set(key, [address.success])
|
|
540
|
+
else if (!addresses.some((existing) => Equal.equals(existing, address.success))) addresses.push(address.success)
|
|
541
|
+
}
|
|
542
|
+
}
|
|
543
|
+
return hosts as unknown as HostsTable
|
|
544
|
+
}
|
package/src/net/NetAddress.ts
CHANGED
|
@@ -864,6 +864,15 @@ export const ipv4Loopback: Ipv4Address = makeIpv4(0x7f000001)
|
|
|
864
864
|
*/
|
|
865
865
|
export const ipv6Loopback: Ipv6Address = makeIpv6(0, 0, 0, 1)
|
|
866
866
|
|
|
867
|
+
/**
|
|
868
|
+
* The loopback addresses `127.0.0.1` and `::1`.
|
|
869
|
+
*
|
|
870
|
+
* @stability unstable
|
|
871
|
+
* @category constants
|
|
872
|
+
* @since 4.0.0
|
|
873
|
+
*/
|
|
874
|
+
export const loopbackAddresses: readonly [Ipv4Address, Ipv6Address] = [ipv4Loopback, ipv6Loopback]
|
|
875
|
+
|
|
867
876
|
/**
|
|
868
877
|
* The unspecified IPv4 address `0.0.0.0`.
|
|
869
878
|
*
|
package/src/rpc/RpcServer.ts
CHANGED
|
@@ -494,6 +494,57 @@ export const makeNoSerialization: <Rpcs extends Rpc.Any>(
|
|
|
494
494
|
})
|
|
495
495
|
})
|
|
496
496
|
|
|
497
|
+
// Built-in JSON codecs depend only on the RPC schemas and can be shared.
|
|
498
|
+
// Other factories may capture state at construction, so keep them per server.
|
|
499
|
+
const isSharedCodecFor = (codecFor: RpcSerialization.CodecFor) => codecFor === RpcSerialization.json.codecFor
|
|
500
|
+
|
|
501
|
+
interface RpcCodecs {
|
|
502
|
+
readonly decode: (u: unknown) => Effect.Effect<any, Schema.SchemaError>
|
|
503
|
+
readonly encodeChunk: (
|
|
504
|
+
u: NonEmptyReadonlyArray<unknown>
|
|
505
|
+
) => Effect.Effect<NonEmptyReadonlyArray<unknown>, Schema.SchemaError>
|
|
506
|
+
readonly encodeExit: (u: unknown) => Effect.Effect<ResponseExitEncoded["exit"], Schema.SchemaError>
|
|
507
|
+
}
|
|
508
|
+
|
|
509
|
+
const sharedRpcCodecs = new WeakMap<Rpc.AnyWithProps, RpcCodecs>()
|
|
510
|
+
|
|
511
|
+
const rpcCodecs = (codecFor: RpcSerialization.CodecFor, rpc: Rpc.AnyWithProps): RpcCodecs => {
|
|
512
|
+
const shared = isSharedCodecFor(codecFor)
|
|
513
|
+
let codecs = shared ? sharedRpcCodecs.get(rpc) : undefined
|
|
514
|
+
if (codecs === undefined) {
|
|
515
|
+
const streamSchemas = RpcSchema.getStreamSchemas(rpc.successSchema)
|
|
516
|
+
codecs = {
|
|
517
|
+
decode: Schema.decodeUnknownEffect(codecFor(rpc.payloadSchema)) as any,
|
|
518
|
+
encodeChunk: Schema.encodeUnknownEffect(
|
|
519
|
+
codecFor(
|
|
520
|
+
Schema.NonEmptyArray(Option.isSome(streamSchemas) ? streamSchemas.value.success : Schema.Any)
|
|
521
|
+
)
|
|
522
|
+
) as any,
|
|
523
|
+
encodeExit: Schema.encodeUnknownEffect(codecFor(Rpc.exitSchema(rpc as any))) as any
|
|
524
|
+
}
|
|
525
|
+
if (shared) sharedRpcCodecs.set(rpc, codecs)
|
|
526
|
+
}
|
|
527
|
+
return codecs
|
|
528
|
+
}
|
|
529
|
+
|
|
530
|
+
interface DefectCodecs {
|
|
531
|
+
readonly encodeDefectUnsafe: (defect: unknown) => unknown
|
|
532
|
+
readonly encodeUnknownRequestExit: (u: unknown) => Effect.Effect<ResponseExitEncoded["exit"], Schema.SchemaError>
|
|
533
|
+
}
|
|
534
|
+
|
|
535
|
+
let sharedDefectCodecs: DefectCodecs | undefined
|
|
536
|
+
|
|
537
|
+
const makeDefectCodecs = (codecFor: RpcSerialization.CodecFor): DefectCodecs => ({
|
|
538
|
+
encodeDefectUnsafe: Schema.encodeSync(codecFor(Schema.Defect())),
|
|
539
|
+
// Unknown tags have no RPC schema, so use a defect-only exit schema.
|
|
540
|
+
encodeUnknownRequestExit: Schema.encodeUnknownEffect(
|
|
541
|
+
codecFor(Schema.Exit(Schema.Never, Schema.Never, Schema.Defect()))
|
|
542
|
+
) as any
|
|
543
|
+
})
|
|
544
|
+
|
|
545
|
+
const defectCodecs = (codecFor: RpcSerialization.CodecFor): DefectCodecs =>
|
|
546
|
+
isSharedCodecFor(codecFor) ? (sharedDefectCodecs ??= makeDefectCodecs(codecFor)) : makeDefectCodecs(codecFor)
|
|
547
|
+
|
|
497
548
|
// Merging the handler entry services with the request fiber services costs
|
|
498
549
|
// O(services), so the merged context is cached per (entry, fiber context)
|
|
499
550
|
// pair. Request fibers on persistent connections keep one context across
|
|
@@ -586,11 +637,7 @@ export const make: <Rpcs extends Rpc.Any>(
|
|
|
586
637
|
supportsSpanPropagation,
|
|
587
638
|
supportsTransferables
|
|
588
639
|
} = yield* Protocol
|
|
589
|
-
const encodeDefectUnsafe =
|
|
590
|
-
// Unknown tags have no RPC schema, so use a defect-only exit schema.
|
|
591
|
-
const encodeUnknownRequestExit: Schemas["encodeExit"] = Schema.encodeUnknownEffect(
|
|
592
|
-
codecFor(Schema.Exit(Schema.Never, Schema.Never, Schema.Defect()))
|
|
593
|
-
) as any
|
|
640
|
+
const { encodeDefectUnsafe, encodeUnknownRequestExit } = defectCodecs(codecFor)
|
|
594
641
|
const services = yield* Effect.context<Rpc.ToHandler<Rpcs> | Rpc.Middleware<Rpcs>>()
|
|
595
642
|
const scope = yield* Scope.make()
|
|
596
643
|
|
|
@@ -666,16 +713,9 @@ export const make: <Rpcs extends Rpc.Any>(
|
|
|
666
713
|
let schemas = schemasCache.get(rpc)
|
|
667
714
|
if (!schemas) {
|
|
668
715
|
const entry = Context.getOrUndefinedUnsafe(services, rpc.key) as Rpc.Handler<Rpcs["_tag"]>
|
|
669
|
-
const streamSchemas = RpcSchema.getStreamSchemas(rpc.successSchema)
|
|
670
716
|
schemas = {
|
|
671
717
|
tag: rpc._tag,
|
|
672
|
-
|
|
673
|
-
encodeChunk: Schema.encodeUnknownEffect(
|
|
674
|
-
codecFor(
|
|
675
|
-
Schema.NonEmptyArray(Option.isSome(streamSchemas) ? streamSchemas.value.success : Schema.Any)
|
|
676
|
-
)
|
|
677
|
-
) as any,
|
|
678
|
-
encodeExit: Schema.encodeUnknownEffect(codecFor(Rpc.exitSchema(rpc as any))) as any,
|
|
718
|
+
...rpcCodecs(codecFor, rpc),
|
|
679
719
|
context: entry.context
|
|
680
720
|
}
|
|
681
721
|
schemasCache.set(rpc, schemas)
|
|
@@ -1109,7 +1149,7 @@ export const makeProtocolWithHttpEffect: (
|
|
|
1109
1149
|
RpcSerialization.RpcSerialization
|
|
1110
1150
|
> = Effect.fnUntraced(function*(options = {}) {
|
|
1111
1151
|
const serialization = yield* RpcSerialization.RpcSerialization
|
|
1112
|
-
const encodeDefectUnsafe =
|
|
1152
|
+
const { encodeDefectUnsafe } = defectCodecs(serialization.codecFor)
|
|
1113
1153
|
const includesFraming = serialization.includesFraming
|
|
1114
1154
|
const isBinary = !serialization.contentType.includes("json")
|
|
1115
1155
|
|
|
@@ -1569,7 +1609,7 @@ const makeSocketProtocol: Effect.Effect<
|
|
|
1569
1609
|
RpcSerialization.RpcSerialization
|
|
1570
1610
|
> = Effect.gen(function*() {
|
|
1571
1611
|
const serialization = yield* RpcSerialization.RpcSerialization
|
|
1572
|
-
const encodeDefectUnsafe =
|
|
1612
|
+
const { encodeDefectUnsafe } = defectCodecs(serialization.codecFor)
|
|
1573
1613
|
const disconnects = yield* Queue.make<number>()
|
|
1574
1614
|
|
|
1575
1615
|
let clientId = 0
|
package/src/socket/Socket.ts
CHANGED
|
@@ -161,6 +161,12 @@ export interface Writer {
|
|
|
161
161
|
* @since 4.0.0
|
|
162
162
|
*/
|
|
163
163
|
export interface TlsUpgradeOptions {
|
|
164
|
+
/**
|
|
165
|
+
* DNS hostname used for Server Name Indication (SNI) and certificate identity
|
|
166
|
+
* verification during client upgrades. Server upgrades ignore this option.
|
|
167
|
+
* When omitted, the adapter retains its existing hostname behavior.
|
|
168
|
+
*/
|
|
169
|
+
readonly servername?: string | undefined
|
|
164
170
|
readonly key?:
|
|
165
171
|
| Redacted.Redacted<string | Uint8Array>
|
|
166
172
|
| ReadonlyArray<Redacted.Redacted<string | Uint8Array>>
|