| 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 | */ |
| 14 | import * as Arr from "../Array.ts" |
| 15 | import * as Cause from "../Cause.ts" |
| 16 | import * as Effect from "../Effect.ts" |
| 17 | import type * as Option from "../Option.ts" |
| 18 | import * 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 | */ |
| 35 | export 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 | */ |
| 68 | export 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 | |
| 89 | const 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 | */ |
| 104 | export { |
| 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 | */ |
| 123 | export 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 | */ |
| 157 | export 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 | } |