alepha 0.13.8 → 0.14.1
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/dist/api/audits/index.d.ts +418 -338
- package/dist/api/audits/index.d.ts.map +1 -0
- package/dist/api/files/index.d.ts +81 -1
- package/dist/api/files/index.d.ts.map +1 -0
- package/dist/api/jobs/index.d.ts +107 -27
- package/dist/api/jobs/index.d.ts.map +1 -0
- package/dist/api/notifications/index.d.ts +21 -1
- package/dist/api/notifications/index.d.ts.map +1 -0
- package/dist/api/parameters/index.d.ts +455 -8
- package/dist/api/parameters/index.d.ts.map +1 -0
- package/dist/api/users/index.d.ts +844 -840
- package/dist/api/users/index.d.ts.map +1 -0
- package/dist/api/verifications/index.d.ts.map +1 -0
- package/dist/batch/index.d.ts.map +1 -0
- package/dist/bucket/index.d.ts.map +1 -0
- package/dist/cache/core/index.d.ts.map +1 -0
- package/dist/cache/redis/index.d.ts.map +1 -0
- package/dist/cli/index.d.ts +254 -59
- package/dist/cli/index.d.ts.map +1 -0
- package/dist/cli/index.js +499 -127
- package/dist/cli/index.js.map +1 -1
- package/dist/command/index.d.ts +217 -10
- package/dist/command/index.d.ts.map +1 -0
- package/dist/command/index.js +350 -74
- package/dist/command/index.js.map +1 -1
- package/dist/core/index.browser.js +1334 -1318
- package/dist/core/index.browser.js.map +1 -1
- package/dist/core/index.d.ts +76 -72
- package/dist/core/index.d.ts.map +1 -0
- package/dist/core/index.js +1337 -1321
- package/dist/core/index.js.map +1 -1
- package/dist/core/index.native.js +1337 -1321
- package/dist/core/index.native.js.map +1 -1
- package/dist/datetime/index.d.ts.map +1 -0
- package/dist/email/index.d.ts.map +1 -0
- package/dist/fake/index.d.ts.map +1 -0
- package/dist/file/index.d.ts.map +1 -0
- package/dist/file/index.js.map +1 -1
- package/dist/lock/core/index.d.ts.map +1 -0
- package/dist/lock/redis/index.d.ts.map +1 -0
- package/dist/logger/index.d.ts +1 -0
- package/dist/logger/index.d.ts.map +1 -0
- package/dist/mcp/index.d.ts +820 -0
- package/dist/mcp/index.d.ts.map +1 -0
- package/dist/mcp/index.js +978 -0
- package/dist/mcp/index.js.map +1 -0
- package/dist/orm/index.d.ts +234 -107
- package/dist/orm/index.d.ts.map +1 -0
- package/dist/orm/index.js +376 -316
- package/dist/orm/index.js.map +1 -1
- package/dist/queue/core/index.d.ts +4 -4
- package/dist/queue/core/index.d.ts.map +1 -0
- package/dist/queue/redis/index.d.ts.map +1 -0
- 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 -0
- package/dist/redis/index.js +412 -21
- package/dist/redis/index.js.map +1 -1
- package/dist/retry/index.d.ts.map +1 -0
- package/dist/router/index.d.ts.map +1 -0
- package/dist/scheduler/index.d.ts +6 -6
- package/dist/scheduler/index.d.ts.map +1 -0
- package/dist/security/index.d.ts +28 -28
- package/dist/security/index.d.ts.map +1 -0
- package/dist/server/auth/index.d.ts +155 -155
- package/dist/server/auth/index.d.ts.map +1 -0
- package/dist/server/cache/index.d.ts.map +1 -0
- package/dist/server/compress/index.d.ts.map +1 -0
- package/dist/server/cookies/index.d.ts.map +1 -0
- package/dist/server/core/index.d.ts +0 -1
- package/dist/server/core/index.d.ts.map +1 -0
- package/dist/server/core/index.js.map +1 -1
- package/dist/server/cors/index.d.ts.map +1 -0
- package/dist/server/health/index.d.ts +17 -17
- package/dist/server/health/index.d.ts.map +1 -0
- package/dist/server/helmet/index.d.ts +4 -1
- package/dist/server/helmet/index.d.ts.map +1 -0
- package/dist/server/links/index.d.ts +33 -33
- package/dist/server/links/index.d.ts.map +1 -0
- package/dist/server/metrics/index.d.ts.map +1 -0
- package/dist/server/multipart/index.d.ts.map +1 -0
- package/dist/server/multipart/index.js.map +1 -1
- package/dist/server/proxy/index.d.ts.map +1 -0
- package/dist/server/proxy/index.js.map +1 -1
- package/dist/server/rate-limit/index.d.ts.map +1 -0
- package/dist/server/security/index.d.ts +9 -9
- package/dist/server/security/index.d.ts.map +1 -0
- package/dist/server/static/index.d.ts.map +1 -0
- package/dist/server/swagger/index.d.ts.map +1 -0
- package/dist/sms/index.d.ts.map +1 -0
- package/dist/thread/index.d.ts.map +1 -0
- package/dist/topic/core/index.d.ts.map +1 -0
- package/dist/topic/redis/index.d.ts.map +1 -0
- package/dist/topic/redis/index.js +3 -3
- package/dist/topic/redis/index.js.map +1 -1
- package/dist/vite/index.d.ts +10 -2
- package/dist/vite/index.d.ts.map +1 -0
- package/dist/vite/index.js +45 -20
- package/dist/vite/index.js.map +1 -1
- package/dist/websocket/index.d.ts.map +1 -0
- package/package.json +9 -4
- package/src/cli/apps/AlephaCli.ts +10 -3
- package/src/cli/apps/AlephaPackageBuilderCli.ts +15 -8
- package/src/cli/assets/mainTs.ts +9 -10
- package/src/cli/atoms/changelogOptions.ts +45 -0
- package/src/cli/commands/ChangelogCommands.ts +259 -0
- package/src/cli/commands/DeployCommands.ts +118 -0
- package/src/cli/commands/DrizzleCommands.ts +230 -10
- package/src/cli/commands/ViteCommands.ts +47 -23
- package/src/cli/defineConfig.ts +15 -0
- package/src/cli/index.ts +3 -0
- package/src/cli/services/AlephaCliUtils.ts +10 -154
- 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 +499 -95
- package/src/core/Alepha.ts +1 -1
- package/src/core/providers/SchemaValidator.ts +23 -1
- package/src/file/providers/NodeFileSystemProvider.ts +3 -1
- package/src/mcp/errors/McpError.ts +72 -0
- package/src/mcp/helpers/jsonrpc.ts +163 -0
- package/src/mcp/index.ts +132 -0
- package/src/mcp/interfaces/McpTypes.ts +248 -0
- package/src/mcp/primitives/$prompt.ts +188 -0
- package/src/mcp/primitives/$resource.ts +171 -0
- package/src/mcp/primitives/$tool.ts +285 -0
- package/src/mcp/providers/McpServerProvider.ts +382 -0
- package/src/mcp/transports/SseMcpTransport.ts +172 -0
- package/src/mcp/transports/StdioMcpTransport.ts +126 -0
- package/src/orm/index.ts +20 -4
- package/src/orm/interfaces/PgQueryWhere.ts +1 -26
- package/src/orm/providers/drivers/BunPostgresProvider.ts +225 -0
- package/src/orm/providers/drivers/BunSqliteProvider.ts +180 -0
- package/src/orm/providers/drivers/CloudflareD1Provider.ts +164 -0
- package/src/orm/providers/drivers/DatabaseProvider.ts +25 -0
- package/src/orm/providers/drivers/NodePostgresProvider.ts +0 -25
- package/src/orm/providers/drivers/NodeSqliteProvider.ts +3 -1
- 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/plugins/viteAlephaBuild.ts +8 -2
- package/src/vite/plugins/viteAlephaDev.ts +6 -2
- package/src/vite/tasks/buildServer.ts +2 -1
- package/src/vite/tasks/generateCloudflare.ts +43 -15
- package/src/vite/tasks/runAlepha.ts +1 -0
- package/src/orm/services/PgJsonQueryManager.ts +0 -511
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
import { $hook, $inject, Alepha, AlephaError } from "alepha";
|
|
2
|
+
import { $logger } from "alepha/logger";
|
|
3
|
+
import type { RedisClient as BunRedisClient } from "bun";
|
|
4
|
+
import { BunRedisProvider } from "./BunRedisProvider.ts";
|
|
5
|
+
import {
|
|
6
|
+
RedisSubscriberProvider,
|
|
7
|
+
type SubscribeCallback,
|
|
8
|
+
} from "./RedisSubscriberProvider.ts";
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Bun Redis subscriber provider for pub/sub operations.
|
|
12
|
+
*
|
|
13
|
+
* This provider creates a dedicated Redis connection for subscriptions,
|
|
14
|
+
* as Redis requires separate connections for pub/sub operations.
|
|
15
|
+
*
|
|
16
|
+
* @example
|
|
17
|
+
* ```ts
|
|
18
|
+
* const subscriber = alepha.inject(RedisSubscriberProvider);
|
|
19
|
+
* await subscriber.subscribe("channel", (message, channel) => {
|
|
20
|
+
* console.log(`Received: ${message} on ${channel}`);
|
|
21
|
+
* });
|
|
22
|
+
* ```
|
|
23
|
+
*/
|
|
24
|
+
export class BunRedisSubscriberProvider extends RedisSubscriberProvider {
|
|
25
|
+
protected readonly log = $logger();
|
|
26
|
+
protected readonly alepha = $inject(Alepha);
|
|
27
|
+
protected readonly redisProvider = $inject(BunRedisProvider);
|
|
28
|
+
protected client?: BunRedisClient;
|
|
29
|
+
|
|
30
|
+
public get subscriber(): BunRedisClient {
|
|
31
|
+
if (!this.client?.connected) {
|
|
32
|
+
throw new AlephaError("Redis subscriber client is not ready");
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
return this.client;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
public override get isReady(): boolean {
|
|
39
|
+
return this.client?.connected ?? false;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
protected readonly start = $hook({
|
|
43
|
+
on: "start",
|
|
44
|
+
handler: () => this.connect(),
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
protected readonly stop = $hook({
|
|
48
|
+
on: "stop",
|
|
49
|
+
handler: () => this.close(),
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Connect to the Redis server for subscriptions.
|
|
54
|
+
*/
|
|
55
|
+
public override async connect(): Promise<void> {
|
|
56
|
+
this.log.debug("Connecting subscriber...");
|
|
57
|
+
this.client = await this.redisProvider.duplicate();
|
|
58
|
+
this.log.info("Subscriber connection OK");
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Close the subscriber connection.
|
|
63
|
+
*/
|
|
64
|
+
public override async close(): Promise<void> {
|
|
65
|
+
if (this.client) {
|
|
66
|
+
this.log.debug("Closing subscriber connection...");
|
|
67
|
+
this.client.close();
|
|
68
|
+
this.client = undefined;
|
|
69
|
+
this.log.info("Subscriber connection closed");
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
public override async subscribe(
|
|
74
|
+
channel: string,
|
|
75
|
+
callback: SubscribeCallback,
|
|
76
|
+
): Promise<void> {
|
|
77
|
+
await this.subscriber.subscribe(channel, (message, ch) => {
|
|
78
|
+
// Bun's callback provides Buffer or string, normalize to string
|
|
79
|
+
const msg =
|
|
80
|
+
typeof message === "object" && message !== null
|
|
81
|
+
? Buffer.from(message as Uint8Array).toString()
|
|
82
|
+
: String(message);
|
|
83
|
+
callback(msg, ch);
|
|
84
|
+
});
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
public override async unsubscribe(
|
|
88
|
+
channel: string,
|
|
89
|
+
_callback?: SubscribeCallback,
|
|
90
|
+
): Promise<void> {
|
|
91
|
+
// Bun's unsubscribe doesn't support callback filtering
|
|
92
|
+
await this.subscriber.unsubscribe(channel);
|
|
93
|
+
}
|
|
94
|
+
}
|
|
@@ -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
|
+
}
|