opencode-graphiti 0.0.0-development
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 +358 -0
- package/esm/_dnt.polyfills.d.ts +166 -0
- package/esm/_dnt.polyfills.d.ts.map +1 -0
- package/esm/_dnt.polyfills.js +177 -0
- package/esm/_dnt.shims.d.ts +6 -0
- package/esm/_dnt.shims.d.ts.map +1 -0
- package/esm/_dnt.shims.js +61 -0
- package/esm/deno.d.ts +45 -0
- package/esm/deno.d.ts.map +1 -0
- package/esm/deno.js +39 -0
- package/esm/mod.d.ts +3 -0
- package/esm/mod.d.ts.map +1 -0
- package/esm/mod.js +2 -0
- package/esm/package.json +3 -0
- package/esm/src/config.d.ts +20 -0
- package/esm/src/config.d.ts.map +1 -0
- package/esm/src/config.js +246 -0
- package/esm/src/handlers/chat.d.ts +14 -0
- package/esm/src/handlers/chat.d.ts.map +1 -0
- package/esm/src/handlers/chat.js +60 -0
- package/esm/src/handlers/compacting.d.ts +9 -0
- package/esm/src/handlers/compacting.d.ts.map +1 -0
- package/esm/src/handlers/compacting.js +30 -0
- package/esm/src/handlers/event.d.ts +22 -0
- package/esm/src/handlers/event.d.ts.map +1 -0
- package/esm/src/handlers/event.js +287 -0
- package/esm/src/handlers/messages.d.ts +9 -0
- package/esm/src/handlers/messages.d.ts.map +1 -0
- package/esm/src/handlers/messages.js +93 -0
- package/esm/src/index.d.ts +5 -0
- package/esm/src/index.d.ts.map +1 -0
- package/esm/src/index.js +153 -0
- package/esm/src/services/batch-drain.d.ts +23 -0
- package/esm/src/services/batch-drain.d.ts.map +1 -0
- package/esm/src/services/batch-drain.js +217 -0
- package/esm/src/services/connection-manager.d.ts +104 -0
- package/esm/src/services/connection-manager.d.ts.map +1 -0
- package/esm/src/services/connection-manager.js +621 -0
- package/esm/src/services/constants.d.ts +7 -0
- package/esm/src/services/constants.d.ts.map +1 -0
- package/esm/src/services/constants.js +6 -0
- package/esm/src/services/context-limit.d.ts +3 -0
- package/esm/src/services/context-limit.d.ts.map +1 -0
- package/esm/src/services/context-limit.js +44 -0
- package/esm/src/services/event-extractor.d.ts +29 -0
- package/esm/src/services/event-extractor.d.ts.map +1 -0
- package/esm/src/services/event-extractor.js +659 -0
- package/esm/src/services/graphiti-async.d.ts +22 -0
- package/esm/src/services/graphiti-async.d.ts.map +1 -0
- package/esm/src/services/graphiti-async.js +219 -0
- package/esm/src/services/graphiti-mcp.d.ts +57 -0
- package/esm/src/services/graphiti-mcp.d.ts.map +1 -0
- package/esm/src/services/graphiti-mcp.js +194 -0
- package/esm/src/services/logger.d.ts +9 -0
- package/esm/src/services/logger.d.ts.map +1 -0
- package/esm/src/services/logger.js +104 -0
- package/esm/src/services/opencode-warning.d.ts +8 -0
- package/esm/src/services/opencode-warning.d.ts.map +1 -0
- package/esm/src/services/opencode-warning.js +104 -0
- package/esm/src/services/redis-cache.d.ts +27 -0
- package/esm/src/services/redis-cache.d.ts.map +1 -0
- package/esm/src/services/redis-cache.js +215 -0
- package/esm/src/services/redis-client.d.ts +89 -0
- package/esm/src/services/redis-client.d.ts.map +1 -0
- package/esm/src/services/redis-client.js +906 -0
- package/esm/src/services/redis-events.d.ts +46 -0
- package/esm/src/services/redis-events.d.ts.map +1 -0
- package/esm/src/services/redis-events.js +517 -0
- package/esm/src/services/redis-snapshot.d.ts +16 -0
- package/esm/src/services/redis-snapshot.d.ts.map +1 -0
- package/esm/src/services/redis-snapshot.js +184 -0
- package/esm/src/services/render-utils.d.ts +23 -0
- package/esm/src/services/render-utils.d.ts.map +1 -0
- package/esm/src/services/render-utils.js +149 -0
- package/esm/src/services/runtime-teardown.d.ts +23 -0
- package/esm/src/services/runtime-teardown.d.ts.map +1 -0
- package/esm/src/services/runtime-teardown.js +119 -0
- package/esm/src/services/sdk-normalize.d.ts +55 -0
- package/esm/src/services/sdk-normalize.d.ts.map +1 -0
- package/esm/src/services/sdk-normalize.js +61 -0
- package/esm/src/session.d.ts +74 -0
- package/esm/src/session.d.ts.map +1 -0
- package/esm/src/session.js +694 -0
- package/esm/src/types/index.d.ts +120 -0
- package/esm/src/types/index.d.ts.map +1 -0
- package/esm/src/types/index.js +28 -0
- package/esm/src/utils.d.ts +27 -0
- package/esm/src/utils.d.ts.map +1 -0
- package/esm/src/utils.js +76 -0
- package/package.json +59 -0
- package/script/_dnt.polyfills.d.ts +166 -0
- package/script/_dnt.polyfills.d.ts.map +1 -0
- package/script/_dnt.polyfills.js +180 -0
- package/script/_dnt.shims.d.ts +6 -0
- package/script/_dnt.shims.d.ts.map +1 -0
- package/script/_dnt.shims.js +65 -0
- package/script/deno.d.ts +45 -0
- package/script/deno.d.ts.map +1 -0
- package/script/deno.js +41 -0
- package/script/mod.d.ts +3 -0
- package/script/mod.d.ts.map +1 -0
- package/script/mod.js +6 -0
- package/script/package.json +3 -0
- package/script/src/config.d.ts +20 -0
- package/script/src/config.d.ts.map +1 -0
- package/script/src/config.js +256 -0
- package/script/src/handlers/chat.d.ts +14 -0
- package/script/src/handlers/chat.d.ts.map +1 -0
- package/script/src/handlers/chat.js +63 -0
- package/script/src/handlers/compacting.d.ts +9 -0
- package/script/src/handlers/compacting.d.ts.map +1 -0
- package/script/src/handlers/compacting.js +33 -0
- package/script/src/handlers/event.d.ts +22 -0
- package/script/src/handlers/event.d.ts.map +1 -0
- package/script/src/handlers/event.js +290 -0
- package/script/src/handlers/messages.d.ts +9 -0
- package/script/src/handlers/messages.d.ts.map +1 -0
- package/script/src/handlers/messages.js +96 -0
- package/script/src/index.d.ts +5 -0
- package/script/src/index.d.ts.map +1 -0
- package/script/src/index.js +159 -0
- package/script/src/services/batch-drain.d.ts +23 -0
- package/script/src/services/batch-drain.d.ts.map +1 -0
- package/script/src/services/batch-drain.js +221 -0
- package/script/src/services/connection-manager.d.ts +104 -0
- package/script/src/services/connection-manager.d.ts.map +1 -0
- package/script/src/services/connection-manager.js +635 -0
- package/script/src/services/constants.d.ts +7 -0
- package/script/src/services/constants.d.ts.map +1 -0
- package/script/src/services/constants.js +9 -0
- package/script/src/services/context-limit.d.ts +3 -0
- package/script/src/services/context-limit.d.ts.map +1 -0
- package/script/src/services/context-limit.js +47 -0
- package/script/src/services/event-extractor.d.ts +29 -0
- package/script/src/services/event-extractor.d.ts.map +1 -0
- package/script/src/services/event-extractor.js +669 -0
- package/script/src/services/graphiti-async.d.ts +22 -0
- package/script/src/services/graphiti-async.d.ts.map +1 -0
- package/script/src/services/graphiti-async.js +223 -0
- package/script/src/services/graphiti-mcp.d.ts +57 -0
- package/script/src/services/graphiti-mcp.d.ts.map +1 -0
- package/script/src/services/graphiti-mcp.js +198 -0
- package/script/src/services/logger.d.ts +9 -0
- package/script/src/services/logger.d.ts.map +1 -0
- package/script/src/services/logger.js +142 -0
- package/script/src/services/opencode-warning.d.ts +8 -0
- package/script/src/services/opencode-warning.d.ts.map +1 -0
- package/script/src/services/opencode-warning.js +114 -0
- package/script/src/services/redis-cache.d.ts +27 -0
- package/script/src/services/redis-cache.d.ts.map +1 -0
- package/script/src/services/redis-cache.js +219 -0
- package/script/src/services/redis-client.d.ts +89 -0
- package/script/src/services/redis-client.d.ts.map +1 -0
- package/script/src/services/redis-client.js +943 -0
- package/script/src/services/redis-events.d.ts +46 -0
- package/script/src/services/redis-events.d.ts.map +1 -0
- package/script/src/services/redis-events.js +535 -0
- package/script/src/services/redis-snapshot.d.ts +16 -0
- package/script/src/services/redis-snapshot.d.ts.map +1 -0
- package/script/src/services/redis-snapshot.js +189 -0
- package/script/src/services/render-utils.d.ts +23 -0
- package/script/src/services/render-utils.d.ts.map +1 -0
- package/script/src/services/render-utils.js +165 -0
- package/script/src/services/runtime-teardown.d.ts +23 -0
- package/script/src/services/runtime-teardown.d.ts.map +1 -0
- package/script/src/services/runtime-teardown.js +155 -0
- package/script/src/services/sdk-normalize.d.ts +55 -0
- package/script/src/services/sdk-normalize.d.ts.map +1 -0
- package/script/src/services/sdk-normalize.js +67 -0
- package/script/src/session.d.ts +74 -0
- package/script/src/session.d.ts.map +1 -0
- package/script/src/session.js +698 -0
- package/script/src/types/index.d.ts +120 -0
- package/script/src/types/index.d.ts.map +1 -0
- package/script/src/types/index.js +33 -0
- package/script/src/utils.d.ts +27 -0
- package/script/src/utils.d.ts.map +1 -0
- package/script/src/utils.js +87 -0
|
@@ -0,0 +1,943 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.RedisClient = void 0;
|
|
37
|
+
const logger_js_1 = require("./logger.js");
|
|
38
|
+
class InMemoryRedisStore {
|
|
39
|
+
constructor() {
|
|
40
|
+
Object.defineProperty(this, "values", {
|
|
41
|
+
enumerable: true,
|
|
42
|
+
configurable: true,
|
|
43
|
+
writable: true,
|
|
44
|
+
value: new Map()
|
|
45
|
+
});
|
|
46
|
+
}
|
|
47
|
+
ping() {
|
|
48
|
+
return Promise.resolve("PONG");
|
|
49
|
+
}
|
|
50
|
+
quit() {
|
|
51
|
+
return Promise.resolve("OK");
|
|
52
|
+
}
|
|
53
|
+
cleanup(key) {
|
|
54
|
+
const value = this.values.get(key);
|
|
55
|
+
if (!value?.expiresAt)
|
|
56
|
+
return;
|
|
57
|
+
if (value.expiresAt <= Date.now())
|
|
58
|
+
this.values.delete(key);
|
|
59
|
+
}
|
|
60
|
+
ensureList(key) {
|
|
61
|
+
this.cleanup(key);
|
|
62
|
+
const existing = this.values.get(key);
|
|
63
|
+
if (existing) {
|
|
64
|
+
if (!Array.isArray(existing.value)) {
|
|
65
|
+
throw new Error("WRONGTYPE Operation against a key holding the wrong kind of value");
|
|
66
|
+
}
|
|
67
|
+
return existing.value;
|
|
68
|
+
}
|
|
69
|
+
const list = [];
|
|
70
|
+
this.values.set(key, { value: list });
|
|
71
|
+
return list;
|
|
72
|
+
}
|
|
73
|
+
ensureHash(key) {
|
|
74
|
+
this.cleanup(key);
|
|
75
|
+
const existing = this.values.get(key);
|
|
76
|
+
if (existing) {
|
|
77
|
+
if (!(existing.value instanceof Map)) {
|
|
78
|
+
throw new Error("WRONGTYPE Operation against a key holding the wrong kind of value");
|
|
79
|
+
}
|
|
80
|
+
return existing.value;
|
|
81
|
+
}
|
|
82
|
+
const hash = new Map();
|
|
83
|
+
this.values.set(key, { value: hash });
|
|
84
|
+
return hash;
|
|
85
|
+
}
|
|
86
|
+
parseSetArgs(args) {
|
|
87
|
+
let onlyIfAbsent = false;
|
|
88
|
+
let ttlSeconds;
|
|
89
|
+
for (let index = 0; index < args.length; index += 1) {
|
|
90
|
+
const arg = args[index];
|
|
91
|
+
if (arg === "NX") {
|
|
92
|
+
onlyIfAbsent = true;
|
|
93
|
+
continue;
|
|
94
|
+
}
|
|
95
|
+
if (arg === "EX") {
|
|
96
|
+
const next = args[index + 1];
|
|
97
|
+
if (typeof next !== "number") {
|
|
98
|
+
throw new Error("ERR unsupported in-memory Redis SET arguments");
|
|
99
|
+
}
|
|
100
|
+
ttlSeconds = next;
|
|
101
|
+
index += 1;
|
|
102
|
+
continue;
|
|
103
|
+
}
|
|
104
|
+
throw new Error("ERR unsupported in-memory Redis SET arguments");
|
|
105
|
+
}
|
|
106
|
+
return { onlyIfAbsent, ttlSeconds };
|
|
107
|
+
}
|
|
108
|
+
lpush(key, value) {
|
|
109
|
+
const list = this.ensureList(key);
|
|
110
|
+
list.unshift(value);
|
|
111
|
+
return Promise.resolve(list.length);
|
|
112
|
+
}
|
|
113
|
+
rpush(key, value) {
|
|
114
|
+
const list = this.ensureList(key);
|
|
115
|
+
list.push(value);
|
|
116
|
+
return Promise.resolve(list.length);
|
|
117
|
+
}
|
|
118
|
+
lmove(source, destination, sourceSide, destinationSide) {
|
|
119
|
+
this.cleanup(source);
|
|
120
|
+
this.cleanup(destination);
|
|
121
|
+
const existing = this.values.get(source);
|
|
122
|
+
if (!existing || !Array.isArray(existing.value) || existing.value.length === 0) {
|
|
123
|
+
return Promise.resolve(null);
|
|
124
|
+
}
|
|
125
|
+
const sourceList = existing.value;
|
|
126
|
+
const value = sourceSide === "LEFT" ? sourceList.shift() : sourceList.pop();
|
|
127
|
+
if (value === undefined)
|
|
128
|
+
return Promise.resolve(null);
|
|
129
|
+
const destinationList = this.ensureList(destination);
|
|
130
|
+
if (destinationSide === "LEFT") {
|
|
131
|
+
destinationList.unshift(value);
|
|
132
|
+
}
|
|
133
|
+
else {
|
|
134
|
+
destinationList.push(value);
|
|
135
|
+
}
|
|
136
|
+
return Promise.resolve(value);
|
|
137
|
+
}
|
|
138
|
+
lrange(key, start, stop) {
|
|
139
|
+
this.cleanup(key);
|
|
140
|
+
const existing = this.values.get(key);
|
|
141
|
+
if (!existing || !Array.isArray(existing.value))
|
|
142
|
+
return Promise.resolve([]);
|
|
143
|
+
const list = existing.value;
|
|
144
|
+
const normalizeIndex = (index) => index < 0 ? Math.max(list.length + index, 0) : index;
|
|
145
|
+
const from = normalizeIndex(start);
|
|
146
|
+
const to = stop < 0 ? list.length + stop : stop;
|
|
147
|
+
return Promise.resolve(list.slice(from, to + 1));
|
|
148
|
+
}
|
|
149
|
+
llen(key) {
|
|
150
|
+
this.cleanup(key);
|
|
151
|
+
const existing = this.values.get(key);
|
|
152
|
+
return Promise.resolve(existing && Array.isArray(existing.value) ? existing.value.length : 0);
|
|
153
|
+
}
|
|
154
|
+
lindex(key, index) {
|
|
155
|
+
this.cleanup(key);
|
|
156
|
+
const existing = this.values.get(key);
|
|
157
|
+
if (!existing || !Array.isArray(existing.value)) {
|
|
158
|
+
return Promise.resolve(null);
|
|
159
|
+
}
|
|
160
|
+
const list = existing.value;
|
|
161
|
+
const normalized = index < 0 ? list.length + index : index;
|
|
162
|
+
return Promise.resolve(list[normalized] ?? null);
|
|
163
|
+
}
|
|
164
|
+
lset(key, index, value) {
|
|
165
|
+
this.cleanup(key);
|
|
166
|
+
const existing = this.values.get(key);
|
|
167
|
+
if (!existing || !Array.isArray(existing.value)) {
|
|
168
|
+
return Promise.reject(new Error("ERR no such key"));
|
|
169
|
+
}
|
|
170
|
+
const list = existing.value;
|
|
171
|
+
const normalized = index < 0 ? list.length + index : index;
|
|
172
|
+
if (normalized < 0 || normalized >= list.length) {
|
|
173
|
+
return Promise.reject(new Error("ERR index out of range"));
|
|
174
|
+
}
|
|
175
|
+
list[normalized] = value;
|
|
176
|
+
return Promise.resolve();
|
|
177
|
+
}
|
|
178
|
+
ltrim(key, start, stop) {
|
|
179
|
+
this.cleanup(key);
|
|
180
|
+
const existing = this.values.get(key);
|
|
181
|
+
if (!existing || !Array.isArray(existing.value))
|
|
182
|
+
return Promise.resolve();
|
|
183
|
+
const list = existing.value;
|
|
184
|
+
const normalizeIndex = (index) => index < 0 ? Math.max(list.length + index, 0) : index;
|
|
185
|
+
const trimmed = list.slice(normalizeIndex(start), stop < 0 ? list.length + stop + 1 : stop + 1);
|
|
186
|
+
existing.value = trimmed;
|
|
187
|
+
return Promise.resolve();
|
|
188
|
+
}
|
|
189
|
+
get(key) {
|
|
190
|
+
this.cleanup(key);
|
|
191
|
+
const existing = this.values.get(key);
|
|
192
|
+
return Promise.resolve(existing && typeof existing.value === "string" ? existing.value : null);
|
|
193
|
+
}
|
|
194
|
+
hset(key, values) {
|
|
195
|
+
const hash = this.ensureHash(key);
|
|
196
|
+
let added = 0;
|
|
197
|
+
for (const [field, value] of Object.entries(values)) {
|
|
198
|
+
if (!hash.has(field))
|
|
199
|
+
added += 1;
|
|
200
|
+
hash.set(field, value);
|
|
201
|
+
}
|
|
202
|
+
return Promise.resolve(added);
|
|
203
|
+
}
|
|
204
|
+
hgetall(key) {
|
|
205
|
+
this.cleanup(key);
|
|
206
|
+
const existing = this.values.get(key);
|
|
207
|
+
if (!existing)
|
|
208
|
+
return Promise.resolve({});
|
|
209
|
+
if (!(existing.value instanceof Map)) {
|
|
210
|
+
return Promise.reject(new Error("WRONGTYPE Operation against a key holding the wrong kind of value"));
|
|
211
|
+
}
|
|
212
|
+
return Promise.resolve(Object.fromEntries(existing.value.entries()));
|
|
213
|
+
}
|
|
214
|
+
set(key, value, ...args) {
|
|
215
|
+
this.cleanup(key);
|
|
216
|
+
const { onlyIfAbsent, ttlSeconds } = this.parseSetArgs(args);
|
|
217
|
+
if (onlyIfAbsent && this.values.has(key))
|
|
218
|
+
return Promise.resolve(null);
|
|
219
|
+
this.values.set(key, {
|
|
220
|
+
value,
|
|
221
|
+
expiresAt: ttlSeconds ? Date.now() + ttlSeconds * 1000 : undefined,
|
|
222
|
+
});
|
|
223
|
+
return Promise.resolve("OK");
|
|
224
|
+
}
|
|
225
|
+
expire(key, ttlSeconds) {
|
|
226
|
+
this.cleanup(key);
|
|
227
|
+
const existing = this.values.get(key);
|
|
228
|
+
if (!existing)
|
|
229
|
+
return Promise.resolve(0);
|
|
230
|
+
existing.expiresAt = Date.now() + ttlSeconds * 1000;
|
|
231
|
+
return Promise.resolve(1);
|
|
232
|
+
}
|
|
233
|
+
del(key) {
|
|
234
|
+
return Promise.resolve(this.values.delete(key) ? 1 : 0);
|
|
235
|
+
}
|
|
236
|
+
setIfAbsent(key, value, ttlSeconds) {
|
|
237
|
+
this.cleanup(key);
|
|
238
|
+
if (this.values.has(key))
|
|
239
|
+
return Promise.resolve(false);
|
|
240
|
+
this.values.set(key, {
|
|
241
|
+
value,
|
|
242
|
+
expiresAt: ttlSeconds ? Date.now() + ttlSeconds * 1000 : undefined,
|
|
243
|
+
});
|
|
244
|
+
return Promise.resolve(true);
|
|
245
|
+
}
|
|
246
|
+
deleteIfValue(key, expectedValue) {
|
|
247
|
+
this.cleanup(key);
|
|
248
|
+
const existing = this.values.get(key);
|
|
249
|
+
if (!existing || typeof existing.value !== "string") {
|
|
250
|
+
return Promise.resolve(false);
|
|
251
|
+
}
|
|
252
|
+
if (existing.value !== expectedValue)
|
|
253
|
+
return Promise.resolve(false);
|
|
254
|
+
this.values.delete(key);
|
|
255
|
+
return Promise.resolve(true);
|
|
256
|
+
}
|
|
257
|
+
compareAndExpire(key, expectedValue, ttlSeconds) {
|
|
258
|
+
this.cleanup(key);
|
|
259
|
+
const existing = this.values.get(key);
|
|
260
|
+
if (!existing || typeof existing.value !== "string") {
|
|
261
|
+
return Promise.resolve(false);
|
|
262
|
+
}
|
|
263
|
+
if (existing.value !== expectedValue)
|
|
264
|
+
return Promise.resolve(false);
|
|
265
|
+
existing.expiresAt = Date.now() + ttlSeconds * 1000;
|
|
266
|
+
return Promise.resolve(true);
|
|
267
|
+
}
|
|
268
|
+
snapshot(key) {
|
|
269
|
+
this.cleanup(key);
|
|
270
|
+
const existing = this.values.get(key);
|
|
271
|
+
if (!existing)
|
|
272
|
+
return { kind: "missing" };
|
|
273
|
+
const ttlSeconds = existing.expiresAt
|
|
274
|
+
? Math.max(Math.ceil((existing.expiresAt - Date.now()) / 1000), 1)
|
|
275
|
+
: undefined;
|
|
276
|
+
if (typeof existing.value === "string") {
|
|
277
|
+
return { kind: "string", value: existing.value, ttlSeconds };
|
|
278
|
+
}
|
|
279
|
+
if (Array.isArray(existing.value)) {
|
|
280
|
+
return { kind: "list", values: [...existing.value], ttlSeconds };
|
|
281
|
+
}
|
|
282
|
+
return {
|
|
283
|
+
kind: "hash",
|
|
284
|
+
values: Object.fromEntries(existing.value.entries()),
|
|
285
|
+
ttlSeconds,
|
|
286
|
+
};
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
class RedisClient {
|
|
290
|
+
constructor(options) {
|
|
291
|
+
Object.defineProperty(this, "options", {
|
|
292
|
+
enumerable: true,
|
|
293
|
+
configurable: true,
|
|
294
|
+
writable: true,
|
|
295
|
+
value: options
|
|
296
|
+
});
|
|
297
|
+
Object.defineProperty(this, "memory", {
|
|
298
|
+
enumerable: true,
|
|
299
|
+
configurable: true,
|
|
300
|
+
writable: true,
|
|
301
|
+
value: new InMemoryRedisStore()
|
|
302
|
+
});
|
|
303
|
+
Object.defineProperty(this, "hashFallbackKeys", {
|
|
304
|
+
enumerable: true,
|
|
305
|
+
configurable: true,
|
|
306
|
+
writable: true,
|
|
307
|
+
value: new Set()
|
|
308
|
+
});
|
|
309
|
+
Object.defineProperty(this, "pendingFallbackReplays", {
|
|
310
|
+
enumerable: true,
|
|
311
|
+
configurable: true,
|
|
312
|
+
writable: true,
|
|
313
|
+
value: []
|
|
314
|
+
});
|
|
315
|
+
Object.defineProperty(this, "runtimeListeners", {
|
|
316
|
+
enumerable: true,
|
|
317
|
+
configurable: true,
|
|
318
|
+
writable: true,
|
|
319
|
+
value: new WeakMap()
|
|
320
|
+
});
|
|
321
|
+
Object.defineProperty(this, "redis", {
|
|
322
|
+
enumerable: true,
|
|
323
|
+
configurable: true,
|
|
324
|
+
writable: true,
|
|
325
|
+
value: null
|
|
326
|
+
});
|
|
327
|
+
Object.defineProperty(this, "connected", {
|
|
328
|
+
enumerable: true,
|
|
329
|
+
configurable: true,
|
|
330
|
+
writable: true,
|
|
331
|
+
value: false
|
|
332
|
+
});
|
|
333
|
+
Object.defineProperty(this, "closed", {
|
|
334
|
+
enumerable: true,
|
|
335
|
+
configurable: true,
|
|
336
|
+
writable: true,
|
|
337
|
+
value: false
|
|
338
|
+
});
|
|
339
|
+
Object.defineProperty(this, "finalizingRuntime", {
|
|
340
|
+
enumerable: true,
|
|
341
|
+
configurable: true,
|
|
342
|
+
writable: true,
|
|
343
|
+
value: false
|
|
344
|
+
});
|
|
345
|
+
Object.defineProperty(this, "reconnectTimer", {
|
|
346
|
+
enumerable: true,
|
|
347
|
+
configurable: true,
|
|
348
|
+
writable: true,
|
|
349
|
+
value: null
|
|
350
|
+
});
|
|
351
|
+
Object.defineProperty(this, "reconnectAttempts", {
|
|
352
|
+
enumerable: true,
|
|
353
|
+
configurable: true,
|
|
354
|
+
writable: true,
|
|
355
|
+
value: 0
|
|
356
|
+
});
|
|
357
|
+
Object.defineProperty(this, "connectAttempt", {
|
|
358
|
+
enumerable: true,
|
|
359
|
+
configurable: true,
|
|
360
|
+
writable: true,
|
|
361
|
+
value: null
|
|
362
|
+
});
|
|
363
|
+
}
|
|
364
|
+
async connect() {
|
|
365
|
+
this.closed = false;
|
|
366
|
+
await this.tryConnectOnce();
|
|
367
|
+
}
|
|
368
|
+
isConnected() {
|
|
369
|
+
return this.connected;
|
|
370
|
+
}
|
|
371
|
+
async close() {
|
|
372
|
+
this.closed = true;
|
|
373
|
+
this.clearReconnectTimer();
|
|
374
|
+
const runtime = this.redis;
|
|
375
|
+
if (!runtime)
|
|
376
|
+
return;
|
|
377
|
+
this.detachRuntimeListeners(runtime);
|
|
378
|
+
try {
|
|
379
|
+
await runtime.quit();
|
|
380
|
+
}
|
|
381
|
+
finally {
|
|
382
|
+
this.redis = null;
|
|
383
|
+
this.connected = false;
|
|
384
|
+
}
|
|
385
|
+
}
|
|
386
|
+
getReconnectDelayMs() {
|
|
387
|
+
const baseDelay = this.options.reconnectBaseDelayMs ?? 1_000;
|
|
388
|
+
const maxDelay = this.options.reconnectMaxDelayMs ?? 30_000;
|
|
389
|
+
return Math.min(baseDelay * (2 ** Math.max(this.reconnectAttempts - 1, 0)), maxDelay);
|
|
390
|
+
}
|
|
391
|
+
clearReconnectTimer() {
|
|
392
|
+
if (this.reconnectTimer === null)
|
|
393
|
+
return;
|
|
394
|
+
clearTimeout(this.reconnectTimer);
|
|
395
|
+
this.reconnectTimer = null;
|
|
396
|
+
}
|
|
397
|
+
scheduleReconnect() {
|
|
398
|
+
if (this.closed || this.connected || this.reconnectTimer !== null)
|
|
399
|
+
return;
|
|
400
|
+
const delayMs = this.getReconnectDelayMs();
|
|
401
|
+
this.reconnectTimer = setTimeout(() => {
|
|
402
|
+
this.reconnectTimer = null;
|
|
403
|
+
void this.tryConnectOnce();
|
|
404
|
+
}, delayMs);
|
|
405
|
+
}
|
|
406
|
+
async createRuntime() {
|
|
407
|
+
if (this.options.runtimeFactory) {
|
|
408
|
+
return await this.options.runtimeFactory(this.options.endpoint);
|
|
409
|
+
}
|
|
410
|
+
const module = await Promise.resolve().then(() => __importStar(require("ioredis")));
|
|
411
|
+
const RedisCtor = module.default;
|
|
412
|
+
return new RedisCtor(this.options.endpoint, {
|
|
413
|
+
lazyConnect: true,
|
|
414
|
+
maxRetriesPerRequest: 1,
|
|
415
|
+
retryStrategy: () => null,
|
|
416
|
+
});
|
|
417
|
+
}
|
|
418
|
+
attachRuntimeListeners(runtime) {
|
|
419
|
+
const listeners = {
|
|
420
|
+
close: () => {
|
|
421
|
+
this.handleDisconnect(runtime);
|
|
422
|
+
},
|
|
423
|
+
end: () => {
|
|
424
|
+
this.handleDisconnect(runtime);
|
|
425
|
+
},
|
|
426
|
+
error: (error) => {
|
|
427
|
+
this.handleDisconnect(runtime, error);
|
|
428
|
+
},
|
|
429
|
+
ready: () => {
|
|
430
|
+
if (runtime !== this.redis || this.finalizingRuntime)
|
|
431
|
+
return;
|
|
432
|
+
this.connected = true;
|
|
433
|
+
this.reconnectAttempts = 0;
|
|
434
|
+
this.clearReconnectTimer();
|
|
435
|
+
},
|
|
436
|
+
};
|
|
437
|
+
this.runtimeListeners.set(runtime, listeners);
|
|
438
|
+
runtime.on?.("close", listeners.close);
|
|
439
|
+
runtime.on?.("end", listeners.end);
|
|
440
|
+
runtime.on?.("error", listeners.error);
|
|
441
|
+
runtime.on?.("ready", listeners.ready);
|
|
442
|
+
}
|
|
443
|
+
detachRuntimeListeners(runtime) {
|
|
444
|
+
const listeners = this.runtimeListeners.get(runtime);
|
|
445
|
+
if (!listeners)
|
|
446
|
+
return;
|
|
447
|
+
runtime.off?.("close", listeners.close);
|
|
448
|
+
runtime.off?.("end", listeners.end);
|
|
449
|
+
runtime.off?.("error", listeners.error);
|
|
450
|
+
runtime.off?.("ready", listeners.ready);
|
|
451
|
+
this.runtimeListeners.delete(runtime);
|
|
452
|
+
}
|
|
453
|
+
async replaceRuntime(runtime) {
|
|
454
|
+
if (this.closed) {
|
|
455
|
+
await this.disposeFailedRuntime(runtime);
|
|
456
|
+
this.connected = false;
|
|
457
|
+
return;
|
|
458
|
+
}
|
|
459
|
+
const previous = this.redis;
|
|
460
|
+
if (previous === runtime)
|
|
461
|
+
return;
|
|
462
|
+
this.redis = runtime;
|
|
463
|
+
this.connected = false;
|
|
464
|
+
this.finalizingRuntime = true;
|
|
465
|
+
try {
|
|
466
|
+
await this.replayPendingFallbackMutations(runtime);
|
|
467
|
+
this.connected = true;
|
|
468
|
+
this.reconnectAttempts = 0;
|
|
469
|
+
this.clearReconnectTimer();
|
|
470
|
+
}
|
|
471
|
+
catch (error) {
|
|
472
|
+
this.finalizingRuntime = false;
|
|
473
|
+
this.handleDisconnect(runtime, error);
|
|
474
|
+
return;
|
|
475
|
+
}
|
|
476
|
+
this.finalizingRuntime = false;
|
|
477
|
+
if (!previous)
|
|
478
|
+
return;
|
|
479
|
+
this.detachRuntimeListeners(previous);
|
|
480
|
+
try {
|
|
481
|
+
await previous.quit();
|
|
482
|
+
}
|
|
483
|
+
catch {
|
|
484
|
+
// Ignore teardown errors for stale runtimes.
|
|
485
|
+
}
|
|
486
|
+
}
|
|
487
|
+
async disposeFailedRuntime(runtime) {
|
|
488
|
+
this.detachRuntimeListeners(runtime);
|
|
489
|
+
try {
|
|
490
|
+
await runtime.quit();
|
|
491
|
+
}
|
|
492
|
+
catch {
|
|
493
|
+
// Best-effort cleanup only.
|
|
494
|
+
}
|
|
495
|
+
}
|
|
496
|
+
handleDisconnect(runtime, error) {
|
|
497
|
+
if (this.closed)
|
|
498
|
+
return;
|
|
499
|
+
if (runtime && runtime !== this.redis)
|
|
500
|
+
return;
|
|
501
|
+
if (error) {
|
|
502
|
+
logger_js_1.logger.warn("Redis hot tier unavailable; using in-memory fallback", error);
|
|
503
|
+
}
|
|
504
|
+
if (runtime && this.redis === runtime) {
|
|
505
|
+
this.redis = null;
|
|
506
|
+
this.detachRuntimeListeners(runtime);
|
|
507
|
+
void runtime.quit().catch(() => {
|
|
508
|
+
// Ignore teardown errors for disconnected runtimes.
|
|
509
|
+
});
|
|
510
|
+
}
|
|
511
|
+
this.connected = false;
|
|
512
|
+
this.scheduleReconnect();
|
|
513
|
+
}
|
|
514
|
+
async tryConnectOnce() {
|
|
515
|
+
if (this.closed)
|
|
516
|
+
return false;
|
|
517
|
+
if (this.connected && this.redis)
|
|
518
|
+
return true;
|
|
519
|
+
if (this.connectAttempt)
|
|
520
|
+
return await this.connectAttempt;
|
|
521
|
+
this.connectAttempt = (async () => {
|
|
522
|
+
let runtime = null;
|
|
523
|
+
try {
|
|
524
|
+
runtime = await this.createRuntime();
|
|
525
|
+
this.attachRuntimeListeners(runtime);
|
|
526
|
+
await runtime.connect?.();
|
|
527
|
+
await runtime.ping();
|
|
528
|
+
await this.replaceRuntime(runtime);
|
|
529
|
+
return true;
|
|
530
|
+
}
|
|
531
|
+
catch (error) {
|
|
532
|
+
if (runtime) {
|
|
533
|
+
await this.disposeFailedRuntime(runtime);
|
|
534
|
+
}
|
|
535
|
+
this.redis = null;
|
|
536
|
+
this.connected = false;
|
|
537
|
+
this.reconnectAttempts += 1;
|
|
538
|
+
logger_js_1.logger.warn("Redis hot tier unavailable; using in-memory fallback", error);
|
|
539
|
+
this.scheduleReconnect();
|
|
540
|
+
return false;
|
|
541
|
+
}
|
|
542
|
+
finally {
|
|
543
|
+
this.connectAttempt = null;
|
|
544
|
+
}
|
|
545
|
+
})();
|
|
546
|
+
return await this.connectAttempt;
|
|
547
|
+
}
|
|
548
|
+
async useRuntime(operation, options) {
|
|
549
|
+
const runtime = this.redis;
|
|
550
|
+
if (this.connected && runtime) {
|
|
551
|
+
try {
|
|
552
|
+
return await operation(runtime);
|
|
553
|
+
}
|
|
554
|
+
catch (error) {
|
|
555
|
+
this.handleDisconnect(runtime, error);
|
|
556
|
+
if (options?.allowMemoryFallback === false)
|
|
557
|
+
throw error;
|
|
558
|
+
}
|
|
559
|
+
}
|
|
560
|
+
if (options?.allowMemoryFallback === false) {
|
|
561
|
+
throw new Error("Redis hot tier unavailable for durable drain-state mutation");
|
|
562
|
+
}
|
|
563
|
+
return await operation(this.memory);
|
|
564
|
+
}
|
|
565
|
+
queuePendingFallbackReplay(replay) {
|
|
566
|
+
this.pendingFallbackReplays.push(replay);
|
|
567
|
+
}
|
|
568
|
+
async replayPendingFallbackMutations(runtime) {
|
|
569
|
+
while (this.pendingFallbackReplays.length > 0) {
|
|
570
|
+
const replay = this.pendingFallbackReplays[0];
|
|
571
|
+
await replay(runtime);
|
|
572
|
+
this.pendingFallbackReplays.shift();
|
|
573
|
+
}
|
|
574
|
+
}
|
|
575
|
+
queuePendingStringSnapshotReplay(key) {
|
|
576
|
+
this.queuePendingFallbackReplay(async (runtime) => {
|
|
577
|
+
this.hashFallbackKeys.delete(key);
|
|
578
|
+
const snapshot = this.memory.snapshot(key);
|
|
579
|
+
await runtime.del(key);
|
|
580
|
+
if (snapshot.kind === "missing")
|
|
581
|
+
return;
|
|
582
|
+
if (snapshot.kind !== "string")
|
|
583
|
+
return;
|
|
584
|
+
if (snapshot.ttlSeconds) {
|
|
585
|
+
await runtime.set(key, snapshot.value, "EX", snapshot.ttlSeconds);
|
|
586
|
+
return;
|
|
587
|
+
}
|
|
588
|
+
await runtime.set(key, snapshot.value);
|
|
589
|
+
});
|
|
590
|
+
}
|
|
591
|
+
queuePendingHashSnapshotReplay(key) {
|
|
592
|
+
this.queuePendingFallbackReplay(async (runtime) => {
|
|
593
|
+
if (!runtime.hset)
|
|
594
|
+
return;
|
|
595
|
+
const snapshot = this.memory.snapshot(key);
|
|
596
|
+
if (snapshot.kind !== "hash")
|
|
597
|
+
return;
|
|
598
|
+
await runtime.del(key);
|
|
599
|
+
this.hashFallbackKeys.delete(key);
|
|
600
|
+
await runtime.hset(key, snapshot.values);
|
|
601
|
+
if (snapshot.ttlSeconds)
|
|
602
|
+
await runtime.expire(key, snapshot.ttlSeconds);
|
|
603
|
+
});
|
|
604
|
+
}
|
|
605
|
+
isDurableDrainKey(key) {
|
|
606
|
+
return key.startsWith("drain:");
|
|
607
|
+
}
|
|
608
|
+
async replaceMemoryList(key, values, ttlSeconds) {
|
|
609
|
+
await this.memory.del(key);
|
|
610
|
+
for (const value of values) {
|
|
611
|
+
await this.memory.rpush(key, value);
|
|
612
|
+
}
|
|
613
|
+
if (ttlSeconds && values.length > 0) {
|
|
614
|
+
await this.memory.expire(key, ttlSeconds);
|
|
615
|
+
}
|
|
616
|
+
}
|
|
617
|
+
async syncNonDurableSourceListAfterLiveMove(key, side) {
|
|
618
|
+
const snapshot = this.memory.snapshot(key);
|
|
619
|
+
if (snapshot.kind !== "list")
|
|
620
|
+
return;
|
|
621
|
+
const values = side === "LEFT"
|
|
622
|
+
? snapshot.values.slice(1)
|
|
623
|
+
: snapshot.values.slice(0, -1);
|
|
624
|
+
await this.replaceMemoryList(key, values, snapshot.ttlSeconds);
|
|
625
|
+
}
|
|
626
|
+
async syncNonDurableDestinationListAfterLiveMove(key, side, value) {
|
|
627
|
+
if (side === "LEFT") {
|
|
628
|
+
await this.memory.lpush(key, value);
|
|
629
|
+
return;
|
|
630
|
+
}
|
|
631
|
+
await this.memory.rpush(key, value);
|
|
632
|
+
}
|
|
633
|
+
async useMutationRuntime(keys, operation, onFallbackSuccess) {
|
|
634
|
+
return await this.useRuntime(async (runtime) => {
|
|
635
|
+
const result = await operation(runtime);
|
|
636
|
+
if (runtime === this.memory) {
|
|
637
|
+
await onFallbackSuccess?.(result);
|
|
638
|
+
}
|
|
639
|
+
return result;
|
|
640
|
+
}, {
|
|
641
|
+
allowMemoryFallback: !keys.some((key) => this.isDurableDrainKey(key)),
|
|
642
|
+
});
|
|
643
|
+
}
|
|
644
|
+
async prependToList(key, value, ttlSeconds) {
|
|
645
|
+
return await this.useMutationRuntime([key], async (runtime) => {
|
|
646
|
+
const length = await runtime.lpush(key, value);
|
|
647
|
+
if (ttlSeconds)
|
|
648
|
+
await runtime.expire(key, ttlSeconds);
|
|
649
|
+
if (runtime !== this.memory && !this.isDurableDrainKey(key)) {
|
|
650
|
+
await this.memory.lpush(key, value);
|
|
651
|
+
if (ttlSeconds)
|
|
652
|
+
await this.memory.expire(key, ttlSeconds);
|
|
653
|
+
}
|
|
654
|
+
return length;
|
|
655
|
+
}, () => {
|
|
656
|
+
this.queuePendingFallbackReplay(async (runtime) => {
|
|
657
|
+
await runtime.lpush(key, value);
|
|
658
|
+
if (ttlSeconds)
|
|
659
|
+
await runtime.expire(key, ttlSeconds);
|
|
660
|
+
});
|
|
661
|
+
});
|
|
662
|
+
}
|
|
663
|
+
async appendToList(key, value, ttlSeconds) {
|
|
664
|
+
return await this.useMutationRuntime([key], async (runtime) => {
|
|
665
|
+
const length = await runtime.rpush(key, value);
|
|
666
|
+
if (ttlSeconds)
|
|
667
|
+
await runtime.expire(key, ttlSeconds);
|
|
668
|
+
if (runtime !== this.memory && !this.isDurableDrainKey(key)) {
|
|
669
|
+
await this.memory.rpush(key, value);
|
|
670
|
+
if (ttlSeconds)
|
|
671
|
+
await this.memory.expire(key, ttlSeconds);
|
|
672
|
+
}
|
|
673
|
+
return length;
|
|
674
|
+
}, () => {
|
|
675
|
+
this.queuePendingFallbackReplay(async (runtime) => {
|
|
676
|
+
await runtime.rpush(key, value);
|
|
677
|
+
if (ttlSeconds)
|
|
678
|
+
await runtime.expire(key, ttlSeconds);
|
|
679
|
+
});
|
|
680
|
+
});
|
|
681
|
+
}
|
|
682
|
+
async getRecentList(key, limit) {
|
|
683
|
+
return await this.useRuntime((runtime) => runtime.lrange(key, 0, Math.max(limit - 1, 0)));
|
|
684
|
+
}
|
|
685
|
+
async getOldestList(key, limit) {
|
|
686
|
+
return await this.useRuntime(async (runtime) => {
|
|
687
|
+
const length = await runtime.llen(key);
|
|
688
|
+
if (length === 0)
|
|
689
|
+
return [];
|
|
690
|
+
const start = Math.max(length - limit, 0);
|
|
691
|
+
return await runtime.lrange(key, start, length - 1);
|
|
692
|
+
});
|
|
693
|
+
}
|
|
694
|
+
async getListRange(key, start, stop) {
|
|
695
|
+
return await this.useRuntime((runtime) => runtime.lrange(key, start, stop));
|
|
696
|
+
}
|
|
697
|
+
async getListItem(key, index) {
|
|
698
|
+
return await this.useRuntime((runtime) => runtime.lindex(key, index));
|
|
699
|
+
}
|
|
700
|
+
async setListItem(key, index, value) {
|
|
701
|
+
await this.useMutationRuntime([key], async (runtime) => {
|
|
702
|
+
await runtime.lset(key, index, value);
|
|
703
|
+
if (runtime !== this.memory && !this.isDurableDrainKey(key)) {
|
|
704
|
+
await this.memory.lset(key, index, value);
|
|
705
|
+
}
|
|
706
|
+
}, () => {
|
|
707
|
+
this.queuePendingFallbackReplay(async (runtime) => {
|
|
708
|
+
await runtime.lset(key, index, value);
|
|
709
|
+
});
|
|
710
|
+
});
|
|
711
|
+
}
|
|
712
|
+
async getListLength(key) {
|
|
713
|
+
return await this.useRuntime((runtime) => runtime.llen(key));
|
|
714
|
+
}
|
|
715
|
+
async moveListItem(source, destination, sourceSide, destinationSide) {
|
|
716
|
+
return await this.useMutationRuntime([source, destination], async (runtime) => {
|
|
717
|
+
const sourceDurable = this.isDurableDrainKey(source);
|
|
718
|
+
const destinationDurable = this.isDurableDrainKey(destination);
|
|
719
|
+
const result = await runtime.lmove(source, destination, sourceSide, destinationSide);
|
|
720
|
+
if (result !== null && runtime !== this.memory) {
|
|
721
|
+
if (!sourceDurable) {
|
|
722
|
+
await this.syncNonDurableSourceListAfterLiveMove(source, sourceSide);
|
|
723
|
+
}
|
|
724
|
+
if (!destinationDurable) {
|
|
725
|
+
await this.syncNonDurableDestinationListAfterLiveMove(destination, destinationSide, result);
|
|
726
|
+
}
|
|
727
|
+
}
|
|
728
|
+
return result;
|
|
729
|
+
}, (result) => {
|
|
730
|
+
if (result === null)
|
|
731
|
+
return;
|
|
732
|
+
this.queuePendingFallbackReplay(async (runtime) => {
|
|
733
|
+
await runtime.lmove(source, destination, sourceSide, destinationSide);
|
|
734
|
+
});
|
|
735
|
+
});
|
|
736
|
+
}
|
|
737
|
+
async trimOldest(key, count) {
|
|
738
|
+
if (count <= 0)
|
|
739
|
+
return;
|
|
740
|
+
await this.useMutationRuntime([key], async (runtime) => {
|
|
741
|
+
const length = await runtime.llen(key);
|
|
742
|
+
if (length <= count) {
|
|
743
|
+
await runtime.del(key);
|
|
744
|
+
if (runtime !== this.memory && !this.isDurableDrainKey(key)) {
|
|
745
|
+
await this.memory.del(key);
|
|
746
|
+
}
|
|
747
|
+
return length > 0;
|
|
748
|
+
}
|
|
749
|
+
await runtime.ltrim(key, 0, length - count - 1);
|
|
750
|
+
if (runtime !== this.memory && !this.isDurableDrainKey(key)) {
|
|
751
|
+
const memoryLength = await this.memory.llen(key);
|
|
752
|
+
if (memoryLength <= count) {
|
|
753
|
+
await this.memory.del(key);
|
|
754
|
+
}
|
|
755
|
+
else {
|
|
756
|
+
await this.memory.ltrim(key, 0, memoryLength - count - 1);
|
|
757
|
+
}
|
|
758
|
+
}
|
|
759
|
+
return true;
|
|
760
|
+
}, (changed) => {
|
|
761
|
+
if (!changed)
|
|
762
|
+
return;
|
|
763
|
+
this.queuePendingFallbackReplay(async (runtime) => {
|
|
764
|
+
const length = await runtime.llen(key);
|
|
765
|
+
if (length <= count) {
|
|
766
|
+
await runtime.del(key);
|
|
767
|
+
return;
|
|
768
|
+
}
|
|
769
|
+
await runtime.ltrim(key, 0, length - count - 1);
|
|
770
|
+
});
|
|
771
|
+
});
|
|
772
|
+
}
|
|
773
|
+
async getString(key) {
|
|
774
|
+
return await this.useRuntime((runtime) => runtime.get(key));
|
|
775
|
+
}
|
|
776
|
+
async setString(key, value, ttlSeconds) {
|
|
777
|
+
await this.useMutationRuntime([key], async (runtime) => {
|
|
778
|
+
if (ttlSeconds) {
|
|
779
|
+
await runtime.set(key, value, "EX", ttlSeconds);
|
|
780
|
+
if (runtime !== this.memory && !this.isDurableDrainKey(key)) {
|
|
781
|
+
this.hashFallbackKeys.delete(key);
|
|
782
|
+
await this.memory.set(key, value, "EX", ttlSeconds);
|
|
783
|
+
}
|
|
784
|
+
return;
|
|
785
|
+
}
|
|
786
|
+
await runtime.set(key, value);
|
|
787
|
+
if (runtime !== this.memory && !this.isDurableDrainKey(key)) {
|
|
788
|
+
this.hashFallbackKeys.delete(key);
|
|
789
|
+
await this.memory.set(key, value);
|
|
790
|
+
}
|
|
791
|
+
}, () => {
|
|
792
|
+
this.queuePendingStringSnapshotReplay(key);
|
|
793
|
+
});
|
|
794
|
+
}
|
|
795
|
+
async setStringIfAbsent(key, value, ttlSeconds) {
|
|
796
|
+
return await this.useMutationRuntime([key], async (runtime) => {
|
|
797
|
+
if (runtime === this.memory) {
|
|
798
|
+
return await this.memory.setIfAbsent(key, value, ttlSeconds);
|
|
799
|
+
}
|
|
800
|
+
const result = ttlSeconds
|
|
801
|
+
? await runtime.set(key, value, "NX", "EX", ttlSeconds)
|
|
802
|
+
: await runtime.set(key, value, "NX");
|
|
803
|
+
if (result === "OK" && !this.isDurableDrainKey(key)) {
|
|
804
|
+
this.hashFallbackKeys.delete(key);
|
|
805
|
+
if (ttlSeconds) {
|
|
806
|
+
await this.memory.set(key, value, "EX", ttlSeconds);
|
|
807
|
+
}
|
|
808
|
+
else {
|
|
809
|
+
await this.memory.set(key, value);
|
|
810
|
+
}
|
|
811
|
+
}
|
|
812
|
+
return result === "OK";
|
|
813
|
+
}, (acquired) => {
|
|
814
|
+
if (!acquired)
|
|
815
|
+
return;
|
|
816
|
+
this.queuePendingStringSnapshotReplay(key);
|
|
817
|
+
});
|
|
818
|
+
}
|
|
819
|
+
async touch(key, ttlSeconds) {
|
|
820
|
+
await this.useMutationRuntime([key], async (runtime) => {
|
|
821
|
+
const changed = await runtime.expire(key, ttlSeconds);
|
|
822
|
+
if (changed !== 0 && runtime !== this.memory &&
|
|
823
|
+
!this.isDurableDrainKey(key)) {
|
|
824
|
+
await this.memory.expire(key, ttlSeconds);
|
|
825
|
+
}
|
|
826
|
+
return changed;
|
|
827
|
+
}, (changed) => {
|
|
828
|
+
if (changed === 0)
|
|
829
|
+
return;
|
|
830
|
+
this.queuePendingFallbackReplay((runtime) => runtime.expire(key, ttlSeconds).then(() => undefined));
|
|
831
|
+
});
|
|
832
|
+
}
|
|
833
|
+
async getHashAll(key) {
|
|
834
|
+
return await this.useRuntime(async (runtime) => {
|
|
835
|
+
if (runtime === this.memory) {
|
|
836
|
+
return await this.memory.hgetall(key);
|
|
837
|
+
}
|
|
838
|
+
if (this.hashFallbackKeys.has(key)) {
|
|
839
|
+
const fallbackValues = await this.memory.hgetall(key);
|
|
840
|
+
if (!runtime.hgetall) {
|
|
841
|
+
return fallbackValues;
|
|
842
|
+
}
|
|
843
|
+
const liveValues = await runtime.hgetall(key);
|
|
844
|
+
return {
|
|
845
|
+
...liveValues,
|
|
846
|
+
...fallbackValues,
|
|
847
|
+
};
|
|
848
|
+
}
|
|
849
|
+
return await runtime.hgetall?.(key) ?? {};
|
|
850
|
+
});
|
|
851
|
+
}
|
|
852
|
+
async setHashFields(key, values, ttlSeconds) {
|
|
853
|
+
const serialized = Object.fromEntries(Object.entries(values)
|
|
854
|
+
.filter(([, value]) => value !== undefined)
|
|
855
|
+
.map(([field, value]) => [field, String(value)]));
|
|
856
|
+
if (Object.keys(serialized).length === 0)
|
|
857
|
+
return;
|
|
858
|
+
await this.useMutationRuntime([key], async (runtime) => {
|
|
859
|
+
let ttlTarget = runtime;
|
|
860
|
+
if (runtime === this.memory) {
|
|
861
|
+
this.hashFallbackKeys.add(key);
|
|
862
|
+
await this.memory.hset(key, serialized);
|
|
863
|
+
ttlTarget = this.memory;
|
|
864
|
+
}
|
|
865
|
+
else if (runtime.hset) {
|
|
866
|
+
this.hashFallbackKeys.delete(key);
|
|
867
|
+
await runtime.hset(key, serialized);
|
|
868
|
+
if (!this.isDurableDrainKey(key)) {
|
|
869
|
+
await this.memory.hset(key, serialized);
|
|
870
|
+
if (ttlSeconds)
|
|
871
|
+
await this.memory.expire(key, ttlSeconds);
|
|
872
|
+
}
|
|
873
|
+
}
|
|
874
|
+
else {
|
|
875
|
+
const existing = await runtime.get(key);
|
|
876
|
+
if (existing !== null) {
|
|
877
|
+
throw new Error("WRONGTYPE Operation against a key holding the wrong kind of value");
|
|
878
|
+
}
|
|
879
|
+
this.hashFallbackKeys.add(key);
|
|
880
|
+
await this.memory.hset(key, serialized);
|
|
881
|
+
ttlTarget = this.memory;
|
|
882
|
+
}
|
|
883
|
+
if (ttlSeconds)
|
|
884
|
+
await ttlTarget.expire(key, ttlSeconds);
|
|
885
|
+
}, () => {
|
|
886
|
+
this.queuePendingHashSnapshotReplay(key);
|
|
887
|
+
});
|
|
888
|
+
}
|
|
889
|
+
async compareAndTouch(key, expectedValue, ttlSeconds) {
|
|
890
|
+
return await this.useMutationRuntime([key], async (runtime) => {
|
|
891
|
+
if (runtime === this.memory) {
|
|
892
|
+
return await this.memory.compareAndExpire(key, expectedValue, ttlSeconds);
|
|
893
|
+
}
|
|
894
|
+
const extended = await runtime.eval?.("if redis.call('GET', KEYS[1]) == ARGV[1] then return redis.call('EXPIRE', KEYS[1], ARGV[2]) else return 0 end", 1, key, expectedValue, String(ttlSeconds)) ?? 0;
|
|
895
|
+
return extended === 1;
|
|
896
|
+
}, (extended) => {
|
|
897
|
+
if (!extended)
|
|
898
|
+
return;
|
|
899
|
+
this.queuePendingFallbackReplay(async (runtime) => {
|
|
900
|
+
await runtime.eval?.("if redis.call('GET', KEYS[1]) == ARGV[1] then return redis.call('EXPIRE', KEYS[1], ARGV[2]) else return 0 end", 1, key, expectedValue, String(ttlSeconds));
|
|
901
|
+
});
|
|
902
|
+
});
|
|
903
|
+
}
|
|
904
|
+
async deleteKey(key) {
|
|
905
|
+
await this.useMutationRuntime([key], async (runtime) => {
|
|
906
|
+
const deleted = await runtime.del(key);
|
|
907
|
+
if (deleted !== 0 && runtime !== this.memory &&
|
|
908
|
+
!this.isDurableDrainKey(key)) {
|
|
909
|
+
this.hashFallbackKeys.delete(key);
|
|
910
|
+
await this.memory.del(key);
|
|
911
|
+
}
|
|
912
|
+
return deleted;
|
|
913
|
+
}, (deleted) => {
|
|
914
|
+
if (deleted === 0)
|
|
915
|
+
return;
|
|
916
|
+
this.queuePendingFallbackReplay(async (runtime) => {
|
|
917
|
+
this.hashFallbackKeys.delete(key);
|
|
918
|
+
await runtime.del(key);
|
|
919
|
+
});
|
|
920
|
+
});
|
|
921
|
+
}
|
|
922
|
+
async deleteKeyIfValue(key, expectedValue) {
|
|
923
|
+
return await this.useMutationRuntime([key], async (runtime) => {
|
|
924
|
+
if (runtime === this.memory) {
|
|
925
|
+
return await this.memory.deleteIfValue(key, expectedValue);
|
|
926
|
+
}
|
|
927
|
+
const deleted = await runtime.eval?.("if redis.call('GET', KEYS[1]) == ARGV[1] then return redis.call('DEL', KEYS[1]) else return 0 end", 1, key, expectedValue) ?? 0;
|
|
928
|
+
if (deleted === 1 && !this.isDurableDrainKey(key)) {
|
|
929
|
+
this.hashFallbackKeys.delete(key);
|
|
930
|
+
await this.memory.del(key);
|
|
931
|
+
}
|
|
932
|
+
return deleted === 1;
|
|
933
|
+
}, (deleted) => {
|
|
934
|
+
if (!deleted)
|
|
935
|
+
return;
|
|
936
|
+
this.queuePendingFallbackReplay(async (runtime) => {
|
|
937
|
+
this.hashFallbackKeys.delete(key);
|
|
938
|
+
await runtime.del(key);
|
|
939
|
+
});
|
|
940
|
+
});
|
|
941
|
+
}
|
|
942
|
+
}
|
|
943
|
+
exports.RedisClient = RedisClient;
|