@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.
- package/LICENSE +21 -0
- package/README.md +99 -2
- package/dist/adapter.d.ts +30 -0
- package/dist/adapter.d.ts.map +1 -0
- package/dist/adapter.js +96 -0
- package/dist/adapter.js.map +1 -0
- package/dist/conversations.d.ts +26 -0
- package/dist/conversations.d.ts.map +1 -0
- package/dist/conversations.js +581 -0
- package/dist/conversations.js.map +1 -0
- package/dist/counters.d.ts +11 -0
- package/dist/counters.d.ts.map +1 -0
- package/dist/counters.js +86 -0
- package/dist/counters.js.map +1 -0
- package/dist/events.d.ts +31 -0
- package/dist/events.d.ts.map +1 -0
- package/dist/events.js +165 -0
- package/dist/events.js.map +1 -0
- package/dist/index.d.ts +13 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +32 -0
- package/dist/index.js.map +1 -0
- package/dist/kv.d.ts +17 -0
- package/dist/kv.d.ts.map +1 -0
- package/dist/kv.js +84 -0
- package/dist/kv.js.map +1 -0
- package/dist/types.d.ts +31 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +2 -0
- package/dist/types.js.map +1 -0
- package/package.json +58 -3
package/dist/counters.js
ADDED
|
@@ -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"}
|
package/dist/events.d.ts
ADDED
|
@@ -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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"}
|
package/dist/index.d.ts
ADDED
|
@@ -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
|
package/dist/kv.d.ts.map
ADDED
|
@@ -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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"}
|
package/dist/types.d.ts
ADDED
|
@@ -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 @@
|
|
|
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.
|
|
4
|
-
"
|
|
5
|
-
"
|
|
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
|
}
|