@chatcops/store-redis 0.0.0-stage → 0.2.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,86 @@
1
+ import { decodeEvalResult, formatSlotPrefix } from './adapter.js';
2
+ const INCREMENT_SCRIPT = `
3
+ local by = tonumber(ARGV[1])
4
+ local ttl = tonumber(ARGV[2])
5
+
6
+ local serverTime = redis.call('TIME')
7
+ local now = tonumber(serverTime[1]) * 1000 + math.floor(tonumber(serverTime[2]) / 1000)
8
+
9
+ local pttl = redis.call('PTTL', KEYS[1])
10
+
11
+ if pttl >= 0 then
12
+ local current = redis.call('HGET', KEYS[1], 'value')
13
+ local newVal = (current and tonumber(current) or 0) + by
14
+ local expiresAt = now + pttl
15
+ local newValStr = string.format('%.17g', newVal)
16
+ local expiresAtStr = string.format('%.17g', expiresAt)
17
+ redis.call('HSET', KEYS[1], 'value', newValStr, 'expiresAt', expiresAtStr)
18
+ return cjson.encode({ newValStr, expiresAtStr })
19
+ else
20
+ local newVal = by
21
+ local newValStr = string.format('%.17g', newVal)
22
+ if ttl <= 0 then
23
+ local expiresAtStr = string.format('%.17g', now)
24
+ return cjson.encode({ newValStr, expiresAtStr })
25
+ else
26
+ local expiresAt = now + ttl
27
+ local expiresAtStr = string.format('%.17g', expiresAt)
28
+ redis.call('HSET', KEYS[1], 'value', newValStr, 'expiresAt', expiresAtStr)
29
+ redis.call('PEXPIRE', KEYS[1], ttl)
30
+ return cjson.encode({ newValStr, expiresAtStr })
31
+ end
32
+ end
33
+ `;
34
+ const GET_SCRIPT = `
35
+ local pttl = redis.call('PTTL', KEYS[1])
36
+ if pttl < 0 then
37
+ return nil
38
+ end
39
+
40
+ local serverTime = redis.call('TIME')
41
+ local now = tonumber(serverTime[1]) * 1000 + math.floor(tonumber(serverTime[2]) / 1000)
42
+
43
+ local val = redis.call('HGET', KEYS[1], 'value')
44
+ if not val then
45
+ return nil
46
+ end
47
+
48
+ local numVal = tonumber(val)
49
+ local valStr = numVal and string.format('%.17g', numVal) or tostring(val)
50
+ local expiresAt = now + pttl
51
+ local expiresAtStr = string.format('%.17g', expiresAt)
52
+ return cjson.encode({ valStr, expiresAtStr })
53
+ `;
54
+ export class RedisCounterStore {
55
+ redis;
56
+ slotPrefix;
57
+ constructor(redis, prefix) {
58
+ this.redis = redis;
59
+ this.slotPrefix = formatSlotPrefix(prefix);
60
+ }
61
+ key(key) {
62
+ return `${this.slotPrefix}:counter:${key}`;
63
+ }
64
+ async increment(key, by, ttlMs) {
65
+ const roundedTtl = Math.ceil(ttlMs);
66
+ const raw = await this.redis.eval(INCREMENT_SCRIPT, [this.key(key)], [by, roundedTtl]);
67
+ const res = decodeEvalResult(raw);
68
+ return {
69
+ value: Number(res[0]),
70
+ expiresAt: Number(res[1]),
71
+ };
72
+ }
73
+ async get(key) {
74
+ const raw = await this.redis.eval(GET_SCRIPT, [this.key(key)], []);
75
+ if (!raw)
76
+ return null;
77
+ const res = decodeEvalResult(raw);
78
+ if (!res)
79
+ return null;
80
+ return {
81
+ value: Number(res[0]),
82
+ expiresAt: Number(res[1]),
83
+ };
84
+ }
85
+ }
86
+ //# sourceMappingURL=counters.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"counters.js","sourceRoot":"","sources":["../src/counters.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,gBAAgB,EAAE,gBAAgB,EAAE,MAAM,cAAc,CAAC;AAGlE,MAAM,gBAAgB,GAAG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CA+BxB,CAAC;AAEF,MAAM,UAAU,GAAG;;;;;;;;;;;;;;;;;;;CAmBlB,CAAC;AAEF,MAAM,OAAO,iBAAiB;IAIT;IAHF,UAAU,CAAS;IAEpC,YACmB,KAAgB,EACjC,MAAc;QADG,UAAK,GAAL,KAAK,CAAW;QAGjC,IAAI,CAAC,UAAU,GAAG,gBAAgB,CAAC,MAAM,CAAC,CAAC;IAC7C,CAAC;IAEO,GAAG,CAAC,GAAW;QACrB,OAAO,GAAG,IAAI,CAAC,UAAU,YAAY,GAAG,EAAE,CAAC;IAC7C,CAAC;IAED,KAAK,CAAC,SAAS,CAAC,GAAW,EAAE,EAAU,EAAE,KAAa;QACpD,MAAM,UAAU,GAAG,IAAI,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;QACpC,MAAM,GAAG,GAAG,MAAM,IAAI,CAAC,KAAK,CAAC,IAAI,CAC/B,gBAAgB,EAChB,CAAC,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC,EACf,CAAC,EAAE,EAAE,UAAU,CAAC,CACjB,CAAC;QAEF,MAAM,GAAG,GAAG,gBAAgB,CAAmB,GAAG,CAAC,CAAC;QACpD,OAAO;YACL,KAAK,EAAE,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC;YACrB,SAAS,EAAE,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC;SAC1B,CAAC;IACJ,CAAC;IAED,KAAK,CAAC,GAAG,CAAC,GAAW;QACnB,MAAM,GAAG,GAAG,MAAM,IAAI,CAAC,KAAK,CAAC,IAAI,CAC/B,UAAU,EACV,CAAC,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC,EACf,EAAE,CACH,CAAC;QAEF,IAAI,CAAC,GAAG;YAAE,OAAO,IAAI,CAAC;QACtB,MAAM,GAAG,GAAG,gBAAgB,CAA0B,GAAG,CAAC,CAAC;QAC3D,IAAI,CAAC,GAAG;YAAE,OAAO,IAAI,CAAC;QAEtB,OAAO;YACL,KAAK,EAAE,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC;YACrB,SAAS,EAAE,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC;SAC1B,CAAC;IACJ,CAAC;CACF"}
@@ -0,0 +1,31 @@
1
+ import type { AnalyticsEvent, EventCount, EventQuery, EventSink } from '@chatcops/core';
2
+ import type { RedisLike } from './types.js';
3
+ /**
4
+ * RedisEventSink collects analytics events and aggregates counts using hourly count hashes.
5
+ *
6
+ * Events are stored in hourly buckets ({<prefix>}:events:<hour>) and indexed in a sorted set
7
+ * ({<prefix>}:events:hours). This design provides several critical guarantees:
8
+ * 1. Historical & Out-of-Order Timestamps: Unlike Redis Streams (XADD) which require monotonically
9
+ * increasing entry IDs and reject or misorder past timestamps, hourly hashes increment counters
10
+ * into the appropriate historical hour bucket seamlessly.
11
+ * 2. Efficient Aggregation: Event counts are pre-aggregated inside Redis hashes (HINCRBY), avoiding
12
+ * the need to download and scan raw event payloads over the network.
13
+ * 3. Cluster Compatibility: All keys are hash-tagged with {<prefix>} and all keys accessed in scripts
14
+ * are explicitly passed through the KEYS array.
15
+ *
16
+ * Query Semantics:
17
+ * Because events are pre-aggregated into 1-hour UTC buckets, queries round `from` down to the start
18
+ * of its UTC hour (Math.floor(from / HOUR_MS) * HOUR_MS) and `to` up to the end of its UTC hour
19
+ * (Math.ceil(to / HOUR_MS) * HOUR_MS) to select all hourly buckets overlapping the requested window.
20
+ */
21
+ export declare class RedisEventSink implements EventSink {
22
+ private readonly redis;
23
+ private readonly ttlMs;
24
+ private readonly hoursKey;
25
+ private readonly slotPrefix;
26
+ constructor(redis: RedisLike, prefix: string, ttlMs?: number);
27
+ private hourKey;
28
+ write(events: AnalyticsEvent[]): Promise<void>;
29
+ query(query: EventQuery): Promise<EventCount[]>;
30
+ }
31
+ //# sourceMappingURL=events.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"events.d.ts","sourceRoot":"","sources":["../src/events.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,UAAU,EAAE,UAAU,EAAE,SAAS,EAAE,MAAM,gBAAgB,CAAC;AAExF,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,YAAY,CAAC;AAiE5C;;;;;;;;;;;;;;;;;GAiBG;AACH,qBAAa,cAAe,YAAW,SAAS;IAK5C,OAAO,CAAC,QAAQ,CAAC,KAAK;IAEtB,OAAO,CAAC,QAAQ,CAAC,KAAK;IANxB,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAS;IAClC,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAS;gBAGjB,KAAK,EAAE,SAAS,EACjC,MAAM,EAAE,MAAM,EACG,KAAK,GAAE,MAA6B;IAMvD,OAAO,CAAC,OAAO;IAIT,KAAK,CAAC,MAAM,EAAE,cAAc,EAAE,GAAG,OAAO,CAAC,IAAI,CAAC;IAwC9C,KAAK,CAAC,KAAK,EAAE,UAAU,GAAG,OAAO,CAAC,UAAU,EAAE,CAAC;CA6DtD"}
package/dist/events.js ADDED
@@ -0,0 +1,165 @@
1
+ import { decodeEvalResult, formatSlotPrefix, normalizeList } from './adapter.js';
2
+ const HOUR_MS = 60 * 60 * 1000;
3
+ const DAY_MS = 24 * 60 * 60 * 1000;
4
+ // Retain event hashes for 90 days
5
+ const DEFAULT_EVENT_TTL_MS = 90 * DAY_MS;
6
+ const WRITE_SCRIPT = `
7
+ local ttl = tonumber(ARGV[1])
8
+ local numPairs = tonumber(ARGV[2])
9
+
10
+ for i = 1, numPairs do
11
+ local hour = ARGV[2 + (i - 1) * 3 + 1]
12
+ local eventType = ARGV[2 + (i - 1) * 3 + 2]
13
+ local keyIdx = tonumber(ARGV[2 + (i - 1) * 3 + 3])
14
+ local hashKey = KEYS[keyIdx]
15
+
16
+ redis.call('ZADD', KEYS[1], tonumber(hour), hour)
17
+ redis.call('HINCRBY', hashKey, eventType, 1)
18
+ if ttl > 0 then
19
+ redis.call('PEXPIRE', hashKey, ttl)
20
+ end
21
+ end
22
+ return 1
23
+ `;
24
+ const GET_HOURS_SCRIPT = `
25
+ local from = tonumber(ARGV[1])
26
+ local to = tonumber(ARGV[2])
27
+ local hours = redis.call('ZRANGEBYSCORE', KEYS[1], from, '(' .. to)
28
+ if #hours == 0 then
29
+ return '[]'
30
+ end
31
+ return cjson.encode(hours)
32
+ `;
33
+ const GET_COUNTS_SCRIPT = `
34
+ local filterType = ARGV[1]
35
+ local results = {}
36
+
37
+ for i = 2, #KEYS do
38
+ local hashKey = KEYS[i]
39
+ local hourStr = ARGV[i]
40
+ local hour = tonumber(hourStr)
41
+ if filterType ~= '' then
42
+ local count = redis.call('HGET', hashKey, filterType)
43
+ if count then
44
+ table.insert(results, { hour, filterType, tonumber(count) })
45
+ end
46
+ else
47
+ local all = redis.call('HGETALL', hashKey)
48
+ for j = 1, #all, 2 do
49
+ local t = all[j]
50
+ local c = tonumber(all[j + 1])
51
+ table.insert(results, { hour, t, c })
52
+ end
53
+ end
54
+ end
55
+
56
+ if #results == 0 then
57
+ return '[]'
58
+ end
59
+ return cjson.encode(results)
60
+ `;
61
+ /**
62
+ * RedisEventSink collects analytics events and aggregates counts using hourly count hashes.
63
+ *
64
+ * Events are stored in hourly buckets ({<prefix>}:events:<hour>) and indexed in a sorted set
65
+ * ({<prefix>}:events:hours). This design provides several critical guarantees:
66
+ * 1. Historical & Out-of-Order Timestamps: Unlike Redis Streams (XADD) which require monotonically
67
+ * increasing entry IDs and reject or misorder past timestamps, hourly hashes increment counters
68
+ * into the appropriate historical hour bucket seamlessly.
69
+ * 2. Efficient Aggregation: Event counts are pre-aggregated inside Redis hashes (HINCRBY), avoiding
70
+ * the need to download and scan raw event payloads over the network.
71
+ * 3. Cluster Compatibility: All keys are hash-tagged with {<prefix>} and all keys accessed in scripts
72
+ * are explicitly passed through the KEYS array.
73
+ *
74
+ * Query Semantics:
75
+ * Because events are pre-aggregated into 1-hour UTC buckets, queries round `from` down to the start
76
+ * of its UTC hour (Math.floor(from / HOUR_MS) * HOUR_MS) and `to` up to the end of its UTC hour
77
+ * (Math.ceil(to / HOUR_MS) * HOUR_MS) to select all hourly buckets overlapping the requested window.
78
+ */
79
+ export class RedisEventSink {
80
+ redis;
81
+ ttlMs;
82
+ hoursKey;
83
+ slotPrefix;
84
+ constructor(redis, prefix, ttlMs = DEFAULT_EVENT_TTL_MS) {
85
+ this.redis = redis;
86
+ this.ttlMs = ttlMs;
87
+ this.slotPrefix = formatSlotPrefix(prefix);
88
+ this.hoursKey = `${this.slotPrefix}:events:hours`;
89
+ }
90
+ hourKey(hour) {
91
+ return `${this.slotPrefix}:events:${hour}`;
92
+ }
93
+ async write(events) {
94
+ if (events.length === 0)
95
+ return;
96
+ // Map each unique hour to a key in KEYS to ensure all accessed keys are passed in KEYS
97
+ const hourKeyMap = new Map();
98
+ for (const event of events) {
99
+ const hour = Math.floor(event.timestamp / HOUR_MS) * HOUR_MS;
100
+ if (!hourKeyMap.has(hour)) {
101
+ hourKeyMap.set(hour, this.hourKey(hour));
102
+ }
103
+ }
104
+ const uniqueHours = Array.from(hourKeyMap.keys());
105
+ const keys = [this.hoursKey];
106
+ const hourToKeyIdx = new Map();
107
+ for (let i = 0; i < uniqueHours.length; i++) {
108
+ const hour = uniqueHours[i];
109
+ const hKey = hourKeyMap.get(hour);
110
+ if (hKey) {
111
+ keys.push(hKey);
112
+ // 1-based index in KEYS: KEYS[1] is hoursKey, so KEYS[2 + i] is the hourKey
113
+ hourToKeyIdx.set(hour, 2 + i);
114
+ }
115
+ }
116
+ const scriptArgs = [this.ttlMs, events.length];
117
+ for (const event of events) {
118
+ const hour = Math.floor(event.timestamp / HOUR_MS) * HOUR_MS;
119
+ const keyIdx = hourToKeyIdx.get(hour) ?? 2;
120
+ scriptArgs.push(hour, event.type, keyIdx);
121
+ }
122
+ await this.redis.eval(WRITE_SCRIPT, keys, scriptArgs);
123
+ }
124
+ async query(query) {
125
+ const filterType = query.type ?? '';
126
+ // Hour-rounding semantics: round `from` down to start of hour, `to` up to end of hour
127
+ const fromHour = Math.floor(query.from / HOUR_MS) * HOUR_MS;
128
+ const toHour = Math.ceil(query.to / HOUR_MS) * HOUR_MS;
129
+ // Step 1: Query which hour buckets exist in the [fromHour, toHour) window
130
+ const rawHours = await this.redis.eval(GET_HOURS_SCRIPT, [this.hoursKey], [fromHour, toHour]);
131
+ const hours = normalizeList(decodeEvalResult(rawHours));
132
+ if (hours.length === 0) {
133
+ return [];
134
+ }
135
+ // Step 2: Pass all required hour keys in KEYS for cluster-safe reading
136
+ const keys = [this.hoursKey];
137
+ const scriptArgs = [filterType];
138
+ for (const h of hours) {
139
+ keys.push(this.hourKey(h));
140
+ scriptArgs.push(h);
141
+ }
142
+ const rawCounts = await this.redis.eval(GET_COUNTS_SCRIPT, keys, scriptArgs);
143
+ const results = normalizeList(decodeEvalResult(rawCounts));
144
+ if (results.length === 0) {
145
+ return [];
146
+ }
147
+ const bucketSize = query.groupBy === 'hour' ? HOUR_MS : DAY_MS;
148
+ const counts = new Map();
149
+ if (results) {
150
+ for (const [hour, type, count] of results) {
151
+ const bucket = Math.floor(Number(hour) / bucketSize) * bucketSize;
152
+ const key = `${bucket}:${type}`;
153
+ const existing = counts.get(key);
154
+ if (existing) {
155
+ existing.count += count;
156
+ }
157
+ else {
158
+ counts.set(key, { bucket, type, count });
159
+ }
160
+ }
161
+ }
162
+ return Array.from(counts.values()).sort((a, b) => a.bucket - b.bucket || (a.type < b.type ? -1 : 1));
163
+ }
164
+ }
165
+ //# sourceMappingURL=events.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"events.js","sourceRoot":"","sources":["../src/events.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,13 @@
1
+ import type { ChatCopsStorage } from '@chatcops/core';
2
+ import { fromIoRedis } from './adapter.js';
3
+ import { RedisConversationStore } from './conversations.js';
4
+ import { RedisCounterStore } from './counters.js';
5
+ import { RedisEventSink } from './events.js';
6
+ import { RedisKeyValueStore } from './kv.js';
7
+ import type { IoRedisLike, RedisLike, RedisSetOptions, RedisStorageOptions } from './types.js';
8
+ export declare const DEFAULT_PREFIX = "chatcops";
9
+ export declare const DEFAULT_CONVERSATION_TTL_MS: number;
10
+ export declare function redisStorage(options: RedisStorageOptions): ChatCopsStorage;
11
+ export { fromIoRedis, RedisConversationStore, RedisCounterStore, RedisKeyValueStore, RedisEventSink, };
12
+ export type { RedisLike, IoRedisLike, RedisStorageOptions, RedisSetOptions, };
13
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,gBAAgB,CAAC;AACtD,OAAO,EAAE,WAAW,EAAE,MAAM,cAAc,CAAC;AAC3C,OAAO,EAAE,sBAAsB,EAAE,MAAM,oBAAoB,CAAC;AAC5D,OAAO,EAAE,iBAAiB,EAAE,MAAM,eAAe,CAAC;AAClD,OAAO,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AAC7C,OAAO,EAAE,kBAAkB,EAAE,MAAM,SAAS,CAAC;AAC7C,OAAO,KAAK,EACV,WAAW,EACX,SAAS,EACT,eAAe,EACf,mBAAmB,EACpB,MAAM,YAAY,CAAC;AAEpB,eAAO,MAAM,cAAc,aAAa,CAAC;AACzC,eAAO,MAAM,2BAA2B,QAA2B,CAAC;AAEpE,wBAAgB,YAAY,CAAC,OAAO,EAAE,mBAAmB,GAAG,eAAe,CA0B1E;AAED,OAAO,EACL,WAAW,EACX,sBAAsB,EACtB,iBAAiB,EACjB,kBAAkB,EAClB,cAAc,GACf,CAAC;AAEF,YAAY,EACV,SAAS,EACT,WAAW,EACX,mBAAmB,EACnB,eAAe,GAChB,CAAC"}
package/dist/index.js ADDED
@@ -0,0 +1,32 @@
1
+ import { fromIoRedis } from './adapter.js';
2
+ import { RedisConversationStore } from './conversations.js';
3
+ import { RedisCounterStore } from './counters.js';
4
+ import { RedisEventSink } from './events.js';
5
+ import { RedisKeyValueStore } from './kv.js';
6
+ export const DEFAULT_PREFIX = 'chatcops';
7
+ export const DEFAULT_CONVERSATION_TTL_MS = 30 * 24 * 60 * 60 * 1000; // 30 days
8
+ export function redisStorage(options) {
9
+ if (!options?.redis) {
10
+ throw new TypeError('redisStorage requires a redis client');
11
+ }
12
+ const { redis, prefix = DEFAULT_PREFIX, conversationTtlMs = DEFAULT_CONVERSATION_TTL_MS, } = options;
13
+ return {
14
+ conversations: new RedisConversationStore(redis, prefix, conversationTtlMs),
15
+ counters: new RedisCounterStore(redis, prefix),
16
+ kv: new RedisKeyValueStore(redis, prefix),
17
+ events: new RedisEventSink(redis, prefix),
18
+ close: async () => {
19
+ if (typeof redis.quit === 'function') {
20
+ await redis.quit();
21
+ }
22
+ else if (typeof redis.close === 'function') {
23
+ await redis.close();
24
+ }
25
+ else if (typeof redis.disconnect === 'function') {
26
+ redis.disconnect();
27
+ }
28
+ },
29
+ };
30
+ }
31
+ export { fromIoRedis, RedisConversationStore, RedisCounterStore, RedisKeyValueStore, RedisEventSink, };
32
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,WAAW,EAAE,MAAM,cAAc,CAAC;AAC3C,OAAO,EAAE,sBAAsB,EAAE,MAAM,oBAAoB,CAAC;AAC5D,OAAO,EAAE,iBAAiB,EAAE,MAAM,eAAe,CAAC;AAClD,OAAO,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AAC7C,OAAO,EAAE,kBAAkB,EAAE,MAAM,SAAS,CAAC;AAQ7C,MAAM,CAAC,MAAM,cAAc,GAAG,UAAU,CAAC;AACzC,MAAM,CAAC,MAAM,2BAA2B,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,IAAI,CAAC,CAAC,UAAU;AAE/E,MAAM,UAAU,YAAY,CAAC,OAA4B;IACvD,IAAI,CAAC,OAAO,EAAE,KAAK,EAAE,CAAC;QACpB,MAAM,IAAI,SAAS,CAAC,sCAAsC,CAAC,CAAC;IAC9D,CAAC;IAED,MAAM,EACJ,KAAK,EACL,MAAM,GAAG,cAAc,EACvB,iBAAiB,GAAG,2BAA2B,GAChD,GAAG,OAAO,CAAC;IAEZ,OAAO;QACL,aAAa,EAAE,IAAI,sBAAsB,CAAC,KAAK,EAAE,MAAM,EAAE,iBAAiB,CAAC;QAC3E,QAAQ,EAAE,IAAI,iBAAiB,CAAC,KAAK,EAAE,MAAM,CAAC;QAC9C,EAAE,EAAE,IAAI,kBAAkB,CAAC,KAAK,EAAE,MAAM,CAAC;QACzC,MAAM,EAAE,IAAI,cAAc,CAAC,KAAK,EAAE,MAAM,CAAC;QACzC,KAAK,EAAE,KAAK,IAAI,EAAE;YAChB,IAAI,OAAO,KAAK,CAAC,IAAI,KAAK,UAAU,EAAE,CAAC;gBACrC,MAAM,KAAK,CAAC,IAAI,EAAE,CAAC;YACrB,CAAC;iBAAM,IAAI,OAAO,KAAK,CAAC,KAAK,KAAK,UAAU,EAAE,CAAC;gBAC7C,MAAM,KAAK,CAAC,KAAK,EAAE,CAAC;YACtB,CAAC;iBAAM,IAAI,OAAO,KAAK,CAAC,UAAU,KAAK,UAAU,EAAE,CAAC;gBAClD,KAAK,CAAC,UAAU,EAAE,CAAC;YACrB,CAAC;QACH,CAAC;KACF,CAAC;AACJ,CAAC;AAED,OAAO,EACL,WAAW,EACX,sBAAsB,EACtB,iBAAiB,EACjB,kBAAkB,EAClB,cAAc,GACf,CAAC"}
package/dist/kv.d.ts ADDED
@@ -0,0 +1,17 @@
1
+ import type { KeyValueStore } from '@chatcops/core';
2
+ import type { RedisLike } from './types.js';
3
+ export declare class RedisKeyValueStore implements KeyValueStore {
4
+ private readonly redis;
5
+ private readonly slotPrefix;
6
+ constructor(redis: RedisLike, prefix: string);
7
+ private key;
8
+ get<T = unknown>(key: string): Promise<T | null>;
9
+ set(key: string, value: unknown, options?: {
10
+ ttlMs?: number;
11
+ }): Promise<void>;
12
+ setIfAbsent(key: string, value: unknown, options?: {
13
+ ttlMs?: number;
14
+ }): Promise<boolean>;
15
+ delete(key: string): Promise<void>;
16
+ }
17
+ //# sourceMappingURL=kv.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"kv.d.ts","sourceRoot":"","sources":["../src/kv.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,gBAAgB,CAAC;AAEpD,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,YAAY,CAAC;AAO5C,qBAAa,kBAAmB,YAAW,aAAa;IAIpD,OAAO,CAAC,QAAQ,CAAC,KAAK;IAHxB,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAS;gBAGjB,KAAK,EAAE,SAAS,EACjC,MAAM,EAAE,MAAM;IAKhB,OAAO,CAAC,GAAG;IAIL,GAAG,CAAC,CAAC,GAAG,OAAO,EAAE,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,CAAC,GAAG,IAAI,CAAC;IAyBhD,GAAG,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,OAAO,EAAE,OAAO,CAAC,EAAE;QAAE,KAAK,CAAC,EAAE,MAAM,CAAA;KAAE,GAAG,OAAO,CAAC,IAAI,CAAC;IAiB7E,WAAW,CACf,GAAG,EAAE,MAAM,EACX,KAAK,EAAE,OAAO,EACd,OAAO,CAAC,EAAE;QAAE,KAAK,CAAC,EAAE,MAAM,CAAA;KAAE,GAC3B,OAAO,CAAC,OAAO,CAAC;IA8Bb,MAAM,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;CAGzC"}
package/dist/kv.js ADDED
@@ -0,0 +1,84 @@
1
+ import { formatSlotPrefix } from './adapter.js';
2
+ export class RedisKeyValueStore {
3
+ redis;
4
+ slotPrefix;
5
+ constructor(redis, prefix) {
6
+ this.redis = redis;
7
+ this.slotPrefix = formatSlotPrefix(prefix);
8
+ }
9
+ key(key) {
10
+ return `${this.slotPrefix}:kv:${key}`;
11
+ }
12
+ async get(key) {
13
+ const raw = await this.redis.get(this.key(key));
14
+ if (raw === null || raw === undefined)
15
+ return null;
16
+ // Upstash with default automatic deserialization parses the envelope object directly
17
+ if (typeof raw === 'object' && raw !== null && '__cc_kv' in raw) {
18
+ return raw.v;
19
+ }
20
+ // ioredis returns raw string; clients with deserialization disabled also return string
21
+ if (typeof raw === 'string') {
22
+ try {
23
+ const parsed = JSON.parse(raw);
24
+ if (parsed !== null && typeof parsed === 'object' && '__cc_kv' in parsed) {
25
+ return parsed.v;
26
+ }
27
+ return parsed;
28
+ }
29
+ catch {
30
+ return raw;
31
+ }
32
+ }
33
+ return raw;
34
+ }
35
+ async set(key, value, options) {
36
+ const fullKey = this.key(key);
37
+ if (options?.ttlMs !== undefined) {
38
+ const ttlMs = Math.ceil(options.ttlMs);
39
+ if (ttlMs <= 0) {
40
+ // Immediate expiry: key should not persist
41
+ await this.redis.del(fullKey);
42
+ return;
43
+ }
44
+ const payload = { __cc_kv: 1, v: value };
45
+ await this.redis.set(fullKey, JSON.stringify(payload), { px: ttlMs });
46
+ return;
47
+ }
48
+ const payload = { __cc_kv: 1, v: value };
49
+ await this.redis.set(fullKey, JSON.stringify(payload));
50
+ }
51
+ async setIfAbsent(key, value, options) {
52
+ const fullKey = this.key(key);
53
+ if (options?.ttlMs !== undefined) {
54
+ const ttlMs = Math.ceil(options.ttlMs);
55
+ if (ttlMs <= 0) {
56
+ // Immediate expiry: returns true if key was absent, false if key was already present
57
+ const script = `
58
+ if redis.call('EXISTS', KEYS[1]) == 1 then
59
+ return 0
60
+ else
61
+ return 1
62
+ end
63
+ `;
64
+ const res = await this.redis.eval(script, [fullKey], []);
65
+ return Number(res) === 1;
66
+ }
67
+ const payload = { __cc_kv: 1, v: value };
68
+ const res = await this.redis.set(fullKey, JSON.stringify(payload), {
69
+ nx: true,
70
+ px: ttlMs,
71
+ });
72
+ return res === 'OK' || res === true;
73
+ }
74
+ const payload = { __cc_kv: 1, v: value };
75
+ const res = await this.redis.set(fullKey, JSON.stringify(payload), {
76
+ nx: true,
77
+ });
78
+ return res === 'OK' || res === true;
79
+ }
80
+ async delete(key) {
81
+ await this.redis.del(this.key(key));
82
+ }
83
+ }
84
+ //# sourceMappingURL=kv.js.map
package/dist/kv.js.map ADDED
@@ -0,0 +1 @@
1
+ {"version":3,"file":"kv.js","sourceRoot":"","sources":["../src/kv.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,31 @@
1
+ export interface RedisSetOptions {
2
+ px?: number;
3
+ nx?: boolean;
4
+ ex?: number;
5
+ xx?: boolean;
6
+ keepttl?: boolean;
7
+ [key: string]: unknown;
8
+ }
9
+ export interface RedisLike {
10
+ eval<TArgs extends unknown[] = unknown[], TData = unknown>(script: string, keys: string[], args: TArgs): Promise<TData>;
11
+ get<T = unknown>(key: string): Promise<T | null>;
12
+ set(key: string, value: unknown, opts?: RedisSetOptions): Promise<unknown>;
13
+ del(...keys: string[]): Promise<number>;
14
+ quit?(): Promise<unknown>;
15
+ close?(): Promise<unknown>;
16
+ disconnect?(): void;
17
+ }
18
+ export interface IoRedisLike {
19
+ eval(script: string, numKeys: number, ...args: unknown[]): Promise<unknown>;
20
+ get(key: string): Promise<string | null>;
21
+ set(key: string, value: unknown, ...args: unknown[]): Promise<unknown>;
22
+ del(...keys: string[]): Promise<number>;
23
+ quit?(): Promise<unknown>;
24
+ disconnect?(): void;
25
+ }
26
+ export interface RedisStorageOptions {
27
+ redis: RedisLike;
28
+ prefix?: string;
29
+ conversationTtlMs?: number;
30
+ }
31
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA,MAAM,WAAW,eAAe;IAC9B,EAAE,CAAC,EAAE,MAAM,CAAC;IACZ,EAAE,CAAC,EAAE,OAAO,CAAC;IACb,EAAE,CAAC,EAAE,MAAM,CAAC;IACZ,EAAE,CAAC,EAAE,OAAO,CAAC;IACb,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC;CACxB;AAED,MAAM,WAAW,SAAS;IACxB,IAAI,CAAC,KAAK,SAAS,OAAO,EAAE,GAAG,OAAO,EAAE,EAAE,KAAK,GAAG,OAAO,EACvD,MAAM,EAAE,MAAM,EACd,IAAI,EAAE,MAAM,EAAE,EACd,IAAI,EAAE,KAAK,GACV,OAAO,CAAC,KAAK,CAAC,CAAC;IAClB,GAAG,CAAC,CAAC,GAAG,OAAO,EAAE,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,CAAC,GAAG,IAAI,CAAC,CAAC;IACjD,GAAG,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,OAAO,EAAE,IAAI,CAAC,EAAE,eAAe,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;IAC3E,GAAG,CAAC,GAAG,IAAI,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC;IACxC,IAAI,CAAC,IAAI,OAAO,CAAC,OAAO,CAAC,CAAC;IAC1B,KAAK,CAAC,IAAI,OAAO,CAAC,OAAO,CAAC,CAAC;IAC3B,UAAU,CAAC,IAAI,IAAI,CAAC;CACrB;AAED,MAAM,WAAW,WAAW;IAC1B,IAAI,CAAC,MAAM,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,GAAG,IAAI,EAAE,OAAO,EAAE,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;IAC5E,GAAG,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAAC;IACzC,GAAG,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,OAAO,EAAE,GAAG,IAAI,EAAE,OAAO,EAAE,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;IACvE,GAAG,CAAC,GAAG,IAAI,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC;IACxC,IAAI,CAAC,IAAI,OAAO,CAAC,OAAO,CAAC,CAAC;IAC1B,UAAU,CAAC,IAAI,IAAI,CAAC;CACrB;AAED,MAAM,WAAW,mBAAmB;IAClC,KAAK,EAAE,SAAS,CAAC;IACjB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,iBAAiB,CAAC,EAAE,MAAM,CAAC;CAC5B"}
package/dist/types.js ADDED
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":""}
package/package.json CHANGED
@@ -1,6 +1,61 @@
1
1
  {
2
2
  "name": "@chatcops/store-redis",
3
- "version": "0.0.0-stage",
4
- "stub": true,
5
- "description": "Temporary package placeholder for staged publishing"
3
+ "version": "0.2.0",
4
+ "description": "Redis and Upstash storage for ChatCops",
5
+ "keywords": [
6
+ "chatcops",
7
+ "storage",
8
+ "redis",
9
+ "upstash"
10
+ ],
11
+ "homepage": "https://chat.codercops.com",
12
+ "bugs": "https://github.com/codercops/chatcops/issues",
13
+ "type": "module",
14
+ "main": "dist/index.js",
15
+ "types": "dist/index.d.ts",
16
+ "exports": {
17
+ ".": {
18
+ "types": "./dist/index.d.ts",
19
+ "import": "./dist/index.js"
20
+ }
21
+ },
22
+ "files": [
23
+ "dist"
24
+ ],
25
+ "sideEffects": false,
26
+ "peerDependencies": {
27
+ "@upstash/redis": "^1.34.3",
28
+ "@chatcops/core": "^0.6.0"
29
+ },
30
+ "peerDependenciesMeta": {
31
+ "@upstash/redis": {
32
+ "optional": true
33
+ }
34
+ },
35
+ "devDependencies": {
36
+ "@upstash/redis": "^1.34.3",
37
+ "ioredis": "^5.4.2",
38
+ "typescript": "^5.7.0",
39
+ "vitest": "^4.1.11",
40
+ "@chatcops/core": "0.6.0"
41
+ },
42
+ "repository": {
43
+ "type": "git",
44
+ "url": "git+https://github.com/codercops/chatcops.git",
45
+ "directory": "packages/store-redis"
46
+ },
47
+ "publishConfig": {
48
+ "access": "public"
49
+ },
50
+ "license": "MIT",
51
+ "engines": {
52
+ "node": ">=22"
53
+ },
54
+ "scripts": {
55
+ "build": "tsc",
56
+ "typecheck": "tsc -p tsconfig.test.json --noEmit",
57
+ "test": "vitest run",
58
+ "test:watch": "vitest",
59
+ "clean": "rm -rf dist *.tsbuildinfo"
60
+ }
6
61
  }