Skip to content

Commit

Permalink
✨ Add filtering with queries
Browse files Browse the repository at this point in the history
  • Loading branch information
BetaHuhn committed Sep 14, 2021
1 parent 393e312 commit ba49577
Show file tree
Hide file tree
Showing 5 changed files with 320 additions and 49 deletions.
98 changes: 92 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -92,10 +92,15 @@ See below for a more detailed guide.

### Defining a schema

Before you can properly use the other methods you need to define a schema for your Base. It specifies what values to expect and in what format. You can set a property to required (`false` by default) or specify a default value:

```ts
const schema = new DetaOrm.Schema({
name: 'string',
age: 'number',
age: {
type: 'number',
required: true
},
hungry: {
type: 'boolean',
default: false
Expand All @@ -105,12 +110,16 @@ const schema = new DetaOrm.Schema({

### Creating a Base

Creating a new Base is similar to how you would do it with the regular [Deta Base SDK](https://docs.deta.sh/docs/base/sdk) except that it also accepts a schema. The schema can either be a instance of the Schema class or an object defining a schema (like above):

```ts
const Base = new DetaOrm.Base('name', schema)
const Base = new DetaOrm.Base('name', schema, options)
```

### Creating a new document

Creating a new document/item in your Base is very easy. Just call the `.create()` method of your Base instance and pass it some new data. Make sure the data matches your schema, else it will throw and error telling you what's wrong.

```ts
const document = Base.create({
name: 'Maxi',
Expand All @@ -119,13 +128,17 @@ const document = Base.create({
})
```

> Creating a document doesn't save it to Deta Base automatically. It only exists locally until you call `.save()`
### Saving a document

To save a document to your Deta Base, call `.save()`:

```ts
await document.save()
```

You can also create and save a document in one go:
You can also create and save a document in one go by calling `.save()` on your Base instance instead of `.create()`:

```ts
const document = await Base.save({
Expand All @@ -137,19 +150,23 @@ const document = await Base.save({

### Retrieving documents

There are multiple ways to retrieve documents from your Base:

### Multiple documents

```ts
const documents = await Base.find(query)
```

The query should be a JavaScript object specifing attributes to filter for defined in the schema.
The query should be a JavaScript object specifing attributes to filter for defined in the schema. See [Filtering](#Filtering) below.

### Retrieving a single document

```ts
const document = await Base.findOne(query)
```

The query should be a JavaScript object specifing attributes to filter for defined in the schema.
The query should be a JavaScript object specifing attributes to filter for defined in the schema. See [Filtering](#Filtering) below.

### Updating a document

Expand Down Expand Up @@ -219,6 +236,75 @@ const Kitten = new DetaOrm.Base('Kitten', {
})
```

### Filtering

When getting a document from a base with `.find()` and `.findOne()` you can specify a query object or list of queries to filter the documents with. In the object you can select paths from your schema and define filters and queries to match it against:

```js
const Kitten = new DetaOrm.Base('Kitten', {
name: 'string',
cuteness: 'number'
})

const garfield = await Kitten.findOne({
name: 'Garfield' // Name equals value
})

const cutest = await Kitten.find({
name: {
$con: 'e' // All kittens with the letter e in their name
},
cuteness: {
$gt: 8 // All kittens with a cuteness greater than 8
}
})
```

This library supports all of Deta Bases [queries](https://docs.deta.sh/docs/base/sdk#queries):

| Name | Property | Description |
|-----------------------|----------|--------------------------------------------------|
| Equal | $eq | Equal to value |
| Not Equal | $ne | Not equal to value |
| Less Than | $lt | Less than number |
| Greater Than | $gt | Greater than number |
| Less Than Or Equal | $lte | Less than or equal to number |
| Greater Than Or Equal | $gte | Greater than or equal to number |
| Prefix | $pfx | Prefix of value |
| Range | $rg | Range of numbers i.e. [22,30] |
| Contains | $con | String contains substring or array contains item |
| Not Contains | $ncon | Opposite of contains |

They can be combined together in one query or used in different queries. All queries in an object are interpreted as `AND`:

```js
await Base.find({
name: {
$con: 'repo',
$pfx: 'gh'
}
})
```

> Here: name starts with the prefix `gh` AND contains the substring `repo`
Queries in separate objects are treated as `OR`:

```js
await Base.find([
{
name: 'Hello'
},
{
cuteness: {
$gt: 8
}
}
])
```

> Here: The name must be `Hello` OR the cuteness greater than `8`
## ⚙️ Options

You can optionally pass a options object to the the Base contructor:
Expand Down Expand Up @@ -255,7 +341,7 @@ const Base = new DetaOrm.Base(name, schema, { db })

## 💡 Planned features

- Sorting and [filtering](https://docs.deta.sh/docs/base/sdk#queries)
- Offline mode
- Add custom methods/actions to the Base

## 💻 Development
Expand Down
46 changes: 46 additions & 0 deletions example/filter.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
import * as DetaOrm from '../src/index' // import * as DetaOrm from 'deta-base-orm'

const run = async () => {

// ✨ Define a schema for the kittens
type KittenSchema = {
name: string,
cuteness?: number
}

const schema = new DetaOrm.Schema<KittenSchema>({
name: 'string',
cuteness: {
type: 'number',
default: 0
}
})

// 🛌 Create our Kitten base
const Kitten = new DetaOrm.Base('Kitten', schema)

// 🐱 Create a new kitten
const line = await Kitten.save({
name: 'Line',
cuteness: 8
})
console.log(line)

// 🔍 Filter kittens by their name
const withE = await Kitten.find({
name: {
$con: 'e' // All kittens with the letter e in their name
}
})
console.log(withE) // [{name: 'Line', cuteness: 8}]

// 🧵 Filter kittens by their cuteness
const higherThan5 = await Kitten.find({
cuteness: {
$gt: 5 // All kittens with a cuteness greater than 5
}
})
console.log(higherThan5) // [{name: 'Line', cuteness: 8}]
}

run()
51 changes: 43 additions & 8 deletions lib/index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,10 +31,41 @@ export declare type BaseDocument<Schema> = Document<Schema> & Schema & {
* */
createdAt?: number;
};
/**
* Operators to use in a query
*/
export declare type QueryOperators = {
/** Equal to */
$eq?: BaseValueType;
/** Not equal to */
$ne?: BaseValueType;
/** Less Than */
$lt?: number;
/** Greater than */
$gt?: number;
/** Less than or equal */
$lte?: number;
/** Greater than or equal */
$gte?: number;
/** Prefix */
$pfx?: string;
/** Range */
$rg?: number[];
/** Contains */
$con?: string;
/** Not contains */
$ncon?: string;
};
/**
* Add operators to each property of a Schema
*/
declare type SchemaWithOperators<SchemaType> = {
[Property in keyof SchemaType]: SchemaType[Property] | QueryOperators;
};
/**
* Query to use for finding documents
*/
export declare type Query<Schema> = Partial<Schema | {
export declare type Query<SchemaType> = Partial<SchemaType | {
/**
* The unique key of the document
*
Expand All @@ -48,7 +79,7 @@ export declare type Query<Schema> = Partial<Schema | {
* Note: Only set when timestamp option is true
* */
createdAt?: number;
}>;
} | SchemaWithOperators<SchemaType>>;
interface ParsedOptions {
ascending: boolean;
timestamp: boolean;
Expand Down Expand Up @@ -115,29 +146,33 @@ export declare class Base<SchemaType> {
* @returns {BaseDocument} Document
*/
save(data: SchemaType): Promise<BaseDocument<SchemaType>>;
_parseQuery(queryObj: Query<SchemaType>): any;
/**
* Wrapper around the Deta Base SDK fetch method
*
* Automatically gets all items until the limit or since the last item
* @internal
*/
_fetch(query?: any, limit?: number, last?: string): Promise<any[]>;
_fetch(query?: any, limit?: number, last?: string): Promise<{
items: any[];
last?: string;
}>;
/**
* Find all documents matching the query.
*
* Use limit and last to paginate the result.
*
* @param query A query object
* @param query A query object or array of query objects
* @returns Array of Documents
*/
find(query?: Query<SchemaType>, limit?: number, last?: string): Promise<BaseDocument<SchemaType>[]>;
find(query?: Query<SchemaType> | Query<SchemaType>[], limit?: number, last?: string): Promise<BaseDocument<SchemaType>[]>;
/**
* Find a single document matching the query.
*
* @param query A query object
* @returns Document
*/
findOne(query?: Query<SchemaType>): Promise<BaseDocument<SchemaType> | undefined>;
findOne(query?: Query<SchemaType> | Query<SchemaType>[]): Promise<BaseDocument<SchemaType> | undefined>;
/**
* Find a single document by its key
*
Expand All @@ -152,7 +187,7 @@ export declare class Base<SchemaType> {
* @param data The data to update
* @returns Document
*/
findOneAndUpdate(query: Query<SchemaType> | undefined, data: Partial<SchemaType>): Promise<BaseDocument<SchemaType>>;
findOneAndUpdate(query: Query<SchemaType> | Query<SchemaType>[] | undefined, data: Partial<SchemaType>): Promise<BaseDocument<SchemaType>>;
/**
* Find a single document by its key and update it with the provided data.
*
Expand All @@ -172,7 +207,7 @@ export declare class Base<SchemaType> {
*
* @param query A query object
*/
findOneAndDelete(query?: Query<SchemaType>): Promise<void>;
findOneAndDelete(query?: Query<SchemaType> | Query<SchemaType>[]): Promise<void>;
}
/**
* Represents a Document with all of its data and methods
Expand Down
Loading

0 comments on commit ba49577

Please sign in to comment.