@tsed/redis 8.29.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,95 @@
1
+ import { createClient, createCluster } from "redis";
2
+ const getVal = (value) => JSON.stringify(value) || '"undefined"';
3
+ export class RedisStore {
4
+ constructor(options) {
5
+ this.name = "redis";
6
+ options = options || {};
7
+ this.redisCache =
8
+ options.redisInstance ||
9
+ ("clusterConfig" in options
10
+ ? createCluster({ rootNodes: options.clusterConfig.nodes, ...(options.clusterConfig.options || {}) })
11
+ : createClient(options));
12
+ this.redisCache.connect?.().catch(() => undefined);
13
+ this.storeArgs = options;
14
+ this.isCacheable = this.isCacheableValue =
15
+ this.storeArgs.isCacheable || this.storeArgs.isCacheableValue || ((value) => value !== undefined && value !== null);
16
+ }
17
+ get client() {
18
+ return this.redisCache;
19
+ }
20
+ getClient() {
21
+ return this.redisCache;
22
+ }
23
+ async set(key, value, ttl) {
24
+ this.assertCacheable(value);
25
+ ttl = this.getTtl(ttl);
26
+ const val = getVal(value);
27
+ if (ttl) {
28
+ if (this.redisCache.setEx) {
29
+ await this.redisCache.setEx(key, ttl, val);
30
+ }
31
+ else {
32
+ await this.redisCache.set(key, val, { EX: ttl });
33
+ }
34
+ return;
35
+ }
36
+ await this.redisCache.set(key, val);
37
+ }
38
+ async get(key, options) {
39
+ const val = await this.redisCache.get(key);
40
+ if (val === undefined || val === null) {
41
+ return undefined;
42
+ }
43
+ return JSON.parse(val);
44
+ }
45
+ async del(key) {
46
+ await this.redisCache.del(key);
47
+ }
48
+ async mset(args, ttl) {
49
+ ttl = this.getTtl(ttl);
50
+ if (ttl) {
51
+ const multi = this.redisCache.multi();
52
+ for (const [key, value] of args) {
53
+ this.assertCacheable(value);
54
+ if (multi.setEx) {
55
+ multi.setEx(key, ttl / 1000, getVal(value));
56
+ }
57
+ else {
58
+ multi.set(key, getVal(value), { EX: ttl / 1000 });
59
+ }
60
+ }
61
+ await multi.exec();
62
+ }
63
+ else
64
+ await this.redisCache.mSet(args.reduce((acc, [key, value]) => {
65
+ this.assertCacheable(value);
66
+ acc[key] = getVal(value);
67
+ return acc;
68
+ }, {}));
69
+ }
70
+ async mget(...args) {
71
+ const x = await this.redisCache.mGet(args);
72
+ return x.map((x) => (x === null || x === undefined ? undefined : JSON.parse(x)));
73
+ }
74
+ async mdel(...args) {
75
+ await this.redisCache.del(...args);
76
+ }
77
+ async reset() {
78
+ await this.redisCache.flushAll();
79
+ }
80
+ keys(pattern = "*") {
81
+ return this.redisCache.keys(pattern);
82
+ }
83
+ ttl(key) {
84
+ return this.redisCache.ttl(key);
85
+ }
86
+ getTtl(ttl) {
87
+ return ttl === undefined ? this.storeArgs.ttl : ttl;
88
+ }
89
+ assertCacheable(value) {
90
+ if (!this.isCacheable(value)) {
91
+ throw new Error(`"${getVal(value)}" is not a cacheable value`);
92
+ }
93
+ }
94
+ }
95
+ export const redisStore = (config) => new RedisStore(config);
@@ -0,0 +1,2 @@
1
+ export * from "./RedisConfiguration.js";
2
+ export * from "./RedisStore.js";
@@ -0,0 +1,8 @@
1
+ /**
2
+ * @file Automatically generated by @tsed/barrels.
3
+ */
4
+ export * from "./domain/index.js";
5
+ export * from "./domain/RedisConfiguration.js";
6
+ export * from "./domain/RedisStore.js";
7
+ export * from "./utils/createConnection.js";
8
+ export * from "./utils/registerConnectionProvider.js";
@@ -0,0 +1,43 @@
1
+ import { configuration, logger } from "@tsed/di";
2
+ import { createClient, createCluster } from "redis";
3
+ import { redisStore } from "../domain/RedisStore.js";
4
+ export async function createConnection(config) {
5
+ const { name, cache, ...redisOptions } = config;
6
+ const retryStrategy = (times) => {
7
+ logger().fatal({ event: "REDIS_ERROR", message: `Redis is not responding - Retry count: ${times}` });
8
+ return 2000;
9
+ };
10
+ let connection;
11
+ const reconnectOnError = (err) => {
12
+ logger().fatal({
13
+ event: "REDIS_ERROR",
14
+ message: `Redis - Reconnect on error: ${(err && err.message) || err}`,
15
+ stack: err?.stack
16
+ });
17
+ };
18
+ if ("nodes" in redisOptions) {
19
+ const { nodes, ...clusterOptions } = redisOptions;
20
+ connection = createCluster({
21
+ rootNodes: nodes,
22
+ ...clusterOptions
23
+ });
24
+ }
25
+ else {
26
+ connection = createClient({
27
+ ...redisOptions,
28
+ socket: {
29
+ reconnectStrategy: retryStrategy,
30
+ ...redisOptions.socket
31
+ }
32
+ });
33
+ connection.on?.("error", reconnectOnError);
34
+ }
35
+ await connection.connect();
36
+ logger().info("Connected to redis database...");
37
+ if (configuration().get("cache") && cache) {
38
+ configuration().set("cache.redisInstance", connection);
39
+ configuration().set("cache.store", redisStore);
40
+ }
41
+ connection.name = name;
42
+ return connection;
43
+ }
@@ -0,0 +1,20 @@
1
+ import { constant, injectable } from "@tsed/di";
2
+ import { createConnection } from "./createConnection.js";
3
+ export const REDIS_CONNECTIONS = Symbol.for("redis:connections");
4
+ export function registerConnectionProvider({ token, name = "default" }) {
5
+ return injectable(token, {
6
+ connectionName: name
7
+ })
8
+ .type(REDIS_CONNECTIONS)
9
+ .asyncFactory(async () => {
10
+ const items = constant("redis", []);
11
+ const item = items.find((item) => item.name === name);
12
+ return item
13
+ ? createConnection({ ...item, name }).then((connection) => {
14
+ connection.name = name;
15
+ return connection;
16
+ })
17
+ : { name };
18
+ })
19
+ .token();
20
+ }
@@ -0,0 +1,20 @@
1
+ import { TokenProvider } from "@tsed/di";
2
+ import type { RedisClientOptions, RedisClientType, RedisClusterType } from "redis";
3
+ export type ClusterConfiguration = {
4
+ nodes: any[];
5
+ } & Record<string, any>;
6
+ export interface BaseRedisConfiguration {
7
+ name?: TokenProvider;
8
+ cache?: boolean;
9
+ }
10
+ export type RedisConfiguration = BaseRedisConfiguration & (RedisClientOptions | ClusterConfiguration) & {
11
+ redisInstance?: RedisClientType | RedisClusterType<any, any>;
12
+ sentinelName?: string;
13
+ };
14
+ declare global {
15
+ namespace TsED {
16
+ interface Configuration {
17
+ redis?: RedisConfiguration[];
18
+ }
19
+ }
20
+ }
@@ -0,0 +1,37 @@
1
+ import type { RedisClusterOptions } from "@redis/client";
2
+ import type { Config, FactoryConfig, Store } from "cache-manager";
3
+ import type { RedisClientType, RedisClusterType } from "redis";
4
+ export interface RedisClusterConfig {
5
+ nodes: RedisClusterOptions["rootNodes"][];
6
+ options?: Record<string, any>;
7
+ }
8
+ export type RedisStoreCtrOptions = (Record<string, any> | {
9
+ clusterConfig: RedisClusterConfig;
10
+ }) & Config & {
11
+ redisInstance?: RedisClientType | RedisClusterType<any, any>;
12
+ };
13
+ export declare class RedisStore implements Store {
14
+ name: string;
15
+ /**
16
+ * @deprecated
17
+ */
18
+ isCacheableValue: any;
19
+ isCacheable: any;
20
+ private redisCache;
21
+ private storeArgs;
22
+ constructor(options?: RedisStoreCtrOptions);
23
+ get client(): RedisClientType | RedisClusterType<any, any>;
24
+ getClient(): RedisClientType | RedisClusterType<any, any>;
25
+ set(key: string, value: any, ttl?: number): Promise<void>;
26
+ get<T = any>(key: string, options?: any): Promise<T | undefined>;
27
+ del(key: string): Promise<void>;
28
+ mset(args: [string, unknown][], ttl?: number): Promise<void>;
29
+ mget(...args: string[]): Promise<any>;
30
+ mdel(...args: string[]): Promise<void>;
31
+ reset(): Promise<void>;
32
+ keys(pattern?: string): Promise<string[]>;
33
+ ttl(key: string): Promise<number>;
34
+ private getTtl;
35
+ private assertCacheable;
36
+ }
37
+ export declare const redisStore: (config?: FactoryConfig<any>) => RedisStore;
@@ -0,0 +1,2 @@
1
+ export * from "./RedisConfiguration.js";
2
+ export * from "./RedisStore.js";
@@ -0,0 +1,8 @@
1
+ /**
2
+ * @file Automatically generated by @tsed/barrels.
3
+ */
4
+ export * from "./domain/index.js";
5
+ export * from "./domain/RedisConfiguration.js";
6
+ export * from "./domain/RedisStore.js";
7
+ export * from "./utils/createConnection.js";
8
+ export * from "./utils/registerConnectionProvider.js";
@@ -0,0 +1,3 @@
1
+ import { type RedisClientType, type RedisClusterType } from "redis";
2
+ import { RedisConfiguration } from "../domain/RedisConfiguration.js";
3
+ export declare function createConnection(config: RedisConfiguration): Promise<RedisClientType | RedisClusterType<any, any>>;
@@ -0,0 +1,15 @@
1
+ import { type FactoryTokenProvider, TokenProvider } from "@tsed/di";
2
+ import type { RedisClientType, RedisClusterType } from "redis";
3
+ export interface CreateConnectionProviderProps {
4
+ token: TokenProvider;
5
+ /**
6
+ * @deprecated use `token` instead of `provide`
7
+ */
8
+ provide?: TokenProvider;
9
+ name?: string;
10
+ }
11
+ export declare const REDIS_CONNECTIONS: unique symbol;
12
+ export type RedisConnection = (RedisClientType | RedisClusterType<any, any>) & {
13
+ name: string;
14
+ };
15
+ export declare function registerConnectionProvider({ token, name }: CreateConnectionProviderProps): FactoryTokenProvider<RedisConnection>;
package/package.json ADDED
@@ -0,0 +1,56 @@
1
+ {
2
+ "name": "@tsed/redis",
3
+ "description": "Redis (node-redis) connection for Ts.ED",
4
+ "type": "module",
5
+ "version": "8.29.0",
6
+ "source": "./src/index.ts",
7
+ "main": "./lib/esm/index.js",
8
+ "module": "./lib/esm/index.js",
9
+ "typings": "./lib/types/index.d.ts",
10
+ "exports": {
11
+ ".": {
12
+ "tsed-source": "./src/index.ts",
13
+ "types": "./lib/types/index.d.ts",
14
+ "import": "./lib/esm/index.js",
15
+ "default": "./lib/esm/index.js"
16
+ },
17
+ "./cache": {
18
+ "tsed-source": "./src/domain/index.ts",
19
+ "types": "./lib/types/domain/index.d.ts",
20
+ "import": "./lib/esm/domain/index.js",
21
+ "default": "./lib/esm/domain/index.js"
22
+ }
23
+ },
24
+ "scripts": {
25
+ "build": "yarn barrels && yarn build:ts",
26
+ "barrels": "barrels",
27
+ "test": "vitest run",
28
+ "build:ts": "tsc --build tsconfig.esm.json",
29
+ "test:ci": "vitest run --coverage.thresholds.autoUpdate=true"
30
+ },
31
+ "dependencies": {
32
+ "tslib": ">=2.7.0"
33
+ },
34
+ "devDependencies": {
35
+ "@tsed/barrels": "8.29.0",
36
+ "@tsed/core": "8.29.0",
37
+ "@tsed/di": "8.29.0",
38
+ "@tsed/typescript": "8.29.0",
39
+ "eslint": "9.12.0",
40
+ "redis": "6.0.0",
41
+ "typescript": "6.0.2",
42
+ "vitest": "4.1.4"
43
+ },
44
+ "peerDependencies": {
45
+ "@tsed/core": ">=8.0.0",
46
+ "@tsed/di": ">=8.0.0",
47
+ "redis": ">=5.1.0"
48
+ },
49
+ "repository": "https://github.com/tsedio/tsed",
50
+ "bugs": {
51
+ "url": "https://github.com/tsedio/tsed/issues"
52
+ },
53
+ "homepage": "https://github.com/tsedio/tsed/tree/production/packages/orm/redis",
54
+ "author": "Romain Lenzotti",
55
+ "license": "MIT"
56
+ }
package/readme.md ADDED
@@ -0,0 +1,285 @@
1
+ <p style="text-align: center" align="center">
2
+ <a href="https://tsed.dev" target="_blank"><img src="https://tsed.dev/tsed-og.png" width="200" alt="Ts.ED logo"/></a>
3
+ </p>
4
+
5
+ <div align="center">
6
+ <h1>@tsed/redis</h1>
7
+
8
+ [![Build & Release](https://github.com/tsedio/tsed/workflows/Build%20&%20Release/badge.svg)](https://github.com/tsedio/tsed/actions?query=workflow%3A%22Build+%26+Release%22)
9
+ [![PR Welcome](https://img.shields.io/badge/PRs-welcome-brightgreen.svg)](https://github.com/tsedio/tsed/blob/master/CONTRIBUTING.md)
10
+ [![npm version](https://badge.fury.io/js/%40tsed%2Fcommon.svg)](https://badge.fury.io/js/%40tsed%2Fcommon)
11
+ [![semantic-release](https://img.shields.io/badge/%20%20%F0%9F%93%A6%F0%9F%9A%80-semantic--release-e10079.svg)](https://github.com/semantic-release/semantic-release)
12
+ [![code style: prettier](https://img.shields.io/badge/code_style-prettier-ff69b4.svg?style=flat-square)](https://github.com/prettier/prettier)
13
+ [![github](https://img.shields.io/static/v1?label=Github%20sponsor&message=%E2%9D%A4&logo=GitHub&color=%23fe8e86)](https://github.com/sponsors/romakita)
14
+ [![opencollective](https://img.shields.io/static/v1?label=OpenCollective%20sponsor&message=%E2%9D%A4&logo=OpenCollective&color=%23fe8e86)](https://opencollective.com/tsed)
15
+
16
+ </div>
17
+
18
+ <div align="center">
19
+ <a href="https://tsed.dev/">Website</a>
20
+ <span>&nbsp;&nbsp;•&nbsp;&nbsp;</span>
21
+ <a href="https://tsed.dev/getting-started/">Getting started</a>
22
+ <span>&nbsp;&nbsp;•&nbsp;&nbsp;</span>
23
+ <a href="https://slack.tsed.dev">Slack</a>
24
+ <span>&nbsp;&nbsp;•&nbsp;&nbsp;</span>
25
+ <a href="https://twitter.com/TsED_io">Twitter</a>
26
+ </div>
27
+
28
+ <hr />
29
+
30
+ A package of Ts.ED framework. See website: https://tsed.dev/tutorials/redis.html
31
+
32
+ ## Features
33
+
34
+ Currently, `@tsed/redis` allows you:
35
+
36
+ - Configure one or more Redis database connections via the `@Configuration` configuration.
37
+ - Share redis connection with `@tsed/platform-cache`.
38
+ - Support classic Redis connection and Cluster connection.
39
+ - Inject connection to another service.
40
+ - Mock connection for unit/integration test.
41
+
42
+ ## Installation
43
+
44
+ ```shell
45
+ npm install --save @tsed/redis redis redis-mock
46
+ ```
47
+
48
+ > Minimal supported redis version is v5+
49
+
50
+ ## Create connection
51
+
52
+ Create a new `RedisConnection.ts` file in your project:
53
+
54
+ ```typescript
55
+ import type {RedisClientType} from "redis";
56
+ import {registerConnectionProvider} from "@tsed/redis";
57
+
58
+ export const REDIS_CONNECTION = Symbol.for("REDIS_CONNECTION");
59
+ export type REDIS_CONNECTION = RedisClientType;
60
+
61
+ registerConnectionProvider({
62
+ token: REDIS_CONNECTION,
63
+ name: "default"
64
+ });
65
+ ```
66
+
67
+ > registerConnectionProvider create automatically an injectable RedisConnection
68
+
69
+ Then, edit your `Server.ts`:
70
+
71
+ ```typescript
72
+ import {Configuration} from "@tsed/di";
73
+ import "@tsed/platform-cache"; // add this module if you want to use cache
74
+ import "@tsed/redis";
75
+
76
+ @Configuration({
77
+ redis: [
78
+ {
79
+ name: "default",
80
+ // share the redis connection with @tsed/platform-cache
81
+ cache: true
82
+
83
+ // redis options
84
+ // host: "localhost",
85
+ // port: 6379,
86
+ // username: "...",
87
+ // password: "...",
88
+ // tls: false,
89
+ // db: 0
90
+ }
91
+ ],
92
+ // cache options
93
+ cache: {
94
+ ttl: 300
95
+ }
96
+ })
97
+ class MyModule {}
98
+ ```
99
+
100
+ And finally, use the connection in your services:
101
+
102
+ ```typescript
103
+ import {Injectable} from "@tsed/di";
104
+
105
+ @Injectable()
106
+ export class ClientRepository {
107
+ @Inject(REDIS_CONNECTION)
108
+ protected connection: REDIS_CONNECTION; // redis instance
109
+
110
+ async keys() {
111
+ return this.connection.keys("clients:*");
112
+ }
113
+ }
114
+ ```
115
+
116
+ > See [redis documentation](https://github.com/redis/node-redis) for more details.
117
+
118
+ ## Cluster configuration
119
+
120
+ ```typescript
121
+ import {Configuration} from "@tsed/di";
122
+ import "@tsed/platform-cache"; // add this module if you want to use cache
123
+ import "@tsed/redis";
124
+
125
+ @Configuration({
126
+ redis: [
127
+ {
128
+ name: "default",
129
+ // share the redis connection with @tsed/platform-cache
130
+ cache: true,
131
+ // cluster options
132
+ nodes: ["..."],
133
+ // other cluster options
134
+ scaleReads: "all",
135
+ maxRedirections: 16,
136
+ retryDelayOnTryAgain: 100,
137
+ retryDelayOnFailover: 200,
138
+ retryDelayOnClusterDown: 1000,
139
+ slotsRefreshTimeout: 15000,
140
+ slotsRefreshInterval: 20000,
141
+ enableOfflineQueue: true,
142
+ enableReadyCheck: true,
143
+ redisOptions: {
144
+ noDelay: true,
145
+ connectTimeout: 15000,
146
+ autoResendUnfulfilledCommands: true,
147
+ maxRetriesPerRequest: 5,
148
+ enableAutoPipelining: true,
149
+ autoPipeliningIgnoredCommands: ["scan"]
150
+ }
151
+ //
152
+ }
153
+ ],
154
+ // cache options
155
+ cache: {
156
+ ttl: 300
157
+ }
158
+ })
159
+ class MyModule {}
160
+ ```
161
+
162
+ ## Testing
163
+
164
+ Ts.ED provides a utility that allows you to test a service that consumes a Redis connection.
165
+ This use relies on the awesome [redis-mock](https://www.npmjs.com/package/redis-mock) module.
166
+
167
+ Here is a class that consume a redis connection:
168
+
169
+ ```typescript
170
+ import {v4} from "uuid";
171
+ import {Injectable} from "@tsed/di";
172
+ import {serialize, deserialize} from "@tsed/json-mapper";
173
+ import {REDIS_CONNECTION} from "./RedisConnection.js";
174
+ import {ClientModel} from "./ClientModel.js";
175
+
176
+ @Injectable()
177
+ export class ClientRepository {
178
+ @Inject(REDIS_CONNECTION)
179
+ protected connection: REDIS_CONNECTION; // redis instance
180
+
181
+ async get(id: string) {
182
+ const raw = await this.connection.get("clients:" + id);
183
+
184
+ if (!raw) {
185
+ return undefined;
186
+ }
187
+
188
+ return deserialize(JSON.parse(raw), {type: ClientModel});
189
+ }
190
+
191
+ async save(client: ClientModel) {
192
+ client.id = client.id || v4();
193
+
194
+ this.connection.set("clients:" + client.id, JSON.stringify(serialize(client)));
195
+
196
+ return client;
197
+ }
198
+ }
199
+ ```
200
+
201
+ The ClientModel:
202
+
203
+ ```typescript
204
+ import {Schema} from "@tsed/schema";
205
+
206
+ export class ClientModel {
207
+ @Property()
208
+ id: string;
209
+
210
+ @Property()
211
+ name: string;
212
+ }
213
+ ```
214
+
215
+ And his test:
216
+
217
+ ```typescript
218
+ import {ClientRepository} from "./ClientRepository.js";
219
+ import {REDIS_CONNECTION} from "./RedisConnection.js";
220
+ import {ClientModel} from "./ClientModel.js";
221
+
222
+ describe("RedisTest", () => {
223
+ beforeEach(() => RedisTest.create()); // create a new sandbox with redis-mock connection
224
+ afterEach(() => RedisTest.reset());
225
+
226
+ it("should return nothing", async () => {
227
+ const service = RedisTest.get<MyRepository>(MyRepository);
228
+
229
+ const client = await service.get("uid");
230
+
231
+ expect(client).toEqual(undefined);
232
+ });
233
+
234
+ it("should return all keys", async () => {
235
+ const service = RedisTest.get<MyRepository>(MyRepository);
236
+ const client = new ClientModel();
237
+ client.name = "name";
238
+
239
+ const newClient = await service.save(client);
240
+
241
+ expect(newClient.id).toBeInstanceOf(String);
242
+ expect(newClient.name).toEqual("name");
243
+
244
+ const clientFound = await service.get(newClient.id);
245
+
246
+ expect(clientFound).toBeInstanceOf(ClientModel);
247
+ expect(clientFound.id).toEqual(newClient.id);
248
+ expect(clientFound.name).toEqual("name");
249
+ });
250
+ });
251
+ ```
252
+
253
+ ## Contributors
254
+
255
+ <a href="https://github.com/tsedio/ts-express-decorators/graphs/contributors"><img src="https://opencollective.com/tsed/contributors.svg?width=890" /></a>
256
+
257
+ ## Backers
258
+
259
+ Thank you to all our backers! 🙏 [[Become a backer](https://opencollective.com/tsed#backer)]
260
+
261
+ <a href="https://opencollective.com/tsed#backers" target="_blank"><img src="https://opencollective.com/tsed/backers.svg?width=890"></a>
262
+
263
+ ## Sponsors
264
+
265
+ Support this project by becoming a sponsor. Your logo will show up here with a link to your
266
+ website. [[Become a sponsor](https://opencollective.com/tsed#sponsor)]
267
+
268
+ ## License
269
+
270
+ The MIT License (MIT)
271
+
272
+ Copyright (c) 2016 - 2022 Romain Lenzotti
273
+
274
+ Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated
275
+ documentation files (the "Software"), to deal in the Software without restriction, including without limitation the
276
+ rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit
277
+ persons to whom the Software is furnished to do so, subject to the following conditions:
278
+
279
+ The above copyright notice and this permission notice shall be included in all copies or substantial portions of the
280
+ Software.
281
+
282
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
283
+ WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
284
+ COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
285
+ OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.