@hotmeshio/hotmesh 0.7.0 → 0.8.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/.claude/settings.local.json +7 -0
- package/README.md +1 -1
- package/build/index.d.ts +1 -3
- package/build/index.js +1 -5
- package/build/modules/utils.js +3 -31
- package/build/package.json +16 -27
- package/build/services/activities/activity.d.ts +43 -6
- package/build/services/activities/activity.js +262 -54
- package/build/services/activities/await.js +2 -2
- package/build/services/activities/cycle.js +1 -1
- package/build/services/activities/hook.d.ts +5 -0
- package/build/services/activities/hook.js +22 -19
- package/build/services/activities/interrupt.js +17 -25
- package/build/services/activities/signal.d.ts +4 -2
- package/build/services/activities/signal.js +27 -24
- package/build/services/activities/worker.js +2 -2
- package/build/services/collator/index.d.ts +123 -25
- package/build/services/collator/index.js +224 -101
- package/build/services/connector/factory.d.ts +1 -1
- package/build/services/connector/factory.js +1 -11
- package/build/services/engine/index.d.ts +5 -5
- package/build/services/engine/index.js +36 -15
- package/build/services/router/consumption/index.js +1 -1
- package/build/services/search/factory.js +1 -9
- package/build/services/store/factory.js +1 -9
- package/build/services/store/index.d.ts +5 -0
- package/build/services/store/providers/postgres/kvsql.d.ts +4 -0
- package/build/services/store/providers/postgres/kvsql.js +4 -0
- package/build/services/store/providers/postgres/kvtransaction.d.ts +2 -0
- package/build/services/store/providers/postgres/kvtransaction.js +23 -0
- package/build/services/store/providers/postgres/kvtypes/hash/basic.d.ts +51 -0
- package/build/services/store/providers/postgres/kvtypes/hash/basic.js +193 -1
- package/build/services/store/providers/postgres/kvtypes/hash/index.d.ts +4 -0
- package/build/services/store/providers/postgres/kvtypes/hash/index.js +6 -0
- package/build/services/store/providers/postgres/postgres.d.ts +20 -0
- package/build/services/store/providers/postgres/postgres.js +38 -1
- package/build/services/stream/factory.js +1 -17
- package/build/services/stream/providers/postgres/scout.js +2 -2
- package/build/services/sub/factory.js +1 -9
- package/build/services/sub/index.d.ts +1 -1
- package/build/services/sub/providers/postgres/postgres.d.ts +1 -1
- package/build/services/sub/providers/postgres/postgres.js +25 -10
- package/build/services/task/index.d.ts +1 -1
- package/build/services/task/index.js +2 -6
- package/build/types/index.d.ts +0 -1
- package/build/types/index.js +1 -4
- package/build/types/provider.d.ts +1 -1
- package/index.ts +0 -4
- package/package.json +16 -27
- package/build/services/connector/providers/ioredis.d.ts +0 -9
- package/build/services/connector/providers/ioredis.js +0 -26
- package/build/services/connector/providers/redis.d.ts +0 -9
- package/build/services/connector/providers/redis.js +0 -38
- package/build/services/search/providers/redis/ioredis.d.ts +0 -23
- package/build/services/search/providers/redis/ioredis.js +0 -189
- package/build/services/search/providers/redis/redis.d.ts +0 -23
- package/build/services/search/providers/redis/redis.js +0 -202
- package/build/services/store/providers/redis/_base.d.ts +0 -137
- package/build/services/store/providers/redis/_base.js +0 -980
- package/build/services/store/providers/redis/ioredis.d.ts +0 -20
- package/build/services/store/providers/redis/ioredis.js +0 -190
- package/build/services/store/providers/redis/redis.d.ts +0 -18
- package/build/services/store/providers/redis/redis.js +0 -199
- package/build/services/stream/providers/redis/ioredis.d.ts +0 -61
- package/build/services/stream/providers/redis/ioredis.js +0 -272
- package/build/services/stream/providers/redis/redis.d.ts +0 -61
- package/build/services/stream/providers/redis/redis.js +0 -305
- package/build/services/sub/providers/redis/ioredis.d.ts +0 -20
- package/build/services/sub/providers/redis/ioredis.js +0 -161
- package/build/services/sub/providers/redis/redis.d.ts +0 -18
- package/build/services/sub/providers/redis/redis.js +0 -148
- package/build/types/redis.d.ts +0 -258
- package/build/types/redis.js +0 -11
|
@@ -1,980 +0,0 @@
|
|
|
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 (mod) {
|
|
19
|
-
if (mod && mod.__esModule) return mod;
|
|
20
|
-
var result = {};
|
|
21
|
-
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
|
|
22
|
-
__setModuleDefault(result, mod);
|
|
23
|
-
return result;
|
|
24
|
-
};
|
|
25
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
26
|
-
exports.RedisStoreBase = void 0;
|
|
27
|
-
const errors_1 = require("../../../../modules/errors");
|
|
28
|
-
const key_1 = require("../../../../modules/key");
|
|
29
|
-
const serializer_1 = require("../../../serializer");
|
|
30
|
-
const utils_1 = require("../../../../modules/utils");
|
|
31
|
-
const enums_1 = require("../../../../modules/enums");
|
|
32
|
-
const cache_1 = require("../../cache");
|
|
33
|
-
const __1 = require("../..");
|
|
34
|
-
class RedisStoreBase extends __1.StoreService {
|
|
35
|
-
constructor(storeClient) {
|
|
36
|
-
super(storeClient);
|
|
37
|
-
this.commands = {};
|
|
38
|
-
this.storeClient = storeClient;
|
|
39
|
-
}
|
|
40
|
-
async init(namespace = key_1.HMNS, appId, logger) {
|
|
41
|
-
this.namespace = namespace;
|
|
42
|
-
this.appId = appId;
|
|
43
|
-
this.logger = logger;
|
|
44
|
-
const settings = await this.getSettings(true);
|
|
45
|
-
this.cache = new cache_1.Cache(appId, settings);
|
|
46
|
-
this.serializer = new serializer_1.SerializerService();
|
|
47
|
-
await this.getApp(appId);
|
|
48
|
-
return this.cache.getApps();
|
|
49
|
-
}
|
|
50
|
-
isSuccessful(result) {
|
|
51
|
-
return result > 0 || result === 'OK' || result === true;
|
|
52
|
-
}
|
|
53
|
-
async delistSignalKey(key, target) {
|
|
54
|
-
await this.storeClient[this.commands.del](`${key}:${target}`);
|
|
55
|
-
}
|
|
56
|
-
async zAdd(key, score, value, redisMulti) {
|
|
57
|
-
//default call signature uses 'ioredis' NPM Package format
|
|
58
|
-
return await (redisMulti || this.storeClient)[this.commands.zadd](key, score, value);
|
|
59
|
-
}
|
|
60
|
-
async zRangeByScore(key, score, value) {
|
|
61
|
-
const result = await this.storeClient[this.commands.zrangebyscore](key, score, value);
|
|
62
|
-
if (result?.length > 0) {
|
|
63
|
-
return result[0];
|
|
64
|
-
}
|
|
65
|
-
return null;
|
|
66
|
-
}
|
|
67
|
-
mintKey(type, params) {
|
|
68
|
-
if (!this.namespace)
|
|
69
|
-
throw new Error('namespace not set');
|
|
70
|
-
return key_1.KeyService.mintKey(this.namespace, type, params);
|
|
71
|
-
}
|
|
72
|
-
invalidateCache() {
|
|
73
|
-
this.cache.invalidate();
|
|
74
|
-
}
|
|
75
|
-
/**
|
|
76
|
-
* At any given time only a single engine will
|
|
77
|
-
* check for and process work items in the
|
|
78
|
-
* time and signal task queues.
|
|
79
|
-
*/
|
|
80
|
-
async reserveScoutRole(scoutType, delay = enums_1.HMSH_SCOUT_INTERVAL_SECONDS) {
|
|
81
|
-
const key = this.mintKey(key_1.KeyType.WORK_ITEMS, {
|
|
82
|
-
appId: this.appId,
|
|
83
|
-
scoutType,
|
|
84
|
-
});
|
|
85
|
-
const success = await this.exec('SET', key, `${scoutType}:${(0, utils_1.formatISODate)(new Date())}`, 'NX', 'EX', `${delay - 1}`);
|
|
86
|
-
return this.isSuccessful(success);
|
|
87
|
-
}
|
|
88
|
-
async releaseScoutRole(scoutType) {
|
|
89
|
-
const key = this.mintKey(key_1.KeyType.WORK_ITEMS, {
|
|
90
|
-
appId: this.appId,
|
|
91
|
-
scoutType,
|
|
92
|
-
});
|
|
93
|
-
const success = await this.exec('DEL', key);
|
|
94
|
-
return this.isSuccessful(success);
|
|
95
|
-
}
|
|
96
|
-
async getSettings(bCreate = false) {
|
|
97
|
-
let settings = this.cache?.getSettings();
|
|
98
|
-
if (settings) {
|
|
99
|
-
return settings;
|
|
100
|
-
}
|
|
101
|
-
else {
|
|
102
|
-
if (bCreate) {
|
|
103
|
-
const packageJson = await Promise.resolve().then(() => __importStar(require('../../../../package.json')));
|
|
104
|
-
const version = packageJson['version'] || '0.0.0';
|
|
105
|
-
settings = { namespace: key_1.HMNS, version };
|
|
106
|
-
await this.setSettings(settings);
|
|
107
|
-
return settings;
|
|
108
|
-
}
|
|
109
|
-
}
|
|
110
|
-
throw new Error('settings not found');
|
|
111
|
-
}
|
|
112
|
-
async setSettings(manifest) {
|
|
113
|
-
//HotMesh heartbeat. If a connection is made, the version will be set
|
|
114
|
-
const params = {};
|
|
115
|
-
const key = this.mintKey(key_1.KeyType.HOTMESH, params);
|
|
116
|
-
return await this.storeClient[this.commands.hset](key, manifest);
|
|
117
|
-
}
|
|
118
|
-
async reserveSymbolRange(target, size, type, tryCount = 1) {
|
|
119
|
-
const rangeKey = this.mintKey(key_1.KeyType.SYMKEYS, { appId: this.appId });
|
|
120
|
-
const symbolKey = this.mintKey(key_1.KeyType.SYMKEYS, {
|
|
121
|
-
activityId: target,
|
|
122
|
-
appId: this.appId,
|
|
123
|
-
});
|
|
124
|
-
//reserve the slot in a `pending` state (range will be established in the next step)
|
|
125
|
-
const response = await this.storeClient[this.commands.hsetnx](rangeKey, target, '?:?');
|
|
126
|
-
if (response) {
|
|
127
|
-
//if the key didn't exist, set the inclusive range and seed metadata fields
|
|
128
|
-
const upperLimit = await this.storeClient[this.commands.hincrby](rangeKey, ':cursor', size);
|
|
129
|
-
const lowerLimit = upperLimit - size;
|
|
130
|
-
const inclusiveRange = `${lowerLimit}:${upperLimit - 1}`;
|
|
131
|
-
await this.storeClient[this.commands.hset](rangeKey, target, inclusiveRange);
|
|
132
|
-
const metadataSeeds = this.seedSymbols(target, type, lowerLimit);
|
|
133
|
-
await this.storeClient[this.commands.hset](symbolKey, metadataSeeds);
|
|
134
|
-
return [lowerLimit + serializer_1.MDATA_SYMBOLS.SLOTS, upperLimit - 1, {}];
|
|
135
|
-
}
|
|
136
|
-
else {
|
|
137
|
-
//if the key already existed, get the lower limit and add the number of symbols
|
|
138
|
-
const range = await this.storeClient[this.commands.hget](rangeKey, target);
|
|
139
|
-
const [lowerLimitString] = range.split(':');
|
|
140
|
-
if (lowerLimitString === '?') {
|
|
141
|
-
await (0, utils_1.sleepFor)(tryCount * 1000);
|
|
142
|
-
if (tryCount < 5) {
|
|
143
|
-
return this.reserveSymbolRange(target, size, type, tryCount + 1);
|
|
144
|
-
}
|
|
145
|
-
else {
|
|
146
|
-
throw new Error('Symbol range reservation failed due to deployment contention');
|
|
147
|
-
}
|
|
148
|
-
}
|
|
149
|
-
else {
|
|
150
|
-
const lowerLimit = parseInt(lowerLimitString, 10);
|
|
151
|
-
const symbols = await this.storeClient[this.commands.hgetall](symbolKey);
|
|
152
|
-
const symbolCount = Object.keys(symbols).length;
|
|
153
|
-
const actualLowerLimit = lowerLimit + serializer_1.MDATA_SYMBOLS.SLOTS + symbolCount;
|
|
154
|
-
const upperLimit = Number(lowerLimit + size - 1);
|
|
155
|
-
return [actualLowerLimit, upperLimit, symbols];
|
|
156
|
-
}
|
|
157
|
-
}
|
|
158
|
-
}
|
|
159
|
-
async getAllSymbols() {
|
|
160
|
-
//get hash with all reserved symbol ranges
|
|
161
|
-
const rangeKey = this.mintKey(key_1.KeyType.SYMKEYS, { appId: this.appId });
|
|
162
|
-
const ranges = await this.storeClient[this.commands.hgetall](rangeKey);
|
|
163
|
-
const rangeKeys = Object.keys(ranges).sort();
|
|
164
|
-
delete rangeKeys[':cursor'];
|
|
165
|
-
const transaction = this.transact();
|
|
166
|
-
for (const rangeKey of rangeKeys) {
|
|
167
|
-
const symbolKey = this.mintKey(key_1.KeyType.SYMKEYS, {
|
|
168
|
-
activityId: rangeKey,
|
|
169
|
-
appId: this.appId,
|
|
170
|
-
});
|
|
171
|
-
transaction[this.commands.hgetall](symbolKey);
|
|
172
|
-
}
|
|
173
|
-
const results = (await transaction.exec());
|
|
174
|
-
const symbolSets = {};
|
|
175
|
-
results.forEach((result, index) => {
|
|
176
|
-
if (result) {
|
|
177
|
-
let vals;
|
|
178
|
-
if (Array.isArray(result) && result.length === 2) {
|
|
179
|
-
vals = result[1];
|
|
180
|
-
}
|
|
181
|
-
else {
|
|
182
|
-
vals = result;
|
|
183
|
-
}
|
|
184
|
-
for (const [key, value] of Object.entries(vals)) {
|
|
185
|
-
symbolSets[value] = key.startsWith(rangeKeys[index])
|
|
186
|
-
? key
|
|
187
|
-
: `${rangeKeys[index]}/${key}`;
|
|
188
|
-
}
|
|
189
|
-
}
|
|
190
|
-
});
|
|
191
|
-
return symbolSets;
|
|
192
|
-
}
|
|
193
|
-
async getSymbols(activityId) {
|
|
194
|
-
let symbols = this.cache.getSymbols(this.appId, activityId);
|
|
195
|
-
if (symbols) {
|
|
196
|
-
return symbols;
|
|
197
|
-
}
|
|
198
|
-
else {
|
|
199
|
-
const params = { activityId, appId: this.appId };
|
|
200
|
-
const key = this.mintKey(key_1.KeyType.SYMKEYS, params);
|
|
201
|
-
symbols = (await this.storeClient[this.commands.hgetall](key));
|
|
202
|
-
this.cache.setSymbols(this.appId, activityId, symbols);
|
|
203
|
-
return symbols;
|
|
204
|
-
}
|
|
205
|
-
}
|
|
206
|
-
async addSymbols(activityId, symbols) {
|
|
207
|
-
if (!symbols || !Object.keys(symbols).length)
|
|
208
|
-
return false;
|
|
209
|
-
const params = { activityId, appId: this.appId };
|
|
210
|
-
const key = this.mintKey(key_1.KeyType.SYMKEYS, params);
|
|
211
|
-
const success = await this.storeClient[this.commands.hset](key, symbols);
|
|
212
|
-
this.cache.deleteSymbols(this.appId, activityId);
|
|
213
|
-
return success > 0;
|
|
214
|
-
}
|
|
215
|
-
seedSymbols(target, type, startIndex) {
|
|
216
|
-
if (type === 'JOB') {
|
|
217
|
-
return this.seedJobSymbols(startIndex);
|
|
218
|
-
}
|
|
219
|
-
return this.seedActivitySymbols(startIndex, target);
|
|
220
|
-
}
|
|
221
|
-
seedJobSymbols(startIndex) {
|
|
222
|
-
const hash = {};
|
|
223
|
-
serializer_1.MDATA_SYMBOLS.JOB.KEYS.forEach((key) => {
|
|
224
|
-
hash[`metadata/${key}`] = (0, utils_1.getSymKey)(startIndex);
|
|
225
|
-
startIndex++;
|
|
226
|
-
});
|
|
227
|
-
return hash;
|
|
228
|
-
}
|
|
229
|
-
seedActivitySymbols(startIndex, activityId) {
|
|
230
|
-
const hash = {};
|
|
231
|
-
serializer_1.MDATA_SYMBOLS.ACTIVITY.KEYS.forEach((key) => {
|
|
232
|
-
hash[`${activityId}/output/metadata/${key}`] = (0, utils_1.getSymKey)(startIndex);
|
|
233
|
-
startIndex++;
|
|
234
|
-
});
|
|
235
|
-
return hash;
|
|
236
|
-
}
|
|
237
|
-
async getSymbolValues() {
|
|
238
|
-
let symvals = this.cache.getSymbolValues(this.appId);
|
|
239
|
-
if (symvals) {
|
|
240
|
-
return symvals;
|
|
241
|
-
}
|
|
242
|
-
else {
|
|
243
|
-
const key = this.mintKey(key_1.KeyType.SYMVALS, { appId: this.appId });
|
|
244
|
-
symvals = await this.storeClient[this.commands.hgetall](key);
|
|
245
|
-
this.cache.setSymbolValues(this.appId, symvals);
|
|
246
|
-
return symvals;
|
|
247
|
-
}
|
|
248
|
-
}
|
|
249
|
-
async addSymbolValues(symvals) {
|
|
250
|
-
if (!symvals || !Object.keys(symvals).length)
|
|
251
|
-
return false;
|
|
252
|
-
const key = this.mintKey(key_1.KeyType.SYMVALS, { appId: this.appId });
|
|
253
|
-
const success = await this.storeClient[this.commands.hset](key, symvals);
|
|
254
|
-
this.cache.deleteSymbolValues(this.appId);
|
|
255
|
-
return this.isSuccessful(success);
|
|
256
|
-
}
|
|
257
|
-
async getSymbolKeys(symbolNames) {
|
|
258
|
-
const symbolLookups = [];
|
|
259
|
-
for (const symbolName of symbolNames) {
|
|
260
|
-
symbolLookups.push(this.getSymbols(symbolName));
|
|
261
|
-
}
|
|
262
|
-
const symbolSets = await Promise.all(symbolLookups);
|
|
263
|
-
const symKeys = {};
|
|
264
|
-
for (const symbolName of symbolNames) {
|
|
265
|
-
symKeys[symbolName] = symbolSets.shift();
|
|
266
|
-
}
|
|
267
|
-
return symKeys;
|
|
268
|
-
}
|
|
269
|
-
async getApp(id, refresh = false) {
|
|
270
|
-
let app = this.cache.getApp(id);
|
|
271
|
-
if (refresh || !(app && Object.keys(app).length > 0)) {
|
|
272
|
-
const params = { appId: id };
|
|
273
|
-
const key = this.mintKey(key_1.KeyType.APP, params);
|
|
274
|
-
const sApp = await this.storeClient[this.commands.hgetall](key);
|
|
275
|
-
if (!sApp)
|
|
276
|
-
return null;
|
|
277
|
-
app = {};
|
|
278
|
-
for (const field in sApp) {
|
|
279
|
-
try {
|
|
280
|
-
if (field === 'active') {
|
|
281
|
-
app[field] = sApp[field] === 'true';
|
|
282
|
-
}
|
|
283
|
-
else {
|
|
284
|
-
app[field] = sApp[field];
|
|
285
|
-
}
|
|
286
|
-
}
|
|
287
|
-
catch (e) {
|
|
288
|
-
app[field] = sApp[field];
|
|
289
|
-
}
|
|
290
|
-
}
|
|
291
|
-
this.cache.setApp(id, app);
|
|
292
|
-
}
|
|
293
|
-
return app;
|
|
294
|
-
}
|
|
295
|
-
async setApp(id, version) {
|
|
296
|
-
const params = { appId: id };
|
|
297
|
-
const key = this.mintKey(key_1.KeyType.APP, params);
|
|
298
|
-
const versionId = `versions/${version}`;
|
|
299
|
-
const payload = {
|
|
300
|
-
id,
|
|
301
|
-
version,
|
|
302
|
-
[versionId]: `deployed:${(0, utils_1.formatISODate)(new Date())}`,
|
|
303
|
-
};
|
|
304
|
-
await this.storeClient[this.commands.hset](key, payload);
|
|
305
|
-
this.cache.setApp(id, payload);
|
|
306
|
-
return payload;
|
|
307
|
-
}
|
|
308
|
-
async activateAppVersion(id, version) {
|
|
309
|
-
const params = { appId: id };
|
|
310
|
-
const key = this.mintKey(key_1.KeyType.APP, params);
|
|
311
|
-
const versionId = `versions/${version}`;
|
|
312
|
-
const app = await this.getApp(id, true);
|
|
313
|
-
if (app && app[versionId]) {
|
|
314
|
-
const payload = {
|
|
315
|
-
id,
|
|
316
|
-
version: version.toString(),
|
|
317
|
-
[versionId]: `activated:${(0, utils_1.formatISODate)(new Date())}`,
|
|
318
|
-
active: true,
|
|
319
|
-
};
|
|
320
|
-
Object.entries(payload).forEach(([key, value]) => {
|
|
321
|
-
payload[key] = value.toString();
|
|
322
|
-
});
|
|
323
|
-
await this.storeClient[this.commands.hset](key, payload);
|
|
324
|
-
return true;
|
|
325
|
-
}
|
|
326
|
-
throw new Error(`Version ${version} does not exist for app ${id}`);
|
|
327
|
-
}
|
|
328
|
-
async registerAppVersion(appId, version) {
|
|
329
|
-
const params = { appId };
|
|
330
|
-
const key = this.mintKey(key_1.KeyType.APP, params);
|
|
331
|
-
const payload = {
|
|
332
|
-
id: appId,
|
|
333
|
-
version: version.toString(),
|
|
334
|
-
[`versions/${version}`]: (0, utils_1.formatISODate)(new Date()),
|
|
335
|
-
};
|
|
336
|
-
return await this.storeClient[this.commands.hset](key, payload);
|
|
337
|
-
}
|
|
338
|
-
async setStats(jobKey, jobId, dateTime, stats, appVersion, transaction) {
|
|
339
|
-
const params = {
|
|
340
|
-
appId: appVersion.id,
|
|
341
|
-
jobId,
|
|
342
|
-
jobKey,
|
|
343
|
-
dateTime,
|
|
344
|
-
};
|
|
345
|
-
const privateMulti = transaction || this.transact();
|
|
346
|
-
if (stats.general.length) {
|
|
347
|
-
const generalStatsKey = this.mintKey(key_1.KeyType.JOB_STATS_GENERAL, params);
|
|
348
|
-
for (const { target, value } of stats.general) {
|
|
349
|
-
privateMulti[this.commands.hincrbyfloat](generalStatsKey, target, value);
|
|
350
|
-
}
|
|
351
|
-
}
|
|
352
|
-
for (const { target, value } of stats.index) {
|
|
353
|
-
const indexParams = { ...params, facet: target };
|
|
354
|
-
const indexStatsKey = this.mintKey(key_1.KeyType.JOB_STATS_INDEX, indexParams);
|
|
355
|
-
privateMulti[this.commands.rpush](indexStatsKey, value.toString());
|
|
356
|
-
}
|
|
357
|
-
for (const { target, value } of stats.median) {
|
|
358
|
-
const medianParams = { ...params, facet: target };
|
|
359
|
-
const medianStatsKey = this.mintKey(key_1.KeyType.JOB_STATS_MEDIAN, medianParams);
|
|
360
|
-
this.zAdd(medianStatsKey, value, target, privateMulti);
|
|
361
|
-
}
|
|
362
|
-
if (!transaction) {
|
|
363
|
-
return await privateMulti.exec();
|
|
364
|
-
}
|
|
365
|
-
}
|
|
366
|
-
hGetAllResult(result) {
|
|
367
|
-
//default response signature uses 'redis' NPM Package format
|
|
368
|
-
return result;
|
|
369
|
-
}
|
|
370
|
-
async getJobStats(jobKeys) {
|
|
371
|
-
const transaction = this.transact();
|
|
372
|
-
for (const jobKey of jobKeys) {
|
|
373
|
-
transaction[this.commands.hgetall](jobKey);
|
|
374
|
-
}
|
|
375
|
-
const results = await transaction.exec();
|
|
376
|
-
const output = {};
|
|
377
|
-
for (const [index, result] of results.entries()) {
|
|
378
|
-
const key = jobKeys[index];
|
|
379
|
-
const statsHash = this.hGetAllResult(result);
|
|
380
|
-
if (statsHash && Object.keys(statsHash).length > 0) {
|
|
381
|
-
const resolvedStatsHash = { ...statsHash };
|
|
382
|
-
for (const [key, val] of Object.entries(resolvedStatsHash)) {
|
|
383
|
-
resolvedStatsHash[key] = Number(val);
|
|
384
|
-
}
|
|
385
|
-
output[key] = resolvedStatsHash;
|
|
386
|
-
}
|
|
387
|
-
else {
|
|
388
|
-
output[key] = {};
|
|
389
|
-
}
|
|
390
|
-
}
|
|
391
|
-
return output;
|
|
392
|
-
}
|
|
393
|
-
async getJobIds(indexKeys, idRange) {
|
|
394
|
-
const transaction = this.transact();
|
|
395
|
-
for (const idsKey of indexKeys) {
|
|
396
|
-
transaction[this.commands.lrange](idsKey, idRange[0], idRange[1]); //0,-1 returns all ids
|
|
397
|
-
}
|
|
398
|
-
const results = await transaction.exec();
|
|
399
|
-
const output = {};
|
|
400
|
-
for (const [index, result] of results.entries()) {
|
|
401
|
-
const key = indexKeys[index];
|
|
402
|
-
//todo: resolve this discrepancy between redis/ioredis
|
|
403
|
-
const idsList = result[1] || result;
|
|
404
|
-
if (idsList && idsList.length > 0) {
|
|
405
|
-
output[key] = idsList;
|
|
406
|
-
}
|
|
407
|
-
else {
|
|
408
|
-
output[key] = [];
|
|
409
|
-
}
|
|
410
|
-
}
|
|
411
|
-
return output;
|
|
412
|
-
}
|
|
413
|
-
async setStatus(collationKeyStatus, jobId, appId, transaction) {
|
|
414
|
-
const jobKey = this.mintKey(key_1.KeyType.JOB_STATE, { appId, jobId });
|
|
415
|
-
return await (transaction || this.storeClient)[this.commands.hincrbyfloat](jobKey, ':', collationKeyStatus);
|
|
416
|
-
}
|
|
417
|
-
async getStatus(jobId, appId) {
|
|
418
|
-
const jobKey = this.mintKey(key_1.KeyType.JOB_STATE, { appId, jobId });
|
|
419
|
-
const status = await this.storeClient[this.commands.hget](jobKey, ':');
|
|
420
|
-
if (status === null) {
|
|
421
|
-
throw new Error(`Job ${jobId} not found`);
|
|
422
|
-
}
|
|
423
|
-
return Number(status);
|
|
424
|
-
}
|
|
425
|
-
async setState({ ...state }, status, jobId, symbolNames, dIds, transaction) {
|
|
426
|
-
delete state['metadata/js'];
|
|
427
|
-
const hashKey = this.mintKey(key_1.KeyType.JOB_STATE, {
|
|
428
|
-
appId: this.appId,
|
|
429
|
-
jobId,
|
|
430
|
-
});
|
|
431
|
-
const symKeys = await this.getSymbolKeys(symbolNames);
|
|
432
|
-
const symVals = await this.getSymbolValues();
|
|
433
|
-
this.serializer.resetSymbols(symKeys, symVals, dIds);
|
|
434
|
-
const hashData = this.serializer.package(state, symbolNames);
|
|
435
|
-
if (status !== null) {
|
|
436
|
-
hashData[':'] = status.toString();
|
|
437
|
-
}
|
|
438
|
-
else {
|
|
439
|
-
delete hashData[':'];
|
|
440
|
-
}
|
|
441
|
-
await (transaction || this.storeClient)[this.commands.hset](hashKey, hashData);
|
|
442
|
-
return jobId;
|
|
443
|
-
}
|
|
444
|
-
/**
|
|
445
|
-
* Returns custom search fields and values.
|
|
446
|
-
* NOTE: The `fields` param should NOT prefix items with an underscore.
|
|
447
|
-
* NOTE: Literals are allowed if quoted.
|
|
448
|
-
*/
|
|
449
|
-
async getQueryState(jobId, fields) {
|
|
450
|
-
const key = this.mintKey(key_1.KeyType.JOB_STATE, { appId: this.appId, jobId });
|
|
451
|
-
const _fields = fields.map((field) => {
|
|
452
|
-
if (field.startsWith('"')) {
|
|
453
|
-
return field.slice(1, -1);
|
|
454
|
-
}
|
|
455
|
-
return `_${field}`;
|
|
456
|
-
});
|
|
457
|
-
const jobDataArray = await this.storeClient[this.commands.hmget](key, _fields);
|
|
458
|
-
const jobData = {};
|
|
459
|
-
fields.forEach((field, index) => {
|
|
460
|
-
if (field.startsWith('"')) {
|
|
461
|
-
field = field.slice(1, -1);
|
|
462
|
-
}
|
|
463
|
-
jobData[field] = jobDataArray[index];
|
|
464
|
-
});
|
|
465
|
-
return jobData;
|
|
466
|
-
}
|
|
467
|
-
async getState(jobId, consumes, dIds) {
|
|
468
|
-
//get abbreviated field list (the symbols for the paths)
|
|
469
|
-
const key = this.mintKey(key_1.KeyType.JOB_STATE, { appId: this.appId, jobId });
|
|
470
|
-
const symbolNames = Object.keys(consumes);
|
|
471
|
-
const symKeys = await this.getSymbolKeys(symbolNames);
|
|
472
|
-
this.serializer.resetSymbols(symKeys, {}, dIds);
|
|
473
|
-
const fields = this.serializer.abbreviate(consumes, symbolNames, [':']);
|
|
474
|
-
const jobDataArray = await this.storeClient[this.commands.hmget](key, fields);
|
|
475
|
-
const jobData = {};
|
|
476
|
-
let atLeast1 = false; //if status field (':') isn't present assume 404
|
|
477
|
-
fields.forEach((field, index) => {
|
|
478
|
-
if (jobDataArray[index]) {
|
|
479
|
-
atLeast1 = true;
|
|
480
|
-
}
|
|
481
|
-
jobData[field] = jobDataArray[index];
|
|
482
|
-
});
|
|
483
|
-
if (atLeast1) {
|
|
484
|
-
const symVals = await this.getSymbolValues();
|
|
485
|
-
this.serializer.resetSymbols(symKeys, symVals, dIds);
|
|
486
|
-
const state = this.serializer.unpackage(jobData, symbolNames);
|
|
487
|
-
let status = 0;
|
|
488
|
-
if (state[':']) {
|
|
489
|
-
status = Number(state[':']);
|
|
490
|
-
state[`metadata/js`] = status;
|
|
491
|
-
delete state[':'];
|
|
492
|
-
}
|
|
493
|
-
return [state, status];
|
|
494
|
-
}
|
|
495
|
-
else {
|
|
496
|
-
throw new errors_1.GetStateError(jobId);
|
|
497
|
-
}
|
|
498
|
-
}
|
|
499
|
-
async getRaw(jobId) {
|
|
500
|
-
const jobKey = this.mintKey(key_1.KeyType.JOB_STATE, {
|
|
501
|
-
appId: this.appId,
|
|
502
|
-
jobId,
|
|
503
|
-
});
|
|
504
|
-
const job = await this.storeClient[this.commands.hgetall](jobKey);
|
|
505
|
-
if (!job) {
|
|
506
|
-
throw new errors_1.GetStateError(jobId);
|
|
507
|
-
}
|
|
508
|
-
return job;
|
|
509
|
-
}
|
|
510
|
-
/**
|
|
511
|
-
* collate is a generic method for incrementing a value in a hash
|
|
512
|
-
* in order to track their progress during processing.
|
|
513
|
-
*/
|
|
514
|
-
async collate(jobId, activityId, amount, dIds, transaction) {
|
|
515
|
-
const jobKey = this.mintKey(key_1.KeyType.JOB_STATE, {
|
|
516
|
-
appId: this.appId,
|
|
517
|
-
jobId,
|
|
518
|
-
});
|
|
519
|
-
const collationKey = `${activityId}/output/metadata/as`; //activity state
|
|
520
|
-
const symbolNames = [activityId];
|
|
521
|
-
const symKeys = await this.getSymbolKeys(symbolNames);
|
|
522
|
-
const symVals = await this.getSymbolValues();
|
|
523
|
-
this.serializer.resetSymbols(symKeys, symVals, dIds);
|
|
524
|
-
const payload = { [collationKey]: amount.toString() };
|
|
525
|
-
const hashData = this.serializer.package(payload, symbolNames);
|
|
526
|
-
const targetId = Object.keys(hashData)[0];
|
|
527
|
-
return await (transaction || this.storeClient)[this.commands.hincrbyfloat](jobKey, targetId, amount);
|
|
528
|
-
}
|
|
529
|
-
/**
|
|
530
|
-
* Synthentic collation affects those activities in the graph
|
|
531
|
-
* that represent the synthetic DAG that was materialized during compilation;
|
|
532
|
-
* Synthetic collation distinguishes `re-entry due to failure` from
|
|
533
|
-
* `purposeful re-entry`.
|
|
534
|
-
*/
|
|
535
|
-
async collateSynthetic(jobId, guid, amount, transaction) {
|
|
536
|
-
const jobKey = this.mintKey(key_1.KeyType.JOB_STATE, {
|
|
537
|
-
appId: this.appId,
|
|
538
|
-
jobId,
|
|
539
|
-
});
|
|
540
|
-
return await (transaction || this.storeClient)[this.commands.hincrbyfloat](jobKey, guid, amount.toString());
|
|
541
|
-
}
|
|
542
|
-
async setStateNX(jobId, appId, status, entity) {
|
|
543
|
-
const hashKey = this.mintKey(key_1.KeyType.JOB_STATE, { appId, jobId });
|
|
544
|
-
const result = await this.storeClient[this.commands.hsetnx](hashKey, ':', status?.toString() ?? '1');
|
|
545
|
-
return this.isSuccessful(result);
|
|
546
|
-
}
|
|
547
|
-
async getSchema(activityId, appVersion) {
|
|
548
|
-
const schema = this.cache.getSchema(appVersion.id, appVersion.version, activityId);
|
|
549
|
-
if (schema) {
|
|
550
|
-
return schema;
|
|
551
|
-
}
|
|
552
|
-
else {
|
|
553
|
-
const schemas = await this.getSchemas(appVersion);
|
|
554
|
-
return schemas[activityId];
|
|
555
|
-
}
|
|
556
|
-
}
|
|
557
|
-
async getSchemas(appVersion) {
|
|
558
|
-
let schemas = this.cache.getSchemas(appVersion.id, appVersion.version);
|
|
559
|
-
if (schemas && Object.keys(schemas).length > 0) {
|
|
560
|
-
return schemas;
|
|
561
|
-
}
|
|
562
|
-
else {
|
|
563
|
-
const params = {
|
|
564
|
-
appId: appVersion.id,
|
|
565
|
-
appVersion: appVersion.version,
|
|
566
|
-
};
|
|
567
|
-
const key = this.mintKey(key_1.KeyType.SCHEMAS, params);
|
|
568
|
-
schemas = {};
|
|
569
|
-
const hash = await this.storeClient[this.commands.hgetall](key);
|
|
570
|
-
Object.entries(hash).forEach(([key, value]) => {
|
|
571
|
-
schemas[key] = JSON.parse(value);
|
|
572
|
-
});
|
|
573
|
-
this.cache.setSchemas(appVersion.id, appVersion.version, schemas);
|
|
574
|
-
return schemas;
|
|
575
|
-
}
|
|
576
|
-
}
|
|
577
|
-
async setSchemas(schemas, appVersion) {
|
|
578
|
-
const params = {
|
|
579
|
-
appId: appVersion.id,
|
|
580
|
-
appVersion: appVersion.version,
|
|
581
|
-
};
|
|
582
|
-
const key = this.mintKey(key_1.KeyType.SCHEMAS, params);
|
|
583
|
-
const _schemas = { ...schemas };
|
|
584
|
-
Object.entries(_schemas).forEach(([key, value]) => {
|
|
585
|
-
_schemas[key] = JSON.stringify(value);
|
|
586
|
-
});
|
|
587
|
-
const response = await this.storeClient[this.commands.hset](key, _schemas);
|
|
588
|
-
this.cache.setSchemas(appVersion.id, appVersion.version, schemas);
|
|
589
|
-
return response;
|
|
590
|
-
}
|
|
591
|
-
async setSubscriptions(subscriptions, appVersion) {
|
|
592
|
-
const params = {
|
|
593
|
-
appId: appVersion.id,
|
|
594
|
-
appVersion: appVersion.version,
|
|
595
|
-
};
|
|
596
|
-
const key = this.mintKey(key_1.KeyType.SUBSCRIPTIONS, params);
|
|
597
|
-
const _subscriptions = { ...subscriptions };
|
|
598
|
-
Object.entries(_subscriptions).forEach(([key, value]) => {
|
|
599
|
-
_subscriptions[key] = JSON.stringify(value);
|
|
600
|
-
});
|
|
601
|
-
const status = await this.storeClient[this.commands.hset](key, _subscriptions);
|
|
602
|
-
this.cache.setSubscriptions(appVersion.id, appVersion.version, subscriptions);
|
|
603
|
-
return this.isSuccessful(status);
|
|
604
|
-
}
|
|
605
|
-
async getSubscriptions(appVersion) {
|
|
606
|
-
let subscriptions = this.cache.getSubscriptions(appVersion.id, appVersion.version);
|
|
607
|
-
if (subscriptions && Object.keys(subscriptions).length > 0) {
|
|
608
|
-
return subscriptions;
|
|
609
|
-
}
|
|
610
|
-
else {
|
|
611
|
-
const params = {
|
|
612
|
-
appId: appVersion.id,
|
|
613
|
-
appVersion: appVersion.version,
|
|
614
|
-
};
|
|
615
|
-
const key = this.mintKey(key_1.KeyType.SUBSCRIPTIONS, params);
|
|
616
|
-
subscriptions =
|
|
617
|
-
await this.storeClient[this.commands.hgetall](key) || {};
|
|
618
|
-
Object.entries(subscriptions).forEach(([key, value]) => {
|
|
619
|
-
subscriptions[key] = JSON.parse(value);
|
|
620
|
-
});
|
|
621
|
-
this.cache.setSubscriptions(appVersion.id, appVersion.version, subscriptions);
|
|
622
|
-
return subscriptions;
|
|
623
|
-
}
|
|
624
|
-
}
|
|
625
|
-
async getSubscription(topic, appVersion) {
|
|
626
|
-
const subscriptions = await this.getSubscriptions(appVersion);
|
|
627
|
-
return subscriptions[topic];
|
|
628
|
-
}
|
|
629
|
-
async setTransitions(transitions, appVersion) {
|
|
630
|
-
const params = {
|
|
631
|
-
appId: appVersion.id,
|
|
632
|
-
appVersion: appVersion.version,
|
|
633
|
-
};
|
|
634
|
-
const key = this.mintKey(key_1.KeyType.SUBSCRIPTION_PATTERNS, params);
|
|
635
|
-
const _subscriptions = { ...transitions };
|
|
636
|
-
Object.entries(_subscriptions).forEach(([key, value]) => {
|
|
637
|
-
_subscriptions[key] = JSON.stringify(value);
|
|
638
|
-
});
|
|
639
|
-
if (Object.keys(_subscriptions).length !== 0) {
|
|
640
|
-
const response = await this.storeClient[this.commands.hset](key, _subscriptions);
|
|
641
|
-
this.cache.setTransitions(appVersion.id, appVersion.version, transitions);
|
|
642
|
-
return response;
|
|
643
|
-
}
|
|
644
|
-
}
|
|
645
|
-
async getTransitions(appVersion) {
|
|
646
|
-
let transitions = this.cache.getTransitions(appVersion.id, appVersion.version);
|
|
647
|
-
if (transitions && Object.keys(transitions).length > 0) {
|
|
648
|
-
return transitions;
|
|
649
|
-
}
|
|
650
|
-
else {
|
|
651
|
-
const params = {
|
|
652
|
-
appId: appVersion.id,
|
|
653
|
-
appVersion: appVersion.version,
|
|
654
|
-
};
|
|
655
|
-
const key = this.mintKey(key_1.KeyType.SUBSCRIPTION_PATTERNS, params);
|
|
656
|
-
transitions = {};
|
|
657
|
-
const hash = await this.storeClient[this.commands.hgetall](key);
|
|
658
|
-
Object.entries(hash).forEach(([key, value]) => {
|
|
659
|
-
transitions[key] = JSON.parse(value);
|
|
660
|
-
});
|
|
661
|
-
this.cache.setTransitions(appVersion.id, appVersion.version, transitions);
|
|
662
|
-
return transitions;
|
|
663
|
-
}
|
|
664
|
-
}
|
|
665
|
-
async setHookRules(hookRules) {
|
|
666
|
-
const key = this.mintKey(key_1.KeyType.HOOKS, { appId: this.appId });
|
|
667
|
-
const _hooks = {};
|
|
668
|
-
Object.entries(hookRules).forEach(([key, value]) => {
|
|
669
|
-
_hooks[key.toString()] = JSON.stringify(value);
|
|
670
|
-
});
|
|
671
|
-
if (Object.keys(_hooks).length !== 0) {
|
|
672
|
-
const response = await this.storeClient[this.commands.hset](key, _hooks);
|
|
673
|
-
this.cache.setHookRules(this.appId, hookRules);
|
|
674
|
-
return response;
|
|
675
|
-
}
|
|
676
|
-
}
|
|
677
|
-
async getHookRules() {
|
|
678
|
-
let patterns = this.cache.getHookRules(this.appId);
|
|
679
|
-
if (patterns && Object.keys(patterns).length > 0) {
|
|
680
|
-
return patterns;
|
|
681
|
-
}
|
|
682
|
-
else {
|
|
683
|
-
const key = this.mintKey(key_1.KeyType.HOOKS, { appId: this.appId });
|
|
684
|
-
const _hooks = await this.storeClient[this.commands.hgetall](key);
|
|
685
|
-
patterns = {};
|
|
686
|
-
Object.entries(_hooks).forEach(([key, value]) => {
|
|
687
|
-
patterns[key] = JSON.parse(value);
|
|
688
|
-
});
|
|
689
|
-
this.cache.setHookRules(this.appId, patterns);
|
|
690
|
-
return patterns;
|
|
691
|
-
}
|
|
692
|
-
}
|
|
693
|
-
async setHookSignal(hook, transaction) {
|
|
694
|
-
const key = this.mintKey(key_1.KeyType.SIGNALS, { appId: this.appId });
|
|
695
|
-
const { topic, resolved, jobId } = hook;
|
|
696
|
-
const signalKey = `${topic}:${resolved}`;
|
|
697
|
-
await this.setnxex(`${key}:${signalKey}`, jobId, Math.max(hook.expire, enums_1.HMSH_SIGNAL_EXPIRE));
|
|
698
|
-
}
|
|
699
|
-
async getHookSignal(topic, resolved) {
|
|
700
|
-
const key = this.mintKey(key_1.KeyType.SIGNALS, { appId: this.appId });
|
|
701
|
-
const response = await this.storeClient[this.commands.get](`${key}:${topic}:${resolved}`);
|
|
702
|
-
return response ? response.toString() : undefined;
|
|
703
|
-
}
|
|
704
|
-
async deleteHookSignal(topic, resolved) {
|
|
705
|
-
const key = this.mintKey(key_1.KeyType.SIGNALS, { appId: this.appId });
|
|
706
|
-
const response = await this.storeClient[this.commands.del](`${key}:${topic}:${resolved}`);
|
|
707
|
-
return response ? Number(response) : undefined;
|
|
708
|
-
}
|
|
709
|
-
async addTaskQueues(keys) {
|
|
710
|
-
const transaction = this.transact();
|
|
711
|
-
const zsetKey = this.mintKey(key_1.KeyType.WORK_ITEMS, { appId: this.appId });
|
|
712
|
-
for (const key of keys) {
|
|
713
|
-
transaction[this.commands.zadd](zsetKey, { score: Date.now().toString(), value: key }, { NX: true });
|
|
714
|
-
}
|
|
715
|
-
await transaction.exec();
|
|
716
|
-
}
|
|
717
|
-
async getActiveTaskQueue() {
|
|
718
|
-
let workItemKey = this.cache.getActiveTaskQueue(this.appId) || null;
|
|
719
|
-
if (!workItemKey) {
|
|
720
|
-
const zsetKey = this.mintKey(key_1.KeyType.WORK_ITEMS, { appId: this.appId });
|
|
721
|
-
const result = await this.storeClient[this.commands.zrange](zsetKey, 0, 0);
|
|
722
|
-
workItemKey = result.length > 0 ? result[0] : null;
|
|
723
|
-
if (workItemKey) {
|
|
724
|
-
this.cache.setWorkItem(this.appId, workItemKey);
|
|
725
|
-
}
|
|
726
|
-
}
|
|
727
|
-
return workItemKey;
|
|
728
|
-
}
|
|
729
|
-
async deleteProcessedTaskQueue(workItemKey, key, processedKey, scrub = false) {
|
|
730
|
-
const zsetKey = this.mintKey(key_1.KeyType.WORK_ITEMS, { appId: this.appId });
|
|
731
|
-
const didRemove = await this.storeClient[this.commands.zrem](zsetKey, workItemKey);
|
|
732
|
-
if (didRemove) {
|
|
733
|
-
if (scrub) {
|
|
734
|
-
//indexes can be designed to be self-cleaning; `engine.hookAll` exposes this option
|
|
735
|
-
this.storeClient[this.commands.expire](processedKey, 0);
|
|
736
|
-
this.storeClient[this.commands.expire](key.split(':').slice(0, 5).join(':'), 0);
|
|
737
|
-
}
|
|
738
|
-
else {
|
|
739
|
-
await this.storeClient[this.commands.rename](processedKey, key);
|
|
740
|
-
}
|
|
741
|
-
}
|
|
742
|
-
this.cache.removeWorkItem(this.appId);
|
|
743
|
-
}
|
|
744
|
-
async processTaskQueue(sourceKey, destinationKey) {
|
|
745
|
-
return await this.storeClient[this.commands.lmove](sourceKey, destinationKey, 'LEFT', 'RIGHT');
|
|
746
|
-
}
|
|
747
|
-
async expireJob(jobId, inSeconds, redisMulti) {
|
|
748
|
-
if (!isNaN(inSeconds) && inSeconds > 0) {
|
|
749
|
-
const jobKey = this.mintKey(key_1.KeyType.JOB_STATE, {
|
|
750
|
-
appId: this.appId,
|
|
751
|
-
jobId,
|
|
752
|
-
});
|
|
753
|
-
await (redisMulti || this.storeClient)[this.commands.expire](jobKey, inSeconds);
|
|
754
|
-
}
|
|
755
|
-
}
|
|
756
|
-
async getDependencies(jobId) {
|
|
757
|
-
const depParams = { appId: this.appId, jobId };
|
|
758
|
-
const depKey = this.mintKey(key_1.KeyType.JOB_DEPENDENTS, depParams);
|
|
759
|
-
return this.storeClient[this.commands.lrange](depKey, 0, -1);
|
|
760
|
-
}
|
|
761
|
-
/**
|
|
762
|
-
* registers a hook activity to be awakened (uses ZSET to
|
|
763
|
-
* store the 'sleep group' and LIST to store the events
|
|
764
|
-
* for the given sleep group. Sleep groups are
|
|
765
|
-
* organized into 'n'-second blocks (LISTS))
|
|
766
|
-
*/
|
|
767
|
-
async registerTimeHook(jobId, gId, activityId, type, deletionTime, dad, transaction) {
|
|
768
|
-
const listKey = this.mintKey(key_1.KeyType.TIME_RANGE, {
|
|
769
|
-
appId: this.appId,
|
|
770
|
-
timeValue: deletionTime,
|
|
771
|
-
});
|
|
772
|
-
//construct the composite key (the key has enough info to signal the hook)
|
|
773
|
-
const timeEvent = [type, activityId, gId, dad, jobId].join(key_1.VALSEP);
|
|
774
|
-
const len = await (transaction || this.storeClient)[this.commands.rpush](listKey, timeEvent);
|
|
775
|
-
if (transaction || len === 1) {
|
|
776
|
-
const zsetKey = this.mintKey(key_1.KeyType.TIME_RANGE, { appId: this.appId });
|
|
777
|
-
await this.zAdd(zsetKey, deletionTime.toString(), listKey, transaction);
|
|
778
|
-
}
|
|
779
|
-
}
|
|
780
|
-
async getNextTask(listKey) {
|
|
781
|
-
const zsetKey = this.mintKey(key_1.KeyType.TIME_RANGE, { appId: this.appId });
|
|
782
|
-
listKey = listKey || await this.zRangeByScore(zsetKey, 0, Date.now());
|
|
783
|
-
if (listKey) {
|
|
784
|
-
let [pType, pKey] = this.resolveTaskKeyContext(listKey);
|
|
785
|
-
const timeEvent = await this.storeClient[this.commands.lpop](pKey);
|
|
786
|
-
if (timeEvent) {
|
|
787
|
-
//deconstruct composite key
|
|
788
|
-
let [type, activityId, gId, _pd, ...jobId] = timeEvent.split(key_1.VALSEP);
|
|
789
|
-
const jid = jobId.join(key_1.VALSEP);
|
|
790
|
-
if (type === 'delist') {
|
|
791
|
-
pType = 'delist';
|
|
792
|
-
}
|
|
793
|
-
else if (type === 'child') {
|
|
794
|
-
pType = 'child';
|
|
795
|
-
}
|
|
796
|
-
else if (type === 'expire-child') {
|
|
797
|
-
type = 'expire';
|
|
798
|
-
}
|
|
799
|
-
return [listKey, jid, gId, activityId, pType];
|
|
800
|
-
}
|
|
801
|
-
await this.storeClient[this.commands.zrem](zsetKey, listKey);
|
|
802
|
-
return true;
|
|
803
|
-
}
|
|
804
|
-
return false;
|
|
805
|
-
}
|
|
806
|
-
/**
|
|
807
|
-
* when processing time jobs, the target LIST ID returned
|
|
808
|
-
* from the ZSET query can be prefixed to denote what to
|
|
809
|
-
* do with the work list. (not everything is known in advance,
|
|
810
|
-
* so the ZSET key defines HOW to approach the work in the
|
|
811
|
-
* generic LIST (lists typically contain target job ids)
|
|
812
|
-
* @param {string} listKey - composite key
|
|
813
|
-
*/
|
|
814
|
-
resolveTaskKeyContext(listKey) {
|
|
815
|
-
if (listKey.startsWith(`${key_1.TYPSEP}INTERRUPT`)) {
|
|
816
|
-
return ['interrupt', listKey.split(key_1.TYPSEP)[2]];
|
|
817
|
-
}
|
|
818
|
-
else if (listKey.startsWith(`${key_1.TYPSEP}EXPIRE`)) {
|
|
819
|
-
return ['expire', listKey.split(key_1.TYPSEP)[2]];
|
|
820
|
-
}
|
|
821
|
-
else {
|
|
822
|
-
return ['sleep', listKey];
|
|
823
|
-
}
|
|
824
|
-
}
|
|
825
|
-
/**
|
|
826
|
-
* Interrupts a job and sets sets a job error (410), if 'throw'!=false.
|
|
827
|
-
* This method is called by the engine and not by an activity and is
|
|
828
|
-
* followed by a call to execute job completion/cleanup tasks
|
|
829
|
-
* associated with a job completion event.
|
|
830
|
-
*
|
|
831
|
-
* Todo: move most of this logic to the engine (too much logic for the store)
|
|
832
|
-
*/
|
|
833
|
-
async interrupt(topic, jobId, options = {}) {
|
|
834
|
-
try {
|
|
835
|
-
//verify job exists
|
|
836
|
-
const status = await this.getStatus(jobId, this.appId);
|
|
837
|
-
if (status <= 0) {
|
|
838
|
-
//verify still active; job already completed
|
|
839
|
-
throw new Error(`Job ${jobId} already completed`);
|
|
840
|
-
}
|
|
841
|
-
//decrement job status (:) by 1bil
|
|
842
|
-
const amount = -1000000000;
|
|
843
|
-
const jobKey = this.mintKey(key_1.KeyType.JOB_STATE, {
|
|
844
|
-
appId: this.appId,
|
|
845
|
-
jobId,
|
|
846
|
-
});
|
|
847
|
-
const result = await this.storeClient[this.commands.hincrbyfloat](jobKey, ':', amount);
|
|
848
|
-
if (result <= amount) {
|
|
849
|
-
//verify active state; job already interrupted
|
|
850
|
-
throw new Error(`Job ${jobId} already completed`);
|
|
851
|
-
}
|
|
852
|
-
//persist the error unless specifically told not to
|
|
853
|
-
if (options.throw !== false) {
|
|
854
|
-
const errKey = `metadata/err`; //job errors are stored at the path `metadata/err`
|
|
855
|
-
const symbolNames = [`$${topic}`]; //the symbol for `metadata/err`
|
|
856
|
-
const symKeys = await this.getSymbolKeys(symbolNames);
|
|
857
|
-
const symVals = await this.getSymbolValues();
|
|
858
|
-
this.serializer.resetSymbols(symKeys, symVals, {});
|
|
859
|
-
//persists the standard 410 error (job is `gone`)
|
|
860
|
-
const err = JSON.stringify({
|
|
861
|
-
code: options.code ?? enums_1.HMSH_CODE_INTERRUPT,
|
|
862
|
-
message: options.reason ?? `job [${jobId}] interrupted`,
|
|
863
|
-
stack: options.stack ?? '',
|
|
864
|
-
job_id: jobId,
|
|
865
|
-
});
|
|
866
|
-
const payload = { [errKey]: amount.toString() };
|
|
867
|
-
const hashData = this.serializer.package(payload, symbolNames);
|
|
868
|
-
const errSymbol = Object.keys(hashData)[0];
|
|
869
|
-
await this.storeClient[this.commands.hset](jobKey, errSymbol, err);
|
|
870
|
-
}
|
|
871
|
-
}
|
|
872
|
-
catch (e) {
|
|
873
|
-
if (!options.suppress) {
|
|
874
|
-
throw e;
|
|
875
|
-
}
|
|
876
|
-
else {
|
|
877
|
-
this.logger.debug('suppressed-interrupt', { message: e.message });
|
|
878
|
-
}
|
|
879
|
-
}
|
|
880
|
-
}
|
|
881
|
-
async scrub(jobId) {
|
|
882
|
-
const jobKey = this.mintKey(key_1.KeyType.JOB_STATE, {
|
|
883
|
-
appId: this.appId,
|
|
884
|
-
jobId,
|
|
885
|
-
});
|
|
886
|
-
await this.storeClient[this.commands.del](jobKey);
|
|
887
|
-
}
|
|
888
|
-
async findJobs(queryString = '*', limit = 1000, batchSize = 1000, cursor = '0') {
|
|
889
|
-
const matchKey = this.mintKey(key_1.KeyType.JOB_STATE, {
|
|
890
|
-
appId: this.appId,
|
|
891
|
-
jobId: queryString,
|
|
892
|
-
});
|
|
893
|
-
let keys;
|
|
894
|
-
const matchingKeys = [];
|
|
895
|
-
do {
|
|
896
|
-
const output = (await this.exec('SCAN', cursor, 'MATCH', matchKey, 'COUNT', batchSize.toString()));
|
|
897
|
-
if (Array.isArray(output)) {
|
|
898
|
-
[cursor, keys] = output;
|
|
899
|
-
for (const key of [...keys]) {
|
|
900
|
-
matchingKeys.push(key);
|
|
901
|
-
}
|
|
902
|
-
if (matchingKeys.length >= limit) {
|
|
903
|
-
break;
|
|
904
|
-
}
|
|
905
|
-
}
|
|
906
|
-
else {
|
|
907
|
-
break;
|
|
908
|
-
}
|
|
909
|
-
} while (cursor !== '0');
|
|
910
|
-
return [cursor, matchingKeys];
|
|
911
|
-
}
|
|
912
|
-
async findJobFields(jobId, fieldMatchPattern = '*', limit = 1000, batchSize = 1000, cursor = '0') {
|
|
913
|
-
let fields = [];
|
|
914
|
-
const matchingFields = {};
|
|
915
|
-
const jobKey = this.mintKey(key_1.KeyType.JOB_STATE, {
|
|
916
|
-
appId: this.appId,
|
|
917
|
-
jobId,
|
|
918
|
-
});
|
|
919
|
-
let len = 0;
|
|
920
|
-
do {
|
|
921
|
-
const output = (await this.exec('HSCAN', jobKey, cursor, 'MATCH', fieldMatchPattern, 'COUNT', batchSize.toString()));
|
|
922
|
-
if (Array.isArray(output)) {
|
|
923
|
-
[cursor, fields] = output;
|
|
924
|
-
for (let i = 0; i < fields.length; i += 2) {
|
|
925
|
-
len++;
|
|
926
|
-
matchingFields[fields[i]] = fields[i + 1];
|
|
927
|
-
}
|
|
928
|
-
}
|
|
929
|
-
else {
|
|
930
|
-
break;
|
|
931
|
-
}
|
|
932
|
-
} while (cursor !== '0' && len < limit);
|
|
933
|
-
return [cursor, matchingFields];
|
|
934
|
-
}
|
|
935
|
-
async setThrottleRate(options) {
|
|
936
|
-
const key = this.mintKey(key_1.KeyType.THROTTLE_RATE, { appId: this.appId });
|
|
937
|
-
//engine guids are session specific. no need to persist
|
|
938
|
-
if (options.guid) {
|
|
939
|
-
return;
|
|
940
|
-
}
|
|
941
|
-
//if a topic, update one
|
|
942
|
-
const rate = options.throttle.toString();
|
|
943
|
-
if (options.topic) {
|
|
944
|
-
await this.storeClient[this.commands.hset](key, {
|
|
945
|
-
[options.topic]: rate,
|
|
946
|
-
});
|
|
947
|
-
}
|
|
948
|
-
else {
|
|
949
|
-
//if no topic, update all
|
|
950
|
-
const transaction = this.transact();
|
|
951
|
-
transaction[this.commands.del](key);
|
|
952
|
-
transaction[this.commands.hset](key, { ':': rate });
|
|
953
|
-
await transaction.exec();
|
|
954
|
-
}
|
|
955
|
-
}
|
|
956
|
-
async getThrottleRates() {
|
|
957
|
-
const key = this.mintKey(key_1.KeyType.THROTTLE_RATE, { appId: this.appId });
|
|
958
|
-
const response = await this.storeClient[this.commands.hgetall](key);
|
|
959
|
-
return response ?? {};
|
|
960
|
-
}
|
|
961
|
-
async getThrottleRate(topic) {
|
|
962
|
-
//always return a valid number range
|
|
963
|
-
const resolveRate = (response, topic) => {
|
|
964
|
-
const rate = topic in response ? Number(response[topic]) : 0;
|
|
965
|
-
if (isNaN(rate))
|
|
966
|
-
return 0;
|
|
967
|
-
if (rate == -1)
|
|
968
|
-
return enums_1.MAX_DELAY;
|
|
969
|
-
return Math.max(Math.min(rate, enums_1.MAX_DELAY), 0);
|
|
970
|
-
};
|
|
971
|
-
const response = await this.getThrottleRates();
|
|
972
|
-
const globalRate = resolveRate(response, ':');
|
|
973
|
-
if (topic === ':' || !(topic in response)) {
|
|
974
|
-
//use global rate unless worker specifies rate
|
|
975
|
-
return globalRate;
|
|
976
|
-
}
|
|
977
|
-
return resolveRate(response, topic);
|
|
978
|
-
}
|
|
979
|
-
}
|
|
980
|
-
exports.RedisStoreBase = RedisStoreBase;
|