1import { D1Client } from "@effect/sql-d1"
2import { assert, describe, it } from "@effect/vitest"
3import { Cause, Effect } from "effect"
4import * as Schema from "effect/Schema"
5import { SqlError, SqlResolver } from "effect/sql"
6import { D1Miniflare } from "./utils.ts"
7
8const seededClient = Effect.gen(function*() {
9 const sql = yield* D1Client.D1Client
10 yield* sql`CREATE TABLE test (id INTEGER PRIMARY KEY, name TEXT)`
11 yield* sql`INSERT INTO test ${
12 sql.insert([
13 { id: 1, name: "name1" },
14 { id: 2, name: "name2" },
15 { id: 3, name: "name3" },
16 { id: 100, name: "name100" }
17 ])
18 }`
19 return sql
20})
21
22describe("Resolver", () => {
23 describe("ordered", () => {
24 it.effect("insert", () =>
25 Effect.gen(function*() {
26 const batches: Array<Array<string>> = []
27 const sql = yield* seededClient
28 const Insert = SqlResolver.ordered({
29 Request: Schema.String,
30 Result: Schema.Struct({ id: Schema.Number, name: Schema.String }),
31 execute: (names) => {
32 batches.push(names)
33 return sql`INSERT INTO test ${sql.insert(names.map((name) => ({ name })))} RETURNING *`
34 }
35 })
36 const execute = SqlResolver.request(Insert)
37 assert.deepStrictEqual(
38 yield* Effect.all({
39 one: execute("one"),
40 two: SqlResolver.request("two", Insert)
41 }, { concurrency: "unbounded" }),
42 {
43 one: { id: 101, name: "one" },
44 two: { id: 102, name: "two" }
45 }
46 )
47 assert.deepStrictEqual(batches, [["one", "two"]])
48 }).pipe(Effect.provide(D1Miniflare.layerClient)))
49
50 it.effect("result length mismatch", () =>
51 Effect.gen(function*() {
52 const batches: Array<Array<number>> = []
53 const sql = yield* seededClient
54 const Select = SqlResolver.ordered({
55 Request: Schema.Number,
56 Result: Schema.Struct({ id: Schema.Number, name: Schema.String }),
57 execute: (ids) => {
58 batches.push(ids)
59 return sql`SELECT * FROM test WHERE id IN ${sql.in(ids)}`
60 }
61 })
62 const execute = SqlResolver.request(Select)
63 const error = yield* Effect.all([
64 execute(1),
65 execute(2),
66 execute(3),
67 execute(101)
68 ], { concurrency: "unbounded" }).pipe(
69 Effect.flip
70 )
71 assert(error instanceof SqlError.ResultLengthMismatch)
72 assert.strictEqual(error.actual, 3)
73 assert.strictEqual(error.expected, 4)
74 assert.deepStrictEqual(batches, [[1, 2, 3, 101]])
75 }).pipe(Effect.provide(D1Miniflare.layerClient)))
76 })
77
78 describe("grouped", () => {
79 it.effect("find by name", () =>
80 Effect.gen(function*() {
81 const sql = yield* seededClient
82 const FindByName = SqlResolver.grouped({
83 Request: Schema.String,
84 RequestGroupKey: (name) => name,
85 Result: Schema.Struct({ id: Schema.Number, name: Schema.String }),
86 ResultGroupKey: (result) => result.name,
87 execute: (names) => sql`SELECT * FROM test WHERE name IN ${sql.in(names)}`
88 })
89 const execute = SqlResolver.request(FindByName)
90 yield* sql`INSERT INTO test ${sql.insert({ name: "name1" })}`
91 assert.deepStrictEqual(
92 yield* Effect.all({
93 one: execute("name1"),
94 two: execute("name2"),
95 three: Effect.flip(execute("name0"))
96 }, { concurrency: "unbounded" }),
97 {
98 one: [{ id: 1, name: "name1" }, { id: 101, name: "name1" }],
99 two: [{ id: 2, name: "name2" }],
100 three: new Cause.NoSuchElementError()
101 }
102 )
103 }).pipe(Effect.provide(D1Miniflare.layerClient)))
104
105 it.effect("using raw rows", () =>
106 Effect.gen(function*() {
107 const sql = yield* seededClient
108 const FindByName = SqlResolver.grouped({
109 Request: Schema.String,
110 RequestGroupKey: (name) => name,
111 Result: Schema.Struct({ id: Schema.Number, name: Schema.String }),
112 ResultGroupKey: (_, result: any) => result.name,
113 execute: (names) => sql`SELECT * FROM test WHERE name IN ${sql.in(names)}`
114 })
115 const execute = SqlResolver.request(FindByName)
116 yield* sql`INSERT INTO test ${sql.insert({ name: "name1" })}`
117 assert.deepStrictEqual(
118 yield* Effect.all({
119 one: execute("name1"),
120 two: execute("name2"),
121 three: Effect.flip(execute("name0"))
122 }, { concurrency: "unbounded" }),
123 {
124 one: [{ id: 1, name: "name1" }, { id: 101, name: "name1" }],
125 two: [{ id: 2, name: "name2" }],
126 three: new Cause.NoSuchElementError()
127 }
128 )
129 }).pipe(Effect.provide(D1Miniflare.layerClient)))
130 })
131
132 describe("findById", () => {
133 it.effect("find by id", () =>
134 Effect.gen(function*() {
135 const sql = yield* seededClient
136 const FindById = SqlResolver.findById({
137 Id: Schema.Number,
138 Result: Schema.Struct({ id: Schema.Number, name: Schema.String }),
139 ResultId: (result) => result.id,
140 execute: (ids) => sql`SELECT * FROM test WHERE id IN ${sql.in(ids)}`
141 })
142 const execute = SqlResolver.request(FindById)
143 assert.deepStrictEqual(
144 yield* Effect.all({
145 one: execute(1),
146 two: execute(2),
147 three: Effect.flip(execute(101))
148 }, { concurrency: "unbounded" }),
149 {
150 one: { id: 1, name: "name1" },
151 two: { id: 2, name: "name2" },
152 three: new Cause.NoSuchElementError()
153 }
154 )
155 }).pipe(Effect.provide(D1Miniflare.layerClient)))
156
157 it.effect("using raw rows", () =>
158 Effect.gen(function*() {
159 const sql = yield* seededClient
160 const FindById = SqlResolver.findById({
161 Id: Schema.Number,
162 Result: Schema.Struct({ id: Schema.Number, name: Schema.String }),
163 ResultId: (_, result: any) => result.id,
164 execute: (ids) => sql`SELECT * FROM test WHERE id IN ${sql.in(ids)}`
165 })
166 const execute = SqlResolver.request(FindById)
167 assert.deepStrictEqual(
168 yield* Effect.all({
169 one: execute(1),
170 two: execute(2),
171 three: Effect.flip(execute(101))
172 }, { concurrency: "unbounded" }),
173 {
174 one: { id: 1, name: "name1" },
175 two: { id: 2, name: "name2" },
176 three: new Cause.NoSuchElementError()
177 }
178 )
179 }).pipe(Effect.provide(D1Miniflare.layerClient)))
180 })
181})