1/**
2 * Schema adapters for `Channel` boundaries. The helpers encode typed channel
3 * chunks before they cross an encoded boundary, decode encoded chunks before
4 * application code receives them, and wrap bidirectional channels so callers
5 * work with schema-typed input and output while the inner channel uses encoded
6 * values.
7 *
8 * @since 4.0.0
9 */
10import type * as Arr from "./Array.ts"
11import * as Channel from "./Channel.ts"
12import * as Effect from "./Effect.ts"
13import { dual } from "./Function.ts"
14import * as Schema from "./Schema.ts"
15import type * as SchemaAST from "./SchemaAST.ts"
16
17/**
18 * Creates a channel that encodes non-empty chunks of schema values into the
19 * schema's encoded representation.
20 *
21 * **When to use**
22 *
23 * Use to encode typed channel input into the schema's encoded representation
24 * before passing chunks to an encoded downstream boundary.
25 *
26 * **Details**
27 *
28 * Encoding failures are emitted as `SchemaError`, and any encoding services
29 * required by the schema become channel requirements.
30 *
31 * @see {@link encodeUnknown} for encoded output chunks that should be typed as `unknown`
32 * @see {@link decode} for the inverse channel that decodes encoded chunks into schema values
33 *
34 * @category constructors
35 * @since 4.0.0
36 */
37export const encode = <S extends Schema.Constraint>(
38 schema: S
39) =>
40<IE = never, Done = unknown>(): Channel.Channel<
41 Arr.NonEmptyReadonlyArray<S["Encoded"]>,
42 IE | Schema.SchemaError,
43 Done,
44 Arr.NonEmptyReadonlyArray<S["Type"]>,
45 IE,
46 Done,
47 S["EncodingServices"]
48> => {
49 const encode = Schema.encodeEffect(Schema.NonEmptyArray(schema))
50 return Channel.fromTransform((upstream, _scope) => Effect.succeed(Effect.flatMap(upstream, (chunk) => encode(chunk))))
51}
52
53/**
54 * Creates an `encode` channel variant whose encoded output chunks are typed as
55 * `unknown`.
56 *
57 * **When to use**
58 *
59 * Use when a channel boundary should encode typed input chunks while the encoded
60 * output representation is intentionally untyped.
61 *
62 * @see {@link encode} for the variant that preserves the schema encoded type
63 *
64 * @category constructors
65 * @since 4.0.0
66 */
67export const encodeUnknown: <S extends Schema.Constraint>(
68 schema: S
69) => <IE = never, Done = unknown>() => Channel.Channel<
70 Arr.NonEmptyReadonlyArray<unknown>,
71 IE | Schema.SchemaError,
72 Done,
73 Arr.NonEmptyReadonlyArray<S["Type"]>,
74 IE,
75 Done,
76 S["EncodingServices"]
77> = encode
78
79/**
80 * Creates a channel that decodes non-empty chunks from the schema's encoded
81 * representation into schema values.
82 *
83 * **When to use**
84 *
85 * Use to validate and decode encoded channel output into typed schema values
86 * before application code consumes it.
87 *
88 * **Details**
89 *
90 * Decoding failures are emitted as `SchemaError`, and any decoding services
91 * required by the schema become channel requirements.
92 *
93 * @see {@link decodeUnknown} for boundaries where the encoded input side is intentionally untyped
94 * @see {@link encode} for the inverse adapter that encodes typed schema values
95 *
96 * @category constructors
97 * @since 4.0.0
98 */
99export const decode = <S extends Schema.Constraint>(
100 schema: S,
101 options?: SchemaAST.ParseOptions
102) =>
103<IE = never, Done = unknown>(): Channel.Channel<
104 Arr.NonEmptyReadonlyArray<S["Type"]>,
105 IE | Schema.SchemaError,
106 Done,
107 Arr.NonEmptyReadonlyArray<S["Encoded"]>,
108 IE,
109 Done,
110 S["DecodingServices"]
111> => {
112 const decode = Schema.decodeEffect(Schema.NonEmptyArray(schema), options)
113 return Channel.fromTransform((upstream, _scope) => Effect.succeed(Effect.flatMap(upstream, (chunk) => decode(chunk))))
114}
115
116/**
117 * Creates a `decode` channel variant for schema-decoding channel boundaries.
118 *
119 * **When to use**
120 *
121 * Use when you need an intentionally unknown or untyped encoded input while
122 * keeping only the decoded output statically typed according to the schema.
123 *
124 * **Details**
125 *
126 * The channel decodes non-empty encoded chunks into schema values, emits
127 * `SchemaError` when decoding fails, and requires the schema's decoding
128 * services.
129 *
130 * @see {@link decode} for the typed variant that preserves the schema's encoded type
131 *
132 * @category constructors
133 * @since 4.0.0
134 */
135export const decodeUnknown: <S extends Schema.Constraint>(
136 schema: S,
137 options?: SchemaAST.ParseOptions
138) => <IE = never, Done = unknown>() => Channel.Channel<
139 Arr.NonEmptyReadonlyArray<S["Type"]>,
140 IE | Schema.SchemaError,
141 Done,
142 Arr.NonEmptyReadonlyArray<unknown>,
143 IE,
144 Done,
145 S["DecodingServices"]
146> = decode
147
148/**
149 * Wraps a channel so callers work with typed input and output chunks while the
150 * wrapped channel uses encoded chunks.
151 *
152 * **When to use**
153 *
154 * Use to expose typed input and output at a bidirectional channel boundary
155 * while the wrapped channel continues to operate on schema-encoded chunks.
156 *
157 * **Details**
158 *
159 * Values sent into the resulting channel are encoded with `inputSchema` before
160 * reaching the wrapped channel. Values emitted by the wrapped channel are
161 * decoded with `outputSchema` before they are emitted downstream. Schema
162 * failures are surfaced as `SchemaError`.
163 *
164 * @see {@link duplexUnknown} for the variant whose encoded side is intentionally untyped
165 * @see {@link encode} for encoding typed chunks at one-way channel boundaries
166 * @see {@link decode} for decoding encoded chunks at one-way channel boundaries
167 *
168 * @category combinators
169 * @since 4.0.0
170 */
171export const duplex: {
172 <In extends Schema.Constraint, Out extends Schema.Constraint>(options: {
173 readonly inputSchema: In
174 readonly outputSchema: Out
175 }): <OutErr, OutDone, InErr, InDone, R>(
176 self: Channel.Channel<
177 Arr.NonEmptyReadonlyArray<Out["Encoded"]>,
178 OutErr,
179 OutDone,
180 Arr.NonEmptyReadonlyArray<In["Encoded"]>,
181 Schema.SchemaError | InErr,
182 InDone,
183 R
184 >
185 ) => Channel.Channel<
186 Arr.NonEmptyReadonlyArray<Out["Type"]>,
187 Schema.SchemaError | OutErr,
188 OutDone,
189 Arr.NonEmptyReadonlyArray<In["Type"]>,
190 InErr,
191 InDone,
192 R | In["EncodingServices"] | Out["DecodingServices"]
193 >
194 <Out extends Schema.Constraint, OutErr, OutDone, In extends Schema.Constraint, InErr, InDone, R>(
195 self: Channel.Channel<
196 Arr.NonEmptyReadonlyArray<Out["Encoded"]>,
197 OutErr,
198 OutDone,
199 Arr.NonEmptyReadonlyArray<In["Encoded"]>,
200 Schema.SchemaError | InErr,
201 InDone,
202 R
203 >,
204 options: {
205 readonly inputSchema: In
206 readonly outputSchema: Out
207 }
208 ): Channel.Channel<
209 Arr.NonEmptyReadonlyArray<Out["Type"]>,
210 Schema.SchemaError | OutErr,
211 OutDone,
212 Arr.NonEmptyReadonlyArray<In["Type"]>,
213 InErr,
214 InDone,
215 R | In["EncodingServices"] | Out["DecodingServices"]
216 >
217} = dual(2, <Out extends Schema.Constraint, OutErr, OutDone, In extends Schema.Constraint, InErr, InDone, R>(
218 self: Channel.Channel<
219 Arr.NonEmptyReadonlyArray<Out["Encoded"]>,
220 OutErr,
221 OutDone,
222 Arr.NonEmptyReadonlyArray<In["Encoded"]>,
223 Schema.SchemaError | InErr,
224 InDone,
225 R
226 >,
227 options: {
228 readonly inputSchema: In
229 readonly outputSchema: Out
230 }
231): Channel.Channel<
232 Arr.NonEmptyReadonlyArray<Out["Type"]>,
233 Schema.SchemaError | OutErr,
234 OutDone,
235 Arr.NonEmptyReadonlyArray<In["Type"]>,
236 InErr,
237 InDone,
238 R | In["EncodingServices"] | Out["DecodingServices"]
239> =>
240 encode(options.inputSchema)<InErr, InDone>().pipe(
241 Channel.pipeTo(self),
242 Channel.pipeTo(decode(options.outputSchema)())
243 ))
244
245/**
246 * Wraps a bidirectional channel whose encoded chunks are typed as `unknown`.
247 *
248 * **When to use**
249 *
250 * Use when you need a bidirectional channel to cross an encoded boundary whose
251 * chunk types are intentionally erased, while callers send and receive
252 * schema-typed chunks.
253 *
254 * **Details**
255 *
256 * The resulting channel accepts typed input chunks, encodes them with
257 * `inputSchema`, decodes unknown output chunks with `outputSchema`, and
258 * surfaces schema failures as `SchemaError`.
259 *
260 * @see {@link duplex} for the variant that preserves the schema encoded types on the wrapped channel
261 *
262 * @category combinators
263 * @since 4.0.0
264 */
265export const duplexUnknown: {
266 <In extends Schema.Constraint, Out extends Schema.Constraint>(options: {
267 readonly inputSchema: In
268 readonly outputSchema: Out
269 }): <OutErr, OutDone, InErr, InDone, R>(
270 self: Channel.Channel<
271 Arr.NonEmptyReadonlyArray<unknown>,
272 OutErr,
273 OutDone,
274 Arr.NonEmptyReadonlyArray<any>,
275 Schema.SchemaError | InErr,
276 InDone,
277 R
278 >
279 ) => Channel.Channel<
280 Arr.NonEmptyReadonlyArray<Out["Type"]>,
281 Schema.SchemaError | OutErr,
282 OutDone,
283 Arr.NonEmptyReadonlyArray<In["Type"]>,
284 InErr,
285 InDone,
286 R | In["EncodingServices"] | Out["DecodingServices"]
287 >
288 <Out extends Schema.Constraint, OutErr, OutDone, In extends Schema.Constraint, InErr, InDone, R>(
289 self: Channel.Channel<
290 Arr.NonEmptyReadonlyArray<unknown>,
291 OutErr,
292 OutDone,
293 Arr.NonEmptyReadonlyArray<any>,
294 Schema.SchemaError | InErr,
295 InDone,
296 R
297 >,
298 options: {
299 readonly inputSchema: In
300 readonly outputSchema: Out
301 }
302 ): Channel.Channel<
303 Arr.NonEmptyReadonlyArray<Out["Type"]>,
304 Schema.SchemaError | OutErr,
305 OutDone,
306 Arr.NonEmptyReadonlyArray<In["Type"]>,
307 InErr,
308 InDone,
309 R | In["EncodingServices"] | Out["DecodingServices"]
310 >
311} = duplex