Skip to content

Commit 7b3b894

Browse files
authored
Create README.md
1 parent 0425bf5 commit 7b3b894

1 file changed

Lines changed: 88 additions & 0 deletions

File tree

README.md

Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
<picture>
2+
<source media="(prefers-color-scheme: dark)" srcset="https://github.com/wickwirew/Feather/blob/main/Otter~dark.png?raw=true">
3+
<source media="(prefers-color-scheme: light)" srcset="https://github.com/wickwirew/Feather/blob/main/Otter.png?raw=true">
4+
<p align="center">
5+
<img alt="Otter" src="https://github.com/wickwirew/Feather/blob/main/Otter.png?raw=true" width=33% height=33%>
6+
</p>
7+
</picture>
8+
9+
<p align="center">
10+
A fast, lightweight SQLite library for Swift
11+
</p>
12+
13+
## Overview
14+
Otter generates type safe code from raw plain SQL.
15+
16+
## Basic Usage
17+
As a primer here is a quick example. Below is some SQL. The first part is in the `/Migrations` directory. This is where you can create and modify your schema. The second part is in the `/Queries` directory.
18+
```sql
19+
-- Located in Migrations/1.sql
20+
CREATE TABLE todo (
21+
id INTEGER,
22+
name TEXT NOT NULL,
23+
completedOn INTEGER
24+
)
25+
26+
-- Located in Queries/Todo.sql
27+
DEFINE QUERY selectTodos AS
28+
SELECT * FROM todo;
29+
```
30+
Would generate the following Swift code
31+
```swift
32+
let db = DB()
33+
let todos = try await db.selectTodos.execute()
34+
35+
for todo in todos {
36+
print(todo.id, todo.name, todo.completedOn)
37+
}
38+
39+
for try await todos in db.selectTodos.observe() {
40+
print("Got todos", todos)
41+
}
42+
```
43+
44+
## Dependency Injection
45+
> TLDR; Avoid the repository pattern, inject queries.
46+
47+
Otter was written with application development in mind. One of the common walls when talking to a database is dependecy injection.
48+
Normally this would mean wrapping your database calls in a repository or some other layer to keep the model layer testable without needing a database connection.
49+
This is all good but that means writing different protocols and mocks. When writing the protocol you need to decide whether to just make it `async` or maybe a `publisher`.
50+
Sometimes you need both... Otter solves these problems and was designed to have injection builtin.
51+
52+
At the core, Otter exposes one core type for injections which is `any Query<Input, Output>`. This acts as a wrapper, which knows nothing about the database that can passed into a model or view model. For example, if we have a query that takes in an `Int` and returns a `String` we can setup our view model like:
53+
```swift
54+
class ViewModel {
55+
let fetchString: any Query<Int, String>
56+
}
57+
```
58+
59+
Then in your live application code that will actually ship to users you can pass in your database query
60+
```swift
61+
let viewModel = ViewModel(fetchString: database.fetchString)
62+
```
63+
64+
In unit tests or previews where you don't want a database you can pass a `Just` or a `Fail`
65+
```swift
66+
let viewModel = ViewModel(fetchString: Queries.Just("Just a string, no database needed 😎"))
67+
68+
// Will throw the `MockError` any time the query is executed
69+
let alwaysFails = ViewModel(fetchString: Queries.Fail(MockError()))
70+
```
71+
72+
#### Injecting Type Aliases
73+
The example above is nice but doesn't really represent the common use case.
74+
Most of the time we don't just have a query that has an input and output of simple builtins.
75+
They can be larger generated structs which can be a lot to type. To fix this typealiases are
76+
generated for a query to give them a simple readable name. For example
77+
```sql
78+
DEFINE QUERY latestExpenses AS
79+
SELECT id, title, amount FROM expense
80+
WHERE date BETWEEN ? AND ?;
81+
```
82+
Would generate the following `typealias` for injection
83+
```swift
84+
class ViewModel {
85+
// Equivalent to `any Query<LatestExpensesInput, LatestExpensesOutput>`
86+
let query: any LatestExpensesQuery
87+
}
88+
```

0 commit comments

Comments
 (0)