-
Notifications
You must be signed in to change notification settings - Fork 81
/
Copy pathclient.ts
220 lines (189 loc) · 5.96 KB
/
client.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
import { createClient, createCluster, RediSearchSchema, SearchOptions } from 'redis'
import { Repository } from '../repository'
import {InferSchema, Schema} from '../schema'
import { RedisOmError } from '../error'
/** A conventional Redis connection. */
export type RedisClientConnection = ReturnType<typeof createClient>
/** A clustered Redis connection. */
export type RedisClusterConnection = ReturnType<typeof createCluster>
/** A Redis connection, clustered or conventional. */
export type RedisConnection = RedisClientConnection | RedisClusterConnection
/** @internal This is a defintion for the type that calls to ft.search in Node Redis return. */
export type SearchResults = {
total: number
documents: SearchDocument[]
}
/** @internal This is a defintion for the return type of calls to ft.search in Node Redis. */
export type SearchDocument = {
id: string
value: {
[key: string]: any
}
}
/** @internal */
export type RedisHashData = { [key: string]: string }
/** @internal */
export type RedisJsonData = { [key: string]: any }
/** @internal */
export type SearchDataStructure = 'HASH' | 'JSON'
/**
* @internal This is a simplified redefintion of the CreateOptions type that is not exported by Node Redis.
* TODO: Remove this type once CreateOptions is exported by Node Redis.
* https://github.com/redis/node-redis/blob/master/packages/search/lib/commands/CREATE.ts#L4
*/
export type CreateOptions = {
ON: SearchDataStructure
PREFIX: string
STOPWORDS?: string[]
}
/**
* A Client is the starting point for working with Redis OM. Clients manage the
* connection to Redis and provide limited functionality for executing Redis commands.
* Create a client and open it before you use it:
*
* ```typescript
* const client = new Client()
* await client.open()
* ```
*
* A Client is primarily used by a {@link Repository} which requires a client in
* its constructor.
*
* @deprecated Just used Node Redis client directly and pass it to the Repository.
*/
export class Client {
/** @internal */
#redis?: RedisConnection
/** Returns the underlying Node Redis connection being used. */
get redis() {
return this.#redis
}
/**
* Attaches an existing Node Redis connection to this Redis OM client. Closes
* any existing connection.
*
* @param connection An existing Node Redis client.
* @returns This {@link Client} instance.
*/
async use(connection: RedisConnection): Promise<Client> {
await this.close()
return this.useNoClose(connection)
}
/**
* Attaches an existing Node Redis connection to this Redis OM client. Does
* not close any existing connection.
*
* @param connection An existing Node Redis client.
* @returns This {@link Client} instance.
*/
useNoClose(connection: RedisConnection): Client {
this.#redis = connection
return this
}
/**
* Open a connection to Redis at the provided URL.
*
* @param url A URL to Redis as defined with the [IANA](https://www.iana.org/assignments/uri-schemes/prov/redis).
* @returns This {@link Client} instance.
*/
async open(url: string = 'redis://localhost:6379'): Promise<Client> {
if (!this.isOpen()) {
const redis = createClient({ url })
await redis.connect()
this.#redis = redis
}
return this
}
/**
* Creates a repository for the given schema.
*
* @param schema The schema.
* @returns A repository for the provided schema.
*/
fetchRepository<T extends Schema<any>>(schema: T): Repository<InferSchema<T>> {
this.#validateRedisOpen()
return new Repository(schema, this)
}
/**
* Close the connection to Redis.
*/
async close() {
if (this.#redis) await this.#redis.quit()
this.#redis = undefined
}
/** @internal */
async createIndex(indexName: string, schema: RediSearchSchema, options: CreateOptions) {
this.#validateRedisOpen()
await this.redis.ft.create(indexName, schema, options)
}
/** @internal */
async dropIndex(indexName: string) {
this.#validateRedisOpen()
await this.redis.ft.dropIndex(indexName)
}
/** @internal */
async search(indexName: string, query: string, options?: SearchOptions): Promise<SearchResults> {
this.#validateRedisOpen()
if (options) return await this.redis.ft.search(indexName, query, options)
return await this.redis.ft.search(indexName, query)
}
/** @internal */
async unlink(...keys: string[]) {
this.#validateRedisOpen()
if (keys.length > 0) await this.redis.unlink(keys)
}
/** @internal */
async expire(key: string, ttl: number) {
this.#validateRedisOpen()
await this.redis.expire(key, ttl)
}
/** @internal */
async expireAt(key: string, timestamp: Date) {
this.#validateRedisOpen()
await this.redis.expireAt(key, timestamp)
}
/** @internal */
async get(key: string): Promise<string | null> {
this.#validateRedisOpen()
return this.redis.get(key)
}
/** @internal */
async set(key: string, value: string) {
this.#validateRedisOpen()
await this.redis.set(key, value)
}
/** @internal */
async hgetall(key: string): Promise<RedisHashData> {
this.#validateRedisOpen()
return this.redis.hGetAll(key)
}
/** @internal */
async hsetall(key: string, data: RedisHashData) {
this.#validateRedisOpen()
await this.redis
.multi()
.unlink(key)
.hSet(key, data)
.exec()
}
/** @internal */
async jsonget(key: string): Promise<RedisJsonData | null> {
this.#validateRedisOpen()
const json = await this.redis.json.get(key, { path: '$' })
return json === null ? null : (json as RedisJsonData)[0]
}
/** @internal */
async jsonset(key: string, data: RedisJsonData) {
this.#validateRedisOpen()
await this.redis.json.set(key, '$', data)
}
/**
* @returns Whether a connection is already open.
*/
isOpen() {
return !!this.#redis
}
#validateRedisOpen(): asserts this is { redis: RedisConnection } {
if (!this.redis) throw new RedisOmError("Redis connection needs to be open.")
}
}