Skip to content

Commit

Permalink
feat(ioredis): add new package @tsed/ioredis
Browse files Browse the repository at this point in the history
Closes: #2060
  • Loading branch information
Romakita committed Aug 28, 2022
1 parent 46cd15d commit 9a41744
Show file tree
Hide file tree
Showing 27 changed files with 1,643 additions and 48 deletions.
22 changes: 18 additions & 4 deletions docs/.vuepress/config.base.js
Original file line number Diff line number Diff line change
Expand Up @@ -212,6 +212,10 @@ module.exports = ({title, description, base = "", url, apiRedirectUrl = "", them
text: "Objection.js",
link: `${base}/tutorials/objection.html`
},
{
text: "IORedis",
link: `${base}/tutorials/ioredis.html`
},
{
text: "GraphQL",
link: `${base}/tutorials/graphql.html`
Expand Down Expand Up @@ -289,12 +293,16 @@ module.exports = ({title, description, base = "", url, apiRedirectUrl = "", them
position: "right",
items: [
{
text: "v4 (obsolete)",
link: "http://v4.tsed.io"
text: "v7 (next)",
link: "http://v7.tsed.io"
},
{
text: "v5 (maintenance)",
link: "http://v5.tsed.io"
},
{
text: "v4 (obsolete)",
link: "http://v4.tsed.io"
}
]
}
Expand All @@ -311,6 +319,10 @@ module.exports = ({title, description, base = "", url, apiRedirectUrl = "", them
title: "Migration",
collapsable: true,
children: [
{
title: "Migrate to v7",
path: `https://v7.tsed.io/getting-started/migration-from-v6`
},
{
title: "Migrate from v5",
path: `${base}/getting-started/migration-from-v5`
Expand Down Expand Up @@ -381,8 +393,9 @@ module.exports = ({title, description, base = "", url, apiRedirectUrl = "", them
{title: "Stripe", path: base + "/tutorials/stripe"},
{title: "Agenda", path: base + "/tutorials/agenda"},
{title: "Terminus", path: base + "/tutorials/terminus"},
{title: "Serverless", path: base + "/tutorials/serverless"}
]
{title: "Serverless", path: base + "/tutorials/serverless"},
{title: "IORedis", path: base + "/tutorials/ioredis"}
].sort((a, b) => (a.title < b.title ? -1 : 1))
},
{
title: "Extras",
Expand Down Expand Up @@ -418,6 +431,7 @@ module.exports = ({title, description, base = "", url, apiRedirectUrl = "", them
base + "/tutorials/agenda",
base + "/tutorials/terminus",
base + "/tutorials/serverless",
base + "/tutorials/ioredis",
base + "/docs/controllers",
base + "/docs/providers",
base + "/docs/model",
Expand Down
29 changes: 29 additions & 0 deletions docs/.vuepress/public/ioredis.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
3 changes: 3 additions & 0 deletions docs/readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,9 @@ frameworks:
- title: TypeORM
href: /tutorials/typeorm.html
src: /typeorm.png
- title: IORedis
href: /tutorials/ioredis.html
src: /ioredis.svg
- title: Apollo
href: /tutorials/graphql.html#apollo
src: /apollo-graphql-compact.svg
Expand Down
249 changes: 249 additions & 0 deletions docs/tutorials/ioredis.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,249 @@
---
meta:
- name: description
content: Use IORedis with Express, TypeScript and Ts.ED.
- name: keywords
content: ts.ed express typescript ioredis node.js javascript decorators
projects:
---

# IORedis

<Banner src="/ioredis.svg" height="200" href="https://github.com/luin/ioredis"></Banner>

## Features

Currently, `@tsed/ioredis` allows you:

- Configure one or more Redis database connections via the `@Configuration` configuration.
- Share redis connection with `@tsed/platform-cache`.
- Support classic Redis connection and Cluster connection.
- Inject connection to another service.
- Mock connection for unit/integration test.

## Installation

```shell
npm install --save @tsed/ioredis ioredis ioredis-mock
```

::: tip Note

Minimal supported ioredis version is v5+

:::

## Create connection

Create a new `RedisConnection.ts` file in your project:

```typescript
import Redis from "ioredis";
import {registerConnectionProvider} from "@tsed/ioredis";

export const REDIS_CONNECTION = Symbol.for("REDIS_CONNECTION");
export type REDIS_CONNECTION = Redis;

registerConnectionProvider({
provide: REDIS_CONNECTION,
name: "default"
});
```

::: tip Note

`registerConnectionProvider` create automatically an injectable `RedisConnection`.

:::

Then, edit your `Server.ts`:

```typescript
import {Configuration} from "@tsed/di";
import "@tsed/platform-cache"; // add this module if you want to use cache
import "@tsed/ioredis";

@Configuration({
ioredis: [
{
name: "default",
// share the redis connection with @tsed/platform-cache
cache: true

// redis options
// host: "localhost",
// port: 6379,
// username: "...",
// password: "...",
// tls: false,
// db: 0
}
],
// cache options
cache: {
ttl: 300
}
})
class MyModule {}
```

And finally, use the connection in your services:

```typescript
import {Injectable} from "@tsed/di";

@Injectable()
export class ClientRepository {
@Inject(REDIS_CONNECTION)
protected connection: REDIS_CONNECTION; // ioredis instance

async keys() {
return this.connection.keys("clients:*");
}
}
```

> See [ioredis documentation](https://github.com/luin/ioredis) for more details.
## Cluster configuration

```typescript
import {Configuration} from "@tsed/di";
import "@tsed/platform-cache"; // add this module if you want to use cache
import "@tsed/ioredis";

@Configuration({
ioredis: [
{
name: "default",
// share the redis connection with @tsed/platform-cache
cache: true,
// cluster options
nodes: ["..."],
// other cluster options
scaleReads: "all",
maxRedirections: 16,
retryDelayOnTryAgain: 100,
retryDelayOnFailover: 200,
retryDelayOnClusterDown: 1000,
slotsRefreshTimeout: 15000,
slotsRefreshInterval: 20000,
enableOfflineQueue: true,
enableReadyCheck: true,
redisOptions: {
noDelay: true,
connectTimeout: 15000,
autoResendUnfulfilledCommands: true,
maxRetriesPerRequest: 5,
enableAutoPipelining: true,
autoPipeliningIgnoredCommands: ["scan"]
}
//
}
],
// cache options
cache: {
ttl: 300
}
})
class MyModule {}
```

## Testing

Ts.ED provides a utility that allows you to test a service that consumes a Redis connection.
This use relies on the awesome [ioredis-mock](https://www.npmjs.com/package/ioredis-mock) module.

Here is a class that consume a redis connection:

```typescript
import {v4} from "uuid";
import {Injectable} from "@tsed/di";
import {serialize, deserialize} from "@tsed/json-mapper";
import {REDIS_CONNECTION} from "./RedisConnection";
import {ClientModel} from "./ClientModel";

@Injectable()
export class ClientRepository {
@Inject(REDIS_CONNECTION)
protected connection: REDIS_CONNECTION; // ioredis instance

async get(id: string) {
const raw = await this.connection.get("clients:" + id);

if (!raw) {
return undefined;
}

return deserialize(JSON.parse(raw), {type: ClientModel});
}

async save(client: ClientModel) {
client.id = client.id || v4();

this.connection.set("clients:" + client.id, JSON.stringify(serialize(client)));

return client;
}
}
```

The ClientModel:

```typescript
import {Schema} from "@tsed/schema";

export class ClientModel {
@Property()
id: string;

@Property()
name: string;
}
```

And his test:

```typescript
import {ClientRepository} from "./ClientRepository";
import {REDIS_CONNECTION} from "./RedisConnection";
import {ClientModel} from "./ClientModel";

describe("IORedisTest", () => {
beforeEach(() => IORedisTest.create()); // create a new sandbox with ioredis-mock connection
afterEach(() => IORedisTest.reset());

it("should return nothing", async () => {
const service = IORedisTest.get<MyRepository>(MyRepository);

const client = await service.get("uid");

expect(client).toEqual(undefined);
});

it("should return all keys", async () => {
const service = IORedisTest.get<MyRepository>(MyRepository);
const client = new ClientModel();
client.name = "name";

const newClient = await service.save(client);

expect(newClient.id).toBeInstanceOf(String);
expect(newClient.name).toEqual("name");

const clientFound = await service.get(newClient.id);

expect(clientFound).toBeInstanceOf(ClientModel);
expect(clientFound.id).toEqual(newClient.id);
expect(clientFound.name).toEqual("name");
});
});
```

## Author

<GithubContributors :users="['Romakita']"/>

## Maintainers

<GithubContributors :users="['Romakita']"/>
4 changes: 2 additions & 2 deletions docs/tutorials/mongoose.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,10 +22,10 @@ This tutorial shows you how you can use mongoose package with Ts.ED.

Currently, [`@tsed/mongoose`](https://www.npmjs.com/package/@tsed/mongoose) allows you to:

- Configure one or more MongoDB database connections via the `@ServerSettings` configuration.
- Configure one or more MongoDB database connections via the `@Configuration` configuration.
All databases will be initialized when the server starts during the server's `OnInit` phase.
- Declare a Model from a class with annotation,
- Declare inhertitated models in a single collection via `@DiscriminatorKey`
- Declare inherited models in a single collection via `@DiscriminatorKey`
- Add a plugin, PreHook method and PostHook on your model
- Inject a Model to a Service, Controller, Middleware, etc.
- Create and manage multiple connections
Expand Down
13 changes: 12 additions & 1 deletion packages/di/src/services/DITest.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import {Env, getValue} from "@tsed/core";
import {Env, getValue, setValue} from "@tsed/core";
import {$log} from "@tsed/logger";
import {createContainer} from "../utils/createContainer";
import {LocalsContainer} from "../domain/LocalsContainer";
Expand All @@ -24,6 +24,12 @@ export interface DITestOptions extends Partial<TsED.Configuration> {
export class DITest {
protected static _injector: InjectorService | null = null;

static options: DITestOptions = {};

static set(key: string, value: any) {
setValue(DITest.options, key, value);
}

static get injector(): InjectorService {
if (DITest._injector) {
return DITest._injector!;
Expand All @@ -49,6 +55,11 @@ export class DITest {
}

static async create(settings: DITestOptions = {}) {
settings = {
...DITest.options,
...settings
};

DITest.injector = DITest.createInjector(settings);

await DITest.createContainer(settings);
Expand Down
Loading

0 comments on commit 9a41744

Please sign in to comment.