@lobu/core 3.0.10 → 3.0.12

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.
@@ -1,200 +0,0 @@
1
- import type Redis from "ioredis";
2
- import { createLogger, type Logger } from "../logger";
3
- import { safeJsonParse, safeJsonStringify } from "../utils/json";
4
-
5
- function errMsg(error: unknown): string {
6
- return error instanceof Error ? error.message : String(error);
7
- }
8
-
9
- export interface RedisStoreConfig {
10
- redis: Redis;
11
- keyPrefix: string;
12
- loggerName: string;
13
- }
14
-
15
- /**
16
- * Base class for all Redis-backed stores
17
- * Provides common CRUD operations with JSON serialization
18
- *
19
- * Consolidates:
20
- * - packages/gateway/src/auth/credential-store.ts (BaseCredentialStore)
21
- * - packages/gateway/src/infrastructure/redis/store.ts (BaseRedisStore)
22
- */
23
- export abstract class BaseRedisStore<T> {
24
- protected logger: Logger;
25
- protected redis: Redis;
26
- protected keyPrefix: string;
27
-
28
- constructor(config: RedisStoreConfig) {
29
- this.redis = config.redis;
30
- this.keyPrefix = config.keyPrefix;
31
- this.logger = createLogger(config.loggerName);
32
- }
33
-
34
- /**
35
- * Build Redis key from parts
36
- */
37
- protected buildKey(...parts: string[]): string {
38
- return [this.keyPrefix, ...parts].join(":");
39
- }
40
-
41
- /**
42
- * Get value from Redis
43
- * Returns null if not found or validation fails
44
- */
45
- protected async get(key: string): Promise<T | null> {
46
- try {
47
- const data = await this.redis.get(key);
48
- if (!data) {
49
- return null;
50
- }
51
-
52
- const value = this.deserialize(data);
53
-
54
- // Validate after deserialization
55
- if (!this.validate(value)) {
56
- this.logger.warn("Invalid data after deserialization", { key });
57
- return null;
58
- }
59
-
60
- return value;
61
- } catch (error) {
62
- this.logger.error("Failed to get from Redis", {
63
- error: errMsg(error),
64
- key,
65
- });
66
- return null;
67
- }
68
- }
69
-
70
- /**
71
- * Set value in Redis
72
- */
73
- protected async set(
74
- key: string,
75
- value: T,
76
- ttlSeconds?: number
77
- ): Promise<void> {
78
- try {
79
- const data = this.serialize(value);
80
- if (ttlSeconds) {
81
- await this.redis.setex(key, ttlSeconds, data);
82
- } else {
83
- await this.redis.set(key, data);
84
- }
85
- } catch (error) {
86
- this.logger.error("Failed to set in Redis", {
87
- error: errMsg(error),
88
- key,
89
- });
90
- throw error;
91
- }
92
- }
93
-
94
- /**
95
- * Delete value from Redis
96
- */
97
- protected async delete(key: string): Promise<void> {
98
- try {
99
- await this.redis.del(key);
100
- } catch (error) {
101
- this.logger.error("Failed to delete from Redis", {
102
- error: errMsg(error),
103
- key,
104
- });
105
- }
106
- }
107
-
108
- /**
109
- * Scan for all keys matching a prefix (cursor-based, production-safe).
110
- * Returns full key strings matching `{prefix}*`.
111
- */
112
- protected async scanByPrefix(prefix: string): Promise<string[]> {
113
- const results: string[] = [];
114
- let cursor = "0";
115
- try {
116
- do {
117
- const [nextCursor, keys] = await this.redis.scan(
118
- cursor,
119
- "MATCH",
120
- `${prefix}*`,
121
- "COUNT",
122
- 100
123
- );
124
- cursor = nextCursor;
125
- results.push(...keys);
126
- } while (cursor !== "0");
127
- } catch (error) {
128
- this.logger.error("Failed to scan by prefix", {
129
- error: errMsg(error),
130
- prefix,
131
- });
132
- }
133
- return results;
134
- }
135
-
136
- /**
137
- * Check if key exists in Redis
138
- */
139
- protected async exists(key: string): Promise<boolean> {
140
- try {
141
- const result = await this.redis.exists(key);
142
- return result === 1;
143
- } catch (error) {
144
- this.logger.error("Failed to check existence in Redis", {
145
- error: errMsg(error),
146
- key,
147
- });
148
- return false;
149
- }
150
- }
151
-
152
- /**
153
- * Serialize value to string
154
- * Override for custom serialization
155
- */
156
- protected serialize(value: T): string {
157
- const result = safeJsonStringify(value);
158
- if (result === null) {
159
- throw new Error("Failed to serialize value to JSON");
160
- }
161
- return result;
162
- }
163
-
164
- /**
165
- * Deserialize string to value
166
- * Override for custom deserialization
167
- */
168
- protected deserialize(data: string): T {
169
- const result = safeJsonParse<T>(data);
170
- if (result === null) {
171
- throw new Error("Failed to deserialize JSON data");
172
- }
173
- return result;
174
- }
175
-
176
- /**
177
- * Validate value after deserialization
178
- * Override to add custom validation logic
179
- * Return false to reject invalid data
180
- */
181
- protected validate(_value: T): boolean {
182
- return true;
183
- }
184
- }
185
-
186
- /**
187
- * Specialized base for credential stores
188
- * Validates that accessToken field exists
189
- */
190
- export abstract class BaseCredentialStore<
191
- T extends { accessToken: string },
192
- > extends BaseRedisStore<T> {
193
- protected override validate(value: T): boolean {
194
- if (!value.accessToken) {
195
- this.logger.warn("Invalid credentials: missing accessToken");
196
- return false;
197
- }
198
- return true;
199
- }
200
- }
package/src/sentry.ts DELETED
@@ -1,56 +0,0 @@
1
- import { createLogger, type Logger } from "./logger";
2
-
3
- // Lazy logger initialization to avoid circular dependency
4
- let _logger: Logger | null = null;
5
- function getLogger(): Logger {
6
- if (!_logger) {
7
- _logger = createLogger("sentry");
8
- }
9
- return _logger;
10
- }
11
-
12
- let sentryInstance: typeof import("@sentry/node") | null = null;
13
-
14
- /**
15
- * Initialize Sentry with configuration from environment variables.
16
- * Only initializes if SENTRY_DSN is set — no implicit error reporting.
17
- * Uses dynamic import to avoid module resolution issues in dev mode.
18
- */
19
- export async function initSentry() {
20
- const sentryDsn = process.env.SENTRY_DSN;
21
- if (!sentryDsn) {
22
- getLogger().debug("Sentry disabled (no SENTRY_DSN configured)");
23
- return;
24
- }
25
-
26
- try {
27
- const Sentry = await import("@sentry/node");
28
- sentryInstance = Sentry;
29
-
30
- Sentry.init({
31
- dsn: sentryDsn,
32
- sendDefaultPii: true,
33
- profileSessionSampleRate: 1.0,
34
- tracesSampleRate: 1.0, // Capture 100% of traces for better visibility
35
- integrations: [
36
- Sentry.consoleLoggingIntegration({ levels: ["log", "warn", "error"] }),
37
- Sentry.redisIntegration(),
38
- ],
39
- });
40
-
41
- getLogger().debug("Sentry monitoring initialized");
42
- } catch (error) {
43
- getLogger().warn(
44
- "Sentry initialization failed (continuing without monitoring):",
45
- error
46
- );
47
- }
48
- }
49
-
50
- /**
51
- * Get the initialized Sentry instance
52
- * @returns Sentry instance or null if not initialized
53
- */
54
- export function getSentry(): typeof import("@sentry/node") | null {
55
- return sentryInstance;
56
- }
package/src/trace.ts DELETED
@@ -1,32 +0,0 @@
1
- /**
2
- * Trace ID utilities for end-to-end message lifecycle observability.
3
- * Trace IDs propagate through the entire pipeline:
4
- * [WhatsApp Message] -> [Queue] -> [Worker Creation] -> [PVC Setup] -> [Agent Runtime] -> [Response]
5
- *
6
- * When OpenTelemetry is initialized, spans are sent to Tempo for waterfall visualization.
7
- * Use createSpan/createChildSpan from ./otel.ts for actual span creation.
8
- */
9
-
10
- /**
11
- * Generate a trace ID from a message ID.
12
- * Format: tr-{messageId prefix}-{timestamp base36}-{random}
13
- * Example: tr-abc12345-lx4k-a3b2
14
- */
15
- export function generateTraceId(messageId: string): string {
16
- const timestamp = Date.now().toString(36);
17
- const random = Math.random().toString(36).substring(2, 6);
18
- // Take first 8 chars of messageId, sanitize for safe logging
19
- const shortMessageId = messageId.replace(/[^a-zA-Z0-9]/g, "").substring(0, 8);
20
- return `tr-${shortMessageId}-${timestamp}-${random}`;
21
- }
22
-
23
- /**
24
- * Extract trace ID from various payload formats.
25
- * Checks both top-level and nested platformMetadata.
26
- */
27
- export function extractTraceId(payload: {
28
- traceId?: string;
29
- platformMetadata?: { traceId?: string };
30
- }): string | undefined {
31
- return payload?.traceId || payload?.platformMetadata?.traceId;
32
- }