228 lines
9.4 KiB
TypeScript
228 lines
9.4 KiB
TypeScript
import { assert, describe, it } from "@effect/vitest"
|
|
import { Cause, Effect, Schema } from "effect"
|
|
import { Model } from "effect/unstable/schema"
|
|
import { SqlClient, SqlModel, SqlResolver } from "effect/unstable/sql"
|
|
import { MysqlContainer } from "./utils.ts"
|
|
|
|
class User extends Model.Class<User>("User")({
|
|
id: Schema.Int.pipe(
|
|
Model.FieldExcept(["insert"])
|
|
),
|
|
name: Schema.String,
|
|
age: Schema.Int
|
|
}) {}
|
|
|
|
class SoftDeleteUser extends Model.Class<SoftDeleteUser>("SoftDeleteUser")({
|
|
id: Schema.Int.pipe(
|
|
Model.FieldExcept(["insert"])
|
|
),
|
|
name: Schema.String,
|
|
deletedAt: Schema.NullOr(Schema.String).pipe(
|
|
Model.FieldOnly(["select", "update"])
|
|
)
|
|
}) {}
|
|
|
|
describe("SqlModel", () => {
|
|
it.effect("insert returns result", () =>
|
|
Effect.gen(function*() {
|
|
const repo = yield* SqlModel.makeRepository(User, {
|
|
tableName: "users",
|
|
idColumn: "id",
|
|
spanPrefix: "UserRepository"
|
|
})
|
|
const sql = yield* SqlClient.SqlClient
|
|
yield* sql`CREATE TABLE users (id INT AUTO_INCREMENT PRIMARY KEY, name VARCHAR(255), age INT)`
|
|
|
|
const result = yield* repo.insert(User.insert.make({ name: "Alice", age: 30 }))
|
|
assert.deepStrictEqual(result, new User({ id: 1, name: "Alice", age: 30 }))
|
|
}).pipe(
|
|
Effect.provide(MysqlContainer.layerClient),
|
|
Effect.catchTag("ContainerError", () => Effect.void)
|
|
), { timeout: 60_000 })
|
|
|
|
it.effect("insert returns result with transforms", () =>
|
|
Effect.gen(function*() {
|
|
const repo = yield* SqlModel.makeRepository(User, {
|
|
tableName: "users",
|
|
idColumn: "id",
|
|
spanPrefix: "UserRepository"
|
|
})
|
|
const sql = yield* SqlClient.SqlClient
|
|
yield* sql`CREATE TABLE users (id INT AUTO_INCREMENT PRIMARY KEY, name VARCHAR(255), age INT)`
|
|
|
|
const result = yield* repo.insert(User.insert.make({ name: "Alice", age: 30 }))
|
|
assert.deepStrictEqual(result, new User({ id: 1, name: "Alice", age: 30 }))
|
|
}).pipe(
|
|
Effect.provide(MysqlContainer.layerClientWithTransforms),
|
|
Effect.catchTag("ContainerError", () => Effect.void)
|
|
), { timeout: 60_000 })
|
|
|
|
it.effect("insertVoid", () =>
|
|
Effect.gen(function*() {
|
|
const repo = yield* SqlModel.makeRepository(User, {
|
|
tableName: "users",
|
|
idColumn: "id",
|
|
spanPrefix: "UserRepository"
|
|
})
|
|
const sql = yield* SqlClient.SqlClient
|
|
yield* sql`CREATE TABLE users (id INT AUTO_INCREMENT PRIMARY KEY, name VARCHAR(255), age INT)`
|
|
|
|
const result = yield* repo.insertVoid(User.insert.make({ name: "Alice", age: 30 }))
|
|
assert.strictEqual(result, void 0)
|
|
}).pipe(
|
|
Effect.provide(MysqlContainer.layerClient),
|
|
Effect.catchTag("ContainerError", () => Effect.void)
|
|
), { timeout: 60_000 })
|
|
|
|
it.live("insert data loader returns result", () =>
|
|
Effect.gen(function*() {
|
|
const repo = yield* SqlModel.makeResolvers(User, {
|
|
tableName: "users",
|
|
idColumn: "id",
|
|
spanPrefix: "UserRepository"
|
|
})
|
|
const sql = yield* SqlClient.SqlClient
|
|
yield* sql`CREATE TABLE users (id INT AUTO_INCREMENT PRIMARY KEY, name VARCHAR(255), age INT)`
|
|
|
|
const [alice, john] = yield* Effect.all([
|
|
SqlResolver.request(User.insert.make({ name: "Alice", age: 30 }), repo.insert),
|
|
SqlResolver.request(User.insert.make({ name: "John", age: 30 }), repo.insert)
|
|
], { concurrency: "unbounded" })
|
|
assert.deepStrictEqual(alice.name, "Alice")
|
|
assert.deepStrictEqual(john.name, "John")
|
|
}).pipe(
|
|
Effect.provide(MysqlContainer.layerClient),
|
|
Effect.catchTag("ContainerError", () => Effect.void)
|
|
), { timeout: 60_000 })
|
|
|
|
it.live("findById data loader", () =>
|
|
Effect.gen(function*() {
|
|
const repo = yield* SqlModel.makeResolvers(User, {
|
|
tableName: "users",
|
|
idColumn: "id",
|
|
spanPrefix: "UserRepository"
|
|
})
|
|
const sql = yield* SqlClient.SqlClient
|
|
yield* sql`CREATE TABLE users (id INT AUTO_INCREMENT PRIMARY KEY, name VARCHAR(255), age INT)`
|
|
const alice = yield* SqlResolver.request(User.insert.make({ name: "Alice", age: 30 }), repo.insert)
|
|
const john = yield* SqlResolver.request(User.insert.make({ name: "John", age: 30 }), repo.insert)
|
|
|
|
const [alice2, john2] = yield* Effect.all([
|
|
SqlResolver.request(alice.id, repo.findById),
|
|
SqlResolver.request(john.id, repo.findById)
|
|
], { concurrency: "unbounded" })
|
|
|
|
assert.deepStrictEqual(alice2.name, "Alice")
|
|
assert.deepStrictEqual(john2.name, "John")
|
|
}).pipe(
|
|
Effect.provide(MysqlContainer.layerClient),
|
|
Effect.catchTag("ContainerError", () => Effect.void)
|
|
), { timeout: 60_000 })
|
|
|
|
it.effect("findById ignores soft deleted rows", () =>
|
|
Effect.gen(function*() {
|
|
const repo = yield* SqlModel.makeRepository(SoftDeleteUser, {
|
|
tableName: "soft_delete_users",
|
|
idColumn: "id",
|
|
spanPrefix: "SoftDeleteUserRepository",
|
|
softDeleteColumn: "deletedAt"
|
|
})
|
|
const sql = yield* SqlClient.SqlClient
|
|
yield* sql`CREATE TABLE soft_delete_users (id INT AUTO_INCREMENT PRIMARY KEY, name VARCHAR(255), deletedAt VARCHAR(255) NULL)`
|
|
|
|
const alice = yield* repo.insert(SoftDeleteUser.insert.make({ name: "Alice" }))
|
|
const bob = yield* repo.insert(SoftDeleteUser.insert.make({ name: "Bob" }))
|
|
yield* sql`UPDATE soft_delete_users SET deletedAt = CURRENT_TIMESTAMP WHERE id = ${bob.id}`
|
|
|
|
const aliceResult = yield* repo.findById(alice.id)
|
|
assert.deepStrictEqual(aliceResult, new SoftDeleteUser({ id: alice.id, name: "Alice", deletedAt: null }))
|
|
|
|
const error = yield* Effect.flip(repo.findById(bob.id))
|
|
assert.isTrue(Cause.isNoSuchElementError(error))
|
|
|
|
yield* repo.delete(alice.id)
|
|
const rows = yield* sql<
|
|
{ deletedAt: string | null }
|
|
>`SELECT deletedAt FROM soft_delete_users WHERE id = ${alice.id}`
|
|
assert.strictEqual(rows.length, 1)
|
|
assert.isNotNull(rows[0]!.deletedAt)
|
|
|
|
const deletedError = yield* Effect.flip(repo.findById(alice.id))
|
|
assert.isTrue(Cause.isNoSuchElementError(deletedError))
|
|
}).pipe(
|
|
Effect.provide(MysqlContainer.layerClient),
|
|
Effect.catchTag("ContainerError", () => Effect.void)
|
|
), { timeout: 60_000 })
|
|
|
|
it.live("findById data loader ignores soft deleted rows", () =>
|
|
Effect.gen(function*() {
|
|
const repo = yield* SqlModel.makeResolvers(SoftDeleteUser, {
|
|
tableName: "soft_delete_user_resolvers",
|
|
idColumn: "id",
|
|
spanPrefix: "SoftDeleteUserRepository",
|
|
softDeleteColumn: "deletedAt"
|
|
})
|
|
const sql = yield* SqlClient.SqlClient
|
|
yield* sql`CREATE TABLE soft_delete_user_resolvers (id INT AUTO_INCREMENT PRIMARY KEY, name VARCHAR(255), deletedAt VARCHAR(255) NULL)`
|
|
|
|
const alice = yield* SqlResolver.request(SoftDeleteUser.insert.make({ name: "Alice" }), repo.insert)
|
|
const bob = yield* SqlResolver.request(SoftDeleteUser.insert.make({ name: "Bob" }), repo.insert)
|
|
yield* sql`UPDATE soft_delete_user_resolvers SET deletedAt = CURRENT_TIMESTAMP WHERE id = ${bob.id}`
|
|
|
|
const aliceResult = yield* SqlResolver.request(alice.id, repo.findById)
|
|
assert.deepStrictEqual(aliceResult, new SoftDeleteUser({ id: alice.id, name: "Alice", deletedAt: null }))
|
|
|
|
const error = yield* Effect.flip(SqlResolver.request(bob.id, repo.findById))
|
|
assert.isTrue(Cause.isNoSuchElementError(error))
|
|
|
|
yield* SqlResolver.request(alice.id, repo.delete)
|
|
const rows = yield* sql<
|
|
{ deletedAt: string | null }
|
|
>`SELECT deletedAt FROM soft_delete_user_resolvers WHERE id = ${alice.id}`
|
|
assert.strictEqual(rows.length, 1)
|
|
assert.isNotNull(rows[0]!.deletedAt)
|
|
|
|
const deletedError = yield* Effect.flip(SqlResolver.request(alice.id, repo.findById))
|
|
assert.isTrue(Cause.isNoSuchElementError(deletedError))
|
|
}).pipe(
|
|
Effect.provide(MysqlContainer.layerClient),
|
|
Effect.catchTag("ContainerError", () => Effect.void)
|
|
), { timeout: 60_000 })
|
|
|
|
it.effect("update returns result", () =>
|
|
Effect.gen(function*() {
|
|
const repo = yield* SqlModel.makeRepository(User, {
|
|
tableName: "users",
|
|
idColumn: "id",
|
|
spanPrefix: "UserRepository"
|
|
})
|
|
const sql = yield* SqlClient.SqlClient
|
|
yield* sql`CREATE TABLE users (id INT AUTO_INCREMENT PRIMARY KEY, name VARCHAR(255), age INT)`
|
|
|
|
let result = yield* repo.insert(User.insert.make({ name: "Alice", age: 30 }))
|
|
result = yield* repo.update(User.update.make({ ...result, name: "Bob" }))
|
|
assert.deepStrictEqual(result, new User({ id: 1, name: "Bob", age: 30 }))
|
|
}).pipe(
|
|
Effect.provide(MysqlContainer.layerClient),
|
|
Effect.catchTag("ContainerError", () => Effect.void)
|
|
), { timeout: 60_000 })
|
|
|
|
it.effect("update returns result with transforms", () =>
|
|
Effect.gen(function*() {
|
|
const repo = yield* SqlModel.makeRepository(User, {
|
|
tableName: "users",
|
|
idColumn: "id",
|
|
spanPrefix: "UserRepository"
|
|
})
|
|
const sql = yield* SqlClient.SqlClient
|
|
yield* sql`CREATE TABLE users (id INT AUTO_INCREMENT PRIMARY KEY, name VARCHAR(255), age INT)`
|
|
|
|
let result = yield* repo.insert(User.insert.make({ name: "Alice", age: 30 }))
|
|
result = yield* repo.update(User.update.make({ ...result, name: "Bob" }))
|
|
assert.deepStrictEqual(result, new User({ id: 1, name: "Bob", age: 30 }))
|
|
}).pipe(
|
|
Effect.provide(MysqlContainer.layerClientWithTransforms),
|
|
Effect.catchTag("ContainerError", () => Effect.void)
|
|
), { timeout: 60_000 })
|
|
})
|