Skip to content

Commit

Permalink
Final update/bugfix
Browse files Browse the repository at this point in the history
  • Loading branch information
Tarasikee committed Oct 4, 2022
1 parent f5c0798 commit 0c63ed0
Show file tree
Hide file tree
Showing 13 changed files with 261 additions and 71 deletions.
249 changes: 205 additions & 44 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
![](https://raw.githubusercontent.com/Tarasikee/tinydb/v1.0.0-alpha/images/Logo1.png)

<div align="center">
<img src="https://github.com/Tarasikee/tinydb/actions/workflows/ci.yml/badge.svg" alt="CI status" />
<img src="https://github.com/Tarasikee/tinydb/actions/workflows/codeql-analysis.yml/badge.svg" alt="CODEql analysis" />
<a href="https://deno.land/x/tinydb" target="_blank">
<img width="20" src="https://deno.land/logo.svg" alt="Deno link"/>
</a>
</div>
![](https://github.com/Tarasikee/tinydb/actions/workflows/ci.yml/badge.svg)
![](https://github.com/Tarasikee/tinydb/actions/workflows/codeql-analysis.yml/badge.svg)
![](https://img.shields.io/github/license/Tarasikee/tinydb)
![](https://img.shields.io/github/v/release/Tarasikee/tinydb)
<a href="https://deno.land/x/tinydb" target="_blank">
<img width="20" src="https://deno.land/logo.svg" alt="Deno link"/>
</a>

<hr/>

Expand All @@ -18,8 +18,12 @@ Tiny, Powerful, Beautiful

- [Motivation](#motivation)
- [Let's start](#lets-start)
- [@TinyTable](#lets-start)
- [@Column](#lets-start)
- [CRUD](#crud)
- [Create](#create)
- [Retrieve](#retrieve)
- [Update](#update)
- [Delete (Hunters)](#delete-hunters)
- [Contributing](#contributing)

# Motivation

Expand All @@ -33,62 +37,219 @@ store and retrieve data. It has all the features of a relational database,
but it designed to be as lightweight and
simple as possible.

No need to install software or to set up a server. You're ready to go after
installing dependencies.
No need to install software or to set up a server. Just import the library, and
you are ready to go.

# Let's start

Your entry point is ```@TinyTable``` decorator, where you pass table's name.
Your entry point is ```@TinyTable``` decorator,
where you pass table's name and path to the
file where you want to store your data.

No need to remember ton of decorators. Simply start with ```@Column({})```, add a
small bunch of properties,
and you are ready to go.
In the example below you will see the best way to use create user with TinyDB.
Below you can see an example of how to use ```@TinyTable``` and ```@Column``` decorators.

```typescript
@TinyTable("users")
```ts
@TinyTable({
name: "users",
url: "database/example1/"
})
class User {
@Column({
type: "string",
unique: true,
unique: true
})
name!: string

@Column({
type: "string",
allowNull: false,
})
password!: string

@Column({
type: "date",
allowNull: true,
})
birthday!: string

@Column({
type: "boolean",
default: false,
allowNull: true,
default: false,
})
isAdmin!: boolean

@Column({
allowNull: true,
type: "json",
default: {
theme: 'light',
lang: 'en',
}
})
settings!: Record<string, unknown>

@Column({
type: "array",
allowNull: true,
default: [],
default: {
theme: "dark",
lang: "en"
},
})
friends!: string[]
settings!: {
theme: "dark" | "light"
lang: "en" | "ua"
}
}

export type userDocument = User & Document

const userSchema = Schema.initializeSchema(User)
export const userModel = new Model<userDocument>(userSchema)
// if you want to short:
// export const userModel = new Model<userDocument>(Schema.initializeSchema(User))
```

As you can see, there area bunch of options you can pass to ```@Column``` decorator:

1. `unique`: Type is `boolean`. It will check on each new document if there is already a document with the same value
2. `type`: Type is `"string" | "number" | "boolean" | "date" | "json" | "array"`.
It will define type of column and will check it
3. `allowNull`: Type is `boolean`. If it is `true`, then column can be empty
4. `default`: Type is `any`. If column is empty, then it will be filled with default value

#### Note:

You should remember that there is no need to create `default` value if `allowNull` set to false.

After class is created, you should create type of document and initialize
schema with ```Schema.initializeSchema``` function.
Then you can create model with ```new Model``` function.

# CRUD

CRUD is provided by `Model` class itself. Create and retrieve methods return type of `Instance` class,
that can be converted to JSON with .toJSON() method.

### Create:

To create a new document, you should call `create` method of `Model` class and pass object with data.
TinyDB will check if all required fields are filled and if all fields are valid. Then it will either
throw an error or create a new document and return it.

#### Note:

By default, TinyDB will generate unique id for each document, but if you want to handle it yourself,
you can do it by passing `_id` field to `create` method. Remember: <b>it has to be an unique string.</b>

```ts
const user = userModel.create({
//_id: "1", custom id
name: "Admin",
isAdmin: true
})
user.save()
```

### Retrieve:

Retrieve methods, as said before, return `Instance` class. Below you can see all of them:

1. `find`: Returns all instances that match query

```ts
const users = userModel.find({
settings: {
theme: "dark",
}
})
```

2. `findOne`: Returns first instance that matches query

```ts
const user = userModel.findOne({
settings: {
lang: "ua",
}
})
```

3. `findById`: Returns instance with specified id

```ts
const userById = userModel.findById("1")
```

4. `findAll`: Returns all instances of model

```ts
const allUsers = userModel.findAll()
```

#### Note:

Both `find` and `findOne` methods can search by deeply nested objects. Here is
an [example]("https://github.com/Tarasikee/tinydb/blob/master/tests/findingTests.ts#L41").
But be careful because it can affect performance.

### Update:

Update methods work the exact same way as retrieve methods, but
provide second argument that is update.

1. `update`: Updates all instances that match query

```ts
const users = userModel.findAndUpdate({
settings: {
theme: "dark",
}
}, {
settings: {
theme: "light",
}
})
```

2. `findOneAndUpdate`: Updates first instance that matches query

```ts
const user = userModel.findOneAndUpdate({
settings: {
lang: "ua",
}
}, {
settings: {
lang: "en",
}
})
```

3. `findByIdAndUpdate`: Updates instance with specified id

```ts
const userById = userModel.findByIdAndUpdate("1", {
name: "John",
})
```

### Delete (Hunters):

Delete methods work also the same way as retrieve methods, but delete
instance from table and return string.

1. `hunt`: Deletes all instances that match query

```ts
userModel.hunt({
settings: {
theme: "dark",
}
})
```

2. `huntOne`: Deletes first instance that matches query

```ts
userModel.huntOne({
settings: {
lang: "ua",
}
})
```

3. `huntById`: Deletes instance with specified id

```ts
userModel.huntById("1")
```

4. `huntAll`: Deletes all instances of model

```ts
userModel.huntAll()
```

# Contributing

If you want to contribute to this project, you can do it by creating pull request or by creating issue.
1 change: 1 addition & 0 deletions database/example1/users.json
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
[{"name":"Test5","_id":"ec51f74d-e065-4add-9cdd-e2311d565506","isAdmin":false}]
1 change: 0 additions & 1 deletion deps/deps.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
export {parse, format} from "https://deno.land/[email protected]/datetime/mod.ts"
export {ensureDirSync} from "https://deno.land/[email protected]/fs/mod.ts"
export {faker} from "https://deno.land/x/[email protected]/mod.ts"
export {
Expand Down
10 changes: 9 additions & 1 deletion example/user_with_profile_abc_ex/mod.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import {Application, Context, HttpException} from "https://deno.land/x/[email protected]/mod.ts"
import {logger} from "https://deno.land/x/[email protected]/middleware/logger.ts"
import {userDocument, userModel} from "./models/user.model.ts"
import {Status} from "https://deno.land/[email protected]/http/http_status.ts"
import {userDocument, userModel} from "./models/user.model.ts"
import {ErrorHandler} from "../../src/errors/ErrorHandler.ts"

const app = new Application()
Expand Down Expand Up @@ -37,6 +37,13 @@ const findOne = (ctx: Context) => {
ctx.json(user)
}

const update = async (ctx: Context) => {
const body = await ctx.body
const {id} = ctx.params
const user = userModel.findByIdAndUpdate(id, body as userDocument)
ctx.json(user)
}

const deleteOne = (ctx: Context) => {
const {id} = ctx.params
const message = userModel.huntById(id)
Expand All @@ -46,6 +53,7 @@ const deleteOne = (ctx: Context) => {
app
.get("/users", findAll)
.get("/users/:id", findOne)
.put("/users/:id", update)
.post("/users", create)
.delete("/users/:id", deleteOne)
.start({port: 8080})
12 changes: 5 additions & 7 deletions src/classes/Instance.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import {ColumnsUtils, FileUtils, Schema} from "../mod.ts"
import {ColumnsChecks, FileUtils, Schema} from "../mod.ts"

interface InstanceOptions {
isNew: boolean
Expand Down Expand Up @@ -45,16 +45,14 @@ export class Instance<T extends { _id: string }> {
const table = this.getTable()

if (this._options.isNew) {
const _id = this._fields._id || crypto.randomUUID()

new ColumnsUtils(this._schema.columns, table, this._fields)
table.push({...this._fields, _id})
this._fields._id = _id
this._fields._id = this._fields._id || crypto.randomUUID()
new ColumnsChecks(this._schema.columns, table, this._fields)
table.push(this._fields)

this.writeTable([...table])
} else {
const filteredTable = table.filter(row => row._id !== this._fields._id)
new ColumnsUtils(this._schema.columns, filteredTable, this._fields)
new ColumnsChecks(this._schema.columns, filteredTable, this._fields)
filteredTable.push(this._fields)

this.writeTable([...filteredTable])
Expand Down
23 changes: 23 additions & 0 deletions src/classes/Model.ts
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,29 @@ export class Model<T extends { _id: string }> {
.map(row => new Instance<T>(this.schema, row, {isNew: false}))
}

//Updaters
public findByIdAndUpdate(_id: string, args: Partial<T>) {
const instance = this.findById(_id)
instance.fields = {...instance.fields, ...args}
instance.save()
return instance
}

public findAndUpdate(args: Partial<T>, update: Partial<T>) {
this.find(args).map(instance => {
instance.fields = {...instance.fields, ...update}
instance.save()
})
return "Successful update!"
}

public findOneAndUpdate(args: Partial<T>, update: Partial<T>) {
const instance = this.findOne(args)
instance.fields = {...instance.fields, ...update}
instance.save()
return instance
}

// Hunters
public huntById(_id: string) {
this.findById(_id).delete()
Expand Down
4 changes: 3 additions & 1 deletion src/classes/Schema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,8 @@ export class Schema {
.filter(key => key[0] !== "_")
.map(key => ({name: key, options: getFormat(instance, key)}))

return new Schema(instance["_tableName"], instance["_dir_url"], options)
return new Schema(instance["_tableName"], instance["_dir_url"], [...options, {
name: "_id", options: {type: "string", unique: true}
}])
}
}
Loading

0 comments on commit 0c63ed0

Please sign in to comment.