Skip to content

Commit

Permalink
feat!: new persistence api (#157)
Browse files Browse the repository at this point in the history
* feat: new persistence api

* add: tests

* add: tests

* chore: code optimizations

* chore: clean tests

* docs: update readme

* fix: typescript

* from feedback
  • Loading branch information
Eomm authored Sep 4, 2023
1 parent f2f21bd commit 4196074
Show file tree
Hide file tree
Showing 10 changed files with 440 additions and 175 deletions.
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -147,3 +147,5 @@ orama.json
package-lock.json
pnpm-lock.yaml
yarn.lock
*.msp
orama_[0-9]*.json
165 changes: 128 additions & 37 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,91 +2,182 @@

![Continuous Integration](https://github.com/mateonunez/fastify-orama/workflows/ci/badge.svg)

Orama plugin for Fastify.
[Orama](https://oramasearch.com/) plugin for Fastify.

## Installation

```
npm install fastify-orama
```

****


## Usage

This plugin adds the `orama` decorator to your Fastify application.

The `options` object is passed directly to the `Orama.create` constructor,
so it supports [all the options that Orama supports](https://docs.oramasearch.com/usage/create).

### Example

```js
import Fastify from 'fastify'
import FastifyOrama from 'fastify-orama'
import fastifyOrama from 'fastify-orama'

const app = Fastify()

await app.register(FastifyOrama, {
app.register(fastifyOrama, {
schema: {
quote: "string",
author: "string"
}
})

app.get('/quotes/:query', async function (req, reply) {
try {
const { params: { query } } = req
app.get('/quotes/:query', async function handler (req, reply) {
const { params: { query } } = req

const search = await app.orama.search({
term: query,
properties: ["quote"]
})
const search = await app.orama.search({
term: query,
properties: ["quote"]
})

return { quotes: search.hits }
} catch (err) {
return err;
}
return { quotes: search.hits }
})

app.listen(3000)
app.listen({ port: 3000 })
```


## Usage with data persistence

This plugin implements [@oramasearch/plugin-data-persistence](https://github.com/oramasearch/plugin-data-persistence) to allow users to `load` or `save` database instances.
This plugin supports data persistence out of the box.
You need to pass the `persistence` option to the plugin registration!

This plugin uses [`@oramasearch/plugin-data-persistence`](https://docs.oramasearch.com/plugins/plugin-data-persistence)
under the hood to allow users to `load` or `save` database instances.

Turning on the `persistence` option will add the `fastify.orama.save()` method to your Fastify application.
You must call this method to save the database instance to the persistence layer, otherwise your data will be lost.

### PersistenceInFile

This plugin comes with a `PersistenceInFile` class that allows you to persist your data in a file.
If the file exists, the plugin will load the data from it when the plugin is registered.

Its constructor accepts the following options:

- `filePath`: The path to the file where the data will be persisted. Default: `./orama.msp`
- `format`: The format of the file where the data will be persisted. Default: `binary`
- `mustExistOnStart`: Whether the file must exist when the plugin is registered or not. Default: `false`. Note that if the file does not exist, you must specify the `schema` option in the plugin registration.

### Example

```js
import Fastify from 'fastify'
import FastifyOrama from 'fastify-orama'
import { fastifyOrama, PersistenceInFile } from 'fastify-orama'

const app = Fastify()

// The database must exists to load it in your Fastify application
await app.register(FastifyOrama, {
persistence: true,
persistency: {
name: './quotes.json',
format: 'json'
}
app.register(fastifyOrama, {
schema: {
quote: "string",
author: "string"
},
persistence: new PersistenceInFile({
filePath: './db.json', // Default: './orama.msp'
format: 'json', // Default: 'binary',
mustExistOnStart: true // Default: false
})
})

app.post('/quotes', async function (req, reply) {
try {
const { body: { author, quote } } = req
const { body: { author, quote } } = req

await fastify.orama.insert({
author,
quote
})
await fastify.orama.insert({
author,
quote
})

await fastify.orama.save()
return { success: true }
})

return { success: true }
} catch (err) {
return err;
}
app.addHook('onClose', async function save (app) {
const path = await app.orama.save()
app.log.info(`Database saved to ${path}`)
})

app.listen({ port: 3000 })
```

### PersistenceInMemory

This plugin comes with a `PersistenceInMemory` class that allows you to persist your data in memory.
This adapter may be useful for testing purposes, when you need to share the same database instance between multiple tests.

Its constructor accepts the following options:

- `jsonIndex`: The stringified JSON representation of the database instance. Default: `null`

```js
import Fastify from 'fastify'
import { fastifyOrama, PersistenceInMemory } from 'fastify-orama'

const appOne = Fastify()

await appOne.register(fastifyOrama, {
schema: { author: 'string', quote: 'string' },
persistence: new PersistenceInMemory()
})

// Do some stuff with the database
await appOne.orama.insert({
quote: 'Orama and Fastify are awesome together.',
author: 'Mateo Nunez'
})

const inMemoryDb = await appOne.orama.save()

// Close the Fastify application
await appOne.close()


// Create a new Fastify test case
const appTwo = Fastify()
await appTwo.register(fastifyOrama, {
persistence: new PersistenceInMemory({
jsonIndex: inMemoryDb // Pass the in-memory database to the new Fastify application
})
})

app.listen(3000)
// The database is persisted between Fastify applications
const results = await appTwo.orama.search({ term: 'Mateo Nunez' })
```

### Custom persistence

If you need a custom persistence layer, you can implement your own persistence class.
To do so, you need to implement the following methods:

```js
const customPersistance = {
restore: async function restore () {
// Restore the database instance from the persistence layer
// Return the database instance or null if it does not exist
},

persist: async function persist (db) {
// Persist the database instance to the persistence layer
// Whatever this method returns will be passed to the `app.orama.save()` method
}

await fastify.register(fastifyOrama, {
schema,
persistence: customPersistance
})
```
## License
FastifyOrama is licensed under the [MIT](LICENSE) license.
fastifyOrama is licensed under the [MIT](LICENSE) license.
44 changes: 36 additions & 8 deletions index.d.ts
Original file line number Diff line number Diff line change
@@ -1,20 +1,48 @@
import type { FastifyPluginCallback } from 'fastify'
import type { Document, Orama, Results, SearchParams } from '@orama/orama'
import type { Document, Orama, ProvidedTypes, Results, SearchParams, create } from '@orama/orama'

type OramaInstance = {
schema: Orama['schema'],
interface OramaPersistence {
restore: () => Promise<ReturnType<typeof create> | null>
persist: (data: ReturnType<typeof create>) => Promise<any>
}

declare const FastifyOrama: FastifyPluginCallback<OramaInstance>
declare class PersistenceInMemory implements OramaPersistence {
constructor(options?: {
jsonIndex?: string,
})
restore: () => Promise<Promise<Orama<ProvidedTypes>> | null>
persist: (data: Promise<Orama<ProvidedTypes>>) => Promise<string>
}

declare class PersistenceInFile implements OramaPersistence {
constructor(options?: {
filePath?: string,
format?: string,
mustExistOnStart?: boolean
})
restore: () => Promise<Promise<Orama<ProvidedTypes>> | null>
persist: (data: Promise<Orama<ProvidedTypes>>) => Promise<string>
}

type OramaPluginOptions = {
persistence?: OramaPersistence
} & Partial<Parameters<typeof create>[0]>

declare const fastifyOrama: FastifyPluginCallback<OramaPluginOptions>

declare module 'fastify' {
interface FastifyInstance {
orama: OramaInstance & {
orama: {
insert: (document: Document) => Promise<string>,
search: (params: SearchParams) => Promise<Results>
search: (params: SearchParams) => Promise<Results>,
save?: () => Promise<any>,
}
}
}

export default FastifyOrama
export { FastifyOrama }
export { fastifyOrama as default }
export {
fastifyOrama,
PersistenceInMemory,
PersistenceInFile
}
64 changes: 31 additions & 33 deletions index.js
Original file line number Diff line number Diff line change
@@ -1,55 +1,53 @@
import fp from 'fastify-plugin'
import { create, insert, search } from '@orama/orama'
import path from 'path'
import { existsSync } from 'fs'
import { restoreFromFile, persistToFile } from '@orama/plugin-data-persistence/server'
import { create, insert, search } from '@orama/orama' // todo we are limiting the api to the server side

async function FastifyOrama (fastify, options) {
const {
schema,
defaultLanguage = 'english',
stemming = true,
persistence = false
} = options
import PersistenceInMemory from './lib/persistence/in-memory.js'
import PersistenceInFile from './lib/persistence/in-file.js'

async function fastifyOrama (fastify, options) {
if (fastify.orama) {
throw new Error('fastify-orama is already registered')
}

const {
persistence,
...oramaOptions
} = options

let db
let dbName
let dbFormat

const oramaApi = {
insert: (...args) => insert(db, ...args),
search: (...args) => search(db, ...args),
save: undefined
}

if (persistence) {
dbName = options.persistency?.name || './orama.json'
dbFormat = options.persistency?.format || 'json'
const databaseExists = existsSync(path.resolve(dbName))
db = await persistence.restore()

if (!databaseExists) {
throw new Error(`The database file ${dbName} does not exist`)
oramaApi.save = /* async */ function save () {
return persistence.persist(db)
}
}

db = await restoreFromFile(dbFormat, `./${dbName}`)
} else {
if (!schema) {
if (!db) {
if (!oramaOptions.schema) {
throw new Error('You must provide a schema to create a new database')
}

db = await create({
schema,
defaultLanguage,
stemming
})
db = await create(oramaOptions)
}

fastify.decorate('orama', {
insert: (...args) => insert(db, ...args),
search: (...args) => search(db, ...args),
save: () => persistToFile(db, dbFormat, dbName)
})
fastify.decorate('orama', oramaApi)
}

export default fp(FastifyOrama, {
export default fp(fastifyOrama, {
fastify: '4.x',
name: '@fastify/orama'
name: 'fastify-orama'
})

export {
fastifyOrama,
PersistenceInMemory,
PersistenceInFile
}
Loading

0 comments on commit 4196074

Please sign in to comment.