bullmq 5.12.11 → 5.12.13
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +1 -1
- package/README.md +0 -10
- package/dist/cjs/classes/queue-base.js +3 -0
- package/dist/cjs/classes/queue-base.js.map +1 -1
- package/dist/cjs/classes/queue.js +1 -1
- package/dist/cjs/classes/queue.js.map +1 -1
- package/dist/cjs/classes/repeat.js +28 -22
- package/dist/cjs/classes/repeat.js.map +1 -1
- package/dist/cjs/classes/scripts.js +18 -5
- package/dist/cjs/classes/scripts.js.map +1 -1
- package/dist/cjs/classes/worker.js +3 -1
- package/dist/cjs/classes/worker.js.map +1 -1
- package/dist/cjs/commands/{addRepeatableJob-1.lua → addRepeatableJob-2.lua} +26 -15
- package/dist/cjs/commands/updateRepeatableJobMillis-1.lua +28 -0
- package/dist/cjs/scripts/addRepeatableJob-2.js +228 -0
- package/dist/cjs/scripts/addRepeatableJob-2.js.map +1 -0
- package/dist/cjs/scripts/index.js +2 -1
- package/dist/cjs/scripts/index.js.map +1 -1
- package/dist/cjs/scripts/updateRepeatableJobMillis-1.js +33 -0
- package/dist/cjs/scripts/updateRepeatableJobMillis-1.js.map +1 -0
- package/dist/cjs/tsconfig-cjs.tsbuildinfo +1 -1
- package/dist/esm/classes/queue-base.js +3 -0
- package/dist/esm/classes/queue-base.js.map +1 -1
- package/dist/esm/classes/queue.js +1 -1
- package/dist/esm/classes/queue.js.map +1 -1
- package/dist/esm/classes/repeat.d.ts +3 -1
- package/dist/esm/classes/repeat.js +28 -22
- package/dist/esm/classes/repeat.js.map +1 -1
- package/dist/esm/classes/scripts.d.ts +3 -2
- package/dist/esm/classes/scripts.js +18 -5
- package/dist/esm/classes/scripts.js.map +1 -1
- package/dist/esm/classes/worker.js +3 -1
- package/dist/esm/classes/worker.js.map +1 -1
- package/dist/esm/commands/{addRepeatableJob-1.lua → addRepeatableJob-2.lua} +26 -15
- package/dist/esm/commands/updateRepeatableJobMillis-1.lua +28 -0
- package/dist/esm/scripts/addRepeatableJob-2.js +225 -0
- package/dist/esm/scripts/addRepeatableJob-2.js.map +1 -0
- package/dist/esm/scripts/index.d.ts +2 -1
- package/dist/esm/scripts/index.js +2 -1
- package/dist/esm/scripts/index.js.map +1 -1
- package/dist/esm/scripts/updateRepeatableJobMillis-1.d.ts +5 -0
- package/dist/esm/scripts/updateRepeatableJobMillis-1.js +30 -0
- package/dist/esm/scripts/updateRepeatableJobMillis-1.js.map +1 -0
- package/dist/esm/tsconfig.tsbuildinfo +1 -1
- package/package.json +1 -1
- package/dist/cjs/scripts/addRepeatableJob-1.js +0 -67
- package/dist/cjs/scripts/addRepeatableJob-1.js.map +0 -1
- package/dist/esm/scripts/addRepeatableJob-1.js +0 -64
- package/dist/esm/scripts/addRepeatableJob-1.js.map +0 -1
- /package/dist/esm/scripts/{addRepeatableJob-1.d.ts → addRepeatableJob-2.d.ts} +0 -0
@@ -0,0 +1,28 @@
|
|
1
|
+
--[[
|
2
|
+
Adds a repeatable job
|
3
|
+
|
4
|
+
Input:
|
5
|
+
KEYS[1] 'repeat' key
|
6
|
+
|
7
|
+
ARGV[1] next milliseconds
|
8
|
+
ARGV[2] custom key
|
9
|
+
ARGV[3] legacy custom key TODO: remove this logic in next breaking change
|
10
|
+
|
11
|
+
Output:
|
12
|
+
repeatableKey - OK
|
13
|
+
]]
|
14
|
+
local rcall = redis.call
|
15
|
+
local repeatKey = KEYS[1]
|
16
|
+
local nextMillis = ARGV[1]
|
17
|
+
local customKey = ARGV[2]
|
18
|
+
local legacyCustomKey = ARGV[3]
|
19
|
+
|
20
|
+
if rcall("ZSCORE", repeatKey, customKey) ~= false then
|
21
|
+
rcall("ZADD", repeatKey, nextMillis, customKey)
|
22
|
+
return customKey
|
23
|
+
elseif rcall("ZSCORE", repeatKey, legacyCustomKey) ~= false then
|
24
|
+
rcall("ZADD", repeatKey, nextMillis, legacyCustomKey)
|
25
|
+
return legacyCustomKey
|
26
|
+
end
|
27
|
+
|
28
|
+
return ''
|
@@ -0,0 +1,228 @@
|
|
1
|
+
"use strict";
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
3
|
+
exports.addRepeatableJob = void 0;
|
4
|
+
const content = `--[[
|
5
|
+
Adds a repeatable job
|
6
|
+
Input:
|
7
|
+
KEYS[1] 'repeat' key
|
8
|
+
KEYS[2] 'delayed' key
|
9
|
+
ARGV[1] next milliseconds
|
10
|
+
ARGV[2] msgpacked options
|
11
|
+
[1] name
|
12
|
+
[2] tz?
|
13
|
+
[3] patten?
|
14
|
+
[4] endDate?
|
15
|
+
[5] every?
|
16
|
+
ARGV[3] legacy custom key TODO: remove this logic in next breaking change
|
17
|
+
ARGV[4] custom key
|
18
|
+
ARGV[5] prefix key
|
19
|
+
Output:
|
20
|
+
repeatableKey - OK
|
21
|
+
]]
|
22
|
+
local rcall = redis.call
|
23
|
+
local repeatKey = KEYS[1]
|
24
|
+
local delayedKey = KEYS[2]
|
25
|
+
local nextMillis = ARGV[1]
|
26
|
+
local legacyCustomKey = ARGV[3]
|
27
|
+
local customKey = ARGV[4]
|
28
|
+
local prefixKey = ARGV[5]
|
29
|
+
-- Includes
|
30
|
+
--[[
|
31
|
+
Function to remove job.
|
32
|
+
]]
|
33
|
+
-- Includes
|
34
|
+
--[[
|
35
|
+
Function to remove debounce key.
|
36
|
+
]]
|
37
|
+
local function removeDebounceKey(prefixKey, jobKey)
|
38
|
+
local debounceId = rcall("HGET", jobKey, "deid")
|
39
|
+
if debounceId then
|
40
|
+
local debounceKey = prefixKey .. "de:" .. debounceId
|
41
|
+
rcall("DEL", debounceKey)
|
42
|
+
end
|
43
|
+
end
|
44
|
+
--[[
|
45
|
+
Function to remove job keys.
|
46
|
+
]]
|
47
|
+
local function removeJobKeys(jobKey)
|
48
|
+
return rcall("DEL", jobKey, jobKey .. ':logs',
|
49
|
+
jobKey .. ':dependencies', jobKey .. ':processed', jobKey .. ':failed')
|
50
|
+
end
|
51
|
+
--[[
|
52
|
+
Check if this job has a parent. If so we will just remove it from
|
53
|
+
the parent child list, but if it is the last child we should move the parent to "wait/paused"
|
54
|
+
which requires code from "moveToFinished"
|
55
|
+
]]
|
56
|
+
-- Includes
|
57
|
+
--[[
|
58
|
+
Function to add job in target list and add marker if needed.
|
59
|
+
]]
|
60
|
+
-- Includes
|
61
|
+
--[[
|
62
|
+
Add marker if needed when a job is available.
|
63
|
+
]]
|
64
|
+
local function addBaseMarkerIfNeeded(markerKey, isPausedOrMaxed)
|
65
|
+
if not isPausedOrMaxed then
|
66
|
+
rcall("ZADD", markerKey, 0, "0")
|
67
|
+
end
|
68
|
+
end
|
69
|
+
local function addJobInTargetList(targetKey, markerKey, pushCmd, isPausedOrMaxed, jobId)
|
70
|
+
rcall(pushCmd, targetKey, jobId)
|
71
|
+
addBaseMarkerIfNeeded(markerKey, isPausedOrMaxed)
|
72
|
+
end
|
73
|
+
--[[
|
74
|
+
Functions to destructure job key.
|
75
|
+
Just a bit of warning, these functions may be a bit slow and affect performance significantly.
|
76
|
+
]]
|
77
|
+
local getJobIdFromKey = function (jobKey)
|
78
|
+
return string.match(jobKey, ".*:(.*)")
|
79
|
+
end
|
80
|
+
local getJobKeyPrefix = function (jobKey, jobId)
|
81
|
+
return string.sub(jobKey, 0, #jobKey - #jobId)
|
82
|
+
end
|
83
|
+
--[[
|
84
|
+
Function to check for the meta.paused key to decide if we are paused or not
|
85
|
+
(since an empty list and !EXISTS are not really the same).
|
86
|
+
]]
|
87
|
+
local function getTargetQueueList(queueMetaKey, activeKey, waitKey, pausedKey)
|
88
|
+
local queueAttributes = rcall("HMGET", queueMetaKey, "paused", "concurrency")
|
89
|
+
if queueAttributes[1] then
|
90
|
+
return pausedKey, true
|
91
|
+
else
|
92
|
+
if queueAttributes[2] then
|
93
|
+
local activeCount = rcall("LLEN", activeKey)
|
94
|
+
if activeCount >= tonumber(queueAttributes[2]) then
|
95
|
+
return waitKey, true
|
96
|
+
else
|
97
|
+
return waitKey, false
|
98
|
+
end
|
99
|
+
end
|
100
|
+
end
|
101
|
+
return waitKey, false
|
102
|
+
end
|
103
|
+
local function moveParentToWait(parentPrefix, parentId, emitEvent)
|
104
|
+
local parentTarget, isPausedOrMaxed = getTargetQueueList(parentPrefix .. "meta", parentPrefix .. "active",
|
105
|
+
parentPrefix .. "wait", parentPrefix .. "paused")
|
106
|
+
addJobInTargetList(parentTarget, parentPrefix .. "marker", "RPUSH", isPausedOrMaxed, parentId)
|
107
|
+
if emitEvent then
|
108
|
+
local parentEventStream = parentPrefix .. "events"
|
109
|
+
rcall("XADD", parentEventStream, "*", "event", "waiting", "jobId", parentId, "prev", "waiting-children")
|
110
|
+
end
|
111
|
+
end
|
112
|
+
local function removeParentDependencyKey(jobKey, hard, parentKey, baseKey, debounceId)
|
113
|
+
if parentKey then
|
114
|
+
local parentDependenciesKey = parentKey .. ":dependencies"
|
115
|
+
local result = rcall("SREM", parentDependenciesKey, jobKey)
|
116
|
+
if result > 0 then
|
117
|
+
local pendingDependencies = rcall("SCARD", parentDependenciesKey)
|
118
|
+
if pendingDependencies == 0 then
|
119
|
+
local parentId = getJobIdFromKey(parentKey)
|
120
|
+
local parentPrefix = getJobKeyPrefix(parentKey, parentId)
|
121
|
+
local numRemovedElements = rcall("ZREM", parentPrefix .. "waiting-children", parentId)
|
122
|
+
if numRemovedElements == 1 then
|
123
|
+
if hard then -- remove parent in same queue
|
124
|
+
if parentPrefix == baseKey then
|
125
|
+
removeParentDependencyKey(parentKey, hard, nil, baseKey, nil)
|
126
|
+
removeJobKeys(parentKey)
|
127
|
+
if debounceId then
|
128
|
+
rcall("DEL", parentPrefix .. "de:" .. debounceId)
|
129
|
+
end
|
130
|
+
else
|
131
|
+
moveParentToWait(parentPrefix, parentId)
|
132
|
+
end
|
133
|
+
else
|
134
|
+
moveParentToWait(parentPrefix, parentId, true)
|
135
|
+
end
|
136
|
+
end
|
137
|
+
end
|
138
|
+
return true
|
139
|
+
end
|
140
|
+
else
|
141
|
+
local parentAttributes = rcall("HMGET", jobKey, "parentKey", "deid")
|
142
|
+
local missedParentKey = parentAttributes[1]
|
143
|
+
if( (type(missedParentKey) == "string") and missedParentKey ~= ""
|
144
|
+
and (rcall("EXISTS", missedParentKey) == 1)) then
|
145
|
+
local parentDependenciesKey = missedParentKey .. ":dependencies"
|
146
|
+
local result = rcall("SREM", parentDependenciesKey, jobKey)
|
147
|
+
if result > 0 then
|
148
|
+
local pendingDependencies = rcall("SCARD", parentDependenciesKey)
|
149
|
+
if pendingDependencies == 0 then
|
150
|
+
local parentId = getJobIdFromKey(missedParentKey)
|
151
|
+
local parentPrefix = getJobKeyPrefix(missedParentKey, parentId)
|
152
|
+
local numRemovedElements = rcall("ZREM", parentPrefix .. "waiting-children", parentId)
|
153
|
+
if numRemovedElements == 1 then
|
154
|
+
if hard then
|
155
|
+
if parentPrefix == baseKey then
|
156
|
+
removeParentDependencyKey(missedParentKey, hard, nil, baseKey, nil)
|
157
|
+
removeJobKeys(missedParentKey)
|
158
|
+
if parentAttributes[2] then
|
159
|
+
rcall("DEL", parentPrefix .. "de:" .. parentAttributes[2])
|
160
|
+
end
|
161
|
+
else
|
162
|
+
moveParentToWait(parentPrefix, parentId)
|
163
|
+
end
|
164
|
+
else
|
165
|
+
moveParentToWait(parentPrefix, parentId, true)
|
166
|
+
end
|
167
|
+
end
|
168
|
+
end
|
169
|
+
return true
|
170
|
+
end
|
171
|
+
end
|
172
|
+
end
|
173
|
+
return false
|
174
|
+
end
|
175
|
+
local function removeJob(jobId, hard, baseKey, shouldRemoveDebounceKey)
|
176
|
+
local jobKey = baseKey .. jobId
|
177
|
+
removeParentDependencyKey(jobKey, hard, nil, baseKey)
|
178
|
+
if shouldRemoveDebounceKey then
|
179
|
+
removeDebounceKey(baseKey, jobKey)
|
180
|
+
end
|
181
|
+
removeJobKeys(jobKey)
|
182
|
+
end
|
183
|
+
local function storeRepeatableJob(repeatKey, customKey, nextMillis, rawOpts)
|
184
|
+
rcall("ZADD", repeatKey, nextMillis, customKey)
|
185
|
+
local opts = cmsgpack.unpack(rawOpts)
|
186
|
+
local optionalValues = {}
|
187
|
+
if opts['tz'] then
|
188
|
+
table.insert(optionalValues, "tz")
|
189
|
+
table.insert(optionalValues, opts['tz'])
|
190
|
+
end
|
191
|
+
if opts['pattern'] then
|
192
|
+
table.insert(optionalValues, "pattern")
|
193
|
+
table.insert(optionalValues, opts['pattern'])
|
194
|
+
end
|
195
|
+
if opts['endDate'] then
|
196
|
+
table.insert(optionalValues, "endDate")
|
197
|
+
table.insert(optionalValues, opts['endDate'])
|
198
|
+
end
|
199
|
+
if opts['every'] then
|
200
|
+
table.insert(optionalValues, "every")
|
201
|
+
table.insert(optionalValues, opts['every'])
|
202
|
+
end
|
203
|
+
rcall("HMSET", repeatKey .. ":" .. customKey, "name", opts['name'],
|
204
|
+
unpack(optionalValues))
|
205
|
+
return customKey
|
206
|
+
end
|
207
|
+
-- If we are overriding a repeatable job we must delete the delayed job for
|
208
|
+
-- the next iteration.
|
209
|
+
local prevMillis = rcall("ZSCORE", repeatKey, customKey)
|
210
|
+
if prevMillis ~= false then
|
211
|
+
local delayedJobId = "repeat:" .. customKey .. ":" .. prevMillis
|
212
|
+
if rcall("ZSCORE", delayedKey, delayedJobId) ~= false then
|
213
|
+
removeJob(delayedJobId, true, prefixKey, true --[[remove debounce key]])
|
214
|
+
rcall("ZREM", delayedKey, delayedJobId)
|
215
|
+
end
|
216
|
+
end
|
217
|
+
-- Keep backwards compatibility with old repeatable jobs (<= 3.0.0)
|
218
|
+
if rcall("ZSCORE", repeatKey, legacyCustomKey) ~= false then
|
219
|
+
return storeRepeatableJob(repeatKey, legacyCustomKey, nextMillis, ARGV[2])
|
220
|
+
end
|
221
|
+
return storeRepeatableJob(repeatKey, customKey, nextMillis, ARGV[2])
|
222
|
+
`;
|
223
|
+
exports.addRepeatableJob = {
|
224
|
+
name: 'addRepeatableJob',
|
225
|
+
content,
|
226
|
+
keys: 2,
|
227
|
+
};
|
228
|
+
//# sourceMappingURL=addRepeatableJob-2.js.map
|
@@ -0,0 +1 @@
|
|
1
|
+
{"version":3,"file":"addRepeatableJob-2.js","sourceRoot":"","sources":["../../../src/scripts/addRepeatableJob-2.ts"],"names":[],"mappings":";;;AAAA,MAAM,OAAO,GAAG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CA0Nf,CAAC;AACW,QAAA,gBAAgB,GAAG;IAC9B,IAAI,EAAE,kBAAkB;IACxB,OAAO;IACP,IAAI,EAAE,CAAC;CACR,CAAC"}
|
@@ -5,7 +5,7 @@ tslib_1.__exportStar(require("./addDelayedJob-6"), exports);
|
|
5
5
|
tslib_1.__exportStar(require("./addLog-2"), exports);
|
6
6
|
tslib_1.__exportStar(require("./addParentJob-4"), exports);
|
7
7
|
tslib_1.__exportStar(require("./addPrioritizedJob-8"), exports);
|
8
|
-
tslib_1.__exportStar(require("./addRepeatableJob-
|
8
|
+
tslib_1.__exportStar(require("./addRepeatableJob-2"), exports);
|
9
9
|
tslib_1.__exportStar(require("./addStandardJob-8"), exports);
|
10
10
|
tslib_1.__exportStar(require("./changeDelay-4"), exports);
|
11
11
|
tslib_1.__exportStar(require("./changePriority-7"), exports);
|
@@ -41,4 +41,5 @@ tslib_1.__exportStar(require("./retryJob-11"), exports);
|
|
41
41
|
tslib_1.__exportStar(require("./saveStacktrace-1"), exports);
|
42
42
|
tslib_1.__exportStar(require("./updateData-1"), exports);
|
43
43
|
tslib_1.__exportStar(require("./updateProgress-3"), exports);
|
44
|
+
tslib_1.__exportStar(require("./updateRepeatableJobMillis-1"), exports);
|
44
45
|
//# sourceMappingURL=index.js.map
|
@@ -1 +1 @@
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/scripts/index.ts"],"names":[],"mappings":";;;AAAA,4DAAkC;AAClC,qDAA2B;AAC3B,2DAAiC;AACjC,gEAAsC;AACtC,+DAAqC;AACrC,6DAAmC;AACnC,0DAAgC;AAChC,6DAAmC;AACnC,6DAAmC;AACnC,oDAA0B;AAC1B,yDAA+B;AAC/B,wDAA8B;AAC9B,mEAAyC;AACzC,wDAA8B;AAC9B,8DAAoC;AACpC,uDAA6B;AAC7B,yDAA+B;AAC/B,yDAA+B;AAC/B,0DAAgC;AAChC,sDAA4B;AAC5B,uEAA6C;AAC7C,6DAAmC;AACnC,oEAA0C;AAC1C,4DAAkC;AAClC,4DAAkC;AAClC,8DAAoC;AACpC,oEAA0C;AAC1C,yDAA+B;AAC/B,uDAA6B;AAC7B,oDAA0B;AAC1B,sDAA4B;AAC5B,0DAAgC;AAChC,oEAA0C;AAC1C,wDAA8B;AAC9B,+DAAqC;AACrC,2DAAiC;AACjC,wDAA8B;AAC9B,6DAAmC;AACnC,yDAA+B;AAC/B,6DAAmC"}
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/scripts/index.ts"],"names":[],"mappings":";;;AAAA,4DAAkC;AAClC,qDAA2B;AAC3B,2DAAiC;AACjC,gEAAsC;AACtC,+DAAqC;AACrC,6DAAmC;AACnC,0DAAgC;AAChC,6DAAmC;AACnC,6DAAmC;AACnC,oDAA0B;AAC1B,yDAA+B;AAC/B,wDAA8B;AAC9B,mEAAyC;AACzC,wDAA8B;AAC9B,8DAAoC;AACpC,uDAA6B;AAC7B,yDAA+B;AAC/B,yDAA+B;AAC/B,0DAAgC;AAChC,sDAA4B;AAC5B,uEAA6C;AAC7C,6DAAmC;AACnC,oEAA0C;AAC1C,4DAAkC;AAClC,4DAAkC;AAClC,8DAAoC;AACpC,oEAA0C;AAC1C,yDAA+B;AAC/B,uDAA6B;AAC7B,oDAA0B;AAC1B,sDAA4B;AAC5B,0DAAgC;AAChC,oEAA0C;AAC1C,wDAA8B;AAC9B,+DAAqC;AACrC,2DAAiC;AACjC,wDAA8B;AAC9B,6DAAmC;AACnC,yDAA+B;AAC/B,6DAAmC;AACnC,wEAA8C"}
|
@@ -0,0 +1,33 @@
|
|
1
|
+
"use strict";
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
3
|
+
exports.updateRepeatableJobMillis = void 0;
|
4
|
+
const content = `--[[
|
5
|
+
Adds a repeatable job
|
6
|
+
Input:
|
7
|
+
KEYS[1] 'repeat' key
|
8
|
+
ARGV[1] next milliseconds
|
9
|
+
ARGV[2] custom key
|
10
|
+
ARGV[3] legacy custom key TODO: remove this logic in next breaking change
|
11
|
+
Output:
|
12
|
+
repeatableKey - OK
|
13
|
+
]]
|
14
|
+
local rcall = redis.call
|
15
|
+
local repeatKey = KEYS[1]
|
16
|
+
local nextMillis = ARGV[1]
|
17
|
+
local customKey = ARGV[2]
|
18
|
+
local legacyCustomKey = ARGV[3]
|
19
|
+
if rcall("ZSCORE", repeatKey, customKey) ~= false then
|
20
|
+
rcall("ZADD", repeatKey, nextMillis, customKey)
|
21
|
+
return customKey
|
22
|
+
elseif rcall("ZSCORE", repeatKey, legacyCustomKey) ~= false then
|
23
|
+
rcall("ZADD", repeatKey, nextMillis, legacyCustomKey)
|
24
|
+
return legacyCustomKey
|
25
|
+
end
|
26
|
+
return ''
|
27
|
+
`;
|
28
|
+
exports.updateRepeatableJobMillis = {
|
29
|
+
name: 'updateRepeatableJobMillis',
|
30
|
+
content,
|
31
|
+
keys: 1,
|
32
|
+
};
|
33
|
+
//# sourceMappingURL=updateRepeatableJobMillis-1.js.map
|
@@ -0,0 +1 @@
|
|
1
|
+
{"version":3,"file":"updateRepeatableJobMillis-1.js","sourceRoot":"","sources":["../../../src/scripts/updateRepeatableJobMillis-1.ts"],"names":[],"mappings":";;;AAAA,MAAM,OAAO,GAAG;;;;;;;;;;;;;;;;;;;;;;;CAuBf,CAAC;AACW,QAAA,yBAAyB,GAAG;IACvC,IAAI,EAAE,2BAA2B;IACjC,OAAO;IACP,IAAI,EAAE,CAAC;CACR,CAAC"}
|