Skip to content

Commit

Permalink
feat: add Orchid ORM driver (#17)
Browse files Browse the repository at this point in the history
* feat: add orchid driver

* docs: add Orchid driver docs

* style: fix lint

* chore: add orchid-orm to peerDependencies

* fix: OrchidAdapter support create table
  • Loading branch information
bingtsingw committed May 15, 2024
1 parent 1822312 commit 695cd71
Show file tree
Hide file tree
Showing 8 changed files with 225 additions and 3 deletions.
23 changes: 22 additions & 1 deletion docs/content/docs/cache_drivers.md
Original file line number Diff line number Diff line change
Expand Up @@ -161,7 +161,7 @@ We offer several drivers to use a database as a cache. The database store should

:::note

Note that you can easily create your own adapter by implementing the `DatabaseAdapter` interface if you are using another library not supported by Bentocache. See the [documentation](/docs/advanced/custom-adapters) for more details.
Note that you can easily create your own adapter by implementing the `DatabaseAdapter` interface if you are using another library not supported by Bentocache. See the [documentation](/docs/custom-cache-driver) for more details.

:::

Expand Down Expand Up @@ -221,3 +221,24 @@ const bento = new BentoCache({
}
})
```

### Orchid ORM

You must provide a Orchid ORM instance to use the Orchid driver. Feel free to check the [Orchid ORM documentation](https://orchid-orm.netlify.app/) for more details about the configuration. Orchid support the following databases : PostgreSQL.

You will need to install `orchid-orm` to use this driver.

```ts
import { createDb } from 'orchid-orm'
import { BentoCache, bentostore } from 'bentocache'
import { orchidDriver } from 'bentocache/drivers/orchid'

export const db = createDb({ databaseURL: `postgresql://${DB_USER}:${DB_PASS}@${DB_HOST}:${DB_PORT}/${DB_NAME}` })

export const bento = new BentoCache({
default: 'cache',
stores: {
cache: bentostore().useL2Layer(orchidDriver({ connection: db }))
}
})
```
2 changes: 1 addition & 1 deletion docs/content/docs/extend/custom_cache_driver.md
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ interface L2CacheDriver {

Similarly, the `L1CacheDriver` interface is the same, except that it is not async.

So this should be quite easy to implement. Feel free to take a lot at [the existings drivers](https://github.com/Julien-R44/bentocache/tree/develop/drivers) implementations for inspiration.
So this should be quite easy to implement. Feel free to take a lot at [the existing drivers](https://github.com/Julien-R44/bentocache/tree/main/packages/bentocache/src/drivers) implementations for inspiration.

Also note that your driver will receive two additional parameters in the constructor : `ttl` and `prefix`. These parameters are common to every drivers and their purpose is explained in the [options](../options.md) page.

Expand Down
8 changes: 7 additions & 1 deletion packages/bentocache/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
"./drivers/database": "./build/src/drivers/database/database.js",
"./drivers/knex": "./build/src/drivers/database/adapters/knex.js",
"./drivers/kysely": "./build/src/drivers/database/adapters/kysely.js",
"./drivers/orchid": "./build/src/drivers/database/adapters/orchid.js",
"./types": "./build/src/types/main.js",
"./plugins/*": "./build/plugins/*.js",
"./test_suite": "./build/src/test_suite.js"
Expand Down Expand Up @@ -50,7 +51,8 @@
"@aws-sdk/client-dynamodb": "^3.438.0",
"ioredis": "^5.3.2",
"knex": "^3.0.1",
"kysely": "^0.27.3"
"kysely": "^0.27.3",
"orchid-orm": "^1.24.0"
},
"peerDependenciesMeta": {
"@aws-sdk/client-dynamodb": {
Expand All @@ -64,6 +66,9 @@
},
"kysely": {
"optional": true
},
"orchid-orm": {
"optional": true
}
},
"dependencies": {
Expand Down Expand Up @@ -93,6 +98,7 @@
"knex": "^3.1.0",
"kysely": "^0.27.3",
"mysql2": "^3.9.3",
"orchid-orm": "^1.24.0",
"p-event": "^6.0.1",
"pg": "^8.11.5",
"pino": "^8.19.0",
Expand Down
105 changes: 105 additions & 0 deletions packages/bentocache/src/drivers/database/adapters/orchid.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
import type { DbResult, DefaultColumnTypes, DefaultSchemaConfig } from 'orchid-orm'

import { DatabaseDriver } from '../database.js'
import type { CreateDriverResult, DatabaseAdapter, OrchidConfig } from '../../../types/main.js'

/**
* Create a orchid driver
*/
export function orchidDriver(options: OrchidConfig): CreateDriverResult<DatabaseDriver> {
return {
options,
factory: (config: OrchidConfig) => {
const adapter = new OrchidAdapter(config)
return new DatabaseDriver(adapter, config)
},
}
}

/**
* Orchid adapter for the DatabaseDriver
*/
export class OrchidAdapter implements DatabaseAdapter {
#connection: DbResult<DefaultColumnTypes<DefaultSchemaConfig>>
#tableName!: string

constructor(config: OrchidConfig) {
this.#connection = config.connection
}

private getTable() {
return this.#connection(this.#tableName, (t) => ({
key: t.varchar().primaryKey(),
value: t.varchar(),
expires_at: t
.timestampNoTZ()
.encode((value: Date) => value)
.parse((v: any): number => (v ? new Date(v).valueOf() : v))
.nullable(),
}))
}

setTableName(tableName: string): void {
this.#tableName = tableName
}

async get(key: string): Promise<{ value: string; expiresAt: number | null } | undefined> {
const result = await this.getTable().findByOptional({ key }).select('value', 'expires_at')

if (!result) return

return { value: result.value, expiresAt: result.expires_at }
}

async delete(key: string): Promise<boolean> {
const count = await this.getTable().where({ key }).delete()
return count > 0
}

async deleteMany(keys: string[]): Promise<number> {
return await this.getTable().whereIn('key', keys).delete()
}

async disconnect(): Promise<void> {
await this.#connection.close()
}

async createTableIfNotExists(): Promise<void> {
await this.#connection.adapter.pool.query(`
CREATE TABLE IF NOT EXISTS "public"."${this.#tableName}" (
"key" varchar NOT NULL,
"value" text NOT NULL,
"expires_at" timestamp,
PRIMARY KEY ("key")
);
`)
}

async pruneExpiredEntries(): Promise<void> {
await this.getTable()
.where({ expires_at: { lt: new Date() } })
.delete()
}

async clear(prefix: string): Promise<void> {
await this.getTable()
.where({ key: { startsWith: prefix } })
.delete()
}

async set(row: { key: string; value: any; expiresAt: Date | null }): Promise<void> {
await this.getTable()
.findBy({ key: row.key })
.upsert({
create: {
key: row.key,
value: row.value,
expires_at: row.expiresAt,
},
update: {
value: row.value,
expires_at: row.expiresAt,
},
})
}
}
11 changes: 11 additions & 0 deletions packages/bentocache/src/types/options/drivers_options.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import type { Knex } from 'knex'
import type { Kysely } from 'kysely'
import type { DynamoDBClientConfig } from '@aws-sdk/client-dynamodb'
import type { Redis as IoRedis, RedisOptions as IoRedisOptions } from 'ioredis'
import type { DbResult, DefaultColumnTypes, DefaultSchemaConfig } from 'orchid-orm'

import type { Duration } from '../helpers.js'

Expand Down Expand Up @@ -142,3 +143,13 @@ export interface KyselyConfig extends DatabaseConfig {
*/
connection: Kysely<any>
}

/**
* Configuration accepted by the Orchid ORM adapter
*/
export interface OrchidConfig extends DatabaseConfig {
/**
* The Orchid ORM instance
*/
connection: DbResult<DefaultColumnTypes<DefaultSchemaConfig>>
}
8 changes: 8 additions & 0 deletions packages/bentocache/tests/drivers/orchid/helpers.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import type { OrchidConfig } from '../../../src/types/main.js'
import { DatabaseDriver } from '../../../src/drivers/database/database.js'
import { OrchidAdapter } from '../../../src/drivers/database/adapters/orchid.js'

export function createOrchidStore(options: OrchidConfig) {
const adapter = new OrchidAdapter(options)
return new DatabaseDriver(adapter, options)
}
18 changes: 18 additions & 0 deletions packages/bentocache/tests/drivers/orchid/postgres.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import { test } from '@japa/runner'
import { createDb } from 'orchid-orm'

import { createOrchidStore } from './helpers.js'
import { registerCacheDriverTestSuite } from '../../../src/test_suite.js'

test.group('Orchid | Postgres driver', (group) => {
registerCacheDriverTestSuite({
test,
group,
supportsMilliseconds: false,
createDriver: (options) => {
const db = createDb({ user: 'postgres', password: 'postgres' })

return createOrchidStore({ connection: db, prefix: 'japa', ...options })
},
})
})
53 changes: 53 additions & 0 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

0 comments on commit 695cd71

Please sign in to comment.