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