254 lines
7.8 KiB
TypeScript
254 lines
7.8 KiB
TypeScript
import { IndexedDb, IndexedDbDatabase, IndexedDbTable, IndexedDbVersion } from "@effect/platform-browser"
|
|
import { afterEach, assert, describe, it } from "@effect/vitest"
|
|
import { Effect, Layer, Schema } from "effect"
|
|
import { IDBKeyRange, indexedDB } from "fake-indexeddb"
|
|
|
|
const databaseName = "db"
|
|
|
|
const layerFakeIndexedDb = Layer.succeed(
|
|
IndexedDb.IndexedDb,
|
|
IndexedDb.make({ indexedDB, IDBKeyRange })
|
|
)
|
|
|
|
const provideMigration = (database: IndexedDbDatabase.Any) =>
|
|
Effect.provide(
|
|
database.layer(databaseName).pipe(Layer.provide(layerFakeIndexedDb))
|
|
)
|
|
|
|
afterEach(() => {
|
|
indexedDB.deleteDatabase(databaseName)
|
|
})
|
|
|
|
describe.sequential("IndexedDbDatabase", () => {
|
|
it.effect("insert and read todos", () => {
|
|
const Table = IndexedDbTable.make({
|
|
name: "todo",
|
|
schema: Schema.Struct({
|
|
id: Schema.Number,
|
|
title: Schema.String,
|
|
completed: Schema.Boolean
|
|
}),
|
|
keyPath: "id",
|
|
indexes: { titleIndex: "title" }
|
|
})
|
|
|
|
const V1 = IndexedDbVersion.make(Table)
|
|
|
|
class Db extends IndexedDbDatabase.make(V1, (api) =>
|
|
Effect.gen(function*() {
|
|
yield* api.createObjectStore("todo")
|
|
yield* api.createIndex("todo", "titleIndex")
|
|
yield* api
|
|
.from("todo")
|
|
.insert({ id: 1, title: "test", completed: false })
|
|
}))
|
|
{}
|
|
|
|
return Effect.gen(function*() {
|
|
const db = yield* IndexedDbDatabase.IndexedDbDatabase
|
|
const api = yield* Db.getQueryBuilder
|
|
let todo = yield* api.from("todo").select()
|
|
|
|
const name = yield* api.use((database) => database.name)
|
|
const version = yield* api.use((database) => database.version)
|
|
const objectStoreNames = yield* api.use(
|
|
(database) => database.objectStoreNames
|
|
)
|
|
const indexNames = yield* api.use(
|
|
(database) => database.transaction("todo").objectStore("todo").indexNames
|
|
)
|
|
const index = yield* api.use((database) => database.transaction("todo").objectStore("todo").index("titleIndex"))
|
|
|
|
assert.equal(name, "db")
|
|
assert.equal(version, 1)
|
|
assert.deepStrictEqual(todo, [
|
|
{ id: 1, title: "test", completed: false }
|
|
])
|
|
assert.deepStrictEqual(Array.from(objectStoreNames), ["todo"])
|
|
assert.deepStrictEqual(Array.from(indexNames), ["titleIndex"])
|
|
assert.deepStrictEqual(index.keyPath, "title")
|
|
|
|
yield* api.from("todo").insert({ id: 2, title: "test2", completed: false })
|
|
|
|
yield* db.rebuild
|
|
todo = yield* api.from("todo").select()
|
|
assert.deepStrictEqual(todo, [
|
|
{ id: 1, title: "test", completed: false }
|
|
])
|
|
}).pipe(provideMigration(Db))
|
|
})
|
|
|
|
it.effect("transaction", () => {
|
|
const Table = IndexedDbTable.make({
|
|
name: "todo",
|
|
schema: Schema.Struct({
|
|
id: Schema.Number,
|
|
title: Schema.String,
|
|
completed: Schema.Boolean
|
|
}),
|
|
keyPath: "id",
|
|
indexes: { titleIndex: "title" }
|
|
})
|
|
|
|
const Table2 = IndexedDbTable.make({
|
|
name: "user",
|
|
schema: Schema.Struct({
|
|
id: Schema.Number,
|
|
name: Schema.String
|
|
}),
|
|
keyPath: "id"
|
|
})
|
|
|
|
const Db = IndexedDbVersion.make(Table, Table2)
|
|
|
|
class Migration extends IndexedDbDatabase.make(Db, (api) =>
|
|
Effect.gen(function*() {
|
|
yield* api.createObjectStore("todo")
|
|
yield* api.createIndex("todo", "titleIndex")
|
|
yield* api
|
|
.from("todo")
|
|
.insert({ id: 1, title: "test", completed: false })
|
|
}))
|
|
{}
|
|
|
|
return Effect.gen(function*() {
|
|
const api = yield* Migration.getQueryBuilder
|
|
yield* api.withTransaction({ tables: ["todo"], mode: "readwrite" })(
|
|
api.from("todo").insert({ id: 2, title: "test2", completed: false })
|
|
)
|
|
|
|
const todo = yield* api.from("todo").select()
|
|
assert.deepStrictEqual(todo, [
|
|
{ id: 1, title: "test", completed: false },
|
|
{
|
|
id: 2,
|
|
title: "test2",
|
|
completed: false
|
|
}
|
|
])
|
|
}).pipe(provideMigration(Migration))
|
|
})
|
|
|
|
it.effect("migration sequence", () => {
|
|
const Table1 = IndexedDbTable.make({
|
|
name: "todo",
|
|
schema: Schema.Struct({
|
|
id: Schema.Number,
|
|
title: Schema.String,
|
|
completed: Schema.Boolean
|
|
}),
|
|
keyPath: "id",
|
|
indexes: { titleIndex: "title" }
|
|
})
|
|
|
|
const Table2 = IndexedDbTable.make({
|
|
name: "todo",
|
|
schema: Schema.Struct({
|
|
uuid: Schema.String.check(Schema.isUUID(4)),
|
|
title: Schema.String,
|
|
completed: Schema.Boolean
|
|
}),
|
|
keyPath: "uuid"
|
|
})
|
|
|
|
const V1 = IndexedDbVersion.make(Table1)
|
|
const V2 = IndexedDbVersion.make(Table2)
|
|
const uuid = "9535a059-a61f-42e1-a2e0-35ec87203c24"
|
|
|
|
class Db extends IndexedDbDatabase.make(V1, (api) =>
|
|
Effect.gen(function*() {
|
|
yield* api.createObjectStore("todo")
|
|
yield* api.createIndex("todo", "titleIndex")
|
|
yield* api
|
|
.from("todo")
|
|
.insert({ id: 1, title: "test", completed: false })
|
|
})).add(V2, (from, to) =>
|
|
Effect.gen(function*() {
|
|
const todo = yield* from.from("todo").select()
|
|
yield* from.deleteIndex("todo", "titleIndex")
|
|
yield* from.deleteObjectStore("todo")
|
|
yield* to.createObjectStore("todo")
|
|
yield* to.from("todo").insertAll(
|
|
todo.map((t) => ({
|
|
uuid,
|
|
title: t.title,
|
|
completed: t.completed
|
|
}))
|
|
)
|
|
}))
|
|
{}
|
|
|
|
return Effect.gen(function*() {
|
|
const api = yield* Db.getQueryBuilder
|
|
const todo = yield* api.from("todo").select()
|
|
const name = yield* api.use((database) => database.name)
|
|
const version = yield* api.use((database) => database.version)
|
|
const objectStoreNames = yield* api.use(
|
|
(database) => database.objectStoreNames
|
|
)
|
|
const indexNames = yield* api.use(
|
|
(database) => database.transaction("todo").objectStore("todo").indexNames
|
|
)
|
|
|
|
assert.equal(name, "db")
|
|
assert.equal(version, 2)
|
|
assert.deepStrictEqual(todo, [{ uuid, title: "test", completed: false }])
|
|
assert.deepStrictEqual(Array.from(objectStoreNames), ["todo"])
|
|
assert.deepStrictEqual(Array.from(indexNames), [])
|
|
}).pipe(provideMigration(Db))
|
|
})
|
|
|
|
it.effect("delete object store migration", () => {
|
|
const Table1 = IndexedDbTable.make({
|
|
name: "todo",
|
|
schema: Schema.Struct({
|
|
id: Schema.Number,
|
|
title: Schema.String,
|
|
completed: Schema.Boolean
|
|
}),
|
|
keyPath: "id"
|
|
})
|
|
|
|
const Table2 = IndexedDbTable.make({
|
|
name: "user",
|
|
schema: Schema.Struct({
|
|
userId: Schema.Number,
|
|
name: Schema.String,
|
|
email: Schema.String
|
|
}),
|
|
keyPath: "userId"
|
|
})
|
|
|
|
const V1 = IndexedDbVersion.make(Table1)
|
|
const V2 = IndexedDbVersion.make(Table2, Table1)
|
|
|
|
class Migration extends IndexedDbDatabase.make(V1, (api) => api.createObjectStore("todo")).add(
|
|
V2,
|
|
Effect.fnUntraced(function*(from, to) {
|
|
yield* from.deleteObjectStore("todo")
|
|
yield* to.createObjectStore("user")
|
|
yield* to.from("user").insert({
|
|
userId: 1,
|
|
name: "John Doe",
|
|
email: "john.doe@example.com"
|
|
})
|
|
})
|
|
) {}
|
|
|
|
return Effect.gen(function*() {
|
|
const api = yield* Migration.getQueryBuilder
|
|
const user = yield* api.from("user").select()
|
|
|
|
const name = yield* api.use((database) => database.name)
|
|
const version = yield* api.use((database) => database.version)
|
|
const objectStoreNames = yield* api.use((database) => database.objectStoreNames)
|
|
assert.equal(name, "db")
|
|
assert.equal(version, 2)
|
|
assert.deepStrictEqual(user, [
|
|
{ userId: 1, name: "John Doe", email: "john.doe@example.com" }
|
|
])
|
|
assert.deepStrictEqual(Array.from(objectStoreNames), ["user"])
|
|
}).pipe(provideMigration(Migration))
|
|
})
|
|
})
|