alepha 0.14.0 → 0.14.2
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.
- package/README.md +3 -3
- package/dist/api/audits/index.d.ts +80 -1
- package/dist/api/audits/index.d.ts.map +1 -1
- package/dist/api/audits/index.js.map +1 -1
- package/dist/api/files/index.d.ts +80 -1
- package/dist/api/files/index.d.ts.map +1 -1
- package/dist/api/files/index.js.map +1 -1
- package/dist/api/jobs/index.d.ts +236 -157
- package/dist/api/jobs/index.d.ts.map +1 -1
- package/dist/api/jobs/index.js.map +1 -1
- package/dist/api/notifications/index.d.ts +21 -1
- package/dist/api/notifications/index.d.ts.map +1 -1
- package/dist/api/parameters/index.d.ts +451 -4
- package/dist/api/parameters/index.d.ts.map +1 -1
- package/dist/api/parameters/index.js.map +1 -1
- package/dist/api/users/index.d.ts +252 -249
- package/dist/api/users/index.d.ts.map +1 -1
- package/dist/api/users/index.js +4 -0
- package/dist/api/users/index.js.map +1 -1
- package/dist/api/verifications/index.d.ts +128 -128
- package/dist/api/verifications/index.d.ts.map +1 -1
- package/dist/batch/index.js.map +1 -1
- package/dist/cache/core/index.js.map +1 -1
- package/dist/cli/index.d.ts +304 -115
- package/dist/cli/index.d.ts.map +1 -1
- package/dist/cli/index.js +650 -531
- package/dist/cli/index.js.map +1 -1
- package/dist/command/index.d.ts +210 -13
- package/dist/command/index.d.ts.map +1 -1
- package/dist/command/index.js +306 -69
- package/dist/command/index.js.map +1 -1
- package/dist/core/index.browser.js.map +1 -1
- package/dist/core/index.d.ts +1 -1
- package/dist/core/index.d.ts.map +1 -1
- package/dist/core/index.js +7 -6
- package/dist/core/index.js.map +1 -1
- package/dist/core/index.native.js +7 -6
- package/dist/core/index.native.js.map +1 -1
- package/dist/datetime/index.js.map +1 -1
- package/dist/fake/index.js.map +1 -1
- package/dist/file/index.d.ts.map +1 -1
- package/dist/file/index.js.map +1 -1
- package/dist/lock/redis/index.js.map +1 -1
- package/dist/logger/index.js.map +1 -1
- package/dist/mcp/index.js.map +1 -1
- package/dist/orm/index.browser.js +26 -5
- package/dist/orm/index.browser.js.map +1 -1
- package/dist/orm/index.d.ts +294 -215
- package/dist/orm/index.d.ts.map +1 -1
- package/dist/orm/index.js +522 -523
- package/dist/orm/index.js.map +1 -1
- package/dist/queue/redis/index.js +2 -4
- package/dist/queue/redis/index.js.map +1 -1
- package/dist/redis/index.d.ts +400 -29
- package/dist/redis/index.d.ts.map +1 -1
- package/dist/redis/index.js +412 -21
- package/dist/redis/index.js.map +1 -1
- package/dist/retry/index.js.map +1 -1
- package/dist/router/index.js.map +1 -1
- package/dist/scheduler/index.js.map +1 -1
- package/dist/security/index.d.ts.map +1 -1
- package/dist/security/index.js.map +1 -1
- package/dist/server/auth/index.d.ts +155 -155
- package/dist/server/auth/index.js.map +1 -1
- package/dist/server/cache/index.js.map +1 -1
- package/dist/server/cookies/index.browser.js.map +1 -1
- package/dist/server/cookies/index.js.map +1 -1
- package/dist/server/core/index.browser.js.map +1 -1
- package/dist/server/core/index.d.ts +0 -1
- package/dist/server/core/index.d.ts.map +1 -1
- package/dist/server/core/index.js.map +1 -1
- package/dist/server/helmet/index.d.ts +4 -1
- package/dist/server/helmet/index.d.ts.map +1 -1
- package/dist/server/helmet/index.js.map +1 -1
- package/dist/server/links/index.browser.js.map +1 -1
- package/dist/server/links/index.js.map +1 -1
- package/dist/server/multipart/index.d.ts.map +1 -1
- package/dist/server/multipart/index.js.map +1 -1
- package/dist/server/proxy/index.js.map +1 -1
- package/dist/server/rate-limit/index.js.map +1 -1
- package/dist/server/security/index.d.ts +9 -9
- package/dist/server/security/index.js.map +1 -1
- package/dist/server/swagger/index.js.map +1 -1
- package/dist/thread/index.js.map +1 -1
- package/dist/topic/core/index.js.map +1 -1
- package/dist/topic/redis/index.js +3 -3
- package/dist/topic/redis/index.js.map +1 -1
- package/dist/vite/index.js +9 -6
- package/dist/vite/index.js.map +1 -1
- package/dist/websocket/index.browser.js.map +1 -1
- package/dist/websocket/index.d.ts +7 -7
- package/dist/websocket/index.js.map +1 -1
- package/package.json +3 -3
- package/src/api/users/index.ts +4 -0
- package/src/cli/apps/AlephaCli.ts +36 -14
- package/src/cli/apps/AlephaPackageBuilderCli.ts +5 -1
- package/src/cli/assets/appRouterTs.ts +1 -1
- package/src/cli/atoms/changelogOptions.ts +45 -0
- package/src/cli/commands/{ViteCommands.ts → build.ts} +4 -93
- package/src/cli/commands/changelog.ts +244 -0
- package/src/cli/commands/clean.ts +14 -0
- package/src/cli/commands/{DrizzleCommands.ts → db.ts} +37 -124
- package/src/cli/commands/deploy.ts +118 -0
- package/src/cli/commands/dev.ts +57 -0
- package/src/cli/commands/format.ts +17 -0
- package/src/cli/commands/{CoreCommands.ts → init.ts} +2 -40
- package/src/cli/commands/lint.ts +17 -0
- package/src/cli/commands/root.ts +32 -0
- package/src/cli/commands/run.ts +24 -0
- package/src/cli/commands/test.ts +42 -0
- package/src/cli/commands/typecheck.ts +19 -0
- package/src/cli/commands/{VerifyCommands.ts → verify.ts} +1 -13
- package/src/cli/defineConfig.ts +24 -0
- package/src/cli/index.ts +17 -5
- package/src/cli/services/AlephaCliUtils.ts +4 -21
- package/src/cli/services/GitMessageParser.ts +77 -0
- package/src/command/helpers/EnvUtils.ts +37 -0
- package/src/command/index.ts +3 -1
- package/src/command/primitives/$command.ts +172 -6
- package/src/command/providers/CliProvider.ts +424 -91
- package/src/core/Alepha.ts +8 -5
- package/src/file/providers/NodeFileSystemProvider.ts +3 -1
- package/src/orm/index.browser.ts +1 -1
- package/src/orm/index.ts +18 -10
- package/src/orm/interfaces/PgQueryWhere.ts +1 -26
- package/src/orm/providers/{PostgresTypeProvider.ts → DatabaseTypeProvider.ts} +25 -3
- package/src/orm/providers/drivers/BunPostgresProvider.ts +225 -0
- package/src/orm/providers/drivers/BunSqliteProvider.ts +180 -0
- package/src/orm/providers/drivers/DatabaseProvider.ts +25 -0
- package/src/orm/providers/drivers/NodePostgresProvider.ts +0 -25
- package/src/orm/services/QueryManager.ts +10 -125
- package/src/queue/redis/providers/RedisQueueProvider.ts +2 -7
- package/src/redis/index.ts +65 -3
- package/src/redis/providers/BunRedisProvider.ts +304 -0
- package/src/redis/providers/BunRedisSubscriberProvider.ts +94 -0
- package/src/redis/providers/NodeRedisProvider.ts +280 -0
- package/src/redis/providers/NodeRedisSubscriberProvider.ts +94 -0
- package/src/redis/providers/RedisProvider.ts +134 -140
- package/src/redis/providers/RedisSubscriberProvider.ts +58 -49
- package/src/server/core/providers/BunHttpServerProvider.ts +0 -3
- package/src/server/core/providers/ServerBodyParserProvider.ts +3 -1
- package/src/server/core/providers/ServerProvider.ts +7 -4
- package/src/server/multipart/providers/ServerMultipartProvider.ts +3 -1
- package/src/server/proxy/providers/ServerProxyProvider.ts +1 -1
- package/src/topic/redis/providers/RedisTopicProvider.ts +3 -3
- package/src/vite/tasks/buildServer.ts +1 -0
- package/src/cli/commands/BiomeCommands.ts +0 -29
- package/src/cli/commands/ChangelogCommands.ts +0 -389
- package/src/orm/services/PgJsonQueryManager.ts +0 -511
|
@@ -0,0 +1,280 @@
|
|
|
1
|
+
import {
|
|
2
|
+
createClient,
|
|
3
|
+
RESP_TYPES,
|
|
4
|
+
type RedisClientType,
|
|
5
|
+
type SetOptions,
|
|
6
|
+
} from "@redis/client";
|
|
7
|
+
import { $env, $hook, $inject, Alepha, type Static, t } from "alepha";
|
|
8
|
+
import { $logger } from "alepha/logger";
|
|
9
|
+
import { RedisProvider, type RedisSetOptions } from "./RedisProvider.ts";
|
|
10
|
+
|
|
11
|
+
const envSchema = t.object({
|
|
12
|
+
REDIS_URL: t.optional(t.text()),
|
|
13
|
+
REDIS_PORT: t.integer({
|
|
14
|
+
default: "6379",
|
|
15
|
+
}),
|
|
16
|
+
REDIS_HOST: t.text({
|
|
17
|
+
default: "localhost",
|
|
18
|
+
}),
|
|
19
|
+
REDIS_PASSWORD: t.optional(t.text()),
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
declare module "alepha" {
|
|
23
|
+
interface Env extends Partial<Static<typeof envSchema>> {}
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export type NodeRedisClient = RedisClientType<
|
|
27
|
+
{},
|
|
28
|
+
{},
|
|
29
|
+
{},
|
|
30
|
+
3,
|
|
31
|
+
{ 36: BufferConstructor }
|
|
32
|
+
>;
|
|
33
|
+
export type NodeRedisClientOptions = Parameters<typeof createClient>[0];
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Node.js Redis client provider using `@redis/client`.
|
|
37
|
+
*
|
|
38
|
+
* This provider uses the official Redis client for Node.js runtime.
|
|
39
|
+
*
|
|
40
|
+
* @example
|
|
41
|
+
* ```ts
|
|
42
|
+
* // Set REDIS_URL environment variable
|
|
43
|
+
* // REDIS_URL=redis://localhost:6379
|
|
44
|
+
*
|
|
45
|
+
* // Or configure via REDIS_HOST, REDIS_PORT, REDIS_PASSWORD
|
|
46
|
+
*
|
|
47
|
+
* // Or configure programmatically
|
|
48
|
+
* alepha.with({
|
|
49
|
+
* provide: RedisProvider,
|
|
50
|
+
* use: NodeRedisProvider,
|
|
51
|
+
* });
|
|
52
|
+
* ```
|
|
53
|
+
*/
|
|
54
|
+
export class NodeRedisProvider extends RedisProvider {
|
|
55
|
+
protected readonly log = $logger();
|
|
56
|
+
protected readonly alepha = $inject(Alepha);
|
|
57
|
+
protected readonly env = $env(envSchema);
|
|
58
|
+
protected readonly client = this.createClient();
|
|
59
|
+
|
|
60
|
+
public get publisher(): NodeRedisClient {
|
|
61
|
+
if (!this.client.isReady) {
|
|
62
|
+
throw new Error("Redis client is not ready");
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
return this.client;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
public override get isReady(): boolean {
|
|
69
|
+
return this.client.isReady;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
protected readonly start = $hook({
|
|
73
|
+
on: "start",
|
|
74
|
+
handler: () => this.connect(),
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
protected readonly stop = $hook({
|
|
78
|
+
on: "stop",
|
|
79
|
+
handler: () => this.close(),
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* Connect to the Redis server.
|
|
84
|
+
*/
|
|
85
|
+
public override async connect(): Promise<void> {
|
|
86
|
+
this.log.debug("Connecting...");
|
|
87
|
+
await this.client.connect();
|
|
88
|
+
this.log.info("Connection OK");
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* Close the connection to the Redis server.
|
|
93
|
+
*/
|
|
94
|
+
public override async close(): Promise<void> {
|
|
95
|
+
this.log.debug("Closing connection...");
|
|
96
|
+
await this.client.close();
|
|
97
|
+
this.log.info("Connection closed");
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
public duplicate(options?: Partial<NodeRedisClientOptions>): NodeRedisClient {
|
|
101
|
+
return this.client
|
|
102
|
+
.duplicate({
|
|
103
|
+
...options,
|
|
104
|
+
RESP: 3,
|
|
105
|
+
})
|
|
106
|
+
.withTypeMapping({
|
|
107
|
+
[RESP_TYPES.BLOB_STRING]: Buffer,
|
|
108
|
+
});
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
public override async get(key: string): Promise<Buffer | undefined> {
|
|
112
|
+
this.log.trace(`Getting key ${key}`);
|
|
113
|
+
const resp = await this.publisher.get(key);
|
|
114
|
+
|
|
115
|
+
if (resp === null) {
|
|
116
|
+
return undefined;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
return Buffer.from(resp);
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
public override async set(
|
|
123
|
+
key: string,
|
|
124
|
+
value: Buffer | string,
|
|
125
|
+
options?: RedisSetOptions,
|
|
126
|
+
): Promise<Buffer> {
|
|
127
|
+
const buf = Buffer.isBuffer(value) ? value : Buffer.from(value, "utf-8");
|
|
128
|
+
|
|
129
|
+
// Convert RedisSetOptions to @redis/client SetOptions
|
|
130
|
+
const setOptions: SetOptions = {};
|
|
131
|
+
|
|
132
|
+
// Handle expiration object format (from alepha/cache-redis, alepha/lock-redis)
|
|
133
|
+
if (options?.expiration) {
|
|
134
|
+
if (options.expiration.type === "KEEPTTL") {
|
|
135
|
+
setOptions.KEEPTTL = true;
|
|
136
|
+
} else {
|
|
137
|
+
setOptions[options.expiration.type] = options.expiration.value;
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
// Handle direct expiration properties
|
|
142
|
+
if (options?.EX !== undefined) {
|
|
143
|
+
setOptions.EX = options.EX;
|
|
144
|
+
}
|
|
145
|
+
if (options?.PX !== undefined) {
|
|
146
|
+
setOptions.PX = options.PX;
|
|
147
|
+
}
|
|
148
|
+
if (options?.EXAT !== undefined) {
|
|
149
|
+
setOptions.EXAT = options.EXAT;
|
|
150
|
+
}
|
|
151
|
+
if (options?.PXAT !== undefined) {
|
|
152
|
+
setOptions.PXAT = options.PXAT;
|
|
153
|
+
}
|
|
154
|
+
if (options?.KEEPTTL) {
|
|
155
|
+
setOptions.KEEPTTL = true;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
// Handle condition object format
|
|
159
|
+
if (options?.condition === "NX") {
|
|
160
|
+
setOptions.NX = true;
|
|
161
|
+
} else if (options?.condition === "XX") {
|
|
162
|
+
setOptions.XX = true;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
// Handle direct condition properties
|
|
166
|
+
if (options?.NX) {
|
|
167
|
+
setOptions.NX = true;
|
|
168
|
+
}
|
|
169
|
+
if (options?.XX) {
|
|
170
|
+
setOptions.XX = true;
|
|
171
|
+
}
|
|
172
|
+
if (options?.GET) {
|
|
173
|
+
setOptions.GET = true;
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
const resp = await this.publisher.set(
|
|
177
|
+
key,
|
|
178
|
+
buf,
|
|
179
|
+
Object.keys(setOptions).length > 0 ? setOptions : undefined,
|
|
180
|
+
);
|
|
181
|
+
|
|
182
|
+
if (resp === "OK" || !resp) {
|
|
183
|
+
return buf;
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
return Buffer.from(resp);
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
public override async has(key: string): Promise<boolean> {
|
|
190
|
+
const resp = await this.publisher.exists(key);
|
|
191
|
+
return resp > 0;
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
public override async keys(pattern: string): Promise<string[]> {
|
|
195
|
+
const keys = await this.publisher.keys(pattern);
|
|
196
|
+
return keys.map((key) => key.toString());
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
public override async del(keys: string[]): Promise<void> {
|
|
200
|
+
if (keys.length === 0) {
|
|
201
|
+
return;
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
await this.publisher.del(keys);
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
// ---------------------------------------------------------
|
|
208
|
+
// Queue operations
|
|
209
|
+
// ---------------------------------------------------------
|
|
210
|
+
|
|
211
|
+
public override async lpush(key: string, value: string): Promise<void> {
|
|
212
|
+
await this.publisher.LPUSH(key, value);
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
public override async rpop(key: string): Promise<string | undefined> {
|
|
216
|
+
const value = await this.publisher.RPOP(key);
|
|
217
|
+
if (value == null) {
|
|
218
|
+
return undefined;
|
|
219
|
+
}
|
|
220
|
+
return String(value);
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
// ---------------------------------------------------------
|
|
224
|
+
// Pub/Sub operations
|
|
225
|
+
// ---------------------------------------------------------
|
|
226
|
+
|
|
227
|
+
public override async publish(
|
|
228
|
+
channel: string,
|
|
229
|
+
message: string,
|
|
230
|
+
): Promise<void> {
|
|
231
|
+
await this.publisher.publish(channel, message);
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
/**
|
|
235
|
+
* Get the Redis connection URL.
|
|
236
|
+
*/
|
|
237
|
+
protected getUrl(): string {
|
|
238
|
+
// Prefer REDIS_URL if set
|
|
239
|
+
if (this.env.REDIS_URL) {
|
|
240
|
+
return this.env.REDIS_URL;
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
// Build URL from components
|
|
244
|
+
const url = new URL("redis://127.0.0.1:6379");
|
|
245
|
+
|
|
246
|
+
if (this.env.REDIS_PASSWORD) {
|
|
247
|
+
url.password = this.env.REDIS_PASSWORD;
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
if (this.env.REDIS_HOST) {
|
|
251
|
+
url.hostname = this.env.REDIS_HOST;
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
if (this.env.REDIS_PORT) {
|
|
255
|
+
url.port = String(this.env.REDIS_PORT);
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
return url.toString();
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
/**
|
|
262
|
+
* Redis client factory method.
|
|
263
|
+
*/
|
|
264
|
+
protected createClient(): NodeRedisClient {
|
|
265
|
+
const client = createClient({
|
|
266
|
+
url: this.getUrl(),
|
|
267
|
+
RESP: 3,
|
|
268
|
+
}).withTypeMapping({
|
|
269
|
+
[RESP_TYPES.BLOB_STRING]: Buffer,
|
|
270
|
+
});
|
|
271
|
+
|
|
272
|
+
client.on("error", (error) => {
|
|
273
|
+
if (this.alepha.isStarted()) {
|
|
274
|
+
this.log.error(error);
|
|
275
|
+
}
|
|
276
|
+
});
|
|
277
|
+
|
|
278
|
+
return client;
|
|
279
|
+
}
|
|
280
|
+
}
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
import { $hook, $inject, Alepha } from "alepha";
|
|
2
|
+
import { $logger } from "alepha/logger";
|
|
3
|
+
import {
|
|
4
|
+
type NodeRedisClient,
|
|
5
|
+
NodeRedisProvider,
|
|
6
|
+
} from "./NodeRedisProvider.ts";
|
|
7
|
+
import {
|
|
8
|
+
RedisSubscriberProvider,
|
|
9
|
+
type SubscribeCallback,
|
|
10
|
+
} from "./RedisSubscriberProvider.ts";
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Node.js Redis subscriber provider using `@redis/client`.
|
|
14
|
+
*
|
|
15
|
+
* This provider creates a dedicated Redis connection for subscriptions,
|
|
16
|
+
* as Redis requires separate connections for pub/sub operations.
|
|
17
|
+
*
|
|
18
|
+
* @example
|
|
19
|
+
* ```ts
|
|
20
|
+
* const subscriber = alepha.inject(RedisSubscriberProvider);
|
|
21
|
+
* await subscriber.subscribe("channel", (message, channel) => {
|
|
22
|
+
* console.log(`Received: ${message} on ${channel}`);
|
|
23
|
+
* });
|
|
24
|
+
* ```
|
|
25
|
+
*/
|
|
26
|
+
export class NodeRedisSubscriberProvider extends RedisSubscriberProvider {
|
|
27
|
+
protected readonly log = $logger();
|
|
28
|
+
protected readonly alepha = $inject(Alepha);
|
|
29
|
+
protected readonly redisProvider = $inject(NodeRedisProvider);
|
|
30
|
+
protected readonly client: NodeRedisClient = this.createClient();
|
|
31
|
+
|
|
32
|
+
public get subscriber(): NodeRedisClient {
|
|
33
|
+
if (!this.client.isReady) {
|
|
34
|
+
throw new Error("Redis subscriber client is not ready");
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
return this.client;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
public override get isReady(): boolean {
|
|
41
|
+
return this.client.isReady;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
protected readonly start = $hook({
|
|
45
|
+
on: "start",
|
|
46
|
+
handler: () => this.connect(),
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
protected readonly stop = $hook({
|
|
50
|
+
on: "stop",
|
|
51
|
+
handler: () => this.close(),
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
public override async connect(): Promise<void> {
|
|
55
|
+
this.log.debug("Connecting subscriber...");
|
|
56
|
+
await this.client.connect();
|
|
57
|
+
this.log.info("Subscriber connection OK");
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
public override async close(): Promise<void> {
|
|
61
|
+
this.log.debug("Closing subscriber connection...");
|
|
62
|
+
await this.subscriber.close();
|
|
63
|
+
this.log.info("Subscriber connection closed");
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
public override async subscribe(
|
|
67
|
+
channel: string,
|
|
68
|
+
callback: SubscribeCallback,
|
|
69
|
+
): Promise<void> {
|
|
70
|
+
await this.subscriber.subscribe(channel, callback);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
public override async unsubscribe(
|
|
74
|
+
channel: string,
|
|
75
|
+
callback?: SubscribeCallback,
|
|
76
|
+
): Promise<void> {
|
|
77
|
+
await this.subscriber.unsubscribe(channel, callback);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Redis subscriber client factory method.
|
|
82
|
+
*/
|
|
83
|
+
protected createClient(): NodeRedisClient {
|
|
84
|
+
const client = this.redisProvider.duplicate();
|
|
85
|
+
|
|
86
|
+
client.on("error", (error) => {
|
|
87
|
+
if (this.alepha.isStarted()) {
|
|
88
|
+
this.log.error(error);
|
|
89
|
+
}
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
return client;
|
|
93
|
+
}
|
|
94
|
+
}
|
|
@@ -1,167 +1,161 @@
|
|
|
1
|
-
import {
|
|
2
|
-
createClient,
|
|
3
|
-
RESP_TYPES,
|
|
4
|
-
type RedisClientType,
|
|
5
|
-
type SetOptions,
|
|
6
|
-
} from "@redis/client";
|
|
7
|
-
import { $env, $hook, $inject, Alepha, type Static, t } from "alepha";
|
|
8
|
-
import { $logger } from "alepha/logger";
|
|
9
|
-
|
|
10
|
-
const envSchema = t.object({
|
|
11
|
-
REDIS_PORT: t.integer({
|
|
12
|
-
default: "6379",
|
|
13
|
-
}),
|
|
14
|
-
REDIS_HOST: t.text({
|
|
15
|
-
default: "localhost",
|
|
16
|
-
}),
|
|
17
|
-
REDIS_PASSWORD: t.optional(t.text()),
|
|
18
|
-
});
|
|
19
|
-
|
|
20
|
-
declare module "alepha" {
|
|
21
|
-
interface Env extends Partial<Static<typeof envSchema>> {}
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
export type RedisClient = RedisClientType<
|
|
25
|
-
{},
|
|
26
|
-
{},
|
|
27
|
-
{},
|
|
28
|
-
3,
|
|
29
|
-
{ 36: BufferConstructor }
|
|
30
|
-
>;
|
|
31
|
-
export type RedisClientOptions = Parameters<typeof createClient>[0];
|
|
32
|
-
export type RedisSetOptions = SetOptions;
|
|
33
|
-
|
|
34
1
|
/**
|
|
35
|
-
* Redis
|
|
2
|
+
* Abstract Redis provider interface.
|
|
3
|
+
*
|
|
4
|
+
* This abstract class defines the common interface for Redis operations.
|
|
5
|
+
* Implementations include:
|
|
6
|
+
* - {@link NodeRedisProvider} - Uses `@redis/client` for Node.js runtime
|
|
7
|
+
* - {@link BunRedisProvider} - Uses Bun's native `RedisClient` for Bun runtime
|
|
8
|
+
*
|
|
9
|
+
* @example
|
|
10
|
+
* ```ts
|
|
11
|
+
* // Inject the abstract provider - runtime selects the implementation
|
|
12
|
+
* const redis = alepha.inject(RedisProvider);
|
|
13
|
+
*
|
|
14
|
+
* // Use common operations
|
|
15
|
+
* await redis.set("key", "value");
|
|
16
|
+
* const value = await redis.get("key");
|
|
17
|
+
* ```
|
|
36
18
|
*/
|
|
37
|
-
export class RedisProvider {
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
public get publisher(): RedisClient {
|
|
44
|
-
if (!this.client.isReady) {
|
|
45
|
-
throw new Error("Redis client is not ready");
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
return this.client;
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
protected readonly start = $hook({
|
|
52
|
-
on: "start",
|
|
53
|
-
handler: () => this.connect(),
|
|
54
|
-
});
|
|
55
|
-
|
|
56
|
-
protected readonly stop = $hook({
|
|
57
|
-
on: "stop",
|
|
58
|
-
handler: () => this.close(),
|
|
59
|
-
});
|
|
19
|
+
export abstract class RedisProvider {
|
|
20
|
+
/**
|
|
21
|
+
* Whether the Redis client is ready to accept commands.
|
|
22
|
+
*/
|
|
23
|
+
public abstract readonly isReady: boolean;
|
|
60
24
|
|
|
61
25
|
/**
|
|
62
26
|
* Connect to the Redis server.
|
|
63
27
|
*/
|
|
64
|
-
public
|
|
65
|
-
this.log.debug("Connecting...");
|
|
66
|
-
await this.client.connect();
|
|
67
|
-
this.log.info("Connection OK");
|
|
68
|
-
}
|
|
28
|
+
public abstract connect(): Promise<void>;
|
|
69
29
|
|
|
70
30
|
/**
|
|
71
31
|
* Close the connection to the Redis server.
|
|
72
32
|
*/
|
|
73
|
-
public
|
|
74
|
-
this.log.debug("Closing connection...");
|
|
75
|
-
await this.client.close();
|
|
76
|
-
this.log.info("Connection closed");
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
public duplicate(options?: Partial<RedisClientOptions>): RedisClient {
|
|
80
|
-
return this.client
|
|
81
|
-
.duplicate({
|
|
82
|
-
...options,
|
|
83
|
-
RESP: 3,
|
|
84
|
-
})
|
|
85
|
-
.withTypeMapping({
|
|
86
|
-
[RESP_TYPES.BLOB_STRING]: Buffer,
|
|
87
|
-
});
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
public async get(key: string): Promise<Buffer | undefined> {
|
|
91
|
-
this.log.trace(`Getting key ${key}`);
|
|
92
|
-
const resp = await this.publisher.get(key);
|
|
93
|
-
|
|
94
|
-
if (resp === null) {
|
|
95
|
-
return undefined;
|
|
96
|
-
}
|
|
33
|
+
public abstract close(): Promise<void>;
|
|
97
34
|
|
|
98
|
-
|
|
99
|
-
|
|
35
|
+
/**
|
|
36
|
+
* Get the value of a key.
|
|
37
|
+
*
|
|
38
|
+
* @param key The key to get.
|
|
39
|
+
* @returns The value as a Buffer, or undefined if the key does not exist.
|
|
40
|
+
*/
|
|
41
|
+
public abstract get(key: string): Promise<Buffer | undefined>;
|
|
100
42
|
|
|
101
|
-
|
|
43
|
+
/**
|
|
44
|
+
* Set the value of a key.
|
|
45
|
+
*
|
|
46
|
+
* @param key The key to set.
|
|
47
|
+
* @param value The value to set (Buffer or string).
|
|
48
|
+
* @param options Optional set options (EX, PX, NX, XX, etc.).
|
|
49
|
+
* @returns The value as a Buffer.
|
|
50
|
+
*/
|
|
51
|
+
public abstract set(
|
|
102
52
|
key: string,
|
|
103
53
|
value: Buffer | string,
|
|
104
54
|
options?: RedisSetOptions,
|
|
105
|
-
): Promise<Buffer
|
|
106
|
-
const buf = Buffer.isBuffer(value) ? value : Buffer.from(value, "utf-8");
|
|
107
|
-
const resp = await this.publisher.set(key, buf, options);
|
|
108
|
-
|
|
109
|
-
if (resp === "OK" || !resp) {
|
|
110
|
-
return buf;
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
return Buffer.from(resp);
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
public async has(key: string): Promise<boolean> {
|
|
117
|
-
const resp = await this.publisher.exists(key);
|
|
118
|
-
return resp > 0;
|
|
119
|
-
}
|
|
55
|
+
): Promise<Buffer>;
|
|
120
56
|
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
return;
|
|
129
|
-
}
|
|
57
|
+
/**
|
|
58
|
+
* Check if a key exists.
|
|
59
|
+
*
|
|
60
|
+
* @param key The key to check.
|
|
61
|
+
* @returns True if the key exists.
|
|
62
|
+
*/
|
|
63
|
+
public abstract has(key: string): Promise<boolean>;
|
|
130
64
|
|
|
131
|
-
|
|
132
|
-
|
|
65
|
+
/**
|
|
66
|
+
* Get all keys matching a pattern.
|
|
67
|
+
*
|
|
68
|
+
* @param pattern The glob-style pattern to match.
|
|
69
|
+
* @returns Array of matching key names.
|
|
70
|
+
*/
|
|
71
|
+
public abstract keys(pattern: string): Promise<string[]>;
|
|
133
72
|
|
|
134
73
|
/**
|
|
135
|
-
*
|
|
74
|
+
* Delete one or more keys.
|
|
75
|
+
*
|
|
76
|
+
* @param keys The keys to delete.
|
|
136
77
|
*/
|
|
137
|
-
|
|
138
|
-
const url = new URL("redis://127.0.0.1:6379");
|
|
78
|
+
public abstract del(keys: string[]): Promise<void>;
|
|
139
79
|
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
80
|
+
// ---------------------------------------------------------
|
|
81
|
+
// Queue operations (for alepha/queue-redis)
|
|
82
|
+
// ---------------------------------------------------------
|
|
143
83
|
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
84
|
+
/**
|
|
85
|
+
* Push a value to the left (head) of a list.
|
|
86
|
+
*
|
|
87
|
+
* @param key The list key.
|
|
88
|
+
* @param value The value to push.
|
|
89
|
+
*/
|
|
90
|
+
public abstract lpush(key: string, value: string): Promise<void>;
|
|
147
91
|
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
92
|
+
/**
|
|
93
|
+
* Pop a value from the right (tail) of a list.
|
|
94
|
+
*
|
|
95
|
+
* @param key The list key.
|
|
96
|
+
* @returns The value, or undefined if the list is empty.
|
|
97
|
+
*/
|
|
98
|
+
public abstract rpop(key: string): Promise<string | undefined>;
|
|
151
99
|
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
}).withTypeMapping({
|
|
156
|
-
[RESP_TYPES.BLOB_STRING]: Buffer,
|
|
157
|
-
});
|
|
100
|
+
// ---------------------------------------------------------
|
|
101
|
+
// Pub/Sub operations (for alepha/topic-redis)
|
|
102
|
+
// ---------------------------------------------------------
|
|
158
103
|
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
104
|
+
/**
|
|
105
|
+
* Publish a message to a channel.
|
|
106
|
+
*
|
|
107
|
+
* @param channel The channel name.
|
|
108
|
+
* @param message The message to publish.
|
|
109
|
+
*/
|
|
110
|
+
public abstract publish(channel: string, message: string): Promise<void>;
|
|
111
|
+
}
|
|
164
112
|
|
|
165
|
-
|
|
166
|
-
|
|
113
|
+
/**
|
|
114
|
+
* Common Redis SET command options.
|
|
115
|
+
* Compatible with @redis/client SetOptions format.
|
|
116
|
+
*/
|
|
117
|
+
export interface RedisSetOptions {
|
|
118
|
+
/**
|
|
119
|
+
* Set the specified expire time, in seconds.
|
|
120
|
+
*/
|
|
121
|
+
EX?: number;
|
|
122
|
+
/**
|
|
123
|
+
* Set the specified expire time, in milliseconds.
|
|
124
|
+
*/
|
|
125
|
+
PX?: number;
|
|
126
|
+
/**
|
|
127
|
+
* Set the specified Unix time at which the key will expire, in seconds.
|
|
128
|
+
*/
|
|
129
|
+
EXAT?: number;
|
|
130
|
+
/**
|
|
131
|
+
* Set the specified Unix time at which the key will expire, in milliseconds.
|
|
132
|
+
*/
|
|
133
|
+
PXAT?: number;
|
|
134
|
+
/**
|
|
135
|
+
* Only set the key if it does not already exist.
|
|
136
|
+
*/
|
|
137
|
+
NX?: boolean;
|
|
138
|
+
/**
|
|
139
|
+
* Only set the key if it already exists.
|
|
140
|
+
*/
|
|
141
|
+
XX?: boolean;
|
|
142
|
+
/**
|
|
143
|
+
* Retain the time to live associated with the key.
|
|
144
|
+
*/
|
|
145
|
+
KEEPTTL?: boolean;
|
|
146
|
+
/**
|
|
147
|
+
* Return the old string stored at key, or nil if key did not exist.
|
|
148
|
+
*/
|
|
149
|
+
GET?: boolean;
|
|
150
|
+
/**
|
|
151
|
+
* Alternative expiration format (compatible with @redis/client).
|
|
152
|
+
*/
|
|
153
|
+
expiration?: {
|
|
154
|
+
type: "EX" | "PX" | "EXAT" | "PXAT" | "KEEPTTL";
|
|
155
|
+
value: number;
|
|
156
|
+
};
|
|
157
|
+
/**
|
|
158
|
+
* Alternative condition format (compatible with @redis/client).
|
|
159
|
+
*/
|
|
160
|
+
condition?: "NX" | "XX";
|
|
167
161
|
}
|