@hotmeshio/hotmesh 0.1.0 → 0.1.1
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.d.ts +1 -0
- package/build/modules/enums.js +2 -1
- package/build/modules/utils.d.ts +1 -0
- package/build/modules/utils.js +5 -1
- package/build/package.json +1 -1
- package/build/services/activities/activity.js +3 -3
- package/build/services/store/clients/ioredis.d.ts +6 -0
- package/build/services/store/clients/ioredis.js +82 -0
- package/build/services/store/clients/redis.d.ts +6 -0
- package/build/services/store/clients/redis.js +116 -0
- package/build/services/store/index.js +0 -1
- package/build/types/durable.d.ts +81 -77
- package/build/types/index.d.ts +1 -1
- package/build/types/redis.d.ts +6 -1
- package/package.json +1 -1
- package/types/durable.ts +95 -91
- package/types/index.ts +1 -0
- package/types/redis.ts +26 -1
package/build/modules/enums.d.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { LogLevel } from '../types/logger';
|
|
2
2
|
export declare const HMSH_LOGLEVEL: LogLevel;
|
|
3
|
+
export declare const HMSH_IS_CLUSTER: boolean;
|
|
3
4
|
export declare const HMSH_CODE_SUCCESS = 200;
|
|
4
5
|
export declare const HMSH_CODE_PENDING = 202;
|
|
5
6
|
export declare const HMSH_CODE_NOTFOUND = 404;
|
package/build/modules/enums.js
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.HMSH_GUID_SIZE = exports.HMSH_SCOUT_INTERVAL_SECONDS = exports.HMSH_FIDELITY_SECONDS = exports.HMSH_EXPIRE_DURATION = exports.HMSH_XPENDING_COUNT = exports.HMSH_XCLAIM_COUNT = exports.HMSH_XCLAIM_DELAY_MS = exports.HMSH_BLOCK_TIME_MS = exports.HMSH_DURABLE_EXP_BACKOFF = exports.HMSH_DURABLE_MAX_INTERVAL = exports.HMSH_DURABLE_MAX_ATTEMPTS = exports.HMSH_GRADUATED_INTERVAL_MS = exports.HMSH_MAX_TIMEOUT_MS = exports.HMSH_MAX_RETRIES = exports.HMSH_EXPIRE_JOB_SECONDS = exports.HMSH_OTT_WAIT_TIME = exports.HMSH_ACTIVATION_MAX_RETRY = exports.HMSH_QUORUM_DELAY_MS = exports.HMSH_QUORUM_ROLLCALL_CYCLES = exports.HMSH_STATUS_UNKNOWN = exports.HMSH_CODE_DURABLE_RETRYABLE = exports.HMSH_CODE_DURABLE_FATAL = exports.HMSH_CODE_DURABLE_MAXED = exports.HMSH_CODE_DURABLE_TIMEOUT = exports.HMSH_CODE_DURABLE_WAIT = exports.HMSH_CODE_DURABLE_PROXY = exports.HMSH_CODE_DURABLE_CHILD = exports.HMSH_CODE_DURABLE_ALL = exports.HMSH_CODE_DURABLE_SLEEP = exports.HMSH_CODE_UNACKED = exports.HMSH_CODE_TIMEOUT = exports.HMSH_CODE_UNKNOWN = exports.HMSH_CODE_INTERRUPT = exports.HMSH_CODE_NOTFOUND = exports.HMSH_CODE_PENDING = exports.HMSH_CODE_SUCCESS = exports.HMSH_LOGLEVEL = void 0;
|
|
3
|
+
exports.HMSH_GUID_SIZE = exports.HMSH_SCOUT_INTERVAL_SECONDS = exports.HMSH_FIDELITY_SECONDS = exports.HMSH_EXPIRE_DURATION = exports.HMSH_XPENDING_COUNT = exports.HMSH_XCLAIM_COUNT = exports.HMSH_XCLAIM_DELAY_MS = exports.HMSH_BLOCK_TIME_MS = exports.HMSH_DURABLE_EXP_BACKOFF = exports.HMSH_DURABLE_MAX_INTERVAL = exports.HMSH_DURABLE_MAX_ATTEMPTS = exports.HMSH_GRADUATED_INTERVAL_MS = exports.HMSH_MAX_TIMEOUT_MS = exports.HMSH_MAX_RETRIES = exports.HMSH_EXPIRE_JOB_SECONDS = exports.HMSH_OTT_WAIT_TIME = exports.HMSH_ACTIVATION_MAX_RETRY = exports.HMSH_QUORUM_DELAY_MS = exports.HMSH_QUORUM_ROLLCALL_CYCLES = exports.HMSH_STATUS_UNKNOWN = exports.HMSH_CODE_DURABLE_RETRYABLE = exports.HMSH_CODE_DURABLE_FATAL = exports.HMSH_CODE_DURABLE_MAXED = exports.HMSH_CODE_DURABLE_TIMEOUT = exports.HMSH_CODE_DURABLE_WAIT = exports.HMSH_CODE_DURABLE_PROXY = exports.HMSH_CODE_DURABLE_CHILD = exports.HMSH_CODE_DURABLE_ALL = exports.HMSH_CODE_DURABLE_SLEEP = exports.HMSH_CODE_UNACKED = exports.HMSH_CODE_TIMEOUT = exports.HMSH_CODE_UNKNOWN = exports.HMSH_CODE_INTERRUPT = exports.HMSH_CODE_NOTFOUND = exports.HMSH_CODE_PENDING = exports.HMSH_CODE_SUCCESS = exports.HMSH_IS_CLUSTER = exports.HMSH_LOGLEVEL = void 0;
|
|
4
4
|
// HOTMESH SYSTEM
|
|
5
5
|
exports.HMSH_LOGLEVEL = process.env.HMSH_LOGLEVEL || 'info';
|
|
6
|
+
exports.HMSH_IS_CLUSTER = process.env.HMSH_IS_CLUSTER === 'true';
|
|
6
7
|
// HOTMESH STATUS CODES
|
|
7
8
|
exports.HMSH_CODE_SUCCESS = 200;
|
|
8
9
|
exports.HMSH_CODE_PENDING = 202;
|
package/build/modules/utils.d.ts
CHANGED
|
@@ -8,6 +8,7 @@ import { SystemHealth } from '../types/quorum';
|
|
|
8
8
|
export declare function getSystemHealth(): Promise<SystemHealth>;
|
|
9
9
|
export declare function sleepFor(ms: number): Promise<unknown>;
|
|
10
10
|
export declare function sleepImmediate(): Promise<void>;
|
|
11
|
+
export declare function deepCopy<T>(obj: T): T;
|
|
11
12
|
export declare function guid(size?: number): string;
|
|
12
13
|
export declare function deterministicRandom(seed: number): number;
|
|
13
14
|
export declare function identifyRedisType(redisInstance: any): 'redis' | 'ioredis' | null;
|
package/build/modules/utils.js
CHANGED
|
@@ -3,7 +3,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
3
3
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
4
|
};
|
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
-
exports.restoreHierarchy = exports.getValueByPath = exports.getIndexedHash = exports.getSymVal = exports.getSymKey = exports.formatISODate = exports.getTimeSeries = exports.getSubscriptionTopic = exports.findSubscriptionForTrigger = exports.findTopKey = exports.XSleepFor = exports.matchesStatus = exports.matchesStatusCode = exports.identifyRedisTypeFromClass = exports.polyfill = exports.identifyRedisType = exports.deterministicRandom = exports.guid = exports.sleepImmediate = exports.sleepFor = exports.getSystemHealth = void 0;
|
|
6
|
+
exports.restoreHierarchy = exports.getValueByPath = exports.getIndexedHash = exports.getSymVal = exports.getSymKey = exports.formatISODate = exports.getTimeSeries = exports.getSubscriptionTopic = exports.findSubscriptionForTrigger = exports.findTopKey = exports.XSleepFor = exports.matchesStatus = exports.matchesStatusCode = exports.identifyRedisTypeFromClass = exports.polyfill = exports.identifyRedisType = exports.deterministicRandom = exports.guid = exports.deepCopy = exports.sleepImmediate = exports.sleepFor = exports.getSystemHealth = void 0;
|
|
7
7
|
const os_1 = __importDefault(require("os"));
|
|
8
8
|
const systeminformation_1 = __importDefault(require("systeminformation"));
|
|
9
9
|
const nanoid_1 = require("nanoid");
|
|
@@ -50,6 +50,10 @@ function sleepImmediate() {
|
|
|
50
50
|
return new Promise((resolve) => setImmediate(resolve));
|
|
51
51
|
}
|
|
52
52
|
exports.sleepImmediate = sleepImmediate;
|
|
53
|
+
function deepCopy(obj) {
|
|
54
|
+
return JSON.parse(JSON.stringify(obj));
|
|
55
|
+
}
|
|
56
|
+
exports.deepCopy = deepCopy;
|
|
53
57
|
function guid(size = enums_1.HMSH_GUID_SIZE) {
|
|
54
58
|
return `H` + (0, nanoid_1.nanoid)(size);
|
|
55
59
|
}
|
package/build/package.json
CHANGED
|
@@ -175,7 +175,7 @@ class Activity {
|
|
|
175
175
|
}
|
|
176
176
|
mapJobData() {
|
|
177
177
|
if (this.config.job?.maps) {
|
|
178
|
-
const mapper = new mapper_1.MapperService(this.config.job.maps, this.context);
|
|
178
|
+
const mapper = new mapper_1.MapperService((0, utils_1.deepCopy)(this.config.job.maps), this.context);
|
|
179
179
|
const output = mapper.mapRules();
|
|
180
180
|
if (output) {
|
|
181
181
|
for (const key in output) {
|
|
@@ -205,14 +205,14 @@ class Activity {
|
|
|
205
205
|
}
|
|
206
206
|
mapInputData() {
|
|
207
207
|
if (this.config.input?.maps) {
|
|
208
|
-
const mapper = new mapper_1.MapperService(this.config.input.maps, this.context);
|
|
208
|
+
const mapper = new mapper_1.MapperService((0, utils_1.deepCopy)(this.config.input.maps), this.context);
|
|
209
209
|
this.context.data = mapper.mapRules();
|
|
210
210
|
}
|
|
211
211
|
}
|
|
212
212
|
mapOutputData() {
|
|
213
213
|
//activity YAML may include output map data that produces/extends activity output data.
|
|
214
214
|
if (this.config.output?.maps) {
|
|
215
|
-
const mapper = new mapper_1.MapperService(this.config.output.maps, this.context);
|
|
215
|
+
const mapper = new mapper_1.MapperService((0, utils_1.deepCopy)(this.config.output.maps), this.context);
|
|
216
216
|
const actOutData = mapper.mapRules();
|
|
217
217
|
const activityId = this.metadata.aid;
|
|
218
218
|
const data = { ...this.context[activityId].output, ...actOutData };
|
|
@@ -13,6 +13,12 @@ declare class IORedisStoreService extends StoreService<RedisClientType, RedisMul
|
|
|
13
13
|
logger: ILogger;
|
|
14
14
|
serializer: Serializer;
|
|
15
15
|
constructor(redisClient: RedisClientType);
|
|
16
|
+
/**
|
|
17
|
+
* When in cluster mode, the getMulti wrapper only
|
|
18
|
+
* sends commands to the same node/shard.
|
|
19
|
+
* All other commands are sent simultaneously
|
|
20
|
+
* using Promise.all and are then collated
|
|
21
|
+
*/
|
|
16
22
|
getMulti(): RedisMultiType;
|
|
17
23
|
exec(...args: any[]): Promise<string | string[] | string[][]>;
|
|
18
24
|
hGetAllResult(result: any): any;
|
|
@@ -7,7 +7,89 @@ class IORedisStoreService extends index_1.StoreService {
|
|
|
7
7
|
constructor(redisClient) {
|
|
8
8
|
super(redisClient);
|
|
9
9
|
}
|
|
10
|
+
/**
|
|
11
|
+
* When in cluster mode, the getMulti wrapper only
|
|
12
|
+
* sends commands to the same node/shard.
|
|
13
|
+
* All other commands are sent simultaneously
|
|
14
|
+
* using Promise.all and are then collated
|
|
15
|
+
*/
|
|
10
16
|
getMulti() {
|
|
17
|
+
const my = this;
|
|
18
|
+
if (process.env.HMSH_IS_CLUSTER === 'true') {
|
|
19
|
+
const commands = [];
|
|
20
|
+
const addCommand = (command, args) => {
|
|
21
|
+
commands.push({ command, args });
|
|
22
|
+
return multiInstance;
|
|
23
|
+
};
|
|
24
|
+
const multiInstance = {
|
|
25
|
+
sendCommand(command) {
|
|
26
|
+
return my.redisClient.sendCommand(command);
|
|
27
|
+
},
|
|
28
|
+
async exec() {
|
|
29
|
+
if (commands.length === 0)
|
|
30
|
+
return [];
|
|
31
|
+
const sameCommand = commands.every(cmd => cmd.command === commands[0].command);
|
|
32
|
+
if (sameCommand) {
|
|
33
|
+
const multi = my.redisClient.multi();
|
|
34
|
+
commands.forEach(cmd => multi[cmd.command](...cmd.args));
|
|
35
|
+
const results = await multi.exec();
|
|
36
|
+
return results.map(item => item); // Extract the results from multi.exec response format
|
|
37
|
+
}
|
|
38
|
+
else {
|
|
39
|
+
return Promise.all(commands.map(cmd => my.redisClient[cmd.command](...cmd.args)));
|
|
40
|
+
}
|
|
41
|
+
},
|
|
42
|
+
xadd(key, id, fields, message) {
|
|
43
|
+
return addCommand('xadd', [key, id, fields, message]);
|
|
44
|
+
},
|
|
45
|
+
xack(key, group, id) {
|
|
46
|
+
return addCommand('xack', [key, group, id]);
|
|
47
|
+
},
|
|
48
|
+
xdel(key, id) {
|
|
49
|
+
return addCommand('xdel', [key, id]);
|
|
50
|
+
},
|
|
51
|
+
xlen(key) {
|
|
52
|
+
return addCommand('xlen', [key]);
|
|
53
|
+
},
|
|
54
|
+
xpending(key, group, start, end, count, consumer) {
|
|
55
|
+
return addCommand('xpending', [key, group, start, end, count, consumer]);
|
|
56
|
+
},
|
|
57
|
+
xclaim(key, group, consumer, minIdleTime, id, ...args) {
|
|
58
|
+
return addCommand('xclaim', [key, group, consumer, minIdleTime, id, ...args]);
|
|
59
|
+
},
|
|
60
|
+
hdel(key, itemId) {
|
|
61
|
+
return addCommand('hdel', [key, itemId]);
|
|
62
|
+
},
|
|
63
|
+
hget(key, itemId) {
|
|
64
|
+
return addCommand('hget', [key, itemId]);
|
|
65
|
+
},
|
|
66
|
+
hgetall(key) {
|
|
67
|
+
return addCommand('hgetall', [key]);
|
|
68
|
+
},
|
|
69
|
+
hincrbyfloat(key, itemId, value) {
|
|
70
|
+
return addCommand('hincrbyfloat', [key, itemId, value]);
|
|
71
|
+
},
|
|
72
|
+
hmget(key, itemIds) {
|
|
73
|
+
return addCommand('hmget', [key, itemIds]);
|
|
74
|
+
},
|
|
75
|
+
hset(key, values) {
|
|
76
|
+
return addCommand('hset', [key, values]);
|
|
77
|
+
},
|
|
78
|
+
lrange(key, start, end) {
|
|
79
|
+
return addCommand('lrange', [key, start, end]);
|
|
80
|
+
},
|
|
81
|
+
rpush(key, value) {
|
|
82
|
+
return addCommand('rpush', [key, value]);
|
|
83
|
+
},
|
|
84
|
+
zadd(...args) {
|
|
85
|
+
return addCommand('zadd', args);
|
|
86
|
+
},
|
|
87
|
+
xgroup(command, key, groupName, id, mkStream) {
|
|
88
|
+
return addCommand('xgroup', [command, key, groupName, id, mkStream]);
|
|
89
|
+
}
|
|
90
|
+
};
|
|
91
|
+
return multiInstance;
|
|
92
|
+
}
|
|
11
93
|
return this.redisClient.multi();
|
|
12
94
|
}
|
|
13
95
|
async exec(...args) {
|
|
@@ -14,6 +14,12 @@ declare class RedisStoreService extends StoreService<RedisClientType, RedisMulti
|
|
|
14
14
|
serializer: Serializer;
|
|
15
15
|
commands: Record<string, string>;
|
|
16
16
|
constructor(redisClient: RedisClientType);
|
|
17
|
+
/**
|
|
18
|
+
* When in cluster mode, the getMulti wrapper only
|
|
19
|
+
* sends commands to the same node/shard.
|
|
20
|
+
* All other commands are sent simultaneously
|
|
21
|
+
* using Promise.all and are then collated
|
|
22
|
+
*/
|
|
17
23
|
getMulti(): RedisMultiType;
|
|
18
24
|
exec(...args: any[]): Promise<string | string[] | string[][]>;
|
|
19
25
|
publish(keyType: KeyType.QUORUM, message: Record<string, any>, appId: string, engineId?: string): Promise<boolean>;
|
|
@@ -36,7 +36,123 @@ class RedisStoreService extends index_1.StoreService {
|
|
|
36
36
|
xlen: 'XLEN',
|
|
37
37
|
};
|
|
38
38
|
}
|
|
39
|
+
/**
|
|
40
|
+
* When in cluster mode, the getMulti wrapper only
|
|
41
|
+
* sends commands to the same node/shard.
|
|
42
|
+
* All other commands are sent simultaneously
|
|
43
|
+
* using Promise.all and are then collated
|
|
44
|
+
*/
|
|
39
45
|
getMulti() {
|
|
46
|
+
const my = this;
|
|
47
|
+
if (process.env.HMSH_IS_CLUSTER === 'true') {
|
|
48
|
+
const commands = [];
|
|
49
|
+
const addCommand = (command, args) => {
|
|
50
|
+
commands.push({ command: command.toUpperCase(), args });
|
|
51
|
+
return multiInstance;
|
|
52
|
+
};
|
|
53
|
+
const multiInstance = {
|
|
54
|
+
sendCommand(command, ...args) {
|
|
55
|
+
return my.redisClient.sendCommand([command, ...args]);
|
|
56
|
+
},
|
|
57
|
+
async exec() {
|
|
58
|
+
if (commands.length === 0)
|
|
59
|
+
return [];
|
|
60
|
+
const sameCommand = commands.every(cmd => cmd.command === commands[0].command);
|
|
61
|
+
if (sameCommand) {
|
|
62
|
+
const multi = my.redisClient.multi();
|
|
63
|
+
commands.forEach(cmd => {
|
|
64
|
+
if (cmd.command === 'ZADD') {
|
|
65
|
+
return multi.ZADD(cmd.args[0], cmd.args[1], cmd.args[2]);
|
|
66
|
+
}
|
|
67
|
+
return multi[cmd.command](...cmd.args);
|
|
68
|
+
});
|
|
69
|
+
const results = await multi.exec();
|
|
70
|
+
return results.map(item => item); // Extract the results from multi.exec response format
|
|
71
|
+
}
|
|
72
|
+
else {
|
|
73
|
+
return Promise.all(commands.map(cmd => {
|
|
74
|
+
if (cmd.command === 'ZADD') {
|
|
75
|
+
return my.redisClient.ZADD(cmd.args[0], cmd.args[1], cmd.args[2]);
|
|
76
|
+
}
|
|
77
|
+
return my.redisClient[cmd.command](...cmd.args);
|
|
78
|
+
}));
|
|
79
|
+
}
|
|
80
|
+
},
|
|
81
|
+
XADD(key, id, fields, message) {
|
|
82
|
+
return addCommand('XADD', [key, id, fields, message]);
|
|
83
|
+
},
|
|
84
|
+
XACK(key, group, id) {
|
|
85
|
+
return addCommand('XACK', [key, group, id]);
|
|
86
|
+
},
|
|
87
|
+
XDEL(key, id) {
|
|
88
|
+
return addCommand('XDEL', [key, id]);
|
|
89
|
+
},
|
|
90
|
+
XLEN(key) {
|
|
91
|
+
return addCommand('XLEN', [key]);
|
|
92
|
+
},
|
|
93
|
+
XCLAIM(key, group, consumer, minIdleTime, id, ...args) {
|
|
94
|
+
return addCommand('XCLAIM', [key, group, consumer, minIdleTime, id, ...args]);
|
|
95
|
+
},
|
|
96
|
+
XPENDING(key, group, start, end, count, consumer) {
|
|
97
|
+
return addCommand('XPENDING', [key, group, start, end, count, consumer]);
|
|
98
|
+
},
|
|
99
|
+
HDEL(key, itemId) {
|
|
100
|
+
return addCommand('HDEL', [key, itemId]);
|
|
101
|
+
},
|
|
102
|
+
HGET(key, itemId) {
|
|
103
|
+
return addCommand('HGET', [key, itemId]);
|
|
104
|
+
},
|
|
105
|
+
HGETALL(key) {
|
|
106
|
+
return addCommand('HGETALL', [key]);
|
|
107
|
+
},
|
|
108
|
+
HINCRBYFLOAT(key, itemId, value) {
|
|
109
|
+
return addCommand('HINCRBYFLOAT', [key, itemId, value]);
|
|
110
|
+
},
|
|
111
|
+
HMGET(key, itemIds) {
|
|
112
|
+
return addCommand('HMGET', [key, itemIds]);
|
|
113
|
+
},
|
|
114
|
+
HSET(key, values) {
|
|
115
|
+
return addCommand('HSET', [key, values]);
|
|
116
|
+
},
|
|
117
|
+
LRANGE(key, start, end) {
|
|
118
|
+
return addCommand('LRANGE', [key, start, end]);
|
|
119
|
+
},
|
|
120
|
+
RPUSH(key, items) {
|
|
121
|
+
return addCommand('RPUSH', [key, items]);
|
|
122
|
+
},
|
|
123
|
+
ZADD(key, args, opts) {
|
|
124
|
+
return addCommand('ZADD', [key, args, opts]);
|
|
125
|
+
},
|
|
126
|
+
XGROUP(command, key, groupName, id, mkStream) {
|
|
127
|
+
return addCommand('XGROUP', [command, key, groupName, id, mkStream]);
|
|
128
|
+
},
|
|
129
|
+
DEL: function (key) {
|
|
130
|
+
throw new Error('Function not implemented.');
|
|
131
|
+
},
|
|
132
|
+
EXISTS: function (key) {
|
|
133
|
+
throw new Error('Function not implemented.');
|
|
134
|
+
},
|
|
135
|
+
HMPUSH: function (key, values) {
|
|
136
|
+
throw new Error('Function not implemented.');
|
|
137
|
+
},
|
|
138
|
+
LPUSH: function (key, items) {
|
|
139
|
+
throw new Error('Function not implemented.');
|
|
140
|
+
},
|
|
141
|
+
SET: function (key, value) {
|
|
142
|
+
throw new Error('Function not implemented.');
|
|
143
|
+
},
|
|
144
|
+
ZRANGE_WITHSCORES: function (key, start, end) {
|
|
145
|
+
throw new Error('Function not implemented.');
|
|
146
|
+
},
|
|
147
|
+
ZRANK: function (key, member) {
|
|
148
|
+
throw new Error('Function not implemented.');
|
|
149
|
+
},
|
|
150
|
+
ZSCORE: function (key, value) {
|
|
151
|
+
throw new Error('Function not implemented.');
|
|
152
|
+
}
|
|
153
|
+
};
|
|
154
|
+
return multiInstance;
|
|
155
|
+
}
|
|
40
156
|
return this.redisClient.multi();
|
|
41
157
|
}
|
|
42
158
|
async exec(...args) {
|
|
@@ -168,7 +168,6 @@ class StoreService {
|
|
|
168
168
|
const range = await this.redisClient[this.commands.hget](rangeKey, target);
|
|
169
169
|
const [lowerLimitString] = range.split(':');
|
|
170
170
|
if (lowerLimitString === '?') {
|
|
171
|
-
console.log('symbol range collision!!!', tryCount);
|
|
172
171
|
await (0, utils_1.sleepFor)(tryCount * 1000);
|
|
173
172
|
if (tryCount < 5) {
|
|
174
173
|
return this.reserveSymbolRange(target, size, type, tryCount + 1);
|
package/build/types/durable.d.ts
CHANGED
|
@@ -89,6 +89,86 @@ type WorkflowContext = {
|
|
|
89
89
|
*/
|
|
90
90
|
raw: StreamData;
|
|
91
91
|
};
|
|
92
|
+
/**
|
|
93
|
+
* The schema for the full-text-search (RediSearch) index.
|
|
94
|
+
*/
|
|
95
|
+
export type WorkflowSearchSchema = Record<string, {
|
|
96
|
+
/**
|
|
97
|
+
* The FT.SEARCH field type. One of: TEXT, NUMERIC, TAG. TEXT is
|
|
98
|
+
* most expensive, but also most expressive.
|
|
99
|
+
*/
|
|
100
|
+
type: 'TEXT' | 'NUMERIC' | 'TAG';
|
|
101
|
+
/**
|
|
102
|
+
* FT.SEARCH SORTABLE field. If true, results may be sorted according to this field
|
|
103
|
+
* @default false
|
|
104
|
+
*/
|
|
105
|
+
sortable?: boolean;
|
|
106
|
+
/**
|
|
107
|
+
* FT.SEARCH NOSTEM field. applies to TEXT fields types.
|
|
108
|
+
* If true, the text field index will not stem words
|
|
109
|
+
* @default false
|
|
110
|
+
*/
|
|
111
|
+
nostem?: boolean;
|
|
112
|
+
/**
|
|
113
|
+
* FT.SEARCH NOINDEX field. If true and if the field is sortable, the field will aid
|
|
114
|
+
* in sorting results but not be directly indexed as a standalone
|
|
115
|
+
* @default false
|
|
116
|
+
*/
|
|
117
|
+
noindex?: boolean;
|
|
118
|
+
/**
|
|
119
|
+
* if true, the field is indexed and searchable within the FT.SEARCH index
|
|
120
|
+
* This is different from `noindex` which is FT.SEARCH specific and relates
|
|
121
|
+
* to sorting and indexing. This is a general flag for the field that will
|
|
122
|
+
* enable or disable indexing and searching entirely. Use for fields with
|
|
123
|
+
* absolutely no meaning to query or sorting but which are important
|
|
124
|
+
* nonetheless as part of the data record that is saved and returned.
|
|
125
|
+
* @default true
|
|
126
|
+
*/
|
|
127
|
+
indexed?: boolean;
|
|
128
|
+
/**
|
|
129
|
+
* An array of possible values for the field
|
|
130
|
+
*/
|
|
131
|
+
examples?: string[];
|
|
132
|
+
/**
|
|
133
|
+
* The 'nilable' setting may NOT be set to `true` for
|
|
134
|
+
* NUMBER types as it causes an indexing error;
|
|
135
|
+
* consider a custom (e.g., negative number) value to represent
|
|
136
|
+
* `null` if desired for a NUMERIC field.
|
|
137
|
+
* Set to true only if the field is a TEXT or TAG type and
|
|
138
|
+
* you wish to save the string `null` as a value to search
|
|
139
|
+
* on (the tag, {null}, or the string, (null)
|
|
140
|
+
* @default false
|
|
141
|
+
*/
|
|
142
|
+
nilable?: boolean;
|
|
143
|
+
/**
|
|
144
|
+
* possible scalar/primitive types for the field. Use when
|
|
145
|
+
* serializing and restoring data to ensure the field is
|
|
146
|
+
* properly typed. If not provided, the field will be
|
|
147
|
+
* treated as a string.
|
|
148
|
+
*/
|
|
149
|
+
primitive?: 'string' | 'number' | 'boolean' | 'array' | 'object';
|
|
150
|
+
/**
|
|
151
|
+
* if true, the field is required to be present in the data record
|
|
152
|
+
* @default false
|
|
153
|
+
*/
|
|
154
|
+
required?: boolean;
|
|
155
|
+
/**
|
|
156
|
+
* an enumerated list of allowed values; if field is nilable, it is implied
|
|
157
|
+
* and therefore not necessary to include `null` in the list
|
|
158
|
+
* @default []
|
|
159
|
+
*/
|
|
160
|
+
enum?: string[];
|
|
161
|
+
/**
|
|
162
|
+
* a regular expression pattern for the field
|
|
163
|
+
* @default '.*'
|
|
164
|
+
* @example '^[a-zA-Z0-9_]*$'
|
|
165
|
+
*/
|
|
166
|
+
pattern?: string;
|
|
167
|
+
/**
|
|
168
|
+
* literal value to use for the indexed field name (without including the standard underscore (_) prefix isolate)
|
|
169
|
+
*/
|
|
170
|
+
fieldName?: string;
|
|
171
|
+
}>;
|
|
92
172
|
type WorkflowSearchOptions = {
|
|
93
173
|
/** FT index name (myapp:myindex) */
|
|
94
174
|
index?: string;
|
|
@@ -101,83 +181,7 @@ type WorkflowSearchOptions = {
|
|
|
101
181
|
* key will be used as the indexed field name with an underscore prefix.
|
|
102
182
|
*
|
|
103
183
|
*/
|
|
104
|
-
schema?:
|
|
105
|
-
/**
|
|
106
|
-
* The FT.SEARCH field type. One of: TEXT, NUMERIC, TAG. TEXT is
|
|
107
|
-
* most expensive, but also most expressive.
|
|
108
|
-
*/
|
|
109
|
-
type: 'TEXT' | 'NUMERIC' | 'TAG';
|
|
110
|
-
/**
|
|
111
|
-
* FT.SEARCH SORTABLE field. If true, results may be sorted according to this field
|
|
112
|
-
* @default false
|
|
113
|
-
*/
|
|
114
|
-
sortable?: boolean;
|
|
115
|
-
/**
|
|
116
|
-
* FT.SEARCH NOSTEM field. applies to TEXT fields types.
|
|
117
|
-
* If true, the text field index will not stem words
|
|
118
|
-
* @default false
|
|
119
|
-
*/
|
|
120
|
-
nostem?: boolean;
|
|
121
|
-
/**
|
|
122
|
-
* FT.SEARCH NOINDEX field. If true and if the field is sortable, the field will aid
|
|
123
|
-
* in sorting results but not be directly indexed as a standalone
|
|
124
|
-
* @default false
|
|
125
|
-
*/
|
|
126
|
-
noindex?: boolean;
|
|
127
|
-
/**
|
|
128
|
-
* if true, the field is indexed and searchable within the FT.SEARCH index
|
|
129
|
-
* This is different from `noindex` which is FT.SEARCH specific and relates
|
|
130
|
-
* to sorting and indexing. This is a general flag for the field that will
|
|
131
|
-
* enable or disable indexing and searching entirely. Use for fields with
|
|
132
|
-
* absolutely no meaning to query or sorting but which are important
|
|
133
|
-
* nonetheless as part of the data record that is saved and returned.
|
|
134
|
-
* @default true
|
|
135
|
-
*/
|
|
136
|
-
indexed?: boolean;
|
|
137
|
-
/**
|
|
138
|
-
* An array of possible values for the field
|
|
139
|
-
*/
|
|
140
|
-
examples?: string[];
|
|
141
|
-
/**
|
|
142
|
-
* The 'nilable' setting may NOT be set to `true` for
|
|
143
|
-
* NUMBER types as it causes an indexing error;
|
|
144
|
-
* consider a custom (e.g., negative number) value to represent
|
|
145
|
-
* `null` if desired for a NUMERIC field.
|
|
146
|
-
* Set to true only if the field is a TEXT or TAG type and
|
|
147
|
-
* you wish to save the string `null` as a value to search
|
|
148
|
-
* on (the tag, {null}, or the string, (null)
|
|
149
|
-
* @default false
|
|
150
|
-
*/
|
|
151
|
-
nilable?: boolean;
|
|
152
|
-
/**
|
|
153
|
-
* possible scalar/primitive types for the field. Use when
|
|
154
|
-
* serializing and restoring data to ensure the field is
|
|
155
|
-
* properly typed. If not provided, the field will be
|
|
156
|
-
* treated as a string.
|
|
157
|
-
*/
|
|
158
|
-
primitive?: 'string' | 'number' | 'boolean' | 'array' | 'object';
|
|
159
|
-
/**
|
|
160
|
-
* if true, the field is required to be present in the data record
|
|
161
|
-
* @default false
|
|
162
|
-
*/
|
|
163
|
-
required?: boolean;
|
|
164
|
-
/**
|
|
165
|
-
* an enumerated list of allowed values; if field is nilable, it is implied
|
|
166
|
-
* and therefore not necessary to include `null` in the list
|
|
167
|
-
* @default []
|
|
168
|
-
*/
|
|
169
|
-
enum?: string[];
|
|
170
|
-
/**
|
|
171
|
-
* a regular expression pattern for the field
|
|
172
|
-
* @default '.*'
|
|
173
|
-
* @example '^[a-zA-Z0-9_]*$'
|
|
174
|
-
*/
|
|
175
|
-
pattern?: string;
|
|
176
|
-
/**
|
|
177
|
-
* literal value to use for the indexed field name (without including the standard underscore (_) prefix isolate)
|
|
178
|
-
*/
|
|
179
|
-
fieldName?: string;
|
|
180
|
-
}>;
|
|
184
|
+
schema?: WorkflowSearchSchema;
|
|
181
185
|
/** Additional data as a key-value record */
|
|
182
186
|
data?: StringStringType;
|
|
183
187
|
};
|
package/build/types/index.d.ts
CHANGED
|
@@ -3,7 +3,7 @@ export { App, AppVID, AppTransitions, AppSubscriptions } from './app';
|
|
|
3
3
|
export { AsyncSignal } from './async';
|
|
4
4
|
export { CacheMode } from './cache';
|
|
5
5
|
export { CollationFaultType, CollationStage } from './collator';
|
|
6
|
-
export { ActivityConfig, ActivityWorkflowDataType, ChildResponseType, ClientConfig, ContextType, ConnectionConfig, Connection, ProxyResponseType, ProxyType, Registry, SignalOptions, FindJobsOptions, FindOptions, FindWhereOptions, FindWhereQuery, HookOptions, SearchResults, WorkflowConfig, WorkerConfig, WorkerOptions, WorkflowContext, WorkflowSearchOptions, WorkflowDataType, WorkflowOptions, } from './durable';
|
|
6
|
+
export { ActivityConfig, ActivityWorkflowDataType, ChildResponseType, ClientConfig, ContextType, ConnectionConfig, Connection, ProxyResponseType, ProxyType, Registry, SignalOptions, FindJobsOptions, FindOptions, FindWhereOptions, FindWhereQuery, HookOptions, SearchResults, WorkflowConfig, WorkerConfig, WorkerOptions, WorkflowContext, WorkflowSearchOptions, WorkflowSearchSchema, WorkflowDataType, WorkflowOptions, } from './durable';
|
|
7
7
|
export { DurableChildErrorType, DurableProxyErrorType, DurableSleepErrorType, DurableWaitForAllErrorType, DurableWaitForErrorType, } from './error';
|
|
8
8
|
export { ActivityAction, DependencyExport, DurableJobExport, ExportCycles, ExportItem, ExportOptions, ExportTransitions, JobAction, JobExport, JobActionExport, JobTimeline, } from './exporter';
|
|
9
9
|
export { HookCondition, HookConditions, HookGate, HookInterface, HookRule, HookRules, HookSignal, } from './hook';
|
package/build/types/redis.d.ts
CHANGED
|
@@ -72,12 +72,17 @@ interface RedisRedisMultiType {
|
|
|
72
72
|
HGET(key: string, itemId: string): this;
|
|
73
73
|
HGETALL(key: string): this;
|
|
74
74
|
HINCRBYFLOAT(key: string, itemId: string, value: number): this;
|
|
75
|
-
|
|
75
|
+
HMPUSH(key: string, values: Record<string, string>): this;
|
|
76
|
+
RPUSH(key: string, items: string[]): this;
|
|
77
|
+
HMGET(key: string, itemIds: string[]): this;
|
|
76
78
|
HSET(key: string, values: Record<string, string>): this;
|
|
77
79
|
LPUSH(key: string, items: string[]): this;
|
|
78
80
|
LRANGE(key: string, start: number, end: number): this;
|
|
79
81
|
RPUSH(key: string, items: string[]): this;
|
|
80
82
|
SET(key: string, value: string): this;
|
|
83
|
+
XCLAIM(key: string, group: string, consumer: string, minIdleTime: number, id: string, ...args: string[]): this;
|
|
84
|
+
XGROUP(command: 'CREATE' | string, key: string, groupName: string, id: string, mkStream?: 'MKSTREAM'): this;
|
|
85
|
+
XPENDING(key: string, group: string, start?: string, end?: string, count?: number, consumer?: string): this;
|
|
81
86
|
ZADD(key: string, values: {
|
|
82
87
|
score: string;
|
|
83
88
|
value: string;
|
package/package.json
CHANGED
package/types/durable.ts
CHANGED
|
@@ -108,6 +108,100 @@ type WorkflowContext = {
|
|
|
108
108
|
raw: StreamData;
|
|
109
109
|
};
|
|
110
110
|
|
|
111
|
+
/**
|
|
112
|
+
* The schema for the full-text-search (RediSearch) index.
|
|
113
|
+
*/
|
|
114
|
+
export type WorkflowSearchSchema = Record<
|
|
115
|
+
string,
|
|
116
|
+
{
|
|
117
|
+
/**
|
|
118
|
+
* The FT.SEARCH field type. One of: TEXT, NUMERIC, TAG. TEXT is
|
|
119
|
+
* most expensive, but also most expressive.
|
|
120
|
+
*/
|
|
121
|
+
type: 'TEXT' | 'NUMERIC' | 'TAG';
|
|
122
|
+
|
|
123
|
+
/**
|
|
124
|
+
* FT.SEARCH SORTABLE field. If true, results may be sorted according to this field
|
|
125
|
+
* @default false
|
|
126
|
+
*/
|
|
127
|
+
sortable?: boolean;
|
|
128
|
+
|
|
129
|
+
/**
|
|
130
|
+
* FT.SEARCH NOSTEM field. applies to TEXT fields types.
|
|
131
|
+
* If true, the text field index will not stem words
|
|
132
|
+
* @default false
|
|
133
|
+
*/
|
|
134
|
+
nostem?: boolean;
|
|
135
|
+
|
|
136
|
+
/**
|
|
137
|
+
* FT.SEARCH NOINDEX field. If true and if the field is sortable, the field will aid
|
|
138
|
+
* in sorting results but not be directly indexed as a standalone
|
|
139
|
+
* @default false
|
|
140
|
+
*/
|
|
141
|
+
noindex?: boolean;
|
|
142
|
+
|
|
143
|
+
/**
|
|
144
|
+
* if true, the field is indexed and searchable within the FT.SEARCH index
|
|
145
|
+
* This is different from `noindex` which is FT.SEARCH specific and relates
|
|
146
|
+
* to sorting and indexing. This is a general flag for the field that will
|
|
147
|
+
* enable or disable indexing and searching entirely. Use for fields with
|
|
148
|
+
* absolutely no meaning to query or sorting but which are important
|
|
149
|
+
* nonetheless as part of the data record that is saved and returned.
|
|
150
|
+
* @default true
|
|
151
|
+
*/
|
|
152
|
+
indexed?: boolean;
|
|
153
|
+
|
|
154
|
+
/**
|
|
155
|
+
* An array of possible values for the field
|
|
156
|
+
*/
|
|
157
|
+
examples?: string[];
|
|
158
|
+
|
|
159
|
+
/**
|
|
160
|
+
* The 'nilable' setting may NOT be set to `true` for
|
|
161
|
+
* NUMBER types as it causes an indexing error;
|
|
162
|
+
* consider a custom (e.g., negative number) value to represent
|
|
163
|
+
* `null` if desired for a NUMERIC field.
|
|
164
|
+
* Set to true only if the field is a TEXT or TAG type and
|
|
165
|
+
* you wish to save the string `null` as a value to search
|
|
166
|
+
* on (the tag, {null}, or the string, (null)
|
|
167
|
+
* @default false
|
|
168
|
+
*/
|
|
169
|
+
nilable?: boolean;
|
|
170
|
+
|
|
171
|
+
/**
|
|
172
|
+
* possible scalar/primitive types for the field. Use when
|
|
173
|
+
* serializing and restoring data to ensure the field is
|
|
174
|
+
* properly typed. If not provided, the field will be
|
|
175
|
+
* treated as a string.
|
|
176
|
+
*/
|
|
177
|
+
primitive?: 'string' | 'number' | 'boolean' | 'array' | 'object';
|
|
178
|
+
|
|
179
|
+
/**
|
|
180
|
+
* if true, the field is required to be present in the data record
|
|
181
|
+
* @default false
|
|
182
|
+
*/
|
|
183
|
+
required?: boolean;
|
|
184
|
+
|
|
185
|
+
/**
|
|
186
|
+
* an enumerated list of allowed values; if field is nilable, it is implied
|
|
187
|
+
* and therefore not necessary to include `null` in the list
|
|
188
|
+
* @default []
|
|
189
|
+
*/
|
|
190
|
+
enum?: string[];
|
|
191
|
+
|
|
192
|
+
/**
|
|
193
|
+
* a regular expression pattern for the field
|
|
194
|
+
* @default '.*'
|
|
195
|
+
* @example '^[a-zA-Z0-9_]*$'
|
|
196
|
+
*/
|
|
197
|
+
pattern?: string;
|
|
198
|
+
|
|
199
|
+
/**
|
|
200
|
+
* literal value to use for the indexed field name (without including the standard underscore (_) prefix isolate)
|
|
201
|
+
*/
|
|
202
|
+
fieldName?: string;
|
|
203
|
+
}>;
|
|
204
|
+
|
|
111
205
|
type WorkflowSearchOptions = {
|
|
112
206
|
/** FT index name (myapp:myindex) */
|
|
113
207
|
index?: string;
|
|
@@ -122,97 +216,7 @@ type WorkflowSearchOptions = {
|
|
|
122
216
|
* key will be used as the indexed field name with an underscore prefix.
|
|
123
217
|
*
|
|
124
218
|
*/
|
|
125
|
-
schema?:
|
|
126
|
-
string,
|
|
127
|
-
{
|
|
128
|
-
/**
|
|
129
|
-
* The FT.SEARCH field type. One of: TEXT, NUMERIC, TAG. TEXT is
|
|
130
|
-
* most expensive, but also most expressive.
|
|
131
|
-
*/
|
|
132
|
-
type: 'TEXT' | 'NUMERIC' | 'TAG';
|
|
133
|
-
|
|
134
|
-
/**
|
|
135
|
-
* FT.SEARCH SORTABLE field. If true, results may be sorted according to this field
|
|
136
|
-
* @default false
|
|
137
|
-
*/
|
|
138
|
-
sortable?: boolean;
|
|
139
|
-
|
|
140
|
-
/**
|
|
141
|
-
* FT.SEARCH NOSTEM field. applies to TEXT fields types.
|
|
142
|
-
* If true, the text field index will not stem words
|
|
143
|
-
* @default false
|
|
144
|
-
*/
|
|
145
|
-
nostem?: boolean;
|
|
146
|
-
|
|
147
|
-
/**
|
|
148
|
-
* FT.SEARCH NOINDEX field. If true and if the field is sortable, the field will aid
|
|
149
|
-
* in sorting results but not be directly indexed as a standalone
|
|
150
|
-
* @default false
|
|
151
|
-
*/
|
|
152
|
-
noindex?: boolean;
|
|
153
|
-
|
|
154
|
-
/**
|
|
155
|
-
* if true, the field is indexed and searchable within the FT.SEARCH index
|
|
156
|
-
* This is different from `noindex` which is FT.SEARCH specific and relates
|
|
157
|
-
* to sorting and indexing. This is a general flag for the field that will
|
|
158
|
-
* enable or disable indexing and searching entirely. Use for fields with
|
|
159
|
-
* absolutely no meaning to query or sorting but which are important
|
|
160
|
-
* nonetheless as part of the data record that is saved and returned.
|
|
161
|
-
* @default true
|
|
162
|
-
*/
|
|
163
|
-
indexed?: boolean;
|
|
164
|
-
|
|
165
|
-
/**
|
|
166
|
-
* An array of possible values for the field
|
|
167
|
-
*/
|
|
168
|
-
examples?: string[];
|
|
169
|
-
|
|
170
|
-
/**
|
|
171
|
-
* The 'nilable' setting may NOT be set to `true` for
|
|
172
|
-
* NUMBER types as it causes an indexing error;
|
|
173
|
-
* consider a custom (e.g., negative number) value to represent
|
|
174
|
-
* `null` if desired for a NUMERIC field.
|
|
175
|
-
* Set to true only if the field is a TEXT or TAG type and
|
|
176
|
-
* you wish to save the string `null` as a value to search
|
|
177
|
-
* on (the tag, {null}, or the string, (null)
|
|
178
|
-
* @default false
|
|
179
|
-
*/
|
|
180
|
-
nilable?: boolean;
|
|
181
|
-
|
|
182
|
-
/**
|
|
183
|
-
* possible scalar/primitive types for the field. Use when
|
|
184
|
-
* serializing and restoring data to ensure the field is
|
|
185
|
-
* properly typed. If not provided, the field will be
|
|
186
|
-
* treated as a string.
|
|
187
|
-
*/
|
|
188
|
-
primitive?: 'string' | 'number' | 'boolean' | 'array' | 'object';
|
|
189
|
-
|
|
190
|
-
/**
|
|
191
|
-
* if true, the field is required to be present in the data record
|
|
192
|
-
* @default false
|
|
193
|
-
*/
|
|
194
|
-
required?: boolean;
|
|
195
|
-
|
|
196
|
-
/**
|
|
197
|
-
* an enumerated list of allowed values; if field is nilable, it is implied
|
|
198
|
-
* and therefore not necessary to include `null` in the list
|
|
199
|
-
* @default []
|
|
200
|
-
*/
|
|
201
|
-
enum?: string[];
|
|
202
|
-
|
|
203
|
-
/**
|
|
204
|
-
* a regular expression pattern for the field
|
|
205
|
-
* @default '.*'
|
|
206
|
-
* @example '^[a-zA-Z0-9_]*$'
|
|
207
|
-
*/
|
|
208
|
-
pattern?: string;
|
|
209
|
-
|
|
210
|
-
/**
|
|
211
|
-
* literal value to use for the indexed field name (without including the standard underscore (_) prefix isolate)
|
|
212
|
-
*/
|
|
213
|
-
fieldName?: string;
|
|
214
|
-
}
|
|
215
|
-
>;
|
|
219
|
+
schema?: WorkflowSearchSchema;
|
|
216
220
|
|
|
217
221
|
/** Additional data as a key-value record */
|
|
218
222
|
data?: StringStringType;
|
package/types/index.ts
CHANGED
package/types/redis.ts
CHANGED
|
@@ -78,12 +78,37 @@ interface RedisRedisMultiType {
|
|
|
78
78
|
HGET(key: string, itemId: string): this;
|
|
79
79
|
HGETALL(key: string): this;
|
|
80
80
|
HINCRBYFLOAT(key: string, itemId: string, value: number): this;
|
|
81
|
-
|
|
81
|
+
HMPUSH(key: string, values: Record<string, string>): this;
|
|
82
|
+
RPUSH(key: string, items: string[]): this;
|
|
83
|
+
HMGET(key: string, itemIds: string[]): this;
|
|
82
84
|
HSET(key: string, values: Record<string, string>): this;
|
|
83
85
|
LPUSH(key: string, items: string[]): this;
|
|
84
86
|
LRANGE(key: string, start: number, end: number): this;
|
|
85
87
|
RPUSH(key: string, items: string[]): this;
|
|
86
88
|
SET(key: string, value: string): this;
|
|
89
|
+
XCLAIM(
|
|
90
|
+
key: string,
|
|
91
|
+
group: string,
|
|
92
|
+
consumer: string,
|
|
93
|
+
minIdleTime: number,
|
|
94
|
+
id: string,
|
|
95
|
+
...args: string[]
|
|
96
|
+
): this;
|
|
97
|
+
XGROUP(
|
|
98
|
+
command: 'CREATE' | string,
|
|
99
|
+
key: string,
|
|
100
|
+
groupName: string,
|
|
101
|
+
id: string,
|
|
102
|
+
mkStream?: 'MKSTREAM',
|
|
103
|
+
): this;
|
|
104
|
+
XPENDING(
|
|
105
|
+
key: string,
|
|
106
|
+
group: string,
|
|
107
|
+
start?: string,
|
|
108
|
+
end?: string,
|
|
109
|
+
count?: number,
|
|
110
|
+
consumer?: string,
|
|
111
|
+
): this;
|
|
87
112
|
ZADD(
|
|
88
113
|
key: string,
|
|
89
114
|
values: { score: string; value: string },
|