1/**
2 * Wraps SQL execution callbacks with request encoding and result decoding.
3 *
4 * `SqlSchema` is a small adapter between Effect Schema and SQL statements. Each
5 * helper builds a function that accepts the decoded request type used by
6 * application code, encodes it before calling `execute`, and decodes unknown
7 * driver rows into the result schema. The helpers cover returning all rows, a
8 * non-empty row list, the first row, an optional first row, or discarding the
9 * SQL result for side-effect-only statements.
10 *
11 * @stability unstable
12 * @since 4.0.0
13 */
14import * as Arr from "../Array.ts"
15import * as Cause from "../Cause.ts"
16import * as Effect from "../Effect.ts"
17import type * as Option from "../Option.ts"
18import * as Schema from "../Schema.ts"
19
20/**
21 * Builds a query function that encodes the request and decodes all result rows,
22 * allowing an empty result set.
23 *
24 * **When to use**
25 *
26 * Use when you need to run a query that may return zero or more rows and
27 * represent an empty result as an empty array.
28 *
29 * @see {@link findNonEmpty} for queries where an empty result is a failure
30 *
31 * @stability unstable
32 * @category constructors
33 * @since 4.0.0
34 */
35export const findAll = <Req extends Schema.Constraint, Res extends Schema.Constraint, E, R>(
36 options: {
37 readonly Request: Req
38 readonly Result: Res
39 readonly execute: (request: Req["Encoded"]) => Effect.Effect<ReadonlyArray<unknown>, E, R>
40 }
41) => {
42 const encodeRequest = Schema.encodeEffect(options.Request)
43 const decode = Schema.decodeUnknownEffect(Schema.mutable(Schema.Array(options.Result)))
44 return (
45 request: Req["Type"]
46 ): Effect.Effect<
47 Array<Res["Type"]>,
48 E | Schema.SchemaError,
49 Req["EncodingServices"] | Res["DecodingServices"] | R
50 > => Effect.flatMap(Effect.flatMap(encodeRequest(request), options.execute), decode)
51}
52
53/**
54 * Builds a query function that encodes the request, decodes all result rows,
55 * and fails with `NoSuchElementError` when the result set is empty.
56 *
57 * **When to use**
58 *
59 * Use when you need to run a query that must return at least one row and treat
60 * an empty result as a failure.
61 *
62 * @see {@link findAll} for queries where an empty result should return an empty array
63 *
64 * @stability unstable
65 * @category constructors
66 * @since 4.0.0
67 */
68export const findNonEmpty = <Req extends Schema.Constraint, Res extends Schema.Constraint, E, R>(
69 options: {
70 readonly Request: Req
71 readonly Result: Res
72 readonly execute: (request: Req["Encoded"]) => Effect.Effect<ReadonlyArray<unknown>, E, R>
73 }
74) => {
75 const find = findAll(options)
76 return (
77 request: Req["Type"]
78 ): Effect.Effect<
79 Arr.NonEmptyArray<Res["Type"]>,
80 E | Schema.SchemaError | Cause.NoSuchElementError,
81 Req["EncodingServices"] | Res["DecodingServices"] | R
82 > =>
83 Effect.flatMap(find(request), (results) =>
84 Arr.isArrayNonEmpty(results)
85 ? Effect.succeed(results)
86 : Effect.fail(new Cause.NoSuchElementError()))
87}
88
89const void_ = <Req extends Schema.Constraint, E, R>(
90 options: {
91 readonly Request: Req
92 readonly execute: (request: Req["Encoded"]) => Effect.Effect<unknown, E, R>
93 }
94) => {
95 const encode = Schema.encodeEffect(options.Request)
96 return (request: Req["Type"]): Effect.Effect<void, E | Schema.SchemaError, R | Req["EncodingServices"]> =>
97 Effect.asVoid(
98 Effect.flatMap(encode(request), options.execute)
99 )
100}
101/**
102 * @stability unstable
103 */
104export {
105 /**
106 * Runs a sql query with a request schema and discard the result.
107 *
108 * @stability unstable
109 * @category constructors
110 * @since 4.0.0
111 */
112 void_ as void
113}
114
115/**
116 * Builds a query function that encodes the request, decodes the first result
117 * row, and fails with `NoSuchElementError` when no rows are returned.
118 *
119 * @stability unstable
120 * @category constructors
121 * @since 4.0.0
122 */
123export const findOne = <Req extends Schema.Constraint, Res extends Schema.Constraint, E, R>(
124 options: {
125 readonly Request: Req
126 readonly Result: Res
127 readonly execute: (request: Req["Encoded"]) => Effect.Effect<ReadonlyArray<unknown>, E, R>
128 }
129) => {
130 const encodeRequest = Schema.encodeEffect(options.Request)
131 const decode = Schema.decodeUnknownEffect(options.Result)
132 return (
133 request: Req["Type"]
134 ): Effect.Effect<
135 Res["Type"],
136 E | Schema.SchemaError | Cause.NoSuchElementError,
137 R | Req["EncodingServices"] | Res["DecodingServices"]
138 > =>
139 Effect.flatMap(
140 Effect.flatMap(encodeRequest(request), options.execute),
141 (arr): Effect.Effect<
142 Res["Type"],
143 Schema.SchemaError | Cause.NoSuchElementError,
144 Req["EncodingServices"] | Res["DecodingServices"]
145 > => Arr.isReadonlyArrayNonEmpty(arr) ? decode(arr[0]) : Effect.fail(new Cause.NoSuchElementError())
146 )
147}
148
149/**
150 * Builds a query function that encodes the request, decodes the first result row
151 * as `Option.some`, and returns `Option.none` when no rows are returned.
152 *
153 * @stability unstable
154 * @category constructors
155 * @since 4.0.0
156 */
157export const findOneOption = <Req extends Schema.Constraint, Res extends Schema.Constraint, E, R>(
158 options: {
159 readonly Request: Req
160 readonly Result: Res
161 readonly execute: (request: Req["Encoded"]) => Effect.Effect<ReadonlyArray<unknown>, E, R>
162 }
163) => {
164 const encodeRequest = Schema.encodeEffect(options.Request)
165 const decode = Schema.decodeUnknownEffect(options.Result)
166 return (
167 request: Req["Type"]
168 ): Effect.Effect<
169 Option.Option<Res["Type"]>,
170 E | Schema.SchemaError,
171 R | Req["EncodingServices"] | Res["DecodingServices"]
172 > =>
173 Effect.flatMap(
174 Effect.flatMap(encodeRequest(request), options.execute),
175 (arr): Effect.Effect<
176 Option.Option<Res["Type"]>,
177 Schema.SchemaError,
178 Req["EncodingServices"] | Res["DecodingServices"]
179 > => Arr.isReadonlyArrayNonEmpty(arr) ? Effect.asSome(decode(arr[0])) : Effect.succeedNone
180 )
181}