glide-mq 0.15.0 → 0.15.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/CHANGELOG.md CHANGED
@@ -6,6 +6,14 @@ The format follows [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
6
6
 
7
7
  ---
8
8
 
9
+ ## [0.15.1] - 2026-04-06
10
+
11
+ ### Fixed
12
+
13
+ - **`debounce` + `ordering.key` deadlock** (#206): when debounce cancelled a pending ordered job, the deleted sequence created a permanent `nextSeq` gap that blocked all subsequent jobs in the group. Fixed via lightweight skip markers (`skip:<seq>` on the group hash) resolved lazily at all five ordering gates. `LIBRARY_VERSION` bumped to `81` - existing standalone clients reload the fix automatically on next connection.
14
+
15
+ ---
16
+
9
17
  ## [0.15.0] - 2026-04-02
10
18
 
11
19
  ### Added
@@ -1,9 +1,9 @@
1
1
  import type { Client } from '../types';
2
2
  import type { GlideReturnType } from '@glidemq/speedkey';
3
3
  export declare const LIBRARY_NAME = "glidemq";
4
- export declare const LIBRARY_VERSION = "80";
4
+ export declare const LIBRARY_VERSION = "81";
5
5
  export declare const CONSUMER_GROUP = "workers";
6
- export declare const LIBRARY_SOURCE = "#!lua name=glidemq\n\nlocal PRIORITY_SHIFT = 4398046511104\n\nlocal function emitEvent(eventsKey, eventType, jobId, extraFields)\n local fields = {'event', eventType, 'jobId', tostring(jobId)}\n if extraFields then\n for i = 1, #extraFields, 2 do\n fields[#fields + 1] = extraFields[i]\n fields[#fields + 1] = extraFields[i + 1]\n end\n end\n redis.call('XADD', eventsKey, 'MAXLEN', '~', '1000', '*', unpack(fields))\nend\n\nlocal function markOrderingDone(jobKey, jobId, hintOrderingKey, hintOrderingSeq)\n local orderingKey = hintOrderingKey\n if not orderingKey or orderingKey == '' then\n orderingKey = redis.call('HGET', jobKey, 'orderingKey')\n end\n if not orderingKey or orderingKey == '' then\n return\n end\n local orderingSeq = nil\n if hintOrderingSeq ~= nil and hintOrderingSeq ~= '' then\n orderingSeq = tonumber(hintOrderingSeq) or 0\n else\n orderingSeq = tonumber(redis.call('HGET', jobKey, 'orderingSeq')) or 0\n end\n if orderingSeq <= 0 then\n return\n end\n\n local prefix = string.sub(jobKey, 1, #jobKey - #('job:' .. jobId))\n local metaKey = prefix .. 'meta'\n local doneField = 'orderdone:' .. orderingKey\n local pendingKey = prefix .. 'orderdone:pending:' .. orderingKey\n\n local lastDone = tonumber(redis.call('HGET', metaKey, doneField)) or 0\n if orderingSeq <= lastDone then\n redis.call('HDEL', pendingKey, tostring(orderingSeq))\n return\n end\n\n redis.call('HSET', pendingKey, tostring(orderingSeq), '1')\n local advanced = lastDone\n while true do\n local nextSeq = advanced + 1\n if redis.call('HEXISTS', pendingKey, tostring(nextSeq)) == 0 then\n break\n end\n redis.call('HDEL', pendingKey, tostring(nextSeq))\n advanced = nextSeq\n end\n if advanced > lastDone then\n redis.call('HSET', metaKey, doneField, tostring(advanced))\n end\nend\n\n-- Refill token bucket using remainder accumulator for precision.\n-- tbRefillRate is in millitokens/second. Returns current millitokens after refill.\n-- Side effect: updates tbTokens, tbLastRefill, tbRefillRemainder on the group hash.\nlocal function tbRefill(groupHashKey, g, now)\n local tbCapacity = tonumber(g.tbCapacity) or 0\n if tbCapacity <= 0 then return 0 end\n local tbTokens = tonumber(g.tbTokens) or tbCapacity\n if tbTokens >= tbCapacity then return tbCapacity end\n local tbRefillRate = tonumber(g.tbRefillRate) or 0\n local tbLastRefill = tonumber(g.tbLastRefill) or now\n local tbRefillRemainder = tonumber(g.tbRefillRemainder) or 0\n local elapsed = now - tbLastRefill\n if elapsed <= 0 or tbRefillRate <= 0 then return tbTokens end\n -- Cap elapsed to prevent overflow in long-idle buckets\n local maxElapsed = math.ceil(tbCapacity * 1000 / tbRefillRate)\n if elapsed > maxElapsed then elapsed = maxElapsed end\n local raw = elapsed * tbRefillRate + tbRefillRemainder\n local added = math.floor(raw / 1000)\n local newRemainder = raw % 1000\n local newTokens = math.min(tbCapacity, tbTokens + added)\n redis.call('HSET', groupHashKey,\n 'tbTokens', tostring(newTokens),\n 'tbLastRefill', tostring(now),\n 'tbRefillRemainder', tostring(newRemainder))\n return newTokens\nend\n\nlocal function recordMetrics(metricsKey, timestamp, duration)\n local minuteTs = timestamp - (timestamp % 60000)\n local newCount = tonumber(redis.call('HINCRBY', metricsKey, 'm:' .. minuteTs .. ':c', 1))\n if duration > 0 then\n redis.call('HINCRBY', metricsKey, 'm:' .. minuteTs .. ':d', duration)\n end\n if newCount and (newCount % 1000 == 0) then\n local cutoff = minuteTs - 86400000\n local fields = redis.call('HKEYS', metricsKey)\n local toDelete = {}\n for _, f in ipairs(fields) do\n local ts = tonumber(string.match(f, '^m:(%d+):'))\n if ts and ts < cutoff then\n toDelete[#toDelete + 1] = f\n end\n end\n if #toDelete > 0 and #toDelete <= 1000 then\n redis.call('HDEL', metricsKey, unpack(toDelete))\n elseif #toDelete > 1000 then\n for i = 1, #toDelete, 1000 do\n redis.call('HDEL', metricsKey, unpack(toDelete, i, math.min(i + 999, #toDelete)))\n end\n end\n end\nend\n\nlocal function xaddJob(streamKey, jobId, jobName)\n redis.call('XADD', streamKey, '*', 'jobId', jobId, 'name', jobName or '')\nend\nlocal function groupqScore(jobKey, jobId)\n local seq = tonumber(redis.call('HGET', jobKey, 'orderingSeq'))\n if seq and seq > 0 then return seq end\n local num = tonumber(jobId)\n if num then return num end\n -- String IDs: use timestamp from job hash to preserve insertion order\n local ts = tonumber(redis.call('HGET', jobKey, 'timestamp'))\n if ts then return ts end\n return 0\nend\n\n\nlocal function releaseGroupSlotAndPromote(jobKey, jobId, now, hintGroupKey)\n local gk = hintGroupKey\n if not gk or gk == '' then\n gk = redis.call('HGET', jobKey, 'groupKey')\n end\n if not gk or gk == '' then return end\n local prefix = string.sub(jobKey, 1, #jobKey - #('job:' .. jobId))\n local groupHashKey = prefix .. 'group:' .. gk\n -- Load all group fields in one call\n local gFields = redis.call('HGETALL', groupHashKey)\n local g = {}\n for gf = 1, #gFields, 2 do g[gFields[gf]] = gFields[gf + 1] end\n local cur = tonumber(g.active) or 0\n local newActive = (cur > 0) and (cur - 1) or 0\n if cur > 0 then\n redis.call('HSET', groupHashKey, 'active', tostring(newActive))\n end\n local waitListKey = prefix .. 'groupq:' .. gk\n local waitLen = redis.call('ZCARD', waitListKey)\n if waitLen == 0 then return end\n -- Concurrency gate: if still at or above max after decrement, do not promote\n local maxConc = tonumber(g.maxConcurrency) or 0\n if maxConc > 0 and newActive >= maxConc then return end\n -- Rate limit gate (skip if now is nil or 0 for safe fallback)\n -- Only blocks promotion; does NOT increment rateCount. moveToActive handles counting.\n local rateMax = tonumber(g.rateMax) or 0\n local rateRemaining = 0\n local ts = tonumber(now) or 0\n if ts > 0 and rateMax > 0 then\n local rateDuration = tonumber(g.rateDuration) or 0\n if rateDuration > 0 then\n local rateWindowStart = tonumber(g.rateWindowStart) or 0\n local rateCount = tonumber(g.rateCount) or 0\n if ts - rateWindowStart < rateDuration then\n if rateCount >= rateMax then\n -- Window active and at capacity: do not promote, register for scheduler\n local rateLimitedKey = prefix .. 'ratelimited'\n redis.call('ZADD', rateLimitedKey, rateWindowStart + rateDuration, gk)\n return\n end\n rateRemaining = rateMax - rateCount\n end\n end\n end\n -- Token bucket gate: check head job cost before promoting\n local tbCap = tonumber(g.tbCapacity) or 0\n if ts > 0 and tbCap > 0 then\n local tbTokensCur = tbRefill(groupHashKey, g, ts)\n -- Peek at head job, skipping tombstones and DLQ'd jobs (up to 10 iterations)\n local tbCheckPasses = 0\n local tbOk = false\n while tbCheckPasses < 10 do\n tbCheckPasses = tbCheckPasses + 1\n local headMembers = redis.call('ZRANGE', waitListKey, 0, 0)\n local headJobId = headMembers[1]\n if not headJobId then break end\n local headJobKey = prefix .. 'job:' .. headJobId\n -- Tombstone guard: job hash deleted - remove and check next\n if redis.call('EXISTS', headJobKey) == 0 then\n redis.call('ZREM', waitListKey, headJobId)\n else\n local headCost = tonumber(redis.call('HGET', headJobKey, 'cost')) or 1000\n -- DLQ guard: cost > capacity - remove, fail, check next\n if headCost > tbCap then\n local metricsKey = prefix .. 'metrics:failed'\n local processedOn = tonumber(redis.call('HGET', headJobKey, 'processedOn')) or ts\n redis.call('ZREM', waitListKey, headJobId)\n redis.call('ZADD', prefix .. 'failed', ts, headJobId)\n redis.call('HSET', headJobKey,\n 'state', 'failed',\n 'failedReason', 'cost exceeds token bucket capacity',\n 'finishedOn', tostring(ts))\n emitEvent(prefix .. 'events', 'failed', headJobId, {'failedReason', 'cost exceeds token bucket capacity'})\n recordMetrics(metricsKey, ts, ts - processedOn)\n elseif tbTokensCur < headCost then\n -- Not enough tokens: register delay and skip promotion\n local tbRateVal = tonumber(g.tbRefillRate) or 0\n if tbRateVal <= 0 then break end\n local tbDelayMs = math.ceil((headCost - tbTokensCur) * 1000 / tbRateVal)\n local rateLimitedKey = prefix .. 'ratelimited'\n redis.call('ZADD', rateLimitedKey, ts + tbDelayMs, gk)\n return\n else\n tbOk = true\n break\n end\n end\n end\n if not tbOk and tbCheckPasses >= 10 then return end\n end\n -- Calculate how many slots are available for promotion\n local available = 1\n if maxConc > 0 then\n available = maxConc - newActive\n else\n available = math.min(waitLen, 1000)\n end\n -- Cap by rate limit remaining if a window is active\n if rateRemaining > 0 then\n available = math.min(available, rateRemaining)\n end\n local streamKey = prefix .. 'stream'\n local nextSeq = tonumber(g.nextSeq) or 0\n local promoted = 0\n local maxIter = available + 20\n local iter = 0\n while promoted < available and iter < maxIter do\n iter = iter + 1\n local zpResult = redis.call('ZPOPMIN', waitListKey, 1)\n local nextJobId = zpResult[1]\n if not nextJobId then break end\n local nextJobKey = prefix .. 'job:' .. nextJobId\n -- Skip stale entries (job no longer in group-waiting state)\n local nextState = redis.call('HGET', nextJobKey, 'state')\n if nextState ~= 'group-waiting' then\n -- Stale: already processed via another path. Discard.\n else\n if nextSeq > 0 then\n local jobSeq = tonumber(redis.call('HGET', nextJobKey, 'orderingSeq')) or 0\n if jobSeq > nextSeq then\n -- Future job: put back and stop promoting\n redis.call('ZADD', waitListKey, jobSeq, nextJobId)\n break\n end\n end\n xaddJob(streamKey, nextJobId, redis.call('HGET', nextJobKey, 'name'))\n redis.call('HSET', nextJobKey, 'state', 'waiting')\n promoted = promoted + 1\n -- Don't advance nextSeq here; moveToActive does it on actual activation\n if nextSeq > 0 then nextSeq = nextSeq + 1 end -- local only, for stale skip in next iteration\n end\n end\nend\n\nlocal function expireJob(jobKey, jobId, prefix, now, curState, hintOrderingKey, hintOrderingSeq, hintGroupKey)\n if curState == 'failed' then return true end\n local wasActive = (curState == 'active')\n local failedKey = prefix .. 'failed'\n local eventsKey = prefix .. 'events'\n local metricsKey = prefix .. 'metrics:failed'\n local processedOn = tonumber(redis.call('HGET', jobKey, 'processedOn')) or now\n redis.call('ZADD', failedKey, now, jobId)\n redis.call('HSET', jobKey,\n 'state', 'failed',\n 'failedReason', 'expired',\n 'finishedOn', tostring(now))\n markOrderingDone(jobKey, jobId, hintOrderingKey, hintOrderingSeq)\n -- Only release group slot if the job was actually active (held a slot)\n if wasActive then\n releaseGroupSlotAndPromote(jobKey, jobId, now, hintGroupKey)\n end\n emitEvent(eventsKey, 'expired', jobId, nil)\n recordMetrics(metricsKey, now, now - processedOn)\n return true\nend\n\nlocal function checkExpired(jobKey, jobId, prefix, now)\n local expireAt = tonumber(redis.call('HGET', jobKey, 'expireAt'))\n if not expireAt or expireAt <= 0 then return false end\n if now <= expireAt then return false end\n -- Idempotency guard: if already expired, skip side effects\n local curState = redis.call('HGET', jobKey, 'state')\n return expireJob(jobKey, jobId, prefix, now, curState, nil, nil, nil)\nend\n\nlocal function advanceIdCounter(idKey, customId)\n if not string.match(customId, '^%d+$') then return end\n local numericId = tonumber(customId)\n if numericId and numericId > 0 then\n local cur = tonumber(redis.call('GET', idKey)) or 0\n if numericId > cur then\n redis.call('SET', idKey, customId)\n end\n end\nend\n\nlocal function extractOrderingKeyFromOpts(optsJson)\n if not optsJson or optsJson == '' then\n return ''\n end\n local ok, decoded = pcall(cjson.decode, optsJson)\n if not ok or type(decoded) ~= 'table' then\n return ''\n end\n local ordering = decoded['ordering']\n if type(ordering) ~= 'table' then\n return ''\n end\n local key = ordering['key']\n if key == nil then\n return ''\n end\n return tostring(key)\nend\n\nlocal function extractLifoFromOpts(optsJson)\n if not optsJson or optsJson == '' then return 0 end\n local ok, decoded = pcall(cjson.decode, optsJson)\n if not ok or type(decoded) ~= 'table' then return 0 end\n return (decoded['lifo'] == true or decoded['lifo'] == 1) and 1 or 0\nend\n\nlocal function extractGroupConcurrencyFromOpts(optsJson)\n if not optsJson or optsJson == '' then\n return 0\n end\n local ok, decoded = pcall(cjson.decode, optsJson)\n if not ok or type(decoded) ~= 'table' then\n return 0\n end\n local ordering = decoded['ordering']\n if type(ordering) ~= 'table' then\n return 0\n end\n local conc = ordering['concurrency']\n if conc == nil then\n return 0\n end\n return tonumber(conc) or 0\nend\n\nlocal function extractGroupRateLimitFromOpts(optsJson)\n if not optsJson or optsJson == '' then\n return 0, 0\n end\n local ok, decoded = pcall(cjson.decode, optsJson)\n if not ok or type(decoded) ~= 'table' then\n return 0, 0\n end\n local ordering = decoded['ordering']\n if type(ordering) ~= 'table' then\n return 0, 0\n end\n local rl = ordering['rateLimit']\n if type(rl) ~= 'table' then\n return 0, 0\n end\n local max = tonumber(rl['max']) or 0\n local duration = tonumber(rl['duration']) or 0\n return max, duration\nend\n\nlocal function extractTokenBucketFromOpts(optsJson)\n if not optsJson or optsJson == '' then return 0, 0 end\n local ok, decoded = pcall(cjson.decode, optsJson)\n if not ok or type(decoded) ~= 'table' then return 0, 0 end\n local ordering = decoded['ordering']\n if type(ordering) ~= 'table' then return 0, 0 end\n local tb = ordering['tokenBucket']\n if type(tb) ~= 'table' then return 0, 0 end\n local capacity = tonumber(tb['capacity']) or 0\n local refillRate = tonumber(tb['refillRate']) or 0\n return math.floor(capacity * 1000), math.floor(refillRate * 1000)\nend\n\nlocal function extractCostFromOpts(optsJson)\n if not optsJson or optsJson == '' then return 0 end\n local ok, decoded = pcall(cjson.decode, optsJson)\n if not ok or type(decoded) ~= 'table' then return 0 end\n local cost = tonumber(decoded['cost']) or 0\n return math.floor(cost * 1000)\nend\n\nlocal function extractTtlFromOpts(optsJson)\n if not optsJson or optsJson == '' then return 0 end\n local ok, decoded = pcall(cjson.decode, optsJson)\n if not ok or type(decoded) ~= 'table' then return 0 end\n return tonumber(decoded['ttl']) or 0\nend\n\nlocal function extractLockDurationFromOpts(optsJson)\n if not optsJson or optsJson == '' then return 0 end\n local ok, decoded = pcall(cjson.decode, optsJson)\n if not ok or type(decoded) ~= 'table' then return 0 end\n return tonumber(decoded['lockDuration']) or 0\nend\n\n-- Apply stall logic to a job: increment stalledCount, fail if over max, else emit stalled event.\n-- Returns true if the job was moved to failed, false if only stalled.\nlocal function applyStalledLogic(jobKey, jobId, prefix, eventsKey, failedKey, maxStalledCount, timestamp)\n local stalledCount = redis.call('HINCRBY', jobKey, 'stalledCount', 1)\n if stalledCount > maxStalledCount then\n local metricsKey = prefix .. 'metrics:failed'\n local processedOn = tonumber(redis.call('HGET', jobKey, 'processedOn')) or timestamp\n redis.call('ZADD', failedKey, timestamp, jobId)\n redis.call('HSET', jobKey,\n 'state', 'failed',\n 'failedReason', 'job stalled more than maxStalledCount',\n 'finishedOn', tostring(timestamp)\n )\n markOrderingDone(jobKey, jobId)\n releaseGroupSlotAndPromote(jobKey, jobId, timestamp)\n emitEvent(eventsKey, 'failed', jobId, {\n 'failedReason', 'job stalled more than maxStalledCount'\n })\n recordMetrics(metricsKey, timestamp, timestamp - processedOn)\n return true\n else\n emitEvent(eventsKey, 'stalled', jobId, nil)\n return false\n end\nend\n\n-- Remove excess jobs from a sorted set in capped, stack-safe batches.\n-- Deletes job hashes and removes from the set in chunks of 1000.\nlocal function removeExcessJobs(setKey, prefix, ids)\n for i = 1, #ids do\n redis.call('DEL', prefix .. 'job:' .. ids[i])\n end\n for i = 1, #ids, 1000 do\n redis.call('ZREM', setKey, unpack(ids, i, math.min(i + 999, #ids)))\n end\nend\n\nredis.register_function('glidemq_version', function(keys, args)\n return '80'\nend)\n\nredis.register_function('glidemq_addJob', function(keys, args)\n local idKey = keys[1]\n assert(string.sub(idKey, -3) == ':id', 'unexpected key format: ' .. idKey)\n local streamKey = keys[2]\n local scheduledKey = keys[3]\n local eventsKey = keys[4]\n local jobName = args[1]\n local jobData = args[2]\n local jobOpts = args[3]\n local timestamp = tonumber(args[4])\n local delay = tonumber(args[5]) or 0\n local priority = tonumber(args[6]) or 0\n local parentId = args[7] or ''\n local maxAttempts = tonumber(args[8]) or 0\n local orderingKey = args[9] or ''\n local groupConcurrency = tonumber(args[10]) or 0\n local groupRateMax = tonumber(args[11]) or 0\n local groupRateDuration = tonumber(args[12]) or 0\n local tbCapacity = tonumber(args[13]) or 0\n local tbRefillRate = tonumber(args[14]) or 0\n local jobCost = tonumber(args[15]) or 0\n local ttl = tonumber(args[16]) or 0\n local customJobId = args[17] or ''\n local lifo = tonumber(args[18]) or 0\n local parentQueue = args[19] or ''\n local schedulerName = args[20] or ''\n local skipEvents = args[21] or '0'\n local prefix = string.sub(idKey, 1, #idKey - 2)\n local jobIdStr\n local jobKey\n if customJobId ~= '' then\n jobKey = prefix .. 'job:' .. customJobId\n if redis.call('EXISTS', jobKey) == 1 then\n return 'duplicate'\n end\n jobIdStr = customJobId\n advanceIdCounter(idKey, customJobId)\n else\n local jobId = redis.call('INCR', idKey)\n jobIdStr = tostring(jobId)\n jobKey = prefix .. 'job:' .. jobIdStr\n end\n local useGroupConcurrency = (orderingKey ~= '')\n local orderingSeq = 0\n if orderingKey ~= '' then\n local orderingMetaKey = prefix .. 'ordering'\n orderingSeq = redis.call('HINCRBY', orderingMetaKey, orderingKey, 1)\n end\n if useGroupConcurrency then\n if groupConcurrency < 1 then groupConcurrency = 1 end\n local groupHashKey = prefix .. 'group:' .. orderingKey\n local curMax = tonumber(redis.call('HGET', groupHashKey, 'maxConcurrency')) or 0\n if curMax ~= groupConcurrency then\n redis.call('HSET', groupHashKey, 'maxConcurrency', tostring(groupConcurrency))\n end\n -- Initialize nextSeq for ordered promotion\n if orderingSeq > 0 and redis.call('HEXISTS', groupHashKey, 'nextSeq') == 0 then\n redis.call('HSET', groupHashKey, 'nextSeq', '1')\n end\n -- Upsert rate limit fields on group hash\n if groupRateMax > 0 then\n local curRateMax = tonumber(redis.call('HGET', groupHashKey, 'rateMax')) or 0\n if curRateMax ~= groupRateMax then\n redis.call('HSET', groupHashKey, 'rateMax', tostring(groupRateMax))\n end\n local curRateDuration = tonumber(redis.call('HGET', groupHashKey, 'rateDuration')) or 0\n if curRateDuration ~= groupRateDuration then\n redis.call('HSET', groupHashKey, 'rateDuration', tostring(groupRateDuration))\n end\n else\n -- Clear stale rate limit fields if group was previously rate-limited\n local oldRateMax = tonumber(redis.call('HGET', groupHashKey, 'rateMax')) or 0\n if oldRateMax > 0 then\n redis.call('HDEL', groupHashKey, 'rateMax', 'rateDuration', 'rateWindowStart', 'rateCount')\n end\n end\n -- Upsert token bucket fields on group hash\n if tbCapacity > 0 then\n local curTbCap = tonumber(redis.call('HGET', groupHashKey, 'tbCapacity')) or 0\n if curTbCap ~= tbCapacity then\n redis.call('HSET', groupHashKey, 'tbCapacity', tostring(tbCapacity))\n end\n local curTbRate = tonumber(redis.call('HGET', groupHashKey, 'tbRefillRate')) or 0\n if curTbRate ~= tbRefillRate then\n redis.call('HSET', groupHashKey, 'tbRefillRate', tostring(tbRefillRate))\n end\n -- Initialize tokens on first setup\n if curTbCap == 0 then\n redis.call('HSET', groupHashKey,\n 'tbTokens', tostring(tbCapacity),\n 'tbLastRefill', tostring(timestamp),\n 'tbRefillRemainder', '0')\n end\n -- Validate cost (explicit or default 1000 millitokens) against capacity\n local effectiveCost = (jobCost > 0) and jobCost or 1000\n if effectiveCost > tbCapacity then\n return 'ERR:COST_EXCEEDS_CAPACITY'\n end\n else\n -- Clear stale tb fields\n local oldTbCap = tonumber(redis.call('HGET', groupHashKey, 'tbCapacity')) or 0\n if oldTbCap > 0 then\n redis.call('HDEL', groupHashKey, 'tbCapacity', 'tbRefillRate', 'tbTokens', 'tbLastRefill', 'tbRefillRemainder')\n end\n end\n end\n local hashFields = {\n 'id', jobIdStr,\n 'name', jobName,\n 'data', jobData,\n 'opts', jobOpts,\n 'timestamp', tostring(timestamp),\n 'attemptsMade', '0',\n 'delay', tostring(delay),\n 'priority', tostring(priority),\n 'maxAttempts', tostring(maxAttempts)\n }\n if useGroupConcurrency then\n hashFields[#hashFields + 1] = 'groupKey'\n hashFields[#hashFields + 1] = orderingKey\n if orderingSeq > 0 then\n hashFields[#hashFields + 1] = 'orderingSeq'\n hashFields[#hashFields + 1] = tostring(orderingSeq)\n end\n end\n if jobCost > 0 then\n hashFields[#hashFields + 1] = 'cost'\n hashFields[#hashFields + 1] = tostring(jobCost)\n end\n if ttl > 0 then\n hashFields[#hashFields + 1] = 'expireAt'\n hashFields[#hashFields + 1] = tostring(timestamp + ttl)\n end\n if parentId ~= '' then\n hashFields[#hashFields + 1] = 'parentId'\n hashFields[#hashFields + 1] = parentId\n if parentQueue ~= '' then\n hashFields[#hashFields + 1] = 'parentQueue'\n hashFields[#hashFields + 1] = parentQueue\n end\n end\n if schedulerName ~= '' then\n hashFields[#hashFields + 1] = 'schedulerName'\n hashFields[#hashFields + 1] = schedulerName\n end\n if lifo > 0 then\n hashFields[#hashFields + 1] = 'lifo'\n hashFields[#hashFields + 1] = '1'\n end\n if delay > 0 or priority > 0 then\n hashFields[#hashFields + 1] = 'state'\n hashFields[#hashFields + 1] = delay > 0 and 'delayed' or 'prioritized'\n else\n hashFields[#hashFields + 1] = 'state'\n hashFields[#hashFields + 1] = 'waiting'\n end\n redis.call('HSET', jobKey, unpack(hashFields))\n -- Register child in parent's deps set when parentDepsKey is provided (keys[5])\n if parentId ~= '' and parentQueue ~= '' and #keys >= 5 then\n local parentDepsKey = keys[5]\n -- prefix includes trailing colon (glide:{Q}:), so strip it for depsMember\n local queuePrefix = string.sub(prefix, 1, #prefix - 1)\n local depsMember = queuePrefix .. ':' .. jobIdStr\n redis.call('SADD', parentDepsKey, depsMember)\n end\n if delay > 0 then\n local score = priority * PRIORITY_SHIFT + (timestamp + delay)\n redis.call('ZADD', scheduledKey, score, jobIdStr)\n elseif priority > 0 then\n local score = priority * PRIORITY_SHIFT\n redis.call('ZADD', scheduledKey, score, jobIdStr)\n elseif lifo > 0 then\n local lifoKey = prefix .. 'lifo'\n redis.call('RPUSH', lifoKey, jobIdStr)\n else\n xaddJob(streamKey, jobIdStr, jobName)\n end\n if skipEvents ~= '1' then emitEvent(eventsKey, 'added', jobIdStr, {'name', jobName}) end\n return jobIdStr\nend)\n\nredis.register_function('glidemq_promote', function(keys, args)\n local scheduledKey = keys[1]\n local streamKey = keys[2]\n local eventsKey = keys[3]\n local now = tonumber(args[1])\n local MAX_PROMOTIONS = 1000\n local count = 0\n local cursorMin = 0\n while count < MAX_PROMOTIONS do\n local nextEntry = redis.call('ZRANGEBYSCORE', scheduledKey, string.format('%.0f', cursorMin), '+inf', 'WITHSCORES', 'LIMIT', 0, 1)\n if not nextEntry or #nextEntry == 0 then\n break\n end\n local firstScore = tonumber(nextEntry[2]) or 0\n local priority = math.floor(firstScore / PRIORITY_SHIFT)\n local minScore = priority * PRIORITY_SHIFT\n local maxDueScore = minScore + now\n local remaining = MAX_PROMOTIONS - count\n local members = redis.call(\n 'ZRANGEBYSCORE',\n scheduledKey,\n string.format('%.0f', minScore),\n string.format('%.0f', maxDueScore),\n 'LIMIT',\n 0,\n remaining\n )\n for i = 1, #members do\n local jobId = members[i]\n local prefix = string.sub(scheduledKey, 1, #scheduledKey - 9)\n local jobKey = prefix .. 'job:' .. jobId\n redis.call('ZREM', scheduledKey, jobId)\n if not checkExpired(jobKey, jobId, prefix, now) then\n local jobLifo = redis.call('HGET', jobKey, 'lifo')\n if jobLifo == '1' then\n local lifoKey = prefix .. 'lifo'\n redis.call('RPUSH', lifoKey, jobId)\n elseif priority > 0 then\n -- Priority jobs go to dedicated priority list, checked before LIFO and stream.\n -- LPUSH so RPOP retrieves lowest-score (highest-priority) job first.\n local priorityKey = prefix .. 'priority'\n redis.call('LPUSH', priorityKey, jobId)\n else\n xaddJob(streamKey, jobId, redis.call('HGET', jobKey, 'name'))\n end\n redis.call('HSET', jobKey, 'state', 'waiting')\n emitEvent(eventsKey, 'promoted', jobId, nil)\n count = count + 1\n end\n end\n cursorMin = (priority + 1) * PRIORITY_SHIFT\n end\n return count\nend)\n\nredis.register_function('glidemq_nextDue', function(keys, args)\n local scheduledKey = keys[1]\n local rateLimitedKey = keys[2]\n local nextDue = nil\n\n local scheduled = redis.call('ZRANGE', scheduledKey, 0, 0, 'WITHSCORES')\n if scheduled and #scheduled >= 2 then\n local score = tonumber(scheduled[2]) or 0\n local due = score % PRIORITY_SHIFT\n nextDue = due\n end\n\n local limited = redis.call('ZRANGE', rateLimitedKey, 0, 0, 'WITHSCORES')\n if limited and #limited >= 2 then\n local limitedDue = tonumber(limited[2]) or 0\n if (not nextDue) or limitedDue < nextDue then\n nextDue = limitedDue\n end\n end\n\n if not nextDue then\n return -1\n end\n\n return math.floor(nextDue)\nend)\n\nredis.register_function('glidemq_tryLock', function(keys, args)\n local lockKey = keys[1]\n local token = args[1]\n local ttl = tonumber(args[2]) or 1000\n local result = redis.call('SET', lockKey, token, 'PX', tostring(ttl), 'NX')\n if result then\n return 1\n end\n return 0\nend)\n\nredis.register_function('glidemq_unlock', function(keys, args)\n local lockKey = keys[1]\n local token = args[1]\n local current = redis.call('GET', lockKey)\n if current == token then\n redis.call('DEL', lockKey)\n return 1\n end\n return 0\nend)\n\nredis.register_function('glidemq_renewLock', function(keys, args)\n local lockKey = keys[1]\n local token = args[1]\n local ttl = tonumber(args[2]) or 1000\n local current = redis.call('GET', lockKey)\n if current == token then\n redis.call('PEXPIRE', lockKey, ttl)\n return 1\n end\n return 0\nend)\n\nredis.register_function('glidemq_complete', function(keys, args)\n local streamKey = keys[1]\n local completedKey = keys[2]\n local eventsKey = keys[3]\n local jobKey = keys[4]\n local metricsKey = keys[5]\n local jobId = args[1]\n assert(string.sub(jobKey, -(4 + #jobId)) == 'job:' .. jobId, 'unexpected key format: ' .. jobKey)\n local entryId = args[2]\n local returnvalue = args[3]\n local timestamp = tonumber(args[4])\n local group = args[5]\n local removeMode = args[6] or '0'\n local removeCount = tonumber(args[7]) or 0\n local removeAge = tonumber(args[8]) or 0\n local depsMember = args[9] or ''\n local parentId = args[10] or ''\n local broadcastMode = args[11] or '0'\n local processedOn = tonumber(redis.call('HGET', jobKey, 'processedOn')) or timestamp\n if entryId ~= '' then redis.call('XACK', streamKey, group, entryId) end\n if entryId ~= '' and broadcastMode ~= '1' then redis.call('XDEL', streamKey, entryId) end\n redis.call('ZADD', completedKey, timestamp, jobId)\n redis.call('HSET', jobKey,\n 'state', 'completed',\n 'returnvalue', returnvalue,\n 'finishedOn', tostring(timestamp)\n )\n markOrderingDone(jobKey, jobId)\n releaseGroupSlotAndPromote(jobKey, jobId, timestamp)\n emitEvent(eventsKey, 'completed', jobId, {'returnvalue', returnvalue})\n recordMetrics(metricsKey, timestamp, timestamp - processedOn)\n local prefix = string.sub(jobKey, 1, #jobKey - #('job:' .. jobId))\n if broadcastMode ~= '1' then\n if removeMode == 'true' then\n redis.call('ZREM', completedKey, jobId)\n redis.call('DEL', jobKey)\n elseif removeMode == 'count' and removeCount > 0 then\n local total = redis.call('ZCARD', completedKey)\n if total > removeCount then\n local excess = redis.call('ZRANGE', completedKey, 0, math.min(total - removeCount, 1000) - 1)\n if #excess > 0 then removeExcessJobs(completedKey, prefix, excess) end\n end\n elseif removeMode == 'age_count' then\n if removeAge > 0 then\n local cutoff = timestamp - (removeAge * 1000)\n local old = redis.call('ZRANGEBYSCORE', completedKey, '0', string.format('%.0f', cutoff), 'LIMIT', 0, 1000)\n if #old > 0 then removeExcessJobs(completedKey, prefix, old) end\n end\n if removeCount > 0 then\n local total = redis.call('ZCARD', completedKey)\n if total > removeCount then\n local excess = redis.call('ZRANGE', completedKey, 0, math.min(total - removeCount, 1000) - 1)\n if #excess > 0 then removeExcessJobs(completedKey, prefix, excess) end\n end\n end\n end\n end\n if depsMember ~= '' and parentId ~= '' and #keys >= 9 then\n local parentDepsKey = keys[6]\n local parentJobKey = keys[7]\n local parentStreamKey = keys[8]\n local parentEventsKey = keys[9]\n local depMarker = 'depdone:' .. depsMember\n if redis.call('HSETNX', parentJobKey, depMarker, '1') == 1 then\n local doneCount = redis.call('HINCRBY', parentJobKey, 'depsCompleted', 1)\n local totalDeps = redis.call('SCARD', parentDepsKey)\n local remaining = totalDeps - doneCount\n if remaining <= 0 then\n local parentState = redis.call('HGET', parentJobKey, 'state')\n if parentState == 'waiting-children' then\n redis.call('HSET', parentJobKey, 'state', 'waiting')\n xaddJob(parentStreamKey, parentId, redis.call('HGET', parentJobKey, 'name'))\n emitEvent(parentEventsKey, 'active', parentId, nil)\n end\n end\n end\n end\n -- DAG multi-parent: notify additional same-queue parents via parents SET\n local parentsKey = prefix .. 'parents:' .. jobId\n local dagParents = redis.call('SMEMBERS', parentsKey)\n if dagParents and #dagParents > 0 then\n local childQueuePrefix = string.sub(prefix, 1, #prefix - 1)\n local dagDepsMember = childQueuePrefix .. ':' .. jobId\n for pi = 1, #dagParents do\n local pEntry = dagParents[pi]\n -- Format: \"parentQueuePrefix:parentId\" where prefix is glide:{qname}\n -- Must find LAST colon (not first, since prefix contains colons in {})\n local pSep = pEntry:find(':([^:]+)$')\n if pSep then\n local pQueue = string.sub(pEntry, 1, pSep - 1)\n local pId = string.sub(pEntry, pSep + 1)\n -- Only handle same-queue parents atomically in Lua.\n -- Cross-queue parents are skipped here because their keys use a different\n -- hash tag (different prefix), which may route to a different cluster slot.\n -- The TypeScript layer handles cross-queue parent notification separately.\n if pQueue == childQueuePrefix then\n local pPrefix = prefix\n local pJobKey = pPrefix .. 'job:' .. pId\n local pDepsKey = pPrefix .. 'deps:' .. pId\n local pStreamKey = pPrefix .. 'stream'\n local pEventsKey = pPrefix .. 'events'\n local pDepMarker = 'depdone:' .. dagDepsMember\n if redis.call('HSETNX', pJobKey, pDepMarker, '1') == 1 then\n local pDoneCount = redis.call('HINCRBY', pJobKey, 'depsCompleted', 1)\n local pTotalDeps = redis.call('SCARD', pDepsKey)\n if pTotalDeps - pDoneCount <= 0 then\n local pState = redis.call('HGET', pJobKey, 'state')\n if pState == 'waiting-children' then\n redis.call('HSET', pJobKey, 'state', 'waiting')\n xaddJob(pStreamKey, pId, redis.call('HGET', pJobKey, 'name'))\n emitEvent(pEventsKey, 'active', pId, nil)\n end\n end\n end\n end\n end\n end\n end\n if entryId == '' then redis.call('DECR', prefix .. 'list-active') end\n return 1\nend)\n\nredis.register_function('glidemq_completeAndFetchNext', function(keys, args)\n local streamKey = keys[1]\n local completedKey = keys[2]\n local eventsKey = keys[3]\n local jobKey = keys[4]\n local metricsKey = keys[5]\n local jobId = args[1]\n local entryId = args[2]\n local returnvalue = args[3]\n local timestamp = tonumber(args[4])\n local group = args[5]\n local consumer = args[6]\n local removeMode = args[7] or '0'\n local removeCount = tonumber(args[8]) or 0\n local removeAge = tonumber(args[9]) or 0\n local depsMember = args[10] or ''\n local parentId = args[11] or ''\n local currentOrderingKey = args[12] or ''\n local currentOrderingSeq = args[13] or ''\n local currentGroupKey = args[14] or ''\n local broadcastMode = args[15] or '0'\n local hintProcessedOn = args[16] or ''\n local hasParents = args[17] or '0'\n local skipEvents = args[18] or '0'\n local skipMetrics = args[19] or '0'\n\n -- Phase 1: Complete current job (same as glidemq_complete)\n local processedOn\n if hintProcessedOn ~= '' then\n processedOn = tonumber(hintProcessedOn) or timestamp\n else\n processedOn = tonumber(redis.call('HGET', jobKey, 'processedOn')) or timestamp\n end\n if entryId ~= '' then redis.call('XACK', streamKey, group, entryId) end\n if entryId ~= '' and broadcastMode ~= '1' then redis.call('XDEL', streamKey, entryId) end\n redis.call('ZADD', completedKey, timestamp, jobId)\n redis.call('HSET', jobKey,\n 'state', 'completed',\n 'returnvalue', returnvalue,\n 'finishedOn', tostring(timestamp)\n )\n if currentOrderingKey ~= '__' then\n markOrderingDone(jobKey, jobId, currentOrderingKey, currentOrderingSeq)\n end\n if currentGroupKey ~= '__' then\n releaseGroupSlotAndPromote(jobKey, jobId, timestamp, currentGroupKey)\n end\n if skipEvents ~= '1' then emitEvent(eventsKey, 'completed', jobId, {'returnvalue', returnvalue}) end\n if skipMetrics ~= '1' then recordMetrics(metricsKey, timestamp, timestamp - processedOn) end\n local prefix = string.sub(jobKey, 1, #jobKey - #('job:' .. jobId))\n if entryId == '' then redis.call('DECR', prefix .. 'list-active') end\n\n -- Retention cleanup (skip in broadcast mode - job hash must persist for all subscriptions)\n if broadcastMode ~= '1' then\n if removeMode == 'true' then\n redis.call('ZREM', completedKey, jobId)\n redis.call('DEL', jobKey)\n elseif removeMode == 'count' and removeCount > 0 then\n local total = redis.call('ZCARD', completedKey)\n if total > removeCount then\n local excess = redis.call('ZRANGE', completedKey, 0, math.min(total - removeCount, 1000) - 1)\n if #excess > 0 then removeExcessJobs(completedKey, prefix, excess) end\n end\n elseif removeMode == 'age_count' then\n if removeAge > 0 then\n local cutoff = timestamp - (removeAge * 1000)\n local old = redis.call('ZRANGEBYSCORE', completedKey, '0', string.format('%.0f', cutoff), 'LIMIT', 0, 1000)\n if #old > 0 then removeExcessJobs(completedKey, prefix, old) end\n end\n if removeCount > 0 then\n local total = redis.call('ZCARD', completedKey)\n if total > removeCount then\n local excess = redis.call('ZRANGE', completedKey, 0, math.min(total - removeCount, 1000) - 1)\n if #excess > 0 then removeExcessJobs(completedKey, prefix, excess) end\n end\n end\n end\n end\n\n -- Parent deps\n if depsMember ~= '' and parentId ~= '' and #keys >= 9 then\n local parentDepsKey = keys[6]\n local parentJobKey = keys[7]\n local parentStreamKey = keys[8]\n local parentEventsKey = keys[9]\n local depMarker = 'depdone:' .. depsMember\n if redis.call('HSETNX', parentJobKey, depMarker, '1') == 1 then\n local doneCount = redis.call('HINCRBY', parentJobKey, 'depsCompleted', 1)\n local totalDeps = redis.call('SCARD', parentDepsKey)\n if totalDeps - doneCount <= 0 then\n local parentState = redis.call('HGET', parentJobKey, 'state')\n if parentState == 'waiting-children' then\n redis.call('HSET', parentJobKey, 'state', 'waiting')\n xaddJob(parentStreamKey, parentId, redis.call('HGET', parentJobKey, 'name'))\n emitEvent(parentEventsKey, 'active', parentId, nil)\n end\n end\n end\n end\n -- DAG multi-parent: skip SMEMBERS when caller confirms no parents\n if hasParents ~= '0' then\n local parentsKey = prefix .. 'parents:' .. jobId\n local dagParents = redis.call('SMEMBERS', parentsKey)\n if dagParents and #dagParents > 0 then\n local childQueuePrefix = string.sub(prefix, 1, #prefix - 1)\n local dagDepsMember = childQueuePrefix .. ':' .. jobId\n for pi = 1, #dagParents do\n local pEntry = dagParents[pi]\n local pSep = pEntry:find(':([^:]+)$')\n if pSep then\n local pQueue = string.sub(pEntry, 1, pSep - 1)\n local pId = string.sub(pEntry, pSep + 1)\n if pQueue == childQueuePrefix then\n local pPrefix = prefix\n local pJobKey = pPrefix .. 'job:' .. pId\n local pDepsKey = pPrefix .. 'deps:' .. pId\n local pStreamKey = pPrefix .. 'stream'\n local pEventsKey = pPrefix .. 'events'\n local pDepMarker = 'depdone:' .. dagDepsMember\n if redis.call('HSETNX', pJobKey, pDepMarker, '1') == 1 then\n local pDoneCount = redis.call('HINCRBY', pJobKey, 'depsCompleted', 1)\n local pTotalDeps = redis.call('SCARD', pDepsKey)\n if pTotalDeps - pDoneCount <= 0 then\n local pState = redis.call('HGET', pJobKey, 'state')\n if pState == 'waiting-children' then\n redis.call('HSET', pJobKey, 'state', 'waiting')\n xaddJob(pStreamKey, pId, redis.call('HGET', pJobKey, 'name'))\n emitEvent(pEventsKey, 'active', pId, nil)\n end\n end\n end\n end\n end\n end\n end\n end\n\n -- In broadcast mode: do not fetch next (avoids XDEL of next entry which would break other consumer groups)\n if broadcastMode == '1' then\n return {'NEXT_NONE', jobId}\n end\n\n -- Return protocol (array-based to avoid cjson encode/decode per job):\n -- {'NEXT_NONE', completedJobId}\n -- {'NEXT_REVOKED', completedJobId, nextJobId, nextEntryId}\n -- {'NEXT_HASH', completedJobId, nextJobId, nextEntryId, field1, value1, field2, value2, ...}\n\n -- Phase 1.0: Try priority list first (highest priority: priority > LIFO > FIFO)\n local priorityKey = prefix .. 'priority'\n for _priAttempt = 1, 3 do\n local priJobId = redis.call('RPOP', priorityKey)\n if not priJobId then\n break -- priority list is empty\n end\n\n local priJobKey = prefix .. 'job:' .. priJobId\n -- Single HMGET replaces EXISTS + HGET 'revoked' + checkExpired HGET 'expireAt' (3 \u2192 1)\n local priMeta = redis.call('HMGET', priJobKey, 'state', 'revoked', 'expireAt')\n if priMeta[1] then\n if priMeta[2] ~= '1' then\n local priExpireAt = tonumber(priMeta[3])\n if not priExpireAt or priExpireAt <= 0 or timestamp <= priExpireAt then\n -- Check ordering/group gates for priority-list jobs\n local priGroupKey = redis.call('HGET', priJobKey, 'groupKey')\n if priGroupKey and priGroupKey ~= '' then\n local priGroupHashKey = prefix .. 'group:' .. priGroupKey\n local priGrpFields = redis.call('HGETALL', priGroupHashKey)\n local priGrp = {}\n for pf = 1, #priGrpFields, 2 do priGrp[priGrpFields[pf]] = priGrpFields[pf + 1] end\n local priOrdSeq = tonumber(redis.call('HGET', priJobKey, 'orderingSeq')) or 0\n local priNextSeq = tonumber(priGrp.nextSeq) or 0\n local priMaxConc = tonumber(priGrp.maxConcurrency) or 0\n local priActive = tonumber(priGrp.active) or 0\n local priWaitListKey = prefix .. 'groupq:' .. priGroupKey\n local priReturning = (priOrdSeq > 0 and priNextSeq > 0 and priOrdSeq < priNextSeq)\n -- Ordering gate or concurrency gate (skip for returning step-jobs)\n if (priOrdSeq > 0 and priNextSeq > 0 and priOrdSeq > priNextSeq) or\n (priMaxConc > 0 and priActive >= priMaxConc and not priReturning) then\n local priScore = priOrdSeq > 0 and priOrdSeq or tonumber(priJobId) or 0\n redis.call('ZADD', priWaitListKey, priScore, priJobId)\n redis.call('HSET', priJobKey, 'state', 'group-waiting')\n else\n -- All gates pass: activate with group bookkeeping\n redis.call('HSET', priJobKey, 'state', 'active', 'processedOn', tostring(timestamp), 'lastActive', tostring(timestamp))\n if not priReturning then\n redis.call('HINCRBY', priGroupHashKey, 'active', 1)\n end\n if priOrdSeq > 0 and not priReturning then\n redis.call('HSET', priGroupHashKey, 'nextSeq', tostring(priOrdSeq + 1))\n redis.call('ZREM', priWaitListKey, priJobId)\n end\n if skipEvents ~= '1' then emitEvent(eventsKey, 'active', priJobId, nil) end\n local priJobFields = redis.call('HGETALL', priJobKey)\n redis.call('INCR', prefix .. 'list-active')\n return {'NEXT_HASH', jobId, priJobId, '', unpack(priJobFields)}\n end\n else\n -- Non-group job: activate directly\n redis.call('HSET', priJobKey, 'state', 'active', 'processedOn', tostring(timestamp), 'lastActive', tostring(timestamp))\n if skipEvents ~= '1' then emitEvent(eventsKey, 'active', priJobId, nil) end\n local priJobFields = redis.call('HGETALL', priJobKey)\n redis.call('INCR', prefix .. 'list-active')\n return {'NEXT_HASH', jobId, priJobId, '', unpack(priJobFields)}\n end\n else\n expireJob(priJobKey, priJobId, prefix, timestamp, priMeta[1], nil, nil, nil)\n end\n end\n end\n end\n\n -- Phase 1.5: Try LIFO list (before stream), retry up to 3 times\n local lifoKey = prefix .. 'lifo'\n for _lifoAttempt = 1, 3 do\n local lifoJobId = redis.call('RPOP', lifoKey)\n if not lifoJobId then\n break -- LIFO list is empty\n end\n\n local lifoJobKey = prefix .. 'job:' .. lifoJobId\n -- Single HMGET replaces EXISTS + HGET 'revoked' + checkExpired HGET 'expireAt' (3 \u2192 1)\n local lifoMeta = redis.call('HMGET', lifoJobKey, 'state', 'revoked', 'expireAt')\n if lifoMeta[1] then\n if lifoMeta[2] ~= '1' then\n local lifoExpireAt = tonumber(lifoMeta[3])\n if not lifoExpireAt or lifoExpireAt <= 0 or timestamp <= lifoExpireAt then\n redis.call('HSET', lifoJobKey, 'state', 'active', 'processedOn', tostring(timestamp), 'lastActive', tostring(timestamp))\n if skipEvents ~= '1' then emitEvent(eventsKey, 'active', lifoJobId, nil) end\n local lifoJobFields = redis.call('HGETALL', lifoJobKey)\n redis.call('INCR', prefix .. 'list-active')\n return {'NEXT_HASH', jobId, lifoJobId, '', unpack(lifoJobFields)}\n else\n expireJob(lifoJobKey, lifoJobId, prefix, timestamp, lifoMeta[1], nil, nil, nil)\n end\n end\n end\n end\n\n -- Phase 2: Fetch next job (non-blocking XREADGROUP), skip expired (up to 3 attempts)\n local nextJobId, nextEntryId, nextJobKey\n local nextGroupKey = nil\n for _fetchAttempt = 1, 3 do\n local nextEntries = redis.call('XREADGROUP', 'GROUP', group, consumer, 'COUNT', 1, 'STREAMS', streamKey, '>')\n if not nextEntries or #nextEntries == 0 then\n return {'NEXT_NONE', jobId}\n end\n local streamData = nextEntries[1]\n local entries = streamData[2]\n if not entries or #entries == 0 then\n return {'NEXT_NONE', jobId}\n end\n local nextEntry = entries[1]\n nextEntryId = nextEntry[1]\n local nextFields = nextEntry[2]\n nextJobId = nil\n for i = 1, #nextFields, 2 do\n if nextFields[i] == 'jobId' then\n nextJobId = nextFields[i + 1]\n break\n end\n end\n if not nextJobId then\n return {'NEXT_NONE', jobId}\n end\n nextJobKey = prefix .. 'job:' .. nextJobId\n -- Single HMGET replaces EXISTS + HGET 'revoked' + checkExpired HGET 'expireAt' + HGET 'groupKey' (4 \u2192 1)\n local nextMeta = redis.call('HMGET', nextJobKey, 'state', 'revoked', 'expireAt', 'groupKey')\n if not nextMeta[1] then\n -- state is nil: job hash does not exist\n return {'NEXT_NONE', jobId}\n end\n if nextMeta[2] == '1' then\n return {'NEXT_REVOKED', jobId, nextJobId, nextEntryId}\n end\n -- Inline expiry check (avoids checkExpired's redundant HGET)\n local nextExpireAt = tonumber(nextMeta[3])\n if nextExpireAt and nextExpireAt > 0 and timestamp > nextExpireAt then\n local curState = nextMeta[1]\n expireJob(nextJobKey, nextJobId, prefix, timestamp, curState, nil, nil, nil)\n redis.call('XACK', streamKey, group, nextEntryId)\n redis.call('XDEL', streamKey, nextEntryId)\n nextJobId = nil\n else\n nextGroupKey = nextMeta[4]\n break\n end\n end\n if not nextJobId then\n return {'NEXT_NONE', jobId}\n end\n\n -- Phase 3: Activate next job (same as moveToActive)\n if nextGroupKey and nextGroupKey ~= '' then\n local nextGroupHashKey = prefix .. 'group:' .. nextGroupKey\n -- Load all group fields in one call\n local nGrpFields = redis.call('HGETALL', nextGroupHashKey)\n local nGrp = {}\n for nf = 1, #nGrpFields, 2 do nGrp[nGrpFields[nf]] = nGrpFields[nf + 1] end\n local nextMaxConc = tonumber(nGrp.maxConcurrency) or 0\n local nextActive = tonumber(nGrp.active) or 0\n local nextWaitListKey = prefix .. 'groupq:' .. nextGroupKey\n -- Ordering gate\n local nextJobOrderingSeq = tonumber(redis.call('HGET', nextJobKey, 'orderingSeq')) or 0\n local nextReturning = false\n if nextJobOrderingSeq > 0 then\n local nextExpectedSeq = tonumber(nGrp.nextSeq) or 0\n if nextExpectedSeq > 0 and nextJobOrderingSeq > nextExpectedSeq then\n redis.call('XACK', streamKey, group, nextEntryId)\n redis.call('XDEL', streamKey, nextEntryId)\n redis.call('ZADD', nextWaitListKey, nextJobOrderingSeq, nextJobId)\n redis.call('HSET', nextJobKey, 'state', 'group-waiting')\n return {'NEXT_NONE', jobId}\n end\n nextReturning = (nextExpectedSeq > 0 and nextJobOrderingSeq < nextExpectedSeq)\n end\n -- Concurrency gate (skip for returning step-jobs)\n if nextMaxConc > 0 and nextActive >= nextMaxConc and not nextReturning then\n redis.call('XACK', streamKey, group, nextEntryId)\n redis.call('XDEL', streamKey, nextEntryId)\n redis.call('ZADD', nextWaitListKey, groupqScore(nextJobKey, nextJobId), nextJobId)\n redis.call('HSET', nextJobKey, 'state', 'group-waiting')\n return {'NEXT_NONE', jobId}\n end\n -- Token bucket gate (read-only)\n local nextTbCapacity = tonumber(nGrp.tbCapacity) or 0\n local nextTbBlocked = false\n local nextTbDelay = 0\n local nextTbTokens = 0\n local nextJobCostVal = 0\n if nextTbCapacity > 0 then\n nextTbTokens = tbRefill(nextGroupHashKey, nGrp, tonumber(timestamp))\n nextJobCostVal = tonumber(redis.call('HGET', nextJobKey, 'cost')) or 1000\n -- DLQ guard: cost > capacity\n if nextJobCostVal > nextTbCapacity then\n local nextProcessedOn = tonumber(redis.call('HGET', nextJobKey, 'processedOn')) or tonumber(timestamp)\n redis.call('XACK', streamKey, group, nextEntryId)\n redis.call('XDEL', streamKey, nextEntryId)\n redis.call('ZADD', prefix .. 'failed', tonumber(timestamp), nextJobId)\n redis.call('HSET', nextJobKey,\n 'state', 'failed',\n 'failedReason', 'cost exceeds token bucket capacity',\n 'finishedOn', tostring(timestamp))\n if skipEvents ~= '1' then emitEvent(prefix .. 'events', 'failed', nextJobId, {'failedReason', 'cost exceeds token bucket capacity'}) end\n if skipMetrics ~= '1' then recordMetrics(metricsKey, tonumber(timestamp), tonumber(timestamp) - nextProcessedOn) end\n return {'NEXT_NONE', jobId}\n end\n if nextTbTokens < nextJobCostVal then\n nextTbBlocked = true\n local nextTbRefillRateVal = math.max(tonumber(nGrp.tbRefillRate) or 0, 1)\n nextTbDelay = math.ceil((nextJobCostVal - nextTbTokens) * 1000 / nextTbRefillRateVal)\n end\n end\n -- Sliding window gate (read-only)\n local nextRateMax = tonumber(nGrp.rateMax) or 0\n local nextRlBlocked = false\n local nextRlDelay = 0\n if nextRateMax > 0 then\n local nextRateDuration = tonumber(nGrp.rateDuration) or 0\n local nextRateWindowStart = tonumber(nGrp.rateWindowStart) or 0\n local nextRateCount = tonumber(nGrp.rateCount) or 0\n if nextRateDuration > 0 and timestamp - nextRateWindowStart < nextRateDuration and nextRateCount >= nextRateMax then\n nextRlBlocked = true\n nextRlDelay = (nextRateWindowStart + nextRateDuration) - timestamp\n end\n end\n -- If ANY gate blocked: park + register\n if nextTbBlocked or nextRlBlocked then\n redis.call('XACK', streamKey, group, nextEntryId)\n redis.call('XDEL', streamKey, nextEntryId)\n local nextWaitListKey = prefix .. 'groupq:' .. nextGroupKey\n redis.call('ZADD', nextWaitListKey, groupqScore(nextJobKey, nextJobId), nextJobId)\n redis.call('HSET', nextJobKey, 'state', 'group-waiting')\n local nextMaxDelay = math.max(nextTbDelay, nextRlDelay)\n local rateLimitedKey = prefix .. 'ratelimited'\n redis.call('ZADD', rateLimitedKey, tonumber(timestamp) + nextMaxDelay, nextGroupKey)\n return {'NEXT_NONE', jobId}\n end\n -- All gates passed: mutate state\n if nextTbCapacity > 0 then\n redis.call('HINCRBY', nextGroupHashKey, 'tbTokens', -nextJobCostVal)\n end\n if nextRateMax > 0 then\n local nextRateDuration = tonumber(nGrp.rateDuration) or 0\n if nextRateDuration > 0 then\n local nextRateWindowStart = tonumber(nGrp.rateWindowStart) or 0\n if timestamp - nextRateWindowStart >= nextRateDuration then\n redis.call('HSET', nextGroupHashKey, 'rateWindowStart', tostring(timestamp), 'rateCount', '1')\n else\n redis.call('HINCRBY', nextGroupHashKey, 'rateCount', 1)\n end\n end\n end\n if not nextReturning then\n redis.call('HINCRBY', nextGroupHashKey, 'active', 1)\n end\n if nextJobOrderingSeq > 0 and not nextReturning then\n redis.call('HSET', nextGroupHashKey, 'nextSeq', tostring(nextJobOrderingSeq + 1))\n redis.call('ZREM', nextWaitListKey, nextJobId)\n end\n end\n redis.call('HSET', nextJobKey, 'state', 'active', 'processedOn', tostring(timestamp), 'lastActive', tostring(timestamp))\n local nextHash = redis.call('HGETALL', nextJobKey)\n local out = {'NEXT_HASH', jobId, nextJobId, nextEntryId}\n for i = 1, #nextHash do\n out[#out + 1] = nextHash[i]\n end\n return out\nend)\n\nredis.register_function('glidemq_fail', function(keys, args)\n local streamKey = keys[1]\n local failedKey = keys[2]\n local scheduledKey = keys[3]\n local eventsKey = keys[4]\n local jobKey = keys[5]\n local metricsKey = keys[6]\n local jobId = args[1]\n assert(string.sub(jobKey, -(4 + #jobId)) == 'job:' .. jobId, 'unexpected key format: ' .. jobKey)\n local entryId = args[2]\n local failedReason = args[3]\n local timestamp = tonumber(args[4])\n local maxAttempts = tonumber(args[5]) or 0\n local backoffDelay = tonumber(args[6]) or 0\n local group = args[7]\n local removeMode = args[8] or '0'\n local removeCount = tonumber(args[9]) or 0\n local removeAge = tonumber(args[10]) or 0\n local broadcastMode = args[11] or '0'\n local processedOn = tonumber(redis.call('HGET', jobKey, 'processedOn')) or timestamp\n if entryId ~= '' then redis.call('XACK', streamKey, group, entryId) end\n if entryId ~= '' and broadcastMode ~= '1' then redis.call('XDEL', streamKey, entryId) end\n local attemptsMade\n if broadcastMode == '1' then\n local subKey = jobKey .. ':sub:' .. group\n attemptsMade = redis.call('HINCRBY', subKey, 'a', 1)\n redis.call('EXPIRE', subKey, 86400)\n else\n attemptsMade = redis.call('HINCRBY', jobKey, 'attemptsMade', 1)\n end\n if maxAttempts > 0 and attemptsMade < maxAttempts then\n -- Advance fallback chain if the job has fallbacks configured\n local optsRaw = redis.call('HGET', jobKey, 'opts')\n if optsRaw and string.find(optsRaw, '\"fallbacks\"') then\n local fbIdx = tonumber(redis.call('HGET', jobKey, 'fallbackIndex') or '0') or 0\n redis.call('HSET', jobKey, 'fallbackIndex', tostring(fbIdx + 1))\n end\n local retryAt = timestamp + backoffDelay\n local priority = tonumber(redis.call('HGET', jobKey, 'priority')) or 0\n local score = priority * PRIORITY_SHIFT + retryAt\n redis.call('ZADD', scheduledKey, score, jobId)\n redis.call('HSET', jobKey,\n 'state', 'delayed',\n 'failedReason', failedReason,\n 'processedOn', tostring(timestamp)\n )\n -- Only release group slot if not an ordering-key job (ordering jobs hold the slot through retries)\n local failOrdSeq = tonumber(redis.call('HGET', jobKey, 'orderingSeq')) or 0\n if failOrdSeq <= 0 then\n releaseGroupSlotAndPromote(jobKey, jobId, timestamp)\n end\n emitEvent(eventsKey, 'retrying', jobId, {\n 'failedReason', failedReason,\n 'attemptsMade', tostring(attemptsMade),\n 'delay', tostring(backoffDelay)\n })\n if entryId == '' then redis.call('DECR', string.sub(jobKey, 1, #jobKey - #('job:' .. jobId)) .. 'list-active') end\n return 'retrying'\n else\n redis.call('ZADD', failedKey, timestamp, jobId)\n redis.call('HSET', jobKey,\n 'state', 'failed',\n 'failedReason', failedReason,\n 'finishedOn', tostring(timestamp),\n 'processedOn', tostring(timestamp)\n )\n markOrderingDone(jobKey, jobId)\n releaseGroupSlotAndPromote(jobKey, jobId, timestamp)\n emitEvent(eventsKey, 'failed', jobId, {'failedReason', failedReason})\n recordMetrics(metricsKey, timestamp, timestamp - processedOn)\n local prefix = string.sub(jobKey, 1, #jobKey - #('job:' .. jobId))\n -- In broadcast mode, skip job hash deletion: the job must persist for all subscriptions\n if broadcastMode ~= '1' then\n if removeMode == 'true' then\n redis.call('ZREM', failedKey, jobId)\n redis.call('DEL', jobKey)\n elseif removeMode == 'count' and removeCount > 0 then\n local total = redis.call('ZCARD', failedKey)\n if total > removeCount then\n local excess = redis.call('ZRANGE', failedKey, 0, math.min(total - removeCount, 1000) - 1)\n if #excess > 0 then removeExcessJobs(failedKey, prefix, excess) end\n end\n elseif removeMode == 'age_count' then\n if removeAge > 0 then\n local cutoff = timestamp - (removeAge * 1000)\n local old = redis.call('ZRANGEBYSCORE', failedKey, '0', string.format('%.0f', cutoff), 'LIMIT', 0, 1000)\n if #old > 0 then removeExcessJobs(failedKey, prefix, old) end\n end\n if removeCount > 0 then\n local total = redis.call('ZCARD', failedKey)\n if total > removeCount then\n local excess = redis.call('ZRANGE', failedKey, 0, math.min(total - removeCount, 1000) - 1)\n if #excess > 0 then removeExcessJobs(failedKey, prefix, excess) end\n end\n end\n end\n end\n if entryId == '' then redis.call('DECR', prefix .. 'list-active') end\n return 'failed'\n end\nend)\n\nredis.register_function('glidemq_reclaimStalled', function(keys, args)\n local streamKey = keys[1]\n local eventsKey = keys[2]\n local group = args[1]\n local consumer = args[2]\n local minIdleMs = tonumber(args[3])\n local maxStalledCount = tonumber(args[4]) or 1\n local timestamp = tonumber(args[5])\n local failedKey = args[6]\n local broadcastMode = args[7] or '0'\n local result = redis.call('XAUTOCLAIM', streamKey, group, consumer, minIdleMs, '0-0')\n local entries = result[2]\n if not entries or #entries == 0 then\n return 0\n end\n local prefix = string.sub(streamKey, 1, #streamKey - 6)\n local count = 0\n for i = 1, #entries do\n local entry = entries[i]\n local entryId = entry[1]\n local fields = entry[2]\n local jobId = nil\n if type(fields) == 'table' then\n for j = 1, #fields, 2 do\n if fields[j] == 'jobId' then\n jobId = fields[j + 1]\n break\n end\n end\n end\n if jobId then\n local jobKey = prefix .. 'job:' .. jobId\n if checkExpired(jobKey, jobId, prefix, timestamp) then\n if entryId ~= '' then redis.call('XACK', streamKey, group, entryId) end\n if entryId ~= '' and broadcastMode ~= '1' then redis.call('XDEL', streamKey, entryId) end\n count = count + 1\n else\n local vals = redis.call('HMGET', jobKey, 'lastActive', 'opts')\n local lastActive = tonumber(vals[1])\n local jobLockDuration = extractLockDurationFromOpts(vals[2])\n local effectiveIdle = (jobLockDuration > 0) and jobLockDuration or minIdleMs\n if lastActive and (timestamp - lastActive) < effectiveIdle then\n count = count + 1\n else\n local failed = applyStalledLogic(jobKey, jobId, prefix, eventsKey, failedKey, maxStalledCount, timestamp)\n if failed then\n if entryId ~= '' then redis.call('XACK', streamKey, group, entryId) end\n if entryId ~= '' and broadcastMode ~= '1' then redis.call('XDEL', streamKey, entryId) end\n else\n redis.call('HSET', jobKey, 'state', 'active')\n end\n count = count + 1\n end\n end\n end\n end\n return count\nend)\n\n-- Reclaim stalled list-sourced jobs (LIFO/priority) that are invisible to XAUTOCLAIM.\n-- Uses bounded SCAN to find active list jobs with stale lastActive, then applies stall logic.\n-- KEYS: [streamKey, eventsKey]\n-- ARGS: [minIdleMs, maxStalledCount, timestamp, failedKey]\nredis.register_function('glidemq_reclaimStalledListJobs', function(keys, args)\n local streamKey = keys[1]\n local eventsKey = keys[2]\n local minIdleMs = tonumber(args[1])\n local maxStalledCount = tonumber(args[2]) or 1\n local timestamp = tonumber(args[3])\n if not minIdleMs or not timestamp then return 0 end\n local failedKey = args[4]\n local prefix = string.sub(streamKey, 1, #streamKey - 6)\n local listActiveKey = prefix .. 'list-active'\n local currentActive = tonumber(redis.call('GET', listActiveKey)) or 0\n if currentActive <= 0 then return 0 end\n local pattern = prefix .. 'job:*'\n local cursor = '0'\n local maxIter = 1000\n local iter = 0\n local count = 0\n repeat\n iter = iter + 1\n local sr = redis.call('SCAN', cursor, 'MATCH', pattern, 'COUNT', 500)\n cursor = sr[1]\n local scannedKeys = sr[2]\n for i = 1, #scannedKeys do\n local jk = scannedKeys[i]\n local state = redis.call('HGET', jk, 'state')\n if state == 'active' then\n local vals = redis.call('HMGET', jk, 'lastActive', 'lifo', 'priority', 'opts')\n local lastActive = tonumber(vals[1])\n local isListSourced = vals[2] == '1' or (tonumber(vals[3]) or 0) > 0\n local jobLockDuration = extractLockDurationFromOpts(vals[4])\n local effectiveIdle = (jobLockDuration > 0) and jobLockDuration or minIdleMs\n if isListSourced and lastActive and (timestamp - lastActive) >= effectiveIdle then\n local jobId = string.sub(jk, #prefix + 5)\n local shouldDecr = false\n if checkExpired(jk, jobId, prefix, timestamp) then\n shouldDecr = true\n else\n if applyStalledLogic(jk, jobId, prefix, eventsKey, failedKey, maxStalledCount, timestamp) then\n shouldDecr = true\n end\n end\n if shouldDecr then\n local la = tonumber(redis.call('GET', listActiveKey)) or 0\n if la > 0 then redis.call('DECR', listActiveKey) end\n end\n count = count + 1\n end\n end\n end\n until cursor == '0' or iter >= maxIter\n return count\nend)\n\nredis.register_function('glidemq_pause', function(keys, args)\n local metaKey = keys[1]\n local eventsKey = keys[2]\n redis.call('HSET', metaKey, 'paused', '1')\n emitEvent(eventsKey, 'paused', '0', nil)\n return 1\nend)\n\nredis.register_function('glidemq_resume', function(keys, args)\n local metaKey = keys[1]\n local eventsKey = keys[2]\n redis.call('HSET', metaKey, 'paused', '0')\n emitEvent(eventsKey, 'resumed', '0', nil)\n return 1\nend)\n\nredis.register_function('glidemq_dedup', function(keys, args)\n local dedupKey = keys[1]\n local idKey = keys[2]\n local streamKey = keys[3]\n local scheduledKey = keys[4]\n local eventsKey = keys[5]\n local dedupId = args[1]\n local ttlMs = tonumber(args[2]) or 0\n local mode = args[3]\n local jobName = args[4]\n local jobData = args[5]\n local jobOpts = args[6]\n local timestamp = tonumber(args[7])\n local delay = tonumber(args[8]) or 0\n local priority = tonumber(args[9]) or 0\n local parentId = args[10] or ''\n local maxAttempts = tonumber(args[11]) or 0\n local orderingKey = args[12] or ''\n local groupConcurrency = tonumber(args[13]) or 0\n local lifo = tonumber(args[21]) or 0\n local groupRateMax = tonumber(args[14]) or 0\n local groupRateDuration = tonumber(args[15]) or 0\n local tbCapacity = tonumber(args[16]) or 0\n local tbRefillRate = tonumber(args[17]) or 0\n local jobCost = tonumber(args[18]) or 0\n local ttl = tonumber(args[19]) or 0\n local customJobId = args[20] or ''\n local parentQueue = args[22] or ''\n local skipEvents = args[23] or '0'\n local prefix = string.sub(idKey, 1, #idKey - 2)\n local existing = redis.call('HGET', dedupKey, dedupId)\n if mode == 'simple' then\n if existing then\n local sep = string.find(existing, ':')\n if sep then\n local existingJobId = string.sub(existing, 1, sep - 1)\n local jobKey = prefix .. 'job:' .. existingJobId\n local state = redis.call('HGET', jobKey, 'state')\n if state and state ~= 'completed' and state ~= 'failed' then\n return 'skipped'\n end\n end\n end\n elseif mode == 'throttle' then\n if existing and ttlMs > 0 then\n local sep = string.find(existing, ':')\n if sep then\n local storedTs = tonumber(string.sub(existing, sep + 1))\n if storedTs and (timestamp - storedTs) < ttlMs then\n return 'skipped'\n end\n end\n end\n elseif mode == 'debounce' then\n if existing then\n local sep = string.find(existing, ':')\n if sep then\n local existingJobId = string.sub(existing, 1, sep - 1)\n local jobKey = prefix .. 'job:' .. existingJobId\n local state = redis.call('HGET', jobKey, 'state')\n if state == 'delayed' or state == 'prioritized' then\n redis.call('ZREM', scheduledKey, existingJobId)\n markOrderingDone(jobKey, existingJobId)\n redis.call('DEL', jobKey)\n if skipEvents ~= '1' then emitEvent(eventsKey, 'removed', existingJobId, nil) end\n elseif state and state ~= 'completed' and state ~= 'failed' then\n return 'skipped'\n end\n end\n end\n end\n local jobIdStr\n local jobKey\n if customJobId ~= '' then\n jobKey = prefix .. 'job:' .. customJobId\n if redis.call('EXISTS', jobKey) == 1 then\n return 'duplicate'\n end\n jobIdStr = customJobId\n advanceIdCounter(idKey, customJobId)\n else\n local jobId = redis.call('INCR', idKey)\n jobIdStr = tostring(jobId)\n jobKey = prefix .. 'job:' .. jobIdStr\n end\n local useGroupConcurrency = (orderingKey ~= '')\n local orderingSeq = 0\n if orderingKey ~= '' then\n local orderingMetaKey = prefix .. 'ordering'\n orderingSeq = redis.call('HINCRBY', orderingMetaKey, orderingKey, 1)\n end\n if useGroupConcurrency then\n if groupConcurrency < 1 then groupConcurrency = 1 end\n local groupHashKey = prefix .. 'group:' .. orderingKey\n local curMax = tonumber(redis.call('HGET', groupHashKey, 'maxConcurrency')) or 0\n if curMax ~= groupConcurrency then\n redis.call('HSET', groupHashKey, 'maxConcurrency', tostring(groupConcurrency))\n end\n -- Initialize nextSeq for ordered promotion\n if orderingSeq > 0 and redis.call('HEXISTS', groupHashKey, 'nextSeq') == 0 then\n redis.call('HSET', groupHashKey, 'nextSeq', '1')\n end\n if groupRateMax > 0 then\n local curRateMax = tonumber(redis.call('HGET', groupHashKey, 'rateMax')) or 0\n if curRateMax ~= groupRateMax then\n redis.call('HSET', groupHashKey, 'rateMax', tostring(groupRateMax))\n end\n local curRateDuration = tonumber(redis.call('HGET', groupHashKey, 'rateDuration')) or 0\n if curRateDuration ~= groupRateDuration then\n redis.call('HSET', groupHashKey, 'rateDuration', tostring(groupRateDuration))\n end\n else\n local oldRateMax = tonumber(redis.call('HGET', groupHashKey, 'rateMax')) or 0\n if oldRateMax > 0 then\n redis.call('HDEL', groupHashKey, 'rateMax', 'rateDuration', 'rateWindowStart', 'rateCount')\n end\n end\n -- Upsert token bucket fields on group hash\n if tbCapacity > 0 then\n local curTbCap = tonumber(redis.call('HGET', groupHashKey, 'tbCapacity')) or 0\n if curTbCap ~= tbCapacity then\n redis.call('HSET', groupHashKey, 'tbCapacity', tostring(tbCapacity))\n end\n local curTbRate = tonumber(redis.call('HGET', groupHashKey, 'tbRefillRate')) or 0\n if curTbRate ~= tbRefillRate then\n redis.call('HSET', groupHashKey, 'tbRefillRate', tostring(tbRefillRate))\n end\n -- Initialize tokens on first setup\n if curTbCap == 0 then\n redis.call('HSET', groupHashKey,\n 'tbTokens', tostring(tbCapacity),\n 'tbLastRefill', tostring(timestamp),\n 'tbRefillRemainder', '0')\n end\n -- Validate cost (explicit or default 1000 millitokens) against capacity\n local effectiveCost = (jobCost > 0) and jobCost or 1000\n if effectiveCost > tbCapacity then\n return 'ERR:COST_EXCEEDS_CAPACITY'\n end\n else\n -- Clear stale tb fields\n local oldTbCap = tonumber(redis.call('HGET', groupHashKey, 'tbCapacity')) or 0\n if oldTbCap > 0 then\n redis.call('HDEL', groupHashKey, 'tbCapacity', 'tbRefillRate', 'tbTokens', 'tbLastRefill', 'tbRefillRemainder')\n end\n end\n end\n local hashFields = {\n 'id', jobIdStr,\n 'name', jobName,\n 'data', jobData,\n 'opts', jobOpts,\n 'timestamp', tostring(timestamp),\n 'attemptsMade', '0',\n 'delay', tostring(delay),\n 'priority', tostring(priority),\n 'maxAttempts', tostring(maxAttempts)\n }\n if useGroupConcurrency then\n hashFields[#hashFields + 1] = 'groupKey'\n hashFields[#hashFields + 1] = orderingKey\n if orderingSeq > 0 then\n hashFields[#hashFields + 1] = 'orderingSeq'\n hashFields[#hashFields + 1] = tostring(orderingSeq)\n end\n end\n if jobCost > 0 then\n hashFields[#hashFields + 1] = 'cost'\n hashFields[#hashFields + 1] = tostring(jobCost)\n end\n if ttl > 0 then\n hashFields[#hashFields + 1] = 'expireAt'\n hashFields[#hashFields + 1] = tostring(timestamp + ttl)\n end\n if parentId ~= '' then\n hashFields[#hashFields + 1] = 'parentId'\n hashFields[#hashFields + 1] = parentId\n if parentQueue ~= '' then\n hashFields[#hashFields + 1] = 'parentQueue'\n hashFields[#hashFields + 1] = parentQueue\n end\n end\n if lifo > 0 then\n hashFields[#hashFields + 1] = 'lifo'\n hashFields[#hashFields + 1] = '1'\n end\n if delay > 0 or priority > 0 then\n hashFields[#hashFields + 1] = 'state'\n hashFields[#hashFields + 1] = delay > 0 and 'delayed' or 'prioritized'\n else\n hashFields[#hashFields + 1] = 'state'\n hashFields[#hashFields + 1] = 'waiting'\n end\n redis.call('HSET', jobKey, unpack(hashFields))\n -- Register child in parent's deps set when parentDepsKey is provided (keys[6])\n if parentId ~= '' and parentQueue ~= '' and #keys >= 6 then\n local parentDepsKey = keys[6]\n local queuePrefix = string.sub(prefix, 1, #prefix - 1)\n local depsMember = queuePrefix .. ':' .. jobIdStr\n redis.call('SADD', parentDepsKey, depsMember)\n end\n if delay > 0 then\n local score = priority * PRIORITY_SHIFT + (timestamp + delay)\n redis.call('ZADD', scheduledKey, score, jobIdStr)\n elseif priority > 0 then\n local score = priority * PRIORITY_SHIFT\n redis.call('ZADD', scheduledKey, score, jobIdStr)\n elseif lifo > 0 then\n local lifoKey = prefix .. 'lifo'\n redis.call('RPUSH', lifoKey, jobIdStr)\n else\n xaddJob(streamKey, jobIdStr, jobName)\n end\n redis.call('HSET', dedupKey, dedupId, jobIdStr .. ':' .. tostring(timestamp))\n if skipEvents ~= '1' then emitEvent(eventsKey, 'added', jobIdStr, {'name', jobName}) end\n return jobIdStr\nend)\n\nredis.register_function('glidemq_rateLimit', function(keys, args)\n local rateKey = keys[1]\n local metaKey = keys[2]\n local maxPerWindow = tonumber(args[1])\n local windowDuration = tonumber(args[2])\n local now = tonumber(args[3])\n -- Fallback: read rate limit config from meta if not provided inline\n if maxPerWindow <= 0 then\n maxPerWindow = tonumber(redis.call('HGET', metaKey, 'rateLimitMax')) or 0\n windowDuration = tonumber(redis.call('HGET', metaKey, 'rateLimitDuration')) or 0\n if maxPerWindow <= 0 then return 0 end\n end\n local windowStart = tonumber(redis.call('HGET', rateKey, 'windowStart')) or 0\n local count = tonumber(redis.call('HGET', rateKey, 'count')) or 0\n if now - windowStart >= windowDuration then\n redis.call('HSET', rateKey, 'windowStart', tostring(now), 'count', '1')\n return 0\n end\n if count >= maxPerWindow then\n local delayMs = windowDuration - (now - windowStart)\n return delayMs\n end\n redis.call('HSET', rateKey, 'count', tostring(count + 1))\n return 0\nend)\n\nredis.register_function('glidemq_promoteRateLimited', function(keys, args)\n local rateLimitedKey = keys[1]\n local streamKey = keys[2]\n local now = tonumber(args[1])\n -- Derive prefix from the server-validated key instead of caller-supplied arg\n local prefix = string.sub(rateLimitedKey, 1, #rateLimitedKey - #'ratelimited')\n local expired = redis.call('ZRANGEBYSCORE', rateLimitedKey, '0', string.format('%.0f', now), 'LIMIT', 0, 100)\n if not expired or #expired == 0 then return 0 end\n local promoted = 0\n for i = 1, #expired do\n local gk = expired[i]\n redis.call('ZREM', rateLimitedKey, gk)\n local groupHashKey = prefix .. 'group:' .. gk\n local waitListKey = prefix .. 'groupq:' .. gk\n -- Load all group fields in one call for rate limit + token bucket checks\n local prGrpFields = redis.call('HGETALL', groupHashKey)\n local prGrp = {}\n for pf = 1, #prGrpFields, 2 do prGrp[prGrpFields[pf]] = prGrpFields[pf + 1] end\n local rateMax = tonumber(prGrp.rateMax) or 0\n local maxConc = tonumber(prGrp.maxConcurrency) or 0\n local active = tonumber(prGrp.active) or 0\n -- Token bucket pre-check: peek head job cost before promoting\n local prTbCap = tonumber(prGrp.tbCapacity) or 0\n local tbCheckPassed = true\n if prTbCap > 0 then\n local prTbTokens = tbRefill(groupHashKey, prGrp, now)\n local headMembers = redis.call('ZRANGE', waitListKey, 0, 0)\n local headJobId = headMembers[1]\n if headJobId then\n local headJobKey = prefix .. 'job:' .. headJobId\n -- Tombstone guard\n if redis.call('EXISTS', headJobKey) == 0 then\n redis.call('ZREM', waitListKey, headJobId)\n tbCheckPassed = false\n end\n if tbCheckPassed then\n local headCost = tonumber(redis.call('HGET', headJobKey, 'cost')) or 1000\n -- DLQ guard: cost > capacity\n if headCost > prTbCap then\n redis.call('ZREM', waitListKey, headJobId)\n redis.call('ZADD', prefix .. 'failed', now, headJobId)\n redis.call('HSET', headJobKey,\n 'state', 'failed',\n 'failedReason', 'cost exceeds token bucket capacity',\n 'finishedOn', tostring(now))\n emitEvent(prefix .. 'events', 'failed', headJobId, {'failedReason', 'cost exceeds token bucket capacity'})\n tbCheckPassed = false\n end\n if tbCheckPassed and prTbTokens < headCost then\n -- Not enough tokens: re-register with calculated delay\n local prTbRate = math.max(tonumber(prGrp.tbRefillRate) or 0, 1)\n local prTbDelay = math.ceil((headCost - prTbTokens) * 1000 / prTbRate)\n redis.call('ZADD', rateLimitedKey, now + prTbDelay, gk)\n tbCheckPassed = false\n end\n end\n end\n end\n if tbCheckPassed then\n -- Promote up to min(rateMax, available concurrency) jobs.\n -- Do NOT touch rateCount/rateWindowStart here - moveToActive handles\n -- window reset and counting when the worker picks up the promoted jobs.\n local canPromote = 1000\n if rateMax > 0 then\n canPromote = math.min(canPromote, rateMax)\n end\n if maxConc > 0 then\n canPromote = math.min(canPromote, math.max(0, maxConc - active))\n end\n local prNextSeq = tonumber(prGrp.nextSeq) or 0\n local groupPromoted = 0\n local prIter = 0\n local prMaxIter = canPromote + 20\n while groupPromoted < canPromote and prIter < prMaxIter do\n prIter = prIter + 1\n local zpResult = redis.call('ZPOPMIN', waitListKey, 1)\n local nextJobId = zpResult[1]\n if not nextJobId then break end\n local nextJobKey = prefix .. 'job:' .. nextJobId\n local nextState = redis.call('HGET', nextJobKey, 'state')\n if nextState ~= 'group-waiting' then\n -- Stale: skip\n elseif checkExpired(nextJobKey, nextJobId, prefix, now) then\n -- Expired: skip\n else\n if prNextSeq > 0 then\n local jobSeq = tonumber(redis.call('HGET', nextJobKey, 'orderingSeq')) or 0\n if jobSeq > prNextSeq then\n redis.call('ZADD', waitListKey, jobSeq, nextJobId)\n break\n end\n end\n xaddJob(streamKey, nextJobId, redis.call('HGET', nextJobKey, 'name'))\n redis.call('HSET', nextJobKey, 'state', 'waiting')\n promoted = promoted + 1\n groupPromoted = groupPromoted + 1\n if prNextSeq > 0 then prNextSeq = prNextSeq + 1 end\n end\n end\n end\n end\n return promoted\nend)\n\nredis.register_function('glidemq_checkConcurrency', function(keys, args)\n local metaKey = keys[1]\n local streamKey = keys[2]\n local listActiveKey = keys[3]\n local group = args[1]\n local gc = tonumber(redis.call('HGET', metaKey, 'globalConcurrency')) or 0\n if gc <= 0 then\n return -1\n end\n local ok_cc, pending = pcall(redis.call, 'XPENDING', streamKey, group)\n local pendingCount = (ok_cc and pending and tonumber(pending[1])) or 0\n local listActive = tonumber(redis.call('GET', listActiveKey)) or 0\n local remaining = gc - pendingCount - listActive\n if remaining <= 0 then\n return 0\n end\n return remaining\nend)\n\n-- Atomically check global concurrency capacity, then RPOP up to count jobs from a list and INCRBY list-active counter.\n-- Returns an array of popped jobIds (may be empty if at capacity or list is empty).\n-- KEYS: [metaKey, streamKey, listActiveKey, listKey]\n-- ARGS: [group, count]\nredis.register_function('glidemq_rpopAndReserve', function(keys, args)\n local metaKey = keys[1]\n local streamKey = keys[2]\n local listActiveKey = keys[3]\n local listKey = keys[4]\n local group = args[1]\n local requested = tonumber(args[2]) or 1\n local maxPop = requested\n local gc = tonumber(redis.call('HGET', metaKey, 'globalConcurrency')) or 0\n if gc > 0 then\n local ok_ra, pending = pcall(redis.call, 'XPENDING', streamKey, group)\n local pendingCount = (ok_ra and pending and tonumber(pending[1])) or 0\n local listActive = tonumber(redis.call('GET', listActiveKey)) or 0\n local available = gc - pendingCount - listActive\n if available <= 0 then\n return {}\n end\n if available < maxPop then\n maxPop = available\n end\n end\n local results = {}\n for i = 1, maxPop do\n local jobId = redis.call('RPOP', listKey)\n if not jobId then break end\n results[#results + 1] = jobId\n end\n if #results > 0 then\n redis.call('INCRBY', listActiveKey, #results)\n end\n return results\nend)\n\nredis.register_function('glidemq_moveToActive', function(keys, args)\n local jobKey = keys[1]\n local streamKey = keys[2] or ''\n local timestamp = args[1]\n local entryId = args[2] or ''\n local group = args[3] or ''\n local jobId = args[4] or ''\n local broadcastMode = args[5] or '0'\n local ts = tonumber(timestamp) or 0\n local timestampStr = tostring(ts)\n local fields = redis.call('HGETALL', jobKey)\n if not fields or #fields == 0 then\n return ''\n end\n local revoked = ''\n local expireAt = 0\n local curState = ''\n local orderingKey = ''\n local orderingSeq = ''\n local groupKey = ''\n local costVal = ''\n local stateValueIndex = nil\n local processedOnValueIndex = nil\n local lastActiveValueIndex = nil\n for f = 1, #fields, 2 do\n local field = fields[f]\n local value = fields[f + 1]\n if field == 'revoked' then\n revoked = value\n elseif field == 'expireAt' then\n expireAt = tonumber(value) or 0\n elseif field == 'state' then\n curState = value\n stateValueIndex = f + 1\n elseif field == 'orderingKey' then\n orderingKey = value\n elseif field == 'orderingSeq' then\n orderingSeq = value\n elseif field == 'groupKey' then\n groupKey = value\n elseif field == 'cost' then\n costVal = value\n elseif field == 'processedOn' then\n processedOnValueIndex = f + 1\n elseif field == 'lastActive' then\n lastActiveValueIndex = f + 1\n end\n end\n if revoked == '1' then\n return 'REVOKED'\n end\n local prefix = string.sub(jobKey, 1, #jobKey - #('job:' .. jobId))\n if expireAt > 0 and ts > expireAt then\n expireJob(jobKey, jobId, prefix, ts, curState, orderingKey, orderingSeq, groupKey)\n if streamKey ~= '' and entryId ~= '' and group ~= '' then\n redis.call('XACK', streamKey, group, entryId)\n if broadcastMode ~= '1' then redis.call('XDEL', streamKey, entryId) end\n end\n return 'EXPIRED'\n end\n if groupKey and groupKey ~= '' then\n local groupHashKey = prefix .. 'group:' .. groupKey\n -- Load all group fields in one call\n local grpFields = redis.call('HGETALL', groupHashKey)\n local grp = {}\n for f = 1, #grpFields, 2 do grp[grpFields[f]] = grpFields[f + 1] end\n local maxConc = tonumber(grp.maxConcurrency) or 0\n local active = tonumber(grp.active) or 0\n local waitListKey = prefix .. 'groupq:' .. groupKey\n -- Ordering gate: park future jobs (seq > nextSeq) in sorted groupq.\n -- Returning step-jobs (seq <= nextSeq) are allowed through.\n local jobOrderingSeq = tonumber(redis.call('HGET', jobKey, 'orderingSeq')) or 0\n local isReturningStepJob = false\n if jobOrderingSeq > 0 then\n local nextSeq = tonumber(grp.nextSeq) or 0\n if nextSeq > 0 and jobOrderingSeq > nextSeq then\n if streamKey ~= '' and entryId ~= '' and group ~= '' then\n redis.call('XACK', streamKey, group, entryId)\n if broadcastMode ~= '1' then redis.call('XDEL', streamKey, entryId) end\n end\n redis.call('ZADD', waitListKey, jobOrderingSeq, jobId)\n redis.call('HSET', jobKey, 'state', 'group-waiting')\n return 'GROUP_ORDERED'\n end\n isReturningStepJob = (nextSeq > 0 and jobOrderingSeq < nextSeq)\n end\n -- Concurrency gate (skip for returning step-jobs that already hold the slot)\n if maxConc > 0 and active >= maxConc and not isReturningStepJob then\n if streamKey ~= '' and entryId ~= '' and group ~= '' then\n redis.call('XACK', streamKey, group, entryId)\n if broadcastMode ~= '1' then redis.call('XDEL', streamKey, entryId) end\n end\n redis.call('ZADD', waitListKey, groupqScore(jobKey, jobId), jobId)\n redis.call('HSET', jobKey, 'state', 'group-waiting')\n return 'GROUP_FULL'\n end\n -- Token bucket gate (read-only)\n local tbCapacity = tonumber(grp.tbCapacity) or 0\n local tbBlocked = false\n local tbDelay = 0\n local tbTokens = 0\n local jobCostVal = 0\n if tbCapacity > 0 then\n tbTokens = tbRefill(groupHashKey, grp, ts)\n jobCostVal = tonumber(costVal) or 1000\n -- DLQ guard: cost > capacity\n if jobCostVal > tbCapacity then\n if streamKey ~= '' and entryId ~= '' and group ~= '' then\n redis.call('XACK', streamKey, group, entryId)\n if broadcastMode ~= '1' then redis.call('XDEL', streamKey, entryId) end\n end\n redis.call('ZADD', prefix .. 'failed', ts, jobId)\n redis.call('HSET', jobKey,\n 'state', 'failed',\n 'failedReason', 'cost exceeds token bucket capacity',\n 'finishedOn', timestampStr)\n emitEvent(prefix .. 'events', 'failed', jobId, {'failedReason', 'cost exceeds token bucket capacity'})\n return 'ERR:COST_EXCEEDS_CAPACITY'\n end\n if tbTokens < jobCostVal then\n tbBlocked = true\n local tbRefillRateVal = tonumber(grp.tbRefillRate) or 0\n if tbRefillRateVal <= 0 then tbRefillRateVal = 1 end\n tbDelay = math.ceil((jobCostVal - tbTokens) * 1000 / tbRefillRateVal)\n end\n end\n -- Sliding window gate (read-only)\n local rateMax = tonumber(grp.rateMax) or 0\n local rlBlocked = false\n local rlDelay = 0\n if rateMax > 0 then\n local rateDuration = tonumber(grp.rateDuration) or 0\n local rateWindowStart = tonumber(grp.rateWindowStart) or 0\n local rateCount = tonumber(grp.rateCount) or 0\n local now = ts\n if rateDuration > 0 and now - rateWindowStart < rateDuration and rateCount >= rateMax then\n rlBlocked = true\n rlDelay = (rateWindowStart + rateDuration) - now\n end\n end\n -- If ANY gate blocked: park + register (skip for returning step-jobs)\n if (tbBlocked or rlBlocked) and not isReturningStepJob then\n if streamKey ~= '' and entryId ~= '' and group ~= '' then\n redis.call('XACK', streamKey, group, entryId)\n if broadcastMode ~= '1' then redis.call('XDEL', streamKey, entryId) end\n end\n local waitListKey = prefix .. 'groupq:' .. groupKey\n redis.call('ZADD', waitListKey, groupqScore(jobKey, jobId), jobId)\n redis.call('HSET', jobKey, 'state', 'group-waiting')\n local maxDelay = math.max(tbDelay, rlDelay)\n local rateLimitedKey = prefix .. 'ratelimited'\n redis.call('ZADD', rateLimitedKey, ts + maxDelay, groupKey)\n if tbBlocked then return 'GROUP_TOKEN_LIMITED' end\n return 'GROUP_RATE_LIMITED'\n end\n -- All gates passed: mutate state\n if tbCapacity > 0 then\n redis.call('HINCRBY', groupHashKey, 'tbTokens', -jobCostVal)\n end\n if rateMax > 0 then\n local rateDuration = tonumber(grp.rateDuration) or 0\n if rateDuration > 0 then\n local rateWindowStart = tonumber(grp.rateWindowStart) or 0\n local now = ts\n if now - rateWindowStart >= rateDuration then\n redis.call('HSET', groupHashKey, 'rateWindowStart', tostring(now), 'rateCount', '1')\n else\n redis.call('HINCRBY', groupHashKey, 'rateCount', 1)\n end\n end\n end\n if not isReturningStepJob then\n redis.call('HINCRBY', groupHashKey, 'active', 1)\n end\n if jobOrderingSeq > 0 then\n if not isReturningStepJob then\n redis.call('HSET', groupHashKey, 'nextSeq', tostring(jobOrderingSeq + 1))\n end\n -- Remove from groupq in case job was parked earlier and re-delivered via priority list\n redis.call('ZREM', waitListKey, jobId)\n end\n end\n redis.call('HSET', jobKey, 'state', 'active', 'processedOn', timestampStr, 'lastActive', timestampStr)\n if stateValueIndex ~= nil then\n fields[stateValueIndex] = 'active'\n else\n fields[#fields + 1] = 'state'\n fields[#fields + 1] = 'active'\n end\n if processedOnValueIndex ~= nil then\n fields[processedOnValueIndex] = timestampStr\n else\n fields[#fields + 1] = 'processedOn'\n fields[#fields + 1] = timestampStr\n end\n if lastActiveValueIndex ~= nil then\n fields[lastActiveValueIndex] = timestampStr\n else\n fields[#fields + 1] = 'lastActive'\n fields[#fields + 1] = timestampStr\n end\n return fields\nend)\n\nredis.register_function('glidemq_deferActive', function(keys, args)\n local streamKey = keys[1]\n local jobKey = keys[2]\n local listActiveKey = keys[3] or ''\n local jobId = args[1]\n local entryId = args[2]\n local group = args[3]\n local broadcastMode = args[4] or '0'\n local exists = redis.call('EXISTS', jobKey)\n if entryId ~= '' then redis.call('XACK', streamKey, group, entryId) end\n if entryId ~= '' and broadcastMode ~= '1' then redis.call('XDEL', streamKey, entryId) end\n -- List-sourced jobs (entryId='') were counted in list-active; DECR to stay balanced.\n -- Guard > 0: healListActive does not correct negative drift.\n if entryId == '' and listActiveKey ~= '' then\n local la = tonumber(redis.call('GET', listActiveKey)) or 0\n if la > 0 then\n redis.call('DECR', listActiveKey)\n end\n end\n if exists == 0 then\n return 0\n end\n xaddJob(streamKey, jobId, redis.call('HGET', jobKey, 'name'))\n redis.call('HSET', jobKey, 'state', 'waiting')\n return 1\nend)\n\nredis.register_function('glidemq_addFlow', function(keys, args)\n local parentIdKey = keys[1]\n local parentStreamKey = keys[2]\n local parentScheduledKey = keys[3]\n local parentEventsKey = keys[4]\n local parentName = args[1]\n local parentData = args[2]\n local parentOpts = args[3]\n local timestamp = tonumber(args[4])\n local parentDelay = tonumber(args[5]) or 0\n local parentPriority = tonumber(args[6]) or 0\n local parentMaxAttempts = tonumber(args[7]) or 0\n local numChildren = tonumber(args[8])\n local parentCustomId = args[9] or ''\n local parentPrefix = string.sub(parentIdKey, 1, #parentIdKey - 2)\n local parentJobIdStr\n local parentJobKey\n if parentCustomId ~= '' then\n parentJobKey = parentPrefix .. 'job:' .. parentCustomId\n if redis.call('EXISTS', parentJobKey) == 1 then\n return cjson.encode({'duplicate'})\n end\n parentJobIdStr = parentCustomId\n advanceIdCounter(parentIdKey, parentCustomId)\n else\n local parentJobId = redis.call('INCR', parentIdKey)\n parentJobIdStr = tostring(parentJobId)\n parentJobKey = parentPrefix .. 'job:' .. parentJobIdStr\n end\n -- Pre-validate all children's custom IDs for duplicates before any writes\n local seenChildKeys = {}\n for i = 1, numChildren do\n local base = 9 + (i - 1) * 9\n local preChildCustomId = args[base + 9] or ''\n if preChildCustomId ~= '' then\n local ckBase = 4 + (i - 1) * 4\n local preChildIdKey = keys[ckBase + 1]\n local preChildPrefix = string.sub(preChildIdKey, 1, #preChildIdKey - 2)\n local preChildJobKey = preChildPrefix .. 'job:' .. preChildCustomId\n if preChildJobKey == parentJobKey or seenChildKeys[preChildJobKey] then\n return cjson.encode({'duplicate'})\n end\n seenChildKeys[preChildJobKey] = true\n if redis.call('EXISTS', preChildJobKey) == 1 then\n return cjson.encode({'duplicate'})\n end\n end\n end\n local depsKey = parentPrefix .. 'deps:' .. parentJobIdStr\n local parentOrderingKey = extractOrderingKeyFromOpts(parentOpts)\n local parentGroupConc = extractGroupConcurrencyFromOpts(parentOpts)\n local parentRateMax, parentRateDuration = extractGroupRateLimitFromOpts(parentOpts)\n local parentTbCapacity, parentTbRefillRate = extractTokenBucketFromOpts(parentOpts)\n local parentCost = extractCostFromOpts(parentOpts)\n local parentUseGroup = (parentOrderingKey ~= '')\n local parentOrderingSeq = 0\n if parentOrderingKey ~= '' then\n local parentOrderingMetaKey = parentPrefix .. 'ordering'\n parentOrderingSeq = redis.call('HINCRBY', parentOrderingMetaKey, parentOrderingKey, 1)\n end\n local parentHash = {\n 'id', parentJobIdStr,\n 'name', parentName,\n 'data', parentData,\n 'opts', parentOpts,\n 'timestamp', tostring(timestamp),\n 'attemptsMade', '0',\n 'delay', tostring(parentDelay),\n 'priority', tostring(parentPriority),\n 'maxAttempts', tostring(parentMaxAttempts),\n 'state', 'waiting-children'\n }\n if parentUseGroup then\n parentHash[#parentHash + 1] = 'groupKey'\n parentHash[#parentHash + 1] = parentOrderingKey\n if parentOrderingSeq > 0 then\n parentHash[#parentHash + 1] = 'orderingSeq'\n parentHash[#parentHash + 1] = tostring(parentOrderingSeq)\n end\n if parentGroupConc < 1 then parentGroupConc = 1 end\n local groupHashKey = parentPrefix .. 'group:' .. parentOrderingKey\n redis.call('HSET', groupHashKey, 'maxConcurrency', tostring(parentGroupConc))\n redis.call('HSETNX', groupHashKey, 'active', '0')\n if parentOrderingSeq > 0 and redis.call('HEXISTS', groupHashKey, 'nextSeq') == 0 then\n redis.call('HSET', groupHashKey, 'nextSeq', '1')\n end\n if parentRateMax > 0 then\n redis.call('HSET', groupHashKey, 'rateMax', tostring(parentRateMax))\n redis.call('HSET', groupHashKey, 'rateDuration', tostring(parentRateDuration))\n end\n if parentTbCapacity > 0 then\n if parentCost > 0 and parentCost > parentTbCapacity then\n return 'ERR:COST_EXCEEDS_CAPACITY'\n end\n redis.call('HSET', groupHashKey, 'tbCapacity', tostring(parentTbCapacity), 'tbRefillRate', tostring(parentTbRefillRate))\n redis.call('HSETNX', groupHashKey, 'tbTokens', tostring(parentTbCapacity))\n redis.call('HSETNX', groupHashKey, 'tbLastRefill', tostring(timestamp))\n redis.call('HSETNX', groupHashKey, 'tbRefillRemainder', '0')\n end\n end\n if parentCost > 0 then\n parentHash[#parentHash + 1] = 'cost'\n parentHash[#parentHash + 1] = tostring(parentCost)\n end\n local parentTtl = extractTtlFromOpts(parentOpts)\n if parentTtl > 0 then\n parentHash[#parentHash + 1] = 'expireAt'\n parentHash[#parentHash + 1] = tostring(timestamp + parentTtl)\n end\n redis.call('HSET', parentJobKey, unpack(parentHash))\n -- Pre-validate all children's cost vs capacity before any child writes\n local childArgOffset = 9\n local childKeyOffset = 4\n for i = 1, numChildren do\n local base = childArgOffset + (i - 1) * 9\n local preChildOpts = args[base + 3]\n local preChildTbCap, _ = extractTokenBucketFromOpts(preChildOpts)\n if preChildTbCap > 0 then\n local preChildCost = extractCostFromOpts(preChildOpts)\n local preEffective = (preChildCost > 0) and preChildCost or 1000\n if preEffective > preChildTbCap then\n return 'ERR:COST_EXCEEDS_CAPACITY'\n end\n end\n end\n local childIds = {}\n for i = 1, numChildren do\n local base = childArgOffset + (i - 1) * 9\n local childName = args[base + 1]\n local childData = args[base + 2]\n local childOpts = args[base + 3]\n local childDelay = tonumber(args[base + 4]) or 0\n local childPriority = tonumber(args[base + 5]) or 0\n local childMaxAttempts = tonumber(args[base + 6]) or 0\n local childQueuePrefix = args[base + 7]\n local childParentQueue = args[base + 8]\n local childCustomId = args[base + 9] or ''\n local ckBase = childKeyOffset + (i - 1) * 4\n local childIdKey = keys[ckBase + 1]\n local childStreamKey = keys[ckBase + 2]\n local childScheduledKey = keys[ckBase + 3]\n local childEventsKey = keys[ckBase + 4]\n local childPrefix = string.sub(childIdKey, 1, #childIdKey - 2)\n local childJobIdStr\n local childJobKey\n if childCustomId ~= '' then\n childJobKey = childPrefix .. 'job:' .. childCustomId\n childJobIdStr = childCustomId\n advanceIdCounter(childIdKey, childCustomId)\n else\n local childJobId = redis.call('INCR', childIdKey)\n childJobIdStr = tostring(childJobId)\n childJobKey = childPrefix .. 'job:' .. childJobIdStr\n end\n local childOrderingKey = extractOrderingKeyFromOpts(childOpts)\n local childLifo = extractLifoFromOpts(childOpts)\n local childGroupConc = extractGroupConcurrencyFromOpts(childOpts)\n local childRateMax, childRateDuration = extractGroupRateLimitFromOpts(childOpts)\n local childTbCapacity, childTbRefillRate = extractTokenBucketFromOpts(childOpts)\n local childCost = extractCostFromOpts(childOpts)\n local childUseGroup = (childOrderingKey ~= '')\n local childOrderingSeq = 0\n if childOrderingKey ~= '' then\n local childOrderingMetaKey = childPrefix .. 'ordering'\n childOrderingSeq = redis.call('HINCRBY', childOrderingMetaKey, childOrderingKey, 1)\n end\n local childHash = {\n 'id', childJobIdStr,\n 'name', childName,\n 'data', childData,\n 'opts', childOpts,\n 'timestamp', tostring(timestamp),\n 'attemptsMade', '0',\n 'delay', tostring(childDelay),\n 'priority', tostring(childPriority),\n 'maxAttempts', tostring(childMaxAttempts),\n 'parentId', parentJobIdStr,\n 'parentQueue', childParentQueue\n }\n if childUseGroup then\n childHash[#childHash + 1] = 'groupKey'\n childHash[#childHash + 1] = childOrderingKey\n if childOrderingSeq > 0 then\n childHash[#childHash + 1] = 'orderingSeq'\n childHash[#childHash + 1] = tostring(childOrderingSeq)\n end\n if childGroupConc < 1 then childGroupConc = 1 end\n local childGroupHashKey = childPrefix .. 'group:' .. childOrderingKey\n redis.call('HSETNX', childGroupHashKey, 'maxConcurrency', tostring(childGroupConc))\n redis.call('HSETNX', childGroupHashKey, 'active', '0')\n if childOrderingSeq > 0 and redis.call('HEXISTS', childGroupHashKey, 'nextSeq') == 0 then\n redis.call('HSET', childGroupHashKey, 'nextSeq', '1')\n end\n if childRateMax > 0 then\n redis.call('HSET', childGroupHashKey, 'rateMax', tostring(childRateMax))\n redis.call('HSET', childGroupHashKey, 'rateDuration', tostring(childRateDuration))\n end\n if childTbCapacity > 0 then\n redis.call('HSET', childGroupHashKey, 'tbCapacity', tostring(childTbCapacity), 'tbRefillRate', tostring(childTbRefillRate))\n redis.call('HSETNX', childGroupHashKey, 'tbTokens', tostring(childTbCapacity))\n redis.call('HSETNX', childGroupHashKey, 'tbLastRefill', tostring(timestamp))\n redis.call('HSETNX', childGroupHashKey, 'tbRefillRemainder', '0')\n end\n end\n if childCost > 0 then\n childHash[#childHash + 1] = 'cost'\n childHash[#childHash + 1] = tostring(childCost)\n end\n local childTtl = extractTtlFromOpts(childOpts)\n if childTtl > 0 then\n childHash[#childHash + 1] = 'expireAt'\n childHash[#childHash + 1] = tostring(timestamp + childTtl)\n end\n if childLifo > 0 then\n childHash[#childHash + 1] = 'lifo'\n childHash[#childHash + 1] = '1'\n end\n if childDelay > 0 or childPriority > 0 then\n childHash[#childHash + 1] = 'state'\n childHash[#childHash + 1] = childDelay > 0 and 'delayed' or 'prioritized'\n else\n childHash[#childHash + 1] = 'state'\n childHash[#childHash + 1] = 'waiting'\n end\n redis.call('HSET', childJobKey, unpack(childHash))\n local depsMember = childQueuePrefix .. ':' .. childJobIdStr\n redis.call('SADD', depsKey, depsMember)\n if childDelay > 0 then\n local score = childPriority * PRIORITY_SHIFT + (timestamp + childDelay)\n redis.call('ZADD', childScheduledKey, score, childJobIdStr)\n elseif childPriority > 0 then\n local score = childPriority * PRIORITY_SHIFT\n redis.call('ZADD', childScheduledKey, score, childJobIdStr)\n elseif childLifo > 0 then\n redis.call('RPUSH', childPrefix .. 'lifo', childJobIdStr)\n else\n xaddJob(childStreamKey, childJobIdStr, childName)\n end\n emitEvent(childEventsKey, 'added', childJobIdStr, {'name', childName})\n childIds[#childIds + 1] = childJobIdStr\n end\n local extraDepsOffset = childArgOffset + numChildren * 9\n local numExtraDeps = tonumber(args[extraDepsOffset + 1]) or 0\n for i = 1, numExtraDeps do\n local extraMember = args[extraDepsOffset + 1 + i]\n redis.call('SADD', depsKey, extraMember)\n end\n emitEvent(parentEventsKey, 'added', parentJobIdStr, {'name', parentName})\n local result = {parentJobIdStr}\n for i = 1, #childIds do\n result[#result + 1] = childIds[i]\n end\n return cjson.encode(result)\nend)\n\nredis.register_function('glidemq_completeChild', function(keys, args)\n local depsKey = keys[1]\n local parentJobKey = keys[2]\n local parentStreamKey = keys[3]\n local parentEventsKey = keys[4]\n local depsMember = args[1]\n local parentId = args[2]\n local depMarker = 'depdone:' .. depsMember\n if redis.call('HSETNX', parentJobKey, depMarker, '1') == 0 then\n local doneCount = tonumber(redis.call('HGET', parentJobKey, 'depsCompleted')) or 0\n local totalDeps = redis.call('SCARD', depsKey)\n return totalDeps - doneCount\n end\n local doneCount = redis.call('HINCRBY', parentJobKey, 'depsCompleted', 1)\n local totalDeps = redis.call('SCARD', depsKey)\n local remaining = totalDeps - doneCount\n if remaining <= 0 then\n local parentState = redis.call('HGET', parentJobKey, 'state')\n if parentState == 'waiting-children' then\n redis.call('HSET', parentJobKey, 'state', 'waiting')\n xaddJob(parentStreamKey, parentId, redis.call('HGET', parentJobKey, 'name'))\n emitEvent(parentEventsKey, 'active', parentId, nil)\n end\n end\n return remaining\nend)\n\nredis.register_function('glidemq_registerParent', function(keys, args)\n -- Register an additional parent for an existing child job (DAG multi-parent).\n -- Keys: [childJobKey, childParentsKey, parentDepsKey, parentJobKey, parentStreamKey, parentEventsKey]\n -- Args: [childJobId, parentId, parentQueue, depsMember]\n local childJobKey = keys[1]\n local childParentsKey = keys[2]\n local parentDepsKey = keys[3]\n local parentJobKey = keys[4]\n local parentStreamKey = keys[5]\n local parentEventsKey = keys[6]\n local childJobId = args[1]\n local parentId = args[2]\n local parentQueue = args[3]\n local depsMember = args[4]\n -- Verify child exists\n if redis.call('EXISTS', childJobKey) == 0 then\n return 'error:child_not_found'\n end\n -- Add parent entry to child's parents SET (idempotent)\n redis.call('SADD', childParentsKey, parentQueue .. ':' .. parentId)\n -- Add child as dependency in parent's deps SET (idempotent)\n redis.call('SADD', parentDepsKey, depsMember)\n -- Race condition check: if child already completed, trigger parent notification immediately\n local childState = redis.call('HGET', childJobKey, 'state')\n if childState == 'completed' then\n local depMarker = 'depdone:' .. depsMember\n if redis.call('HSETNX', parentJobKey, depMarker, '1') == 1 then\n local doneCount = redis.call('HINCRBY', parentJobKey, 'depsCompleted', 1)\n local totalDeps = redis.call('SCARD', parentDepsKey)\n if totalDeps - doneCount <= 0 then\n local parentState = redis.call('HGET', parentJobKey, 'state')\n if parentState == 'waiting-children' then\n redis.call('HSET', parentJobKey, 'state', 'waiting')\n xaddJob(parentStreamKey, parentId, redis.call('HGET', parentJobKey, 'name'))\n emitEvent(parentEventsKey, 'active', parentId, nil)\n end\n end\n end\n return 'already_completed'\n end\n return 'ok'\nend)\n\nredis.register_function('glidemq_removeJob', function(keys, args)\n local jobKey = keys[1]\n local streamKey = keys[2]\n local scheduledKey = keys[3]\n local completedKey = keys[4]\n local failedKey = keys[5]\n local eventsKey = keys[6]\n local logKey = keys[7]\n local jobId = args[1]\n local exists = redis.call('EXISTS', jobKey)\n if exists == 0 then\n return 0\n end\n local state = redis.call('HGET', jobKey, 'state')\n local groupKey = redis.call('HGET', jobKey, 'groupKey')\n if groupKey and groupKey ~= '' then\n if state == 'active' then\n releaseGroupSlotAndPromote(jobKey, jobId, 0)\n elseif state == 'group-waiting' then\n local prefix = string.sub(jobKey, 1, #jobKey - #('job:' .. jobId))\n local waitListKey = prefix .. 'groupq:' .. groupKey\n redis.call('ZREM', waitListKey, jobId)\n end\n end\n -- DECR list-active if the job was active and list-sourced (LIFO or priority-list)\n if state == 'active' then\n local jobLifo = redis.call('HGET', jobKey, 'lifo')\n local jobPriority = tonumber(redis.call('HGET', jobKey, 'priority')) or 0\n if jobLifo == '1' or jobPriority > 0 then\n local prefix_r = string.sub(jobKey, 1, #jobKey - #('job:' .. jobId))\n redis.call('DECR', prefix_r .. 'list-active')\n end\n end\n redis.call('ZREM', scheduledKey, jobId)\n redis.call('ZREM', completedKey, jobId)\n redis.call('ZREM', failedKey, jobId)\n markOrderingDone(jobKey, jobId)\n -- Clean up DAG parents SET, per-job streaming channel, and signals\n local prefix = string.sub(jobKey, 1, #jobKey - #('job:' .. jobId))\n local parentsKey = prefix .. 'parents:' .. jobId\n local jstreamKey = prefix .. 'jstream:' .. jobId\n local signalsKey = prefix .. 'signals:' .. jobId\n local suspendedKey = prefix .. 'suspended'\n redis.call('DEL', parentsKey)\n redis.call('DEL', jstreamKey)\n redis.call('DEL', signalsKey)\n redis.call('ZREM', suspendedKey, jobId)\n redis.call('DEL', jobKey)\n redis.call('DEL', logKey)\n emitEvent(eventsKey, 'removed', jobId, nil)\n return 1\nend)\n\nredis.register_function('glidemq_clean', function(keys, args)\n local setKey = keys[1]\n local eventsKey = keys[2]\n local idKey = keys[3]\n local cutoff = tonumber(args[1])\n local limit = tonumber(args[2])\n if not limit or limit <= 0 then return {} end\n local prefix = string.sub(idKey, 1, #idKey - 2)\n local ids = redis.call('ZRANGEBYSCORE', setKey, '-inf', string.format('%.0f', cutoff), 'LIMIT', 0, limit)\n if #ids == 0 then\n return {}\n end\n for i = 1, #ids do\n redis.call('DEL', prefix .. 'job:' .. ids[i], prefix .. 'log:' .. ids[i], prefix .. 'deps:' .. ids[i], prefix .. 'parents:' .. ids[i], prefix .. 'jstream:' .. ids[i], prefix .. 'signals:' .. ids[i])\n end\n for i = 1, #ids, 1000 do\n redis.call('ZREM', setKey, unpack(ids, i, math.min(i + 999, #ids)))\n end\n emitEvent(eventsKey, 'cleaned', tostring(#ids), nil)\n return ids\nend)\n\nredis.register_function('glidemq_revoke', function(keys, args)\n local jobKey = keys[1]\n local streamKey = keys[2]\n local scheduledKey = keys[3]\n local failedKey = keys[4]\n local eventsKey = keys[5]\n local jobId = args[1]\n local timestamp = tonumber(args[2])\n local group = args[3]\n local exists = redis.call('EXISTS', jobKey)\n if exists == 0 then\n return 'not_found'\n end\n redis.call('HSET', jobKey, 'revoked', '1')\n local state = redis.call('HGET', jobKey, 'state')\n if state == 'group-waiting' then\n local gk = redis.call('HGET', jobKey, 'groupKey')\n if gk and gk ~= '' then\n local prefix = string.sub(jobKey, 1, #jobKey - #('job:' .. jobId))\n local waitListKey = prefix .. 'groupq:' .. gk\n redis.call('ZREM', waitListKey, jobId)\n end\n redis.call('ZADD', failedKey, timestamp, jobId)\n redis.call('HSET', jobKey,\n 'state', 'failed',\n 'failedReason', 'revoked',\n 'finishedOn', tostring(timestamp)\n )\n emitEvent(eventsKey, 'revoked', jobId, nil)\n return 'revoked'\n end\n if state == 'waiting' or state == 'delayed' or state == 'prioritized' then\n redis.call('ZREM', scheduledKey, jobId)\n local cursor = '-'\n local found = false\n while not found do\n local entries = redis.call('XRANGE', streamKey, cursor, '+', 'COUNT', 1000)\n if #entries == 0 then break end\n for i = 1, #entries do\n local entryId = entries[i][1]\n local fields = entries[i][2]\n for j = 1, #fields, 2 do\n if fields[j] == 'jobId' and fields[j+1] == jobId then\n if entryId ~= '' then redis.call('XACK', streamKey, group, entryId) end\n if entryId ~= '' then redis.call('XDEL', streamKey, entryId) end\n found = true\n break\n end\n end\n if found then break end\n end\n if not found then\n local lastId = entries[#entries][1]\n local dashPos = lastId:find('-')\n cursor = lastId:sub(1, dashPos) .. tostring(tonumber(lastId:sub(dashPos + 1)) + 1)\n end\n end\n redis.call('ZADD', failedKey, timestamp, jobId)\n redis.call('HSET', jobKey,\n 'state', 'failed',\n 'failedReason', 'revoked',\n 'finishedOn', tostring(timestamp)\n )\n markOrderingDone(jobKey, jobId)\n emitEvent(eventsKey, 'revoked', jobId, nil)\n return 'revoked'\n end\n emitEvent(eventsKey, 'revoked', jobId, nil)\n return 'flagged'\nend)\n\nredis.register_function('glidemq_changePriority', function(keys, args)\n local jobKey = keys[1]\n local streamKey = keys[2]\n local scheduledKey = keys[3]\n local eventsKey = keys[4]\n local jobId = args[1]\n local newPriority = tonumber(args[2])\n if newPriority == nil or newPriority < 0 then\n return 'error:invalid_priority'\n end\n local group = args[3]\n local exists = redis.call('EXISTS', jobKey)\n if exists == 0 then\n return 'error:not_found'\n end\n local state = redis.call('HGET', jobKey, 'state')\n if state == 'waiting' then\n if newPriority == 0 then\n return 'no_op'\n end\n local cursor = '-'\n local found = false\n while not found do\n local entries = redis.call('XRANGE', streamKey, cursor, '+', 'COUNT', 1000)\n if #entries == 0 then break end\n for i = 1, #entries do\n local entryId = entries[i][1]\n local fields = entries[i][2]\n for j = 1, #fields, 2 do\n if fields[j] == 'jobId' and fields[j+1] == jobId then\n pcall(redis.call, 'XACK', streamKey, group, entryId)\n if entryId ~= '' then redis.call('XDEL', streamKey, entryId) end\n found = true\n break\n end\n end\n if found then break end\n end\n if not found then\n local lastId = entries[#entries][1]\n local dashPos = lastId:find('-')\n cursor = lastId:sub(1, dashPos) .. tostring(tonumber(lastId:sub(dashPos + 1)) + 1)\n end\n end\n if not found then\n return 'error:not_in_stream'\n end\n redis.call('ZADD', scheduledKey, string.format('%.0f', newPriority * PRIORITY_SHIFT), jobId)\n redis.call('HSET', jobKey, 'state', 'prioritized', 'priority', tostring(newPriority))\n emitEvent(eventsKey, 'priority-changed', jobId, {'priority', tostring(newPriority)})\n return 'ok'\n elseif state == 'prioritized' then\n if newPriority == 0 then\n redis.call('ZREM', scheduledKey, jobId)\n xaddJob(streamKey, jobId, redis.call('HGET', jobKey, 'name'))\n redis.call('HSET', jobKey, 'state', 'waiting', 'priority', '0')\n else\n redis.call('ZADD', scheduledKey, string.format('%.0f', newPriority * PRIORITY_SHIFT), jobId)\n redis.call('HSET', jobKey, 'priority', tostring(newPriority))\n end\n emitEvent(eventsKey, 'priority-changed', jobId, {'priority', tostring(newPriority)})\n return 'ok'\n elseif state == 'delayed' then\n local rawScore = redis.call('ZSCORE', scheduledKey, jobId)\n if rawScore == false then\n return 'error:not_in_scheduled'\n end\n local oldScore = tonumber(rawScore) or 0\n local oldTimestamp = oldScore % PRIORITY_SHIFT\n local newScore = newPriority * PRIORITY_SHIFT + oldTimestamp\n redis.call('ZREM', scheduledKey, jobId)\n redis.call('ZADD', scheduledKey, string.format('%.0f', newScore), jobId)\n redis.call('HSET', jobKey, 'priority', tostring(newPriority))\n emitEvent(eventsKey, 'priority-changed', jobId, {'priority', tostring(newPriority)})\n return 'ok'\n else\n return 'error:invalid_state'\n end\nend)\n\nredis.register_function('glidemq_changeDelay', function(keys, args)\n local jobKey = keys[1]\n local streamKey = keys[2]\n local scheduledKey = keys[3]\n local eventsKey = keys[4]\n local jobId = args[1]\n local newDelay = tonumber(args[2])\n if newDelay == nil or newDelay < 0 then\n return 'error:invalid_delay'\n end\n local now = tonumber(args[3])\n local group = args[4]\n local exists = redis.call('EXISTS', jobKey)\n if exists == 0 then\n return 'error:not_found'\n end\n local state = redis.call('HGET', jobKey, 'state')\n if state == 'delayed' then\n if newDelay == 0 then\n local rawScore = redis.call('ZSCORE', scheduledKey, jobId)\n if rawScore == false then\n return 'error:not_in_scheduled'\n end\n local oldScore = tonumber(rawScore) or 0\n local priority = math.floor(oldScore / PRIORITY_SHIFT)\n if priority > 0 then\n redis.call('ZADD', scheduledKey, 'XX', string.format('%.0f', priority * PRIORITY_SHIFT), jobId)\n redis.call('HSET', jobKey, 'state', 'prioritized', 'delay', '0')\n else\n redis.call('ZREM', scheduledKey, jobId)\n xaddJob(streamKey, jobId, redis.call('HGET', jobKey, 'name'))\n redis.call('HSET', jobKey, 'state', 'waiting', 'delay', '0')\n end\n else\n local rawScore = redis.call('ZSCORE', scheduledKey, jobId)\n if rawScore == false then\n return 'error:not_in_scheduled'\n end\n local oldScore = tonumber(rawScore) or 0\n local priority = math.floor(oldScore / PRIORITY_SHIFT)\n local newScore = priority * PRIORITY_SHIFT + (now + newDelay)\n redis.call('ZADD', scheduledKey, 'XX', string.format('%.0f', newScore), jobId)\n redis.call('HSET', jobKey, 'delay', tostring(newDelay))\n end\n emitEvent(eventsKey, 'delay-changed', jobId, {'delay', tostring(newDelay)})\n return 'ok'\n elseif state == 'waiting' then\n if newDelay == 0 then\n return 'no_op'\n end\n local priority = tonumber(redis.call('HGET', jobKey, 'priority')) or 0\n local cursor = '-'\n local found = false\n while not found do\n local entries = redis.call('XRANGE', streamKey, cursor, '+', 'COUNT', 1000)\n if #entries == 0 then break end\n for i = 1, #entries do\n local entryId = entries[i][1]\n local fields = entries[i][2]\n for j = 1, #fields, 2 do\n if fields[j] == 'jobId' and fields[j+1] == jobId then\n pcall(redis.call, 'XACK', streamKey, group, entryId)\n if entryId ~= '' then redis.call('XDEL', streamKey, entryId) end\n found = true\n break\n end\n end\n if found then break end\n end\n if not found then\n cursor = '(' .. entries[#entries][1]\n end\n end\n if not found then\n return 'error:not_in_stream'\n end\n local newScore = priority * PRIORITY_SHIFT + (now + newDelay)\n redis.call('ZADD', scheduledKey, string.format('%.0f', newScore), jobId)\n redis.call('HSET', jobKey, 'state', 'delayed', 'delay', tostring(newDelay))\n emitEvent(eventsKey, 'delay-changed', jobId, {'delay', tostring(newDelay)})\n return 'ok'\n elseif state == 'prioritized' then\n if newDelay == 0 then\n return 'no_op'\n end\n local rawScore = redis.call('ZSCORE', scheduledKey, jobId)\n if rawScore == false then\n return 'error:not_in_scheduled'\n end\n local oldScore = tonumber(rawScore) or 0\n local priority = math.floor(oldScore / PRIORITY_SHIFT)\n local newScore = priority * PRIORITY_SHIFT + (now + newDelay)\n redis.call('ZADD', scheduledKey, 'XX', string.format('%.0f', newScore), jobId)\n redis.call('HSET', jobKey, 'state', 'delayed', 'delay', tostring(newDelay))\n emitEvent(eventsKey, 'delay-changed', jobId, {'delay', tostring(newDelay)})\n return 'ok'\n else\n return 'error:invalid_state'\n end\nend)\n\nredis.register_function('glidemq_promoteJob', function(keys, args)\n local jobKey = keys[1]\n local streamKey = keys[2]\n local scheduledKey = keys[3]\n local eventsKey = keys[4]\n local jobId = args[1]\n local exists = redis.call('EXISTS', jobKey)\n if exists == 0 then\n return 'error:not_found'\n end\n local state = redis.call('HGET', jobKey, 'state')\n if state ~= 'delayed' then\n return 'error:not_delayed'\n end\n redis.call('ZREM', scheduledKey, jobId)\n local jobPriority = tonumber(redis.call('HGET', jobKey, 'priority')) or 0\n local jobLifo = redis.call('HGET', jobKey, 'lifo')\n local prefix = string.sub(jobKey, 1, #jobKey - #('job:' .. jobId))\n if jobLifo == '1' then\n redis.call('RPUSH', prefix .. 'lifo', jobId)\n elseif jobPriority > 0 then\n redis.call('LPUSH', prefix .. 'priority', jobId)\n else\n xaddJob(streamKey, jobId, redis.call('HGET', jobKey, 'name'))\n end\n redis.call('HSET', jobKey, 'state', 'waiting', 'delay', '0')\n emitEvent(eventsKey, 'promoted', jobId, nil)\n return 'ok'\nend)\n\nredis.register_function('glidemq_moveActiveToDelayed', function(keys, args)\n local jobKey = keys[1]\n local streamKey = keys[2]\n local scheduledKey = keys[3]\n local eventsKey = keys[4]\n local jobId = args[1]\n local entryId = args[2]\n local now = tonumber(args[3]) or 0\n local delayedUntil = tonumber(args[4]) or now\n local group = args[5]\n local nextData = args[6]\n local broadcastMode = args[7] or '0'\n\n if redis.call('EXISTS', jobKey) == 0 then\n return 'error:not_found'\n end\n\n local state = redis.call('HGET', jobKey, 'state')\n if state ~= 'active' then\n return 'error:not_active'\n end\n\n if delayedUntil < now then\n delayedUntil = now\n end\n\n local priority = tonumber(redis.call('HGET', jobKey, 'priority')) or 0\n local delay = delayedUntil - now\n local score = priority * PRIORITY_SHIFT + delayedUntil\n\n pcall(redis.call, 'XACK', streamKey, group, entryId)\n if entryId ~= '' and broadcastMode ~= '1' then redis.call('XDEL', streamKey, entryId) end\n redis.call('ZADD', scheduledKey, string.format('%.0f', score), jobId)\n if nextData and nextData ~= '' then\n redis.call('HSET', jobKey, 'data', nextData, 'state', 'delayed', 'delay', tostring(delay))\n else\n redis.call('HSET', jobKey, 'state', 'delayed', 'delay', tostring(delay))\n end\n if entryId == '' then redis.call('DECR', string.sub(jobKey, 1, #jobKey - #('job:' .. jobId)) .. 'list-active') end\n -- Only release group slot if this is NOT an ordering-key step-job.\n -- Ordering-key jobs hold the slot until full completion to preserve per-key order.\n local jobOrdSeq = tonumber(redis.call('HGET', jobKey, 'orderingSeq')) or 0\n if jobOrdSeq <= 0 then\n releaseGroupSlotAndPromote(jobKey, jobId, now, nil)\n end\n emitEvent(eventsKey, 'delay-changed', jobId, {'delay', tostring(delay)})\n return 'ok'\nend)\n\nredis.register_function('glidemq_moveToWaitingChildren', function(keys, args)\n local jobKey = keys[1]\n local streamKey = keys[2]\n local eventsKey = keys[3]\n local jobId = args[1]\n local entryId = args[2]\n local group = args[3]\n local now = tonumber(args[4]) or 0\n local broadcastMode = args[5] or '0'\n\n local state = redis.call('HGET', jobKey, 'state')\n if not state then\n return 'error:not_found'\n end\n if state ~= 'active' then\n return 'error:not_active'\n end\n\n pcall(redis.call, 'XACK', streamKey, group, entryId)\n if entryId ~= '' and broadcastMode ~= '1' then redis.call('XDEL', streamKey, entryId) end\n redis.call('HSET', jobKey, 'state', 'waiting-children')\n\n local wcOrdSeq = tonumber(redis.call('HGET', jobKey, 'orderingSeq')) or 0\n if wcOrdSeq <= 0 then\n releaseGroupSlotAndPromote(jobKey, jobId, now)\n end\n\n -- Race condition check: children may have already completed before this call\n local prefix = string.sub(jobKey, 1, #jobKey - #('job:' .. jobId))\n local depsKey = prefix .. 'deps:' .. jobId\n local totalDeps = redis.call('SCARD', depsKey)\n if totalDeps > 0 then\n local depsCompleted = tonumber(redis.call('HGET', jobKey, 'depsCompleted')) or 0\n if depsCompleted >= totalDeps then\n redis.call('HSET', jobKey, 'state', 'waiting')\n xaddJob(streamKey, jobId, redis.call('HGET', jobKey, 'name'))\n emitEvent(eventsKey, 'active', jobId, nil)\n if entryId == '' then redis.call('DECR', prefix .. 'list-active') end\n return 'completed'\n end\n end\n\n if entryId == '' then redis.call('DECR', prefix .. 'list-active') end\n emitEvent(eventsKey, 'waiting-children', jobId, nil)\n return 'ok'\nend)\n\nredis.register_function('glidemq_rateLimitGroup', function(keys, args)\n local jobKey = keys[1]\n local streamKey = keys[2]\n local jobId = args[1]\n local entryId = args[2]\n local group = args[3]\n local duration = tonumber(args[4]) or 0\n local timestamp = tonumber(args[5]) or 0\n local broadcastMode = args[6] or '0'\n local currentJob = args[7] or 'requeue'\n local requeuePosition = args[8] or 'front'\n local extend = args[9] or 'max'\n\n local state = redis.call('HGET', jobKey, 'state')\n if state ~= 'active' then return 'error:not_active' end\n\n local groupKey = redis.call('HGET', jobKey, 'groupKey')\n if not groupKey or groupKey == '' then return 'error:no_group' end\n\n local prefix = string.sub(jobKey, 1, #jobKey - #('job:' .. jobId))\n local groupHashKey = prefix .. 'group:' .. groupKey\n local waitListKey = prefix .. 'groupq:' .. groupKey\n local rateLimitedKey = prefix .. 'ratelimited'\n local eventsKey = prefix .. 'events'\n\n if entryId ~= '' then pcall(redis.call, 'XACK', streamKey, group, entryId) end\n if entryId ~= '' and broadcastMode ~= '1' then redis.call('XDEL', streamKey, entryId) end\n if entryId == '' then redis.call('DECR', prefix .. 'list-active') end\n\n local active = tonumber(redis.call('HGET', groupHashKey, 'active')) or 0\n if active > 0 then redis.call('HSET', groupHashKey, 'active', tostring(active - 1)) end\n\n if currentJob == 'requeue' then\n local orderingSeq = tonumber(redis.call('HGET', jobKey, 'orderingSeq')) or 0\n local score\n if requeuePosition == 'front' then\n score = orderingSeq > 0 and orderingSeq or 0\n else\n local lastEntry = redis.call('ZRANGE', waitListKey, -1, -1, 'WITHSCORES')\n if lastEntry and #lastEntry >= 2 then\n score = tonumber(lastEntry[2]) + 1\n else\n score = orderingSeq > 0 and orderingSeq or (tonumber(jobId) or 0)\n end\n end\n redis.call('ZADD', waitListKey, score, jobId)\n redis.call('HSET', jobKey, 'state', 'group-waiting')\n else\n redis.call('ZADD', prefix .. 'failed', timestamp, jobId)\n local processedOn = tonumber(redis.call('HGET', jobKey, 'processedOn')) or timestamp\n redis.call('HSET', jobKey, 'state', 'failed', 'failedReason', 'group rate limited', 'finishedOn', tostring(timestamp), 'processedOn', tostring(processedOn))\n emitEvent(eventsKey, 'failed', jobId, {'failedReason', 'group rate limited'})\n local metricsKey = prefix .. 'metrics:failed'\n recordMetrics(metricsKey, timestamp, timestamp - processedOn)\n end\n\n local resumeAt = timestamp + duration\n if extend == 'max' then\n local existing = tonumber(redis.call('ZSCORE', rateLimitedKey, groupKey))\n if existing and existing > resumeAt then resumeAt = existing end\n end\n redis.call('ZADD', rateLimitedKey, resumeAt, groupKey)\n\n emitEvent(eventsKey, 'group-rate-limited', jobId, {\n 'groupKey', groupKey, 'duration', tostring(duration), 'resumeAt', tostring(resumeAt)\n })\n return tostring(resumeAt)\nend)\n\nredis.register_function('glidemq_rateLimitGroupExternal', function(keys, args)\n local rateLimitedKey = keys[1]\n local eventsKey = keys[2]\n local groupKey = args[1]\n local duration = tonumber(args[2]) or 0\n local timestamp = tonumber(args[3]) or 0\n local extend = args[4] or 'max'\n\n local resumeAt = timestamp + duration\n if extend == 'max' then\n local existing = tonumber(redis.call('ZSCORE', rateLimitedKey, groupKey))\n if existing and existing > resumeAt then resumeAt = existing end\n end\n redis.call('ZADD', rateLimitedKey, resumeAt, groupKey)\n\n emitEvent(eventsKey, 'group-rate-limited', '', {\n 'groupKey', groupKey, 'duration', tostring(duration), 'resumeAt', tostring(resumeAt)\n })\n return resumeAt\nend)\n\nredis.register_function('glidemq_searchByName', function(keys, args)\n local stateKey = keys[1]\n local stateType = args[1]\n local nameFilter = args[2]\n local limit = tonumber(args[3]) or 100\n local prefix = args[4]\n local matched = {}\n if stateType == 'zset' then\n local members = redis.call('ZRANGE', stateKey, 0, -1)\n for i = 1, #members do\n if #matched >= limit then break end\n local jobId = members[i]\n local jobKey = prefix .. 'job:' .. jobId\n local name = redis.call('HGET', jobKey, 'name')\n if name == nameFilter then\n matched[#matched + 1] = jobId\n end\n end\n elseif stateType == 'stream' then\n local cursor = '-'\n while #matched < limit do\n local entries = redis.call('XRANGE', stateKey, cursor, '+', 'COUNT', 1000)\n if #entries == 0 then break end\n for i = 1, #entries do\n if #matched >= limit then break end\n local fields = entries[i][2]\n local jobId = nil\n for j = 1, #fields, 2 do\n if fields[j] == 'jobId' then\n jobId = fields[j + 1]\n break\n end\n end\n if jobId then\n local jobKey = prefix .. 'job:' .. jobId\n local name = redis.call('HGET', jobKey, 'name')\n if name == nameFilter then\n matched[#matched + 1] = jobId\n end\n end\n end\n local lastId = entries[#entries][1]\n local dashPos = lastId:find('-')\n cursor = lastId:sub(1, dashPos) .. tostring(tonumber(lastId:sub(dashPos + 1)) + 1)\n end\n end\n return matched\nend)\n\nredis.register_function('glidemq_drain', function(keys, args)\n local streamKey = keys[1]\n local scheduledKey = keys[2]\n local eventsKey = keys[3]\n local idKey = keys[4]\n local lifoKey = keys[5]\n local priorityKey = keys[6]\n local drainDelayed = args[1] == '1'\n local group = args[2]\n local prefix = string.sub(idKey, 1, #idKey - 2)\n local removed = 0\n\n -- Build set of active entry IDs from PEL via paginated XPENDING\n local activeSet = {}\n local ok, pending = pcall(redis.call, 'XPENDING', streamKey, group, '-', '+', '10000')\n if ok and pending and #pending > 0 then\n for i = 1, #pending do\n activeSet[pending[i][1]] = true\n end\n -- Page through remaining PEL entries if there were exactly 10000\n while #pending == 10000 do\n local lastId = pending[#pending][1]\n local dashPos = lastId:find('-')\n local seq = tonumber(lastId:sub(dashPos + 1))\n local nextStart = lastId:sub(1, dashPos) .. tostring(seq + 1)\n ok, pending = pcall(redis.call, 'XPENDING', streamKey, group, nextStart, '+', '10000')\n if ok and pending and #pending > 0 then\n for i = 1, #pending do\n activeSet[pending[i][1]] = true\n end\n else\n break\n end\n end\n end\n\n -- Paginated XRANGE to avoid loading entire stream into memory\n local cursor = '-'\n while true do\n local entries = redis.call('XRANGE', streamKey, cursor, '+', 'COUNT', 1000)\n if #entries == 0 then break end\n\n local toDelete = {}\n for i = 1, #entries do\n local entryId = entries[i][1]\n if not activeSet[entryId] then\n toDelete[#toDelete + 1] = entryId\n local fields = entries[i][2]\n for j = 1, #fields, 2 do\n if fields[j] == 'jobId' and fields[j + 1] ~= '' then\n local jobId = fields[j + 1]\n redis.call('DEL', prefix .. 'job:' .. jobId, prefix .. 'log:' .. jobId, prefix .. 'deps:' .. jobId, prefix .. 'jstream:' .. jobId, prefix .. 'signals:' .. jobId)\n removed = removed + 1\n break\n end\n end\n end\n end\n if #toDelete > 0 then\n for i = 1, #toDelete, 1000 do\n redis.call('XDEL', streamKey, unpack(toDelete, i, math.min(i + 999, #toDelete)))\n end\n end\n\n -- Advance cursor past the last entry\n local lastId = entries[#entries][1]\n local dashPos = lastId:find('-')\n local seq = tonumber(lastId:sub(dashPos + 1))\n cursor = lastId:sub(1, dashPos) .. tostring(seq + 1)\n end\n\n -- Optionally drain delayed/scheduled jobs\n if drainDelayed then\n local offset = 0\n while true do\n local scheduled = redis.call('ZRANGE', scheduledKey, offset, offset + 999)\n if #scheduled == 0 then break end\n local batch = {}\n for j = 1, #scheduled do\n local jobId = scheduled[j]\n batch[#batch + 1] = prefix .. 'job:' .. jobId\n batch[#batch + 1] = prefix .. 'log:' .. jobId\n batch[#batch + 1] = prefix .. 'deps:' .. jobId\n batch[#batch + 1] = prefix .. 'jstream:' .. jobId\n batch[#batch + 1] = prefix .. 'signals:' .. jobId\n end\n redis.call('DEL', unpack(batch))\n removed = removed + #scheduled\n offset = offset + 1000\n end\n redis.call('DEL', scheduledKey)\n end\n\n -- Drain LIFO list: get all waiting job IDs, delete their hashes, then delete the list\n if lifoKey and lifoKey ~= '' then\n local lifoIds = redis.call('LRANGE', lifoKey, 0, -1)\n for i = 1, #lifoIds do\n local jobId = lifoIds[i]\n redis.call('DEL', prefix .. 'job:' .. jobId, prefix .. 'log:' .. jobId, prefix .. 'deps:' .. jobId, prefix .. 'jstream:' .. jobId, prefix .. 'signals:' .. jobId)\n removed = removed + 1\n end\n redis.call('DEL', lifoKey)\n end\n\n -- Drain priority list: get all waiting job IDs, delete their hashes, then delete the list\n if priorityKey and priorityKey ~= '' then\n local priorityIds = redis.call('LRANGE', priorityKey, 0, -1)\n for i = 1, #priorityIds do\n local jobId = priorityIds[i]\n redis.call('DEL', prefix .. 'job:' .. jobId, prefix .. 'log:' .. jobId, prefix .. 'deps:' .. jobId, prefix .. 'jstream:' .. jobId, prefix .. 'signals:' .. jobId)\n removed = removed + 1\n end\n redis.call('DEL', priorityKey)\n end\n\n if removed > 0 then\n emitEvent(eventsKey, 'drained', tostring(removed), nil)\n end\n return removed\nend)\n\nredis.register_function('glidemq_retryJobs', function(keys, args)\n local failedKey = keys[1]\n local scheduledKey = keys[2]\n local eventsKey = keys[3]\n local idKey = keys[4]\n local count = tonumber(args[1]) or 0\n local timestamp = tonumber(args[2])\n if not timestamp then return redis.error_reply('ERR invalid timestamp') end\n local prefix = string.sub(idKey, 1, #idKey - 2)\n local retried = 0\n\n while true do\n if count > 0 and retried >= count then break end\n local batchSize = 1000\n if count > 0 then\n batchSize = math.min(1000, count - retried)\n end\n local ids = redis.call('ZRANGE', failedKey, 0, batchSize - 1)\n if #ids == 0 then break end\n redis.call('ZREM', failedKey, unpack(ids))\n for i = 1, #ids do\n local jobId = ids[i]\n local jobKey = prefix .. 'job:' .. jobId\n if redis.call('EXISTS', jobKey) == 1 then\n local priority = tonumber(redis.call('HGET', jobKey, 'priority')) or 0\n local score = priority * PRIORITY_SHIFT + timestamp\n redis.call('ZADD', scheduledKey, score, jobId)\n redis.call('HSET', jobKey,\n 'state', 'delayed',\n 'attemptsMade', '0',\n 'failedReason', '',\n 'finishedOn', ''\n )\n retried = retried + 1\n end\n end\n end\n if retried > 0 then\n emitEvent(eventsKey, 'retried', tostring(retried), nil)\n end\n return retried\nend)\n\nredis.register_function('glidemq_healListActive', function(keys, args)\n local idKey = keys[1]\n local prefix = string.sub(idKey, 1, #idKey - 2)\n local listActiveKey = prefix .. 'list-active'\n local counter = tonumber(redis.call('GET', listActiveKey)) or 0\n if counter <= 0 then\n return 0\n end\n local pattern = prefix .. 'job:*'\n local cursor = '0'\n local actual = 0\n local maxIter = 500\n local iter = 0\n repeat\n iter = iter + 1\n local scanResult = redis.call('SCAN', cursor, 'MATCH', pattern, 'COUNT', 100)\n cursor = scanResult[1]\n local scannedKeys = scanResult[2]\n for i = 1, #scannedKeys do\n local jk = scannedKeys[i]\n local state = redis.call('HGET', jk, 'state')\n if state == 'active' then\n local lifo = redis.call('HGET', jk, 'lifo')\n if lifo == '1' then\n actual = actual + 1\n else\n local pri = tonumber(redis.call('HGET', jk, 'priority')) or 0\n if pri > 0 then\n actual = actual + 1\n end\n end\n end\n end\n until cursor == '0' or iter >= maxIter\n local drift = counter - actual\n if drift > 0 then\n redis.call('DECRBY', listActiveKey, drift)\n end\n return drift\nend)\n\nredis.register_function('glidemq_popLists', function(keys, args)\n local priorityKey = keys[1]\n local lifoKey = keys[2]\n local count = tonumber(args[1]) or 1\n local results = {}\n for i = 1, count do\n local id = redis.call('RPOP', priorityKey)\n if not id then break end\n results[#results + 1] = id\n end\n if #results > 0 then return results end\n for i = 1, count do\n local id = redis.call('RPOP', lifoKey)\n if not id then break end\n results[#results + 1] = id\n end\n return results\nend)\n\nredis.register_function('glidemq_suspend', function(keys, args)\n local jobKey = keys[1]\n local streamKey = keys[2]\n local eventsKey = keys[3]\n local suspendedKey = keys[4]\n local jobId = args[1]\n local entryId = args[2]\n local group = args[3]\n local now = tonumber(args[4]) or 0\n local reason = args[5] or ''\n local timeout = tonumber(args[6]) or 0\n local broadcastMode = args[7] or '0'\n\n local state = redis.call('HGET', jobKey, 'state')\n if not state then return 'error:not_found' end\n if state ~= 'active' then return 'error:not_active' end\n\n pcall(redis.call, 'XACK', streamKey, group, entryId)\n if entryId ~= '' and broadcastMode ~= '1' then redis.call('XDEL', streamKey, entryId) end\n\n local fields = {'state', 'suspended', 'suspendedAt', tostring(now)}\n if reason ~= '' then\n fields[#fields + 1] = 'suspendReason'\n fields[#fields + 1] = reason\n end\n if timeout > 0 then\n fields[#fields + 1] = 'suspendTimeout'\n fields[#fields + 1] = tostring(timeout)\n end\n redis.call('HSET', jobKey, unpack(fields))\n\n local deadline = timeout > 0 and (now + timeout) or 9999999999999\n redis.call('ZADD', suspendedKey, deadline, jobId)\n\n local ordSeq = tonumber(redis.call('HGET', jobKey, 'orderingSeq')) or 0\n if ordSeq <= 0 then\n releaseGroupSlotAndPromote(jobKey, jobId, now)\n end\n\n if entryId == '' then\n local prefix = string.sub(jobKey, 1, #jobKey - #('job:' .. jobId))\n redis.call('DECR', prefix .. 'list-active')\n end\n\n emitEvent(eventsKey, 'suspended', jobId, nil)\n return 'ok'\nend)\n\nredis.register_function('glidemq_signal', function(keys, args)\n local jobKey = keys[1]\n local streamKey = keys[2]\n local eventsKey = keys[3]\n local suspendedKey = keys[4]\n local signalsKey = keys[5]\n local jobId = args[1]\n local signalName = args[2]\n local signalData = args[3] or ''\n local now = tonumber(args[4]) or 0\n\n local state = redis.call('HGET', jobKey, 'state')\n if not state or state ~= 'suspended' then return 'not_suspended' end\n\n local signalJson = cjson.encode({name = signalName, data = signalData, receivedAt = now})\n redis.call('RPUSH', signalsKey, signalJson)\n\n local rawSignals = redis.call('LRANGE', signalsKey, 0, -1)\n local signalsArr = {}\n for i, s in ipairs(rawSignals) do signalsArr[i] = cjson.decode(s) end\n redis.call('HSET', jobKey, 'signals', cjson.encode(signalsArr))\n\n redis.call('HSET', jobKey, 'state', 'waiting')\n redis.call('ZREM', suspendedKey, jobId)\n\n local jobName = redis.call('HGET', jobKey, 'name') or ''\n xaddJob(streamKey, jobId, jobName)\n\n emitEvent(eventsKey, 'resumed', jobId, {'signal', signalName})\n return 'ok'\nend)\n\nredis.register_function('glidemq_sweepSuspended', function(keys, args)\n local suspendedKey = keys[1]\n local eventsKey = keys[2]\n local now = tonumber(args[1]) or 0\n local keyPrefix = args[2] or ''\n\n local expired = redis.call('ZRANGEBYSCORE', suspendedKey, '0', string.format('%.0f', now), 'LIMIT', 0, 100)\n if not expired or #expired == 0 then return 0 end\n\n local count = 0\n for _, jobId in ipairs(expired) do\n local id = tostring(jobId)\n local jobKey = keyPrefix .. 'job:' .. id\n local jState = redis.call('HGET', jobKey, 'state')\n if jState == 'suspended' then\n redis.call('HSET', jobKey, 'state', 'failed', 'failedReason', 'Suspend timeout exceeded')\n emitEvent(eventsKey, 'failed', id, {'failedReason', 'Suspend timeout exceeded'})\n count = count + 1\n end\n redis.call('ZREM', suspendedKey, id)\n end\n return count\nend)\n\nredis.register_function('glidemq_checkBudget', function(keys, args)\n local budgetKey = keys[1]\n local exists = redis.call('EXISTS', budgetKey)\n if exists == 0 then return 'no_budget' end\n local exceeded = redis.call('HGET', budgetKey, 'exceeded')\n if exceeded == '1' then return 'exceeded' end\n return 'ok'\nend)\n\nredis.register_function('glidemq_recordUsageAndCheckBudget', function(keys, args)\n local budgetKey = keys[1]\n local tokensJson = args[1]\n local costsJson = args[2]\n local weightedTotal = tonumber(args[3]) or 0\n local totalCost = tonumber(args[4]) or 0\n local maxTokensJson = args[5]\n local maxCostsJson = args[6]\n\n local exists = redis.call('EXISTS', budgetKey)\n if exists == 0 then return 'no_budget' end\n\n -- Increment weighted total and total cost\n if weightedTotal > 0 then redis.call('HINCRBYFLOAT', budgetKey, 'usedTokens', weightedTotal) end\n if totalCost > 0 then redis.call('HINCRBYFLOAT', budgetKey, 'usedCost', totalCost) end\n\n -- Increment per-category token counters\n local ok1, tokens = pcall(cjson.decode, tokensJson)\n if ok1 and type(tokens) == 'table' then\n for cat, val in pairs(tokens) do\n if tonumber(val) and tonumber(val) > 0 then\n redis.call('HINCRBYFLOAT', budgetKey, 'usedTokens:' .. cat, val)\n end\n end\n end\n\n -- Increment per-category cost counters\n local ok2, costs = pcall(cjson.decode, costsJson)\n if ok2 and type(costs) == 'table' then\n for cat, val in pairs(costs) do\n if tonumber(val) and tonumber(val) > 0 then\n redis.call('HINCRBYFLOAT', budgetKey, 'usedCost:' .. cat, val)\n end\n end\n end\n\n -- Check weighted total tokens cap\n local maxTotalTokens = tonumber(redis.call('HGET', budgetKey, 'maxTotalTokens')) or 0\n local usedTokens = tonumber(redis.call('HGET', budgetKey, 'usedTokens')) or 0\n if maxTotalTokens > 0 and usedTokens > maxTotalTokens then\n redis.call('HSET', budgetKey, 'exceeded', '1')\n return 'exceeded'\n end\n\n -- Check total cost cap\n local maxTotalCost = tonumber(redis.call('HGET', budgetKey, 'maxTotalCost')) or 0\n local usedCost = tonumber(redis.call('HGET', budgetKey, 'usedCost')) or 0\n if maxTotalCost > 0 and usedCost > maxTotalCost then\n redis.call('HSET', budgetKey, 'exceeded', '1')\n return 'exceeded'\n end\n\n -- Check per-category token limits\n local ok3, maxTokens = pcall(cjson.decode, maxTokensJson)\n if ok3 and type(maxTokens) == 'table' then\n for cat, limit in pairs(maxTokens) do\n local used = tonumber(redis.call('HGET', budgetKey, 'usedTokens:' .. cat)) or 0\n if tonumber(limit) and tonumber(limit) > 0 and used > tonumber(limit) then\n redis.call('HSET', budgetKey, 'exceeded', '1')\n return 'exceeded'\n end\n end\n end\n\n -- Check per-category cost limits\n local ok4, maxCosts = pcall(cjson.decode, maxCostsJson)\n if ok4 and type(maxCosts) == 'table' then\n for cat, limit in pairs(maxCosts) do\n local used = tonumber(redis.call('HGET', budgetKey, 'usedCost:' .. cat)) or 0\n if tonumber(limit) and tonumber(limit) > 0 and used > tonumber(limit) then\n redis.call('HSET', budgetKey, 'exceeded', '1')\n return 'exceeded'\n end\n end\n end\n\n return 'ok'\nend)\n";
6
+ export declare const LIBRARY_SOURCE = "#!lua name=glidemq\n\nlocal PRIORITY_SHIFT = 4398046511104\n\nlocal function emitEvent(eventsKey, eventType, jobId, extraFields)\n local fields = {'event', eventType, 'jobId', tostring(jobId)}\n if extraFields then\n for i = 1, #extraFields, 2 do\n fields[#fields + 1] = extraFields[i]\n fields[#fields + 1] = extraFields[i + 1]\n end\n end\n redis.call('XADD', eventsKey, 'MAXLEN', '~', '1000', '*', unpack(fields))\nend\n\nlocal function markOrderingDone(jobKey, jobId, hintOrderingKey, hintOrderingSeq)\n local orderingKey = hintOrderingKey\n if not orderingKey or orderingKey == '' then\n orderingKey = redis.call('HGET', jobKey, 'orderingKey')\n end\n if not orderingKey or orderingKey == '' then\n return\n end\n local orderingSeq = nil\n if hintOrderingSeq ~= nil and hintOrderingSeq ~= '' then\n orderingSeq = tonumber(hintOrderingSeq) or 0\n else\n orderingSeq = tonumber(redis.call('HGET', jobKey, 'orderingSeq')) or 0\n end\n if orderingSeq <= 0 then\n return\n end\n\n local prefix = string.sub(jobKey, 1, #jobKey - #('job:' .. jobId))\n local metaKey = prefix .. 'meta'\n local doneField = 'orderdone:' .. orderingKey\n local pendingKey = prefix .. 'orderdone:pending:' .. orderingKey\n\n local lastDone = tonumber(redis.call('HGET', metaKey, doneField)) or 0\n if orderingSeq <= lastDone then\n redis.call('HDEL', pendingKey, tostring(orderingSeq))\n return\n end\n\n redis.call('HSET', pendingKey, tostring(orderingSeq), '1')\n local advanced = lastDone\n while true do\n local nextSeq = advanced + 1\n if redis.call('HEXISTS', pendingKey, tostring(nextSeq)) == 0 then\n break\n end\n redis.call('HDEL', pendingKey, tostring(nextSeq))\n advanced = nextSeq\n end\n if advanced > lastDone then\n redis.call('HSET', metaKey, doneField, tostring(advanced))\n end\nend\n\n-- Advance nextSeq past any skip markers left by debounce on ordered jobs.\n-- Skip markers are hash fields 'skip:<seq>' set when debounce deletes an ordered job.\n-- Returns the advanced nextSeq. Cleans up markers via HDEL as it goes.\nlocal function advancePastSkips(groupHashKey, nextSeq)\n while redis.call('HDEL', groupHashKey, 'skip:' .. tostring(nextSeq)) == 1 do\n nextSeq = nextSeq + 1\n end\n return nextSeq\nend\n\n-- Refill token bucket using remainder accumulator for precision.\n-- tbRefillRate is in millitokens/second. Returns current millitokens after refill.\n-- Side effect: updates tbTokens, tbLastRefill, tbRefillRemainder on the group hash.\nlocal function tbRefill(groupHashKey, g, now)\n local tbCapacity = tonumber(g.tbCapacity) or 0\n if tbCapacity <= 0 then return 0 end\n local tbTokens = tonumber(g.tbTokens) or tbCapacity\n if tbTokens >= tbCapacity then return tbCapacity end\n local tbRefillRate = tonumber(g.tbRefillRate) or 0\n local tbLastRefill = tonumber(g.tbLastRefill) or now\n local tbRefillRemainder = tonumber(g.tbRefillRemainder) or 0\n local elapsed = now - tbLastRefill\n if elapsed <= 0 or tbRefillRate <= 0 then return tbTokens end\n -- Cap elapsed to prevent overflow in long-idle buckets\n local maxElapsed = math.ceil(tbCapacity * 1000 / tbRefillRate)\n if elapsed > maxElapsed then elapsed = maxElapsed end\n local raw = elapsed * tbRefillRate + tbRefillRemainder\n local added = math.floor(raw / 1000)\n local newRemainder = raw % 1000\n local newTokens = math.min(tbCapacity, tbTokens + added)\n redis.call('HSET', groupHashKey,\n 'tbTokens', tostring(newTokens),\n 'tbLastRefill', tostring(now),\n 'tbRefillRemainder', tostring(newRemainder))\n return newTokens\nend\n\nlocal function recordMetrics(metricsKey, timestamp, duration)\n local minuteTs = timestamp - (timestamp % 60000)\n local newCount = tonumber(redis.call('HINCRBY', metricsKey, 'm:' .. minuteTs .. ':c', 1))\n if duration > 0 then\n redis.call('HINCRBY', metricsKey, 'm:' .. minuteTs .. ':d', duration)\n end\n if newCount and (newCount % 1000 == 0) then\n local cutoff = minuteTs - 86400000\n local fields = redis.call('HKEYS', metricsKey)\n local toDelete = {}\n for _, f in ipairs(fields) do\n local ts = tonumber(string.match(f, '^m:(%d+):'))\n if ts and ts < cutoff then\n toDelete[#toDelete + 1] = f\n end\n end\n if #toDelete > 0 and #toDelete <= 1000 then\n redis.call('HDEL', metricsKey, unpack(toDelete))\n elseif #toDelete > 1000 then\n for i = 1, #toDelete, 1000 do\n redis.call('HDEL', metricsKey, unpack(toDelete, i, math.min(i + 999, #toDelete)))\n end\n end\n end\nend\n\nlocal function xaddJob(streamKey, jobId, jobName)\n redis.call('XADD', streamKey, '*', 'jobId', jobId, 'name', jobName or '')\nend\nlocal function groupqScore(jobKey, jobId)\n local seq = tonumber(redis.call('HGET', jobKey, 'orderingSeq'))\n if seq and seq > 0 then return seq end\n local num = tonumber(jobId)\n if num then return num end\n -- String IDs: use timestamp from job hash to preserve insertion order\n local ts = tonumber(redis.call('HGET', jobKey, 'timestamp'))\n if ts then return ts end\n return 0\nend\n\n\nlocal function releaseGroupSlotAndPromote(jobKey, jobId, now, hintGroupKey)\n local gk = hintGroupKey\n if not gk or gk == '' then\n gk = redis.call('HGET', jobKey, 'groupKey')\n end\n if not gk or gk == '' then return end\n local prefix = string.sub(jobKey, 1, #jobKey - #('job:' .. jobId))\n local groupHashKey = prefix .. 'group:' .. gk\n -- Load all group fields in one call\n local gFields = redis.call('HGETALL', groupHashKey)\n local g = {}\n for gf = 1, #gFields, 2 do g[gFields[gf]] = gFields[gf + 1] end\n local cur = tonumber(g.active) or 0\n local newActive = (cur > 0) and (cur - 1) or 0\n if cur > 0 then\n redis.call('HSET', groupHashKey, 'active', tostring(newActive))\n end\n local waitListKey = prefix .. 'groupq:' .. gk\n local waitLen = redis.call('ZCARD', waitListKey)\n if waitLen == 0 then return end\n -- Concurrency gate: if still at or above max after decrement, do not promote\n local maxConc = tonumber(g.maxConcurrency) or 0\n if maxConc > 0 and newActive >= maxConc then return end\n -- Rate limit gate (skip if now is nil or 0 for safe fallback)\n -- Only blocks promotion; does NOT increment rateCount. moveToActive handles counting.\n local rateMax = tonumber(g.rateMax) or 0\n local rateRemaining = 0\n local ts = tonumber(now) or 0\n if ts > 0 and rateMax > 0 then\n local rateDuration = tonumber(g.rateDuration) or 0\n if rateDuration > 0 then\n local rateWindowStart = tonumber(g.rateWindowStart) or 0\n local rateCount = tonumber(g.rateCount) or 0\n if ts - rateWindowStart < rateDuration then\n if rateCount >= rateMax then\n -- Window active and at capacity: do not promote, register for scheduler\n local rateLimitedKey = prefix .. 'ratelimited'\n redis.call('ZADD', rateLimitedKey, rateWindowStart + rateDuration, gk)\n return\n end\n rateRemaining = rateMax - rateCount\n end\n end\n end\n -- Token bucket gate: check head job cost before promoting\n local tbCap = tonumber(g.tbCapacity) or 0\n if ts > 0 and tbCap > 0 then\n local tbTokensCur = tbRefill(groupHashKey, g, ts)\n -- Peek at head job, skipping tombstones and DLQ'd jobs (up to 10 iterations)\n local tbCheckPasses = 0\n local tbOk = false\n while tbCheckPasses < 10 do\n tbCheckPasses = tbCheckPasses + 1\n local headMembers = redis.call('ZRANGE', waitListKey, 0, 0)\n local headJobId = headMembers[1]\n if not headJobId then break end\n local headJobKey = prefix .. 'job:' .. headJobId\n -- Tombstone guard: job hash deleted - remove and check next\n if redis.call('EXISTS', headJobKey) == 0 then\n redis.call('ZREM', waitListKey, headJobId)\n else\n local headCost = tonumber(redis.call('HGET', headJobKey, 'cost')) or 1000\n -- DLQ guard: cost > capacity - remove, fail, check next\n if headCost > tbCap then\n local metricsKey = prefix .. 'metrics:failed'\n local processedOn = tonumber(redis.call('HGET', headJobKey, 'processedOn')) or ts\n redis.call('ZREM', waitListKey, headJobId)\n redis.call('ZADD', prefix .. 'failed', ts, headJobId)\n redis.call('HSET', headJobKey,\n 'state', 'failed',\n 'failedReason', 'cost exceeds token bucket capacity',\n 'finishedOn', tostring(ts))\n emitEvent(prefix .. 'events', 'failed', headJobId, {'failedReason', 'cost exceeds token bucket capacity'})\n recordMetrics(metricsKey, ts, ts - processedOn)\n elseif tbTokensCur < headCost then\n -- Not enough tokens: register delay and skip promotion\n local tbRateVal = tonumber(g.tbRefillRate) or 0\n if tbRateVal <= 0 then break end\n local tbDelayMs = math.ceil((headCost - tbTokensCur) * 1000 / tbRateVal)\n local rateLimitedKey = prefix .. 'ratelimited'\n redis.call('ZADD', rateLimitedKey, ts + tbDelayMs, gk)\n return\n else\n tbOk = true\n break\n end\n end\n end\n if not tbOk and tbCheckPasses >= 10 then return end\n end\n -- Calculate how many slots are available for promotion\n local available = 1\n if maxConc > 0 then\n available = maxConc - newActive\n else\n available = math.min(waitLen, 1000)\n end\n -- Cap by rate limit remaining if a window is active\n if rateRemaining > 0 then\n available = math.min(available, rateRemaining)\n end\n local streamKey = prefix .. 'stream'\n local nextSeq = tonumber(g.nextSeq) or 0\n if nextSeq > 0 then\n local advanced = advancePastSkips(groupHashKey, nextSeq)\n if advanced > nextSeq then\n redis.call('HSET', groupHashKey, 'nextSeq', tostring(advanced))\n nextSeq = advanced\n end\n end\n local promoted = 0\n local maxIter = available + 20\n local iter = 0\n while promoted < available and iter < maxIter do\n iter = iter + 1\n local zpResult = redis.call('ZPOPMIN', waitListKey, 1)\n local nextJobId = zpResult[1]\n if not nextJobId then break end\n local nextJobKey = prefix .. 'job:' .. nextJobId\n -- Skip stale entries (job no longer in group-waiting state)\n local nextState = redis.call('HGET', nextJobKey, 'state')\n if nextState ~= 'group-waiting' then\n -- Stale: already processed via another path. Discard.\n else\n if nextSeq > 0 then\n local jobSeq = tonumber(redis.call('HGET', nextJobKey, 'orderingSeq')) or 0\n if jobSeq > nextSeq then\n -- Future job: put back and stop promoting\n redis.call('ZADD', waitListKey, jobSeq, nextJobId)\n break\n end\n end\n xaddJob(streamKey, nextJobId, redis.call('HGET', nextJobKey, 'name'))\n redis.call('HSET', nextJobKey, 'state', 'waiting')\n promoted = promoted + 1\n -- Don't advance nextSeq here; moveToActive does it on actual activation\n if nextSeq > 0 then nextSeq = nextSeq + 1 end -- local only, for stale skip in next iteration\n end\n end\nend\n\nlocal function expireJob(jobKey, jobId, prefix, now, curState, hintOrderingKey, hintOrderingSeq, hintGroupKey)\n if curState == 'failed' then return true end\n local wasActive = (curState == 'active')\n local failedKey = prefix .. 'failed'\n local eventsKey = prefix .. 'events'\n local metricsKey = prefix .. 'metrics:failed'\n local processedOn = tonumber(redis.call('HGET', jobKey, 'processedOn')) or now\n redis.call('ZADD', failedKey, now, jobId)\n redis.call('HSET', jobKey,\n 'state', 'failed',\n 'failedReason', 'expired',\n 'finishedOn', tostring(now))\n markOrderingDone(jobKey, jobId, hintOrderingKey, hintOrderingSeq)\n -- Only release group slot if the job was actually active (held a slot)\n if wasActive then\n releaseGroupSlotAndPromote(jobKey, jobId, now, hintGroupKey)\n end\n emitEvent(eventsKey, 'expired', jobId, nil)\n recordMetrics(metricsKey, now, now - processedOn)\n return true\nend\n\nlocal function checkExpired(jobKey, jobId, prefix, now)\n local expireAt = tonumber(redis.call('HGET', jobKey, 'expireAt'))\n if not expireAt or expireAt <= 0 then return false end\n if now <= expireAt then return false end\n -- Idempotency guard: if already expired, skip side effects\n local curState = redis.call('HGET', jobKey, 'state')\n return expireJob(jobKey, jobId, prefix, now, curState, nil, nil, nil)\nend\n\nlocal function advanceIdCounter(idKey, customId)\n if not string.match(customId, '^%d+$') then return end\n local numericId = tonumber(customId)\n if numericId and numericId > 0 then\n local cur = tonumber(redis.call('GET', idKey)) or 0\n if numericId > cur then\n redis.call('SET', idKey, customId)\n end\n end\nend\n\nlocal function extractOrderingKeyFromOpts(optsJson)\n if not optsJson or optsJson == '' then\n return ''\n end\n local ok, decoded = pcall(cjson.decode, optsJson)\n if not ok or type(decoded) ~= 'table' then\n return ''\n end\n local ordering = decoded['ordering']\n if type(ordering) ~= 'table' then\n return ''\n end\n local key = ordering['key']\n if key == nil then\n return ''\n end\n return tostring(key)\nend\n\nlocal function extractLifoFromOpts(optsJson)\n if not optsJson or optsJson == '' then return 0 end\n local ok, decoded = pcall(cjson.decode, optsJson)\n if not ok or type(decoded) ~= 'table' then return 0 end\n return (decoded['lifo'] == true or decoded['lifo'] == 1) and 1 or 0\nend\n\nlocal function extractGroupConcurrencyFromOpts(optsJson)\n if not optsJson or optsJson == '' then\n return 0\n end\n local ok, decoded = pcall(cjson.decode, optsJson)\n if not ok or type(decoded) ~= 'table' then\n return 0\n end\n local ordering = decoded['ordering']\n if type(ordering) ~= 'table' then\n return 0\n end\n local conc = ordering['concurrency']\n if conc == nil then\n return 0\n end\n return tonumber(conc) or 0\nend\n\nlocal function extractGroupRateLimitFromOpts(optsJson)\n if not optsJson or optsJson == '' then\n return 0, 0\n end\n local ok, decoded = pcall(cjson.decode, optsJson)\n if not ok or type(decoded) ~= 'table' then\n return 0, 0\n end\n local ordering = decoded['ordering']\n if type(ordering) ~= 'table' then\n return 0, 0\n end\n local rl = ordering['rateLimit']\n if type(rl) ~= 'table' then\n return 0, 0\n end\n local max = tonumber(rl['max']) or 0\n local duration = tonumber(rl['duration']) or 0\n return max, duration\nend\n\nlocal function extractTokenBucketFromOpts(optsJson)\n if not optsJson or optsJson == '' then return 0, 0 end\n local ok, decoded = pcall(cjson.decode, optsJson)\n if not ok or type(decoded) ~= 'table' then return 0, 0 end\n local ordering = decoded['ordering']\n if type(ordering) ~= 'table' then return 0, 0 end\n local tb = ordering['tokenBucket']\n if type(tb) ~= 'table' then return 0, 0 end\n local capacity = tonumber(tb['capacity']) or 0\n local refillRate = tonumber(tb['refillRate']) or 0\n return math.floor(capacity * 1000), math.floor(refillRate * 1000)\nend\n\nlocal function extractCostFromOpts(optsJson)\n if not optsJson or optsJson == '' then return 0 end\n local ok, decoded = pcall(cjson.decode, optsJson)\n if not ok or type(decoded) ~= 'table' then return 0 end\n local cost = tonumber(decoded['cost']) or 0\n return math.floor(cost * 1000)\nend\n\nlocal function extractTtlFromOpts(optsJson)\n if not optsJson or optsJson == '' then return 0 end\n local ok, decoded = pcall(cjson.decode, optsJson)\n if not ok or type(decoded) ~= 'table' then return 0 end\n return tonumber(decoded['ttl']) or 0\nend\n\nlocal function extractLockDurationFromOpts(optsJson)\n if not optsJson or optsJson == '' then return 0 end\n local ok, decoded = pcall(cjson.decode, optsJson)\n if not ok or type(decoded) ~= 'table' then return 0 end\n return tonumber(decoded['lockDuration']) or 0\nend\n\n-- Apply stall logic to a job: increment stalledCount, fail if over max, else emit stalled event.\n-- Returns true if the job was moved to failed, false if only stalled.\nlocal function applyStalledLogic(jobKey, jobId, prefix, eventsKey, failedKey, maxStalledCount, timestamp)\n local stalledCount = redis.call('HINCRBY', jobKey, 'stalledCount', 1)\n if stalledCount > maxStalledCount then\n local metricsKey = prefix .. 'metrics:failed'\n local processedOn = tonumber(redis.call('HGET', jobKey, 'processedOn')) or timestamp\n redis.call('ZADD', failedKey, timestamp, jobId)\n redis.call('HSET', jobKey,\n 'state', 'failed',\n 'failedReason', 'job stalled more than maxStalledCount',\n 'finishedOn', tostring(timestamp)\n )\n markOrderingDone(jobKey, jobId)\n releaseGroupSlotAndPromote(jobKey, jobId, timestamp)\n emitEvent(eventsKey, 'failed', jobId, {\n 'failedReason', 'job stalled more than maxStalledCount'\n })\n recordMetrics(metricsKey, timestamp, timestamp - processedOn)\n return true\n else\n emitEvent(eventsKey, 'stalled', jobId, nil)\n return false\n end\nend\n\n-- Remove excess jobs from a sorted set in capped, stack-safe batches.\n-- Deletes job hashes and removes from the set in chunks of 1000.\nlocal function removeExcessJobs(setKey, prefix, ids)\n for i = 1, #ids do\n redis.call('DEL', prefix .. 'job:' .. ids[i])\n end\n for i = 1, #ids, 1000 do\n redis.call('ZREM', setKey, unpack(ids, i, math.min(i + 999, #ids)))\n end\nend\n\nredis.register_function('glidemq_version', function(keys, args)\n return '81'\nend)\n\nredis.register_function('glidemq_addJob', function(keys, args)\n local idKey = keys[1]\n assert(string.sub(idKey, -3) == ':id', 'unexpected key format: ' .. idKey)\n local streamKey = keys[2]\n local scheduledKey = keys[3]\n local eventsKey = keys[4]\n local jobName = args[1]\n local jobData = args[2]\n local jobOpts = args[3]\n local timestamp = tonumber(args[4])\n local delay = tonumber(args[5]) or 0\n local priority = tonumber(args[6]) or 0\n local parentId = args[7] or ''\n local maxAttempts = tonumber(args[8]) or 0\n local orderingKey = args[9] or ''\n local groupConcurrency = tonumber(args[10]) or 0\n local groupRateMax = tonumber(args[11]) or 0\n local groupRateDuration = tonumber(args[12]) or 0\n local tbCapacity = tonumber(args[13]) or 0\n local tbRefillRate = tonumber(args[14]) or 0\n local jobCost = tonumber(args[15]) or 0\n local ttl = tonumber(args[16]) or 0\n local customJobId = args[17] or ''\n local lifo = tonumber(args[18]) or 0\n local parentQueue = args[19] or ''\n local schedulerName = args[20] or ''\n local skipEvents = args[21] or '0'\n local prefix = string.sub(idKey, 1, #idKey - 2)\n local jobIdStr\n local jobKey\n if customJobId ~= '' then\n jobKey = prefix .. 'job:' .. customJobId\n if redis.call('EXISTS', jobKey) == 1 then\n return 'duplicate'\n end\n jobIdStr = customJobId\n advanceIdCounter(idKey, customJobId)\n else\n local jobId = redis.call('INCR', idKey)\n jobIdStr = tostring(jobId)\n jobKey = prefix .. 'job:' .. jobIdStr\n end\n local useGroupConcurrency = (orderingKey ~= '')\n local orderingSeq = 0\n if orderingKey ~= '' then\n local orderingMetaKey = prefix .. 'ordering'\n orderingSeq = redis.call('HINCRBY', orderingMetaKey, orderingKey, 1)\n end\n if useGroupConcurrency then\n if groupConcurrency < 1 then groupConcurrency = 1 end\n local groupHashKey = prefix .. 'group:' .. orderingKey\n local curMax = tonumber(redis.call('HGET', groupHashKey, 'maxConcurrency')) or 0\n if curMax ~= groupConcurrency then\n redis.call('HSET', groupHashKey, 'maxConcurrency', tostring(groupConcurrency))\n end\n -- Initialize nextSeq for ordered promotion\n if orderingSeq > 0 and redis.call('HEXISTS', groupHashKey, 'nextSeq') == 0 then\n redis.call('HSET', groupHashKey, 'nextSeq', '1')\n end\n -- Upsert rate limit fields on group hash\n if groupRateMax > 0 then\n local curRateMax = tonumber(redis.call('HGET', groupHashKey, 'rateMax')) or 0\n if curRateMax ~= groupRateMax then\n redis.call('HSET', groupHashKey, 'rateMax', tostring(groupRateMax))\n end\n local curRateDuration = tonumber(redis.call('HGET', groupHashKey, 'rateDuration')) or 0\n if curRateDuration ~= groupRateDuration then\n redis.call('HSET', groupHashKey, 'rateDuration', tostring(groupRateDuration))\n end\n else\n -- Clear stale rate limit fields if group was previously rate-limited\n local oldRateMax = tonumber(redis.call('HGET', groupHashKey, 'rateMax')) or 0\n if oldRateMax > 0 then\n redis.call('HDEL', groupHashKey, 'rateMax', 'rateDuration', 'rateWindowStart', 'rateCount')\n end\n end\n -- Upsert token bucket fields on group hash\n if tbCapacity > 0 then\n local curTbCap = tonumber(redis.call('HGET', groupHashKey, 'tbCapacity')) or 0\n if curTbCap ~= tbCapacity then\n redis.call('HSET', groupHashKey, 'tbCapacity', tostring(tbCapacity))\n end\n local curTbRate = tonumber(redis.call('HGET', groupHashKey, 'tbRefillRate')) or 0\n if curTbRate ~= tbRefillRate then\n redis.call('HSET', groupHashKey, 'tbRefillRate', tostring(tbRefillRate))\n end\n -- Initialize tokens on first setup\n if curTbCap == 0 then\n redis.call('HSET', groupHashKey,\n 'tbTokens', tostring(tbCapacity),\n 'tbLastRefill', tostring(timestamp),\n 'tbRefillRemainder', '0')\n end\n -- Validate cost (explicit or default 1000 millitokens) against capacity\n local effectiveCost = (jobCost > 0) and jobCost or 1000\n if effectiveCost > tbCapacity then\n return 'ERR:COST_EXCEEDS_CAPACITY'\n end\n else\n -- Clear stale tb fields\n local oldTbCap = tonumber(redis.call('HGET', groupHashKey, 'tbCapacity')) or 0\n if oldTbCap > 0 then\n redis.call('HDEL', groupHashKey, 'tbCapacity', 'tbRefillRate', 'tbTokens', 'tbLastRefill', 'tbRefillRemainder')\n end\n end\n end\n local hashFields = {\n 'id', jobIdStr,\n 'name', jobName,\n 'data', jobData,\n 'opts', jobOpts,\n 'timestamp', tostring(timestamp),\n 'attemptsMade', '0',\n 'delay', tostring(delay),\n 'priority', tostring(priority),\n 'maxAttempts', tostring(maxAttempts)\n }\n if useGroupConcurrency then\n hashFields[#hashFields + 1] = 'groupKey'\n hashFields[#hashFields + 1] = orderingKey\n if orderingSeq > 0 then\n hashFields[#hashFields + 1] = 'orderingSeq'\n hashFields[#hashFields + 1] = tostring(orderingSeq)\n end\n end\n if jobCost > 0 then\n hashFields[#hashFields + 1] = 'cost'\n hashFields[#hashFields + 1] = tostring(jobCost)\n end\n if ttl > 0 then\n hashFields[#hashFields + 1] = 'expireAt'\n hashFields[#hashFields + 1] = tostring(timestamp + ttl)\n end\n if parentId ~= '' then\n hashFields[#hashFields + 1] = 'parentId'\n hashFields[#hashFields + 1] = parentId\n if parentQueue ~= '' then\n hashFields[#hashFields + 1] = 'parentQueue'\n hashFields[#hashFields + 1] = parentQueue\n end\n end\n if schedulerName ~= '' then\n hashFields[#hashFields + 1] = 'schedulerName'\n hashFields[#hashFields + 1] = schedulerName\n end\n if lifo > 0 then\n hashFields[#hashFields + 1] = 'lifo'\n hashFields[#hashFields + 1] = '1'\n end\n if delay > 0 or priority > 0 then\n hashFields[#hashFields + 1] = 'state'\n hashFields[#hashFields + 1] = delay > 0 and 'delayed' or 'prioritized'\n else\n hashFields[#hashFields + 1] = 'state'\n hashFields[#hashFields + 1] = 'waiting'\n end\n redis.call('HSET', jobKey, unpack(hashFields))\n -- Register child in parent's deps set when parentDepsKey is provided (keys[5])\n if parentId ~= '' and parentQueue ~= '' and #keys >= 5 then\n local parentDepsKey = keys[5]\n -- prefix includes trailing colon (glide:{Q}:), so strip it for depsMember\n local queuePrefix = string.sub(prefix, 1, #prefix - 1)\n local depsMember = queuePrefix .. ':' .. jobIdStr\n redis.call('SADD', parentDepsKey, depsMember)\n end\n if delay > 0 then\n local score = priority * PRIORITY_SHIFT + (timestamp + delay)\n redis.call('ZADD', scheduledKey, score, jobIdStr)\n elseif priority > 0 then\n local score = priority * PRIORITY_SHIFT\n redis.call('ZADD', scheduledKey, score, jobIdStr)\n elseif lifo > 0 then\n local lifoKey = prefix .. 'lifo'\n redis.call('RPUSH', lifoKey, jobIdStr)\n else\n xaddJob(streamKey, jobIdStr, jobName)\n end\n if skipEvents ~= '1' then emitEvent(eventsKey, 'added', jobIdStr, {'name', jobName}) end\n return jobIdStr\nend)\n\nredis.register_function('glidemq_promote', function(keys, args)\n local scheduledKey = keys[1]\n local streamKey = keys[2]\n local eventsKey = keys[3]\n local now = tonumber(args[1])\n local MAX_PROMOTIONS = 1000\n local count = 0\n local cursorMin = 0\n while count < MAX_PROMOTIONS do\n local nextEntry = redis.call('ZRANGEBYSCORE', scheduledKey, string.format('%.0f', cursorMin), '+inf', 'WITHSCORES', 'LIMIT', 0, 1)\n if not nextEntry or #nextEntry == 0 then\n break\n end\n local firstScore = tonumber(nextEntry[2]) or 0\n local priority = math.floor(firstScore / PRIORITY_SHIFT)\n local minScore = priority * PRIORITY_SHIFT\n local maxDueScore = minScore + now\n local remaining = MAX_PROMOTIONS - count\n local members = redis.call(\n 'ZRANGEBYSCORE',\n scheduledKey,\n string.format('%.0f', minScore),\n string.format('%.0f', maxDueScore),\n 'LIMIT',\n 0,\n remaining\n )\n for i = 1, #members do\n local jobId = members[i]\n local prefix = string.sub(scheduledKey, 1, #scheduledKey - 9)\n local jobKey = prefix .. 'job:' .. jobId\n redis.call('ZREM', scheduledKey, jobId)\n if not checkExpired(jobKey, jobId, prefix, now) then\n local jobLifo = redis.call('HGET', jobKey, 'lifo')\n if jobLifo == '1' then\n local lifoKey = prefix .. 'lifo'\n redis.call('RPUSH', lifoKey, jobId)\n elseif priority > 0 then\n -- Priority jobs go to dedicated priority list, checked before LIFO and stream.\n -- LPUSH so RPOP retrieves lowest-score (highest-priority) job first.\n local priorityKey = prefix .. 'priority'\n redis.call('LPUSH', priorityKey, jobId)\n else\n xaddJob(streamKey, jobId, redis.call('HGET', jobKey, 'name'))\n end\n redis.call('HSET', jobKey, 'state', 'waiting')\n emitEvent(eventsKey, 'promoted', jobId, nil)\n count = count + 1\n end\n end\n cursorMin = (priority + 1) * PRIORITY_SHIFT\n end\n return count\nend)\n\nredis.register_function('glidemq_nextDue', function(keys, args)\n local scheduledKey = keys[1]\n local rateLimitedKey = keys[2]\n local nextDue = nil\n\n local scheduled = redis.call('ZRANGE', scheduledKey, 0, 0, 'WITHSCORES')\n if scheduled and #scheduled >= 2 then\n local score = tonumber(scheduled[2]) or 0\n local due = score % PRIORITY_SHIFT\n nextDue = due\n end\n\n local limited = redis.call('ZRANGE', rateLimitedKey, 0, 0, 'WITHSCORES')\n if limited and #limited >= 2 then\n local limitedDue = tonumber(limited[2]) or 0\n if (not nextDue) or limitedDue < nextDue then\n nextDue = limitedDue\n end\n end\n\n if not nextDue then\n return -1\n end\n\n return math.floor(nextDue)\nend)\n\nredis.register_function('glidemq_tryLock', function(keys, args)\n local lockKey = keys[1]\n local token = args[1]\n local ttl = tonumber(args[2]) or 1000\n local result = redis.call('SET', lockKey, token, 'PX', tostring(ttl), 'NX')\n if result then\n return 1\n end\n return 0\nend)\n\nredis.register_function('glidemq_unlock', function(keys, args)\n local lockKey = keys[1]\n local token = args[1]\n local current = redis.call('GET', lockKey)\n if current == token then\n redis.call('DEL', lockKey)\n return 1\n end\n return 0\nend)\n\nredis.register_function('glidemq_renewLock', function(keys, args)\n local lockKey = keys[1]\n local token = args[1]\n local ttl = tonumber(args[2]) or 1000\n local current = redis.call('GET', lockKey)\n if current == token then\n redis.call('PEXPIRE', lockKey, ttl)\n return 1\n end\n return 0\nend)\n\nredis.register_function('glidemq_complete', function(keys, args)\n local streamKey = keys[1]\n local completedKey = keys[2]\n local eventsKey = keys[3]\n local jobKey = keys[4]\n local metricsKey = keys[5]\n local jobId = args[1]\n assert(string.sub(jobKey, -(4 + #jobId)) == 'job:' .. jobId, 'unexpected key format: ' .. jobKey)\n local entryId = args[2]\n local returnvalue = args[3]\n local timestamp = tonumber(args[4])\n local group = args[5]\n local removeMode = args[6] or '0'\n local removeCount = tonumber(args[7]) or 0\n local removeAge = tonumber(args[8]) or 0\n local depsMember = args[9] or ''\n local parentId = args[10] or ''\n local broadcastMode = args[11] or '0'\n local processedOn = tonumber(redis.call('HGET', jobKey, 'processedOn')) or timestamp\n if entryId ~= '' then redis.call('XACK', streamKey, group, entryId) end\n if entryId ~= '' and broadcastMode ~= '1' then redis.call('XDEL', streamKey, entryId) end\n redis.call('ZADD', completedKey, timestamp, jobId)\n redis.call('HSET', jobKey,\n 'state', 'completed',\n 'returnvalue', returnvalue,\n 'finishedOn', tostring(timestamp)\n )\n markOrderingDone(jobKey, jobId)\n releaseGroupSlotAndPromote(jobKey, jobId, timestamp)\n emitEvent(eventsKey, 'completed', jobId, {'returnvalue', returnvalue})\n recordMetrics(metricsKey, timestamp, timestamp - processedOn)\n local prefix = string.sub(jobKey, 1, #jobKey - #('job:' .. jobId))\n if broadcastMode ~= '1' then\n if removeMode == 'true' then\n redis.call('ZREM', completedKey, jobId)\n redis.call('DEL', jobKey)\n elseif removeMode == 'count' and removeCount > 0 then\n local total = redis.call('ZCARD', completedKey)\n if total > removeCount then\n local excess = redis.call('ZRANGE', completedKey, 0, math.min(total - removeCount, 1000) - 1)\n if #excess > 0 then removeExcessJobs(completedKey, prefix, excess) end\n end\n elseif removeMode == 'age_count' then\n if removeAge > 0 then\n local cutoff = timestamp - (removeAge * 1000)\n local old = redis.call('ZRANGEBYSCORE', completedKey, '0', string.format('%.0f', cutoff), 'LIMIT', 0, 1000)\n if #old > 0 then removeExcessJobs(completedKey, prefix, old) end\n end\n if removeCount > 0 then\n local total = redis.call('ZCARD', completedKey)\n if total > removeCount then\n local excess = redis.call('ZRANGE', completedKey, 0, math.min(total - removeCount, 1000) - 1)\n if #excess > 0 then removeExcessJobs(completedKey, prefix, excess) end\n end\n end\n end\n end\n if depsMember ~= '' and parentId ~= '' and #keys >= 9 then\n local parentDepsKey = keys[6]\n local parentJobKey = keys[7]\n local parentStreamKey = keys[8]\n local parentEventsKey = keys[9]\n local depMarker = 'depdone:' .. depsMember\n if redis.call('HSETNX', parentJobKey, depMarker, '1') == 1 then\n local doneCount = redis.call('HINCRBY', parentJobKey, 'depsCompleted', 1)\n local totalDeps = redis.call('SCARD', parentDepsKey)\n local remaining = totalDeps - doneCount\n if remaining <= 0 then\n local parentState = redis.call('HGET', parentJobKey, 'state')\n if parentState == 'waiting-children' then\n redis.call('HSET', parentJobKey, 'state', 'waiting')\n xaddJob(parentStreamKey, parentId, redis.call('HGET', parentJobKey, 'name'))\n emitEvent(parentEventsKey, 'active', parentId, nil)\n end\n end\n end\n end\n -- DAG multi-parent: notify additional same-queue parents via parents SET\n local parentsKey = prefix .. 'parents:' .. jobId\n local dagParents = redis.call('SMEMBERS', parentsKey)\n if dagParents and #dagParents > 0 then\n local childQueuePrefix = string.sub(prefix, 1, #prefix - 1)\n local dagDepsMember = childQueuePrefix .. ':' .. jobId\n for pi = 1, #dagParents do\n local pEntry = dagParents[pi]\n -- Format: \"parentQueuePrefix:parentId\" where prefix is glide:{qname}\n -- Must find LAST colon (not first, since prefix contains colons in {})\n local pSep = pEntry:find(':([^:]+)$')\n if pSep then\n local pQueue = string.sub(pEntry, 1, pSep - 1)\n local pId = string.sub(pEntry, pSep + 1)\n -- Only handle same-queue parents atomically in Lua.\n -- Cross-queue parents are skipped here because their keys use a different\n -- hash tag (different prefix), which may route to a different cluster slot.\n -- The TypeScript layer handles cross-queue parent notification separately.\n if pQueue == childQueuePrefix then\n local pPrefix = prefix\n local pJobKey = pPrefix .. 'job:' .. pId\n local pDepsKey = pPrefix .. 'deps:' .. pId\n local pStreamKey = pPrefix .. 'stream'\n local pEventsKey = pPrefix .. 'events'\n local pDepMarker = 'depdone:' .. dagDepsMember\n if redis.call('HSETNX', pJobKey, pDepMarker, '1') == 1 then\n local pDoneCount = redis.call('HINCRBY', pJobKey, 'depsCompleted', 1)\n local pTotalDeps = redis.call('SCARD', pDepsKey)\n if pTotalDeps - pDoneCount <= 0 then\n local pState = redis.call('HGET', pJobKey, 'state')\n if pState == 'waiting-children' then\n redis.call('HSET', pJobKey, 'state', 'waiting')\n xaddJob(pStreamKey, pId, redis.call('HGET', pJobKey, 'name'))\n emitEvent(pEventsKey, 'active', pId, nil)\n end\n end\n end\n end\n end\n end\n end\n if entryId == '' then redis.call('DECR', prefix .. 'list-active') end\n return 1\nend)\n\nredis.register_function('glidemq_completeAndFetchNext', function(keys, args)\n local streamKey = keys[1]\n local completedKey = keys[2]\n local eventsKey = keys[3]\n local jobKey = keys[4]\n local metricsKey = keys[5]\n local jobId = args[1]\n local entryId = args[2]\n local returnvalue = args[3]\n local timestamp = tonumber(args[4])\n local group = args[5]\n local consumer = args[6]\n local removeMode = args[7] or '0'\n local removeCount = tonumber(args[8]) or 0\n local removeAge = tonumber(args[9]) or 0\n local depsMember = args[10] or ''\n local parentId = args[11] or ''\n local currentOrderingKey = args[12] or ''\n local currentOrderingSeq = args[13] or ''\n local currentGroupKey = args[14] or ''\n local broadcastMode = args[15] or '0'\n local hintProcessedOn = args[16] or ''\n local hasParents = args[17] or '0'\n local skipEvents = args[18] or '0'\n local skipMetrics = args[19] or '0'\n\n -- Phase 1: Complete current job (same as glidemq_complete)\n local processedOn\n if hintProcessedOn ~= '' then\n processedOn = tonumber(hintProcessedOn) or timestamp\n else\n processedOn = tonumber(redis.call('HGET', jobKey, 'processedOn')) or timestamp\n end\n if entryId ~= '' then redis.call('XACK', streamKey, group, entryId) end\n if entryId ~= '' and broadcastMode ~= '1' then redis.call('XDEL', streamKey, entryId) end\n redis.call('ZADD', completedKey, timestamp, jobId)\n redis.call('HSET', jobKey,\n 'state', 'completed',\n 'returnvalue', returnvalue,\n 'finishedOn', tostring(timestamp)\n )\n if currentOrderingKey ~= '__' then\n markOrderingDone(jobKey, jobId, currentOrderingKey, currentOrderingSeq)\n end\n if currentGroupKey ~= '__' then\n releaseGroupSlotAndPromote(jobKey, jobId, timestamp, currentGroupKey)\n end\n if skipEvents ~= '1' then emitEvent(eventsKey, 'completed', jobId, {'returnvalue', returnvalue}) end\n if skipMetrics ~= '1' then recordMetrics(metricsKey, timestamp, timestamp - processedOn) end\n local prefix = string.sub(jobKey, 1, #jobKey - #('job:' .. jobId))\n if entryId == '' then redis.call('DECR', prefix .. 'list-active') end\n\n -- Retention cleanup (skip in broadcast mode - job hash must persist for all subscriptions)\n if broadcastMode ~= '1' then\n if removeMode == 'true' then\n redis.call('ZREM', completedKey, jobId)\n redis.call('DEL', jobKey)\n elseif removeMode == 'count' and removeCount > 0 then\n local total = redis.call('ZCARD', completedKey)\n if total > removeCount then\n local excess = redis.call('ZRANGE', completedKey, 0, math.min(total - removeCount, 1000) - 1)\n if #excess > 0 then removeExcessJobs(completedKey, prefix, excess) end\n end\n elseif removeMode == 'age_count' then\n if removeAge > 0 then\n local cutoff = timestamp - (removeAge * 1000)\n local old = redis.call('ZRANGEBYSCORE', completedKey, '0', string.format('%.0f', cutoff), 'LIMIT', 0, 1000)\n if #old > 0 then removeExcessJobs(completedKey, prefix, old) end\n end\n if removeCount > 0 then\n local total = redis.call('ZCARD', completedKey)\n if total > removeCount then\n local excess = redis.call('ZRANGE', completedKey, 0, math.min(total - removeCount, 1000) - 1)\n if #excess > 0 then removeExcessJobs(completedKey, prefix, excess) end\n end\n end\n end\n end\n\n -- Parent deps\n if depsMember ~= '' and parentId ~= '' and #keys >= 9 then\n local parentDepsKey = keys[6]\n local parentJobKey = keys[7]\n local parentStreamKey = keys[8]\n local parentEventsKey = keys[9]\n local depMarker = 'depdone:' .. depsMember\n if redis.call('HSETNX', parentJobKey, depMarker, '1') == 1 then\n local doneCount = redis.call('HINCRBY', parentJobKey, 'depsCompleted', 1)\n local totalDeps = redis.call('SCARD', parentDepsKey)\n if totalDeps - doneCount <= 0 then\n local parentState = redis.call('HGET', parentJobKey, 'state')\n if parentState == 'waiting-children' then\n redis.call('HSET', parentJobKey, 'state', 'waiting')\n xaddJob(parentStreamKey, parentId, redis.call('HGET', parentJobKey, 'name'))\n emitEvent(parentEventsKey, 'active', parentId, nil)\n end\n end\n end\n end\n -- DAG multi-parent: skip SMEMBERS when caller confirms no parents\n if hasParents ~= '0' then\n local parentsKey = prefix .. 'parents:' .. jobId\n local dagParents = redis.call('SMEMBERS', parentsKey)\n if dagParents and #dagParents > 0 then\n local childQueuePrefix = string.sub(prefix, 1, #prefix - 1)\n local dagDepsMember = childQueuePrefix .. ':' .. jobId\n for pi = 1, #dagParents do\n local pEntry = dagParents[pi]\n local pSep = pEntry:find(':([^:]+)$')\n if pSep then\n local pQueue = string.sub(pEntry, 1, pSep - 1)\n local pId = string.sub(pEntry, pSep + 1)\n if pQueue == childQueuePrefix then\n local pPrefix = prefix\n local pJobKey = pPrefix .. 'job:' .. pId\n local pDepsKey = pPrefix .. 'deps:' .. pId\n local pStreamKey = pPrefix .. 'stream'\n local pEventsKey = pPrefix .. 'events'\n local pDepMarker = 'depdone:' .. dagDepsMember\n if redis.call('HSETNX', pJobKey, pDepMarker, '1') == 1 then\n local pDoneCount = redis.call('HINCRBY', pJobKey, 'depsCompleted', 1)\n local pTotalDeps = redis.call('SCARD', pDepsKey)\n if pTotalDeps - pDoneCount <= 0 then\n local pState = redis.call('HGET', pJobKey, 'state')\n if pState == 'waiting-children' then\n redis.call('HSET', pJobKey, 'state', 'waiting')\n xaddJob(pStreamKey, pId, redis.call('HGET', pJobKey, 'name'))\n emitEvent(pEventsKey, 'active', pId, nil)\n end\n end\n end\n end\n end\n end\n end\n end\n\n -- In broadcast mode: do not fetch next (avoids XDEL of next entry which would break other consumer groups)\n if broadcastMode == '1' then\n return {'NEXT_NONE', jobId}\n end\n\n -- Return protocol (array-based to avoid cjson encode/decode per job):\n -- {'NEXT_NONE', completedJobId}\n -- {'NEXT_REVOKED', completedJobId, nextJobId, nextEntryId}\n -- {'NEXT_HASH', completedJobId, nextJobId, nextEntryId, field1, value1, field2, value2, ...}\n\n -- Phase 1.0: Try priority list first (highest priority: priority > LIFO > FIFO)\n local priorityKey = prefix .. 'priority'\n for _priAttempt = 1, 3 do\n local priJobId = redis.call('RPOP', priorityKey)\n if not priJobId then\n break -- priority list is empty\n end\n\n local priJobKey = prefix .. 'job:' .. priJobId\n -- Single HMGET replaces EXISTS + HGET 'revoked' + checkExpired HGET 'expireAt' (3 \u2192 1)\n local priMeta = redis.call('HMGET', priJobKey, 'state', 'revoked', 'expireAt')\n if priMeta[1] then\n if priMeta[2] ~= '1' then\n local priExpireAt = tonumber(priMeta[3])\n if not priExpireAt or priExpireAt <= 0 or timestamp <= priExpireAt then\n -- Check ordering/group gates for priority-list jobs\n local priGroupKey = redis.call('HGET', priJobKey, 'groupKey')\n if priGroupKey and priGroupKey ~= '' then\n local priGroupHashKey = prefix .. 'group:' .. priGroupKey\n local priGrpFields = redis.call('HGETALL', priGroupHashKey)\n local priGrp = {}\n for pf = 1, #priGrpFields, 2 do priGrp[priGrpFields[pf]] = priGrpFields[pf + 1] end\n local priOrdSeq = tonumber(redis.call('HGET', priJobKey, 'orderingSeq')) or 0\n local priNextSeq = tonumber(priGrp.nextSeq) or 0\n if priNextSeq > 0 then\n local priAdv = advancePastSkips(priGroupHashKey, priNextSeq)\n if priAdv > priNextSeq then\n redis.call('HSET', priGroupHashKey, 'nextSeq', tostring(priAdv))\n priNextSeq = priAdv\n end\n end\n local priMaxConc = tonumber(priGrp.maxConcurrency) or 0\n local priActive = tonumber(priGrp.active) or 0\n local priWaitListKey = prefix .. 'groupq:' .. priGroupKey\n local priReturning = (priOrdSeq > 0 and priNextSeq > 0 and priOrdSeq < priNextSeq)\n -- Ordering gate or concurrency gate (skip for returning step-jobs)\n if (priOrdSeq > 0 and priNextSeq > 0 and priOrdSeq > priNextSeq) or\n (priMaxConc > 0 and priActive >= priMaxConc and not priReturning) then\n local priScore = priOrdSeq > 0 and priOrdSeq or tonumber(priJobId) or 0\n redis.call('ZADD', priWaitListKey, priScore, priJobId)\n redis.call('HSET', priJobKey, 'state', 'group-waiting')\n else\n -- All gates pass: activate with group bookkeeping\n redis.call('HSET', priJobKey, 'state', 'active', 'processedOn', tostring(timestamp), 'lastActive', tostring(timestamp))\n if not priReturning then\n redis.call('HINCRBY', priGroupHashKey, 'active', 1)\n end\n if priOrdSeq > 0 and not priReturning then\n redis.call('HSET', priGroupHashKey, 'nextSeq', tostring(priOrdSeq + 1))\n redis.call('ZREM', priWaitListKey, priJobId)\n end\n if skipEvents ~= '1' then emitEvent(eventsKey, 'active', priJobId, nil) end\n local priJobFields = redis.call('HGETALL', priJobKey)\n redis.call('INCR', prefix .. 'list-active')\n return {'NEXT_HASH', jobId, priJobId, '', unpack(priJobFields)}\n end\n else\n -- Non-group job: activate directly\n redis.call('HSET', priJobKey, 'state', 'active', 'processedOn', tostring(timestamp), 'lastActive', tostring(timestamp))\n if skipEvents ~= '1' then emitEvent(eventsKey, 'active', priJobId, nil) end\n local priJobFields = redis.call('HGETALL', priJobKey)\n redis.call('INCR', prefix .. 'list-active')\n return {'NEXT_HASH', jobId, priJobId, '', unpack(priJobFields)}\n end\n else\n expireJob(priJobKey, priJobId, prefix, timestamp, priMeta[1], nil, nil, nil)\n end\n end\n end\n end\n\n -- Phase 1.5: Try LIFO list (before stream), retry up to 3 times\n local lifoKey = prefix .. 'lifo'\n for _lifoAttempt = 1, 3 do\n local lifoJobId = redis.call('RPOP', lifoKey)\n if not lifoJobId then\n break -- LIFO list is empty\n end\n\n local lifoJobKey = prefix .. 'job:' .. lifoJobId\n -- Single HMGET replaces EXISTS + HGET 'revoked' + checkExpired HGET 'expireAt' (3 \u2192 1)\n local lifoMeta = redis.call('HMGET', lifoJobKey, 'state', 'revoked', 'expireAt')\n if lifoMeta[1] then\n if lifoMeta[2] ~= '1' then\n local lifoExpireAt = tonumber(lifoMeta[3])\n if not lifoExpireAt or lifoExpireAt <= 0 or timestamp <= lifoExpireAt then\n redis.call('HSET', lifoJobKey, 'state', 'active', 'processedOn', tostring(timestamp), 'lastActive', tostring(timestamp))\n if skipEvents ~= '1' then emitEvent(eventsKey, 'active', lifoJobId, nil) end\n local lifoJobFields = redis.call('HGETALL', lifoJobKey)\n redis.call('INCR', prefix .. 'list-active')\n return {'NEXT_HASH', jobId, lifoJobId, '', unpack(lifoJobFields)}\n else\n expireJob(lifoJobKey, lifoJobId, prefix, timestamp, lifoMeta[1], nil, nil, nil)\n end\n end\n end\n end\n\n -- Phase 2: Fetch next job (non-blocking XREADGROUP), skip expired (up to 3 attempts)\n local nextJobId, nextEntryId, nextJobKey\n local nextGroupKey = nil\n for _fetchAttempt = 1, 3 do\n local nextEntries = redis.call('XREADGROUP', 'GROUP', group, consumer, 'COUNT', 1, 'STREAMS', streamKey, '>')\n if not nextEntries or #nextEntries == 0 then\n return {'NEXT_NONE', jobId}\n end\n local streamData = nextEntries[1]\n local entries = streamData[2]\n if not entries or #entries == 0 then\n return {'NEXT_NONE', jobId}\n end\n local nextEntry = entries[1]\n nextEntryId = nextEntry[1]\n local nextFields = nextEntry[2]\n nextJobId = nil\n for i = 1, #nextFields, 2 do\n if nextFields[i] == 'jobId' then\n nextJobId = nextFields[i + 1]\n break\n end\n end\n if not nextJobId then\n return {'NEXT_NONE', jobId}\n end\n nextJobKey = prefix .. 'job:' .. nextJobId\n -- Single HMGET replaces EXISTS + HGET 'revoked' + checkExpired HGET 'expireAt' + HGET 'groupKey' (4 \u2192 1)\n local nextMeta = redis.call('HMGET', nextJobKey, 'state', 'revoked', 'expireAt', 'groupKey')\n if not nextMeta[1] then\n -- state is nil: job hash does not exist\n return {'NEXT_NONE', jobId}\n end\n if nextMeta[2] == '1' then\n return {'NEXT_REVOKED', jobId, nextJobId, nextEntryId}\n end\n -- Inline expiry check (avoids checkExpired's redundant HGET)\n local nextExpireAt = tonumber(nextMeta[3])\n if nextExpireAt and nextExpireAt > 0 and timestamp > nextExpireAt then\n local curState = nextMeta[1]\n expireJob(nextJobKey, nextJobId, prefix, timestamp, curState, nil, nil, nil)\n redis.call('XACK', streamKey, group, nextEntryId)\n redis.call('XDEL', streamKey, nextEntryId)\n nextJobId = nil\n else\n nextGroupKey = nextMeta[4]\n break\n end\n end\n if not nextJobId then\n return {'NEXT_NONE', jobId}\n end\n\n -- Phase 3: Activate next job (same as moveToActive)\n if nextGroupKey and nextGroupKey ~= '' then\n local nextGroupHashKey = prefix .. 'group:' .. nextGroupKey\n -- Load all group fields in one call\n local nGrpFields = redis.call('HGETALL', nextGroupHashKey)\n local nGrp = {}\n for nf = 1, #nGrpFields, 2 do nGrp[nGrpFields[nf]] = nGrpFields[nf + 1] end\n local nextMaxConc = tonumber(nGrp.maxConcurrency) or 0\n local nextActive = tonumber(nGrp.active) or 0\n local nextWaitListKey = prefix .. 'groupq:' .. nextGroupKey\n -- Ordering gate\n local nextJobOrderingSeq = tonumber(redis.call('HGET', nextJobKey, 'orderingSeq')) or 0\n local nextReturning = false\n if nextJobOrderingSeq > 0 then\n local nextExpectedSeq = tonumber(nGrp.nextSeq) or 0\n if nextExpectedSeq > 0 then\n local relAdv = advancePastSkips(nextGroupHashKey, nextExpectedSeq)\n if relAdv > nextExpectedSeq then\n redis.call('HSET', nextGroupHashKey, 'nextSeq', tostring(relAdv))\n nextExpectedSeq = relAdv\n end\n end\n if nextExpectedSeq > 0 and nextJobOrderingSeq > nextExpectedSeq then\n redis.call('XACK', streamKey, group, nextEntryId)\n redis.call('XDEL', streamKey, nextEntryId)\n redis.call('ZADD', nextWaitListKey, nextJobOrderingSeq, nextJobId)\n redis.call('HSET', nextJobKey, 'state', 'group-waiting')\n return {'NEXT_NONE', jobId}\n end\n nextReturning = (nextExpectedSeq > 0 and nextJobOrderingSeq < nextExpectedSeq)\n end\n -- Concurrency gate (skip for returning step-jobs)\n if nextMaxConc > 0 and nextActive >= nextMaxConc and not nextReturning then\n redis.call('XACK', streamKey, group, nextEntryId)\n redis.call('XDEL', streamKey, nextEntryId)\n redis.call('ZADD', nextWaitListKey, groupqScore(nextJobKey, nextJobId), nextJobId)\n redis.call('HSET', nextJobKey, 'state', 'group-waiting')\n return {'NEXT_NONE', jobId}\n end\n -- Token bucket gate (read-only)\n local nextTbCapacity = tonumber(nGrp.tbCapacity) or 0\n local nextTbBlocked = false\n local nextTbDelay = 0\n local nextTbTokens = 0\n local nextJobCostVal = 0\n if nextTbCapacity > 0 then\n nextTbTokens = tbRefill(nextGroupHashKey, nGrp, tonumber(timestamp))\n nextJobCostVal = tonumber(redis.call('HGET', nextJobKey, 'cost')) or 1000\n -- DLQ guard: cost > capacity\n if nextJobCostVal > nextTbCapacity then\n local nextProcessedOn = tonumber(redis.call('HGET', nextJobKey, 'processedOn')) or tonumber(timestamp)\n redis.call('XACK', streamKey, group, nextEntryId)\n redis.call('XDEL', streamKey, nextEntryId)\n redis.call('ZADD', prefix .. 'failed', tonumber(timestamp), nextJobId)\n redis.call('HSET', nextJobKey,\n 'state', 'failed',\n 'failedReason', 'cost exceeds token bucket capacity',\n 'finishedOn', tostring(timestamp))\n if skipEvents ~= '1' then emitEvent(prefix .. 'events', 'failed', nextJobId, {'failedReason', 'cost exceeds token bucket capacity'}) end\n if skipMetrics ~= '1' then recordMetrics(metricsKey, tonumber(timestamp), tonumber(timestamp) - nextProcessedOn) end\n return {'NEXT_NONE', jobId}\n end\n if nextTbTokens < nextJobCostVal then\n nextTbBlocked = true\n local nextTbRefillRateVal = math.max(tonumber(nGrp.tbRefillRate) or 0, 1)\n nextTbDelay = math.ceil((nextJobCostVal - nextTbTokens) * 1000 / nextTbRefillRateVal)\n end\n end\n -- Sliding window gate (read-only)\n local nextRateMax = tonumber(nGrp.rateMax) or 0\n local nextRlBlocked = false\n local nextRlDelay = 0\n if nextRateMax > 0 then\n local nextRateDuration = tonumber(nGrp.rateDuration) or 0\n local nextRateWindowStart = tonumber(nGrp.rateWindowStart) or 0\n local nextRateCount = tonumber(nGrp.rateCount) or 0\n if nextRateDuration > 0 and timestamp - nextRateWindowStart < nextRateDuration and nextRateCount >= nextRateMax then\n nextRlBlocked = true\n nextRlDelay = (nextRateWindowStart + nextRateDuration) - timestamp\n end\n end\n -- If ANY gate blocked: park + register\n if nextTbBlocked or nextRlBlocked then\n redis.call('XACK', streamKey, group, nextEntryId)\n redis.call('XDEL', streamKey, nextEntryId)\n local nextWaitListKey = prefix .. 'groupq:' .. nextGroupKey\n redis.call('ZADD', nextWaitListKey, groupqScore(nextJobKey, nextJobId), nextJobId)\n redis.call('HSET', nextJobKey, 'state', 'group-waiting')\n local nextMaxDelay = math.max(nextTbDelay, nextRlDelay)\n local rateLimitedKey = prefix .. 'ratelimited'\n redis.call('ZADD', rateLimitedKey, tonumber(timestamp) + nextMaxDelay, nextGroupKey)\n return {'NEXT_NONE', jobId}\n end\n -- All gates passed: mutate state\n if nextTbCapacity > 0 then\n redis.call('HINCRBY', nextGroupHashKey, 'tbTokens', -nextJobCostVal)\n end\n if nextRateMax > 0 then\n local nextRateDuration = tonumber(nGrp.rateDuration) or 0\n if nextRateDuration > 0 then\n local nextRateWindowStart = tonumber(nGrp.rateWindowStart) or 0\n if timestamp - nextRateWindowStart >= nextRateDuration then\n redis.call('HSET', nextGroupHashKey, 'rateWindowStart', tostring(timestamp), 'rateCount', '1')\n else\n redis.call('HINCRBY', nextGroupHashKey, 'rateCount', 1)\n end\n end\n end\n if not nextReturning then\n redis.call('HINCRBY', nextGroupHashKey, 'active', 1)\n end\n if nextJobOrderingSeq > 0 and not nextReturning then\n redis.call('HSET', nextGroupHashKey, 'nextSeq', tostring(nextJobOrderingSeq + 1))\n redis.call('ZREM', nextWaitListKey, nextJobId)\n end\n end\n redis.call('HSET', nextJobKey, 'state', 'active', 'processedOn', tostring(timestamp), 'lastActive', tostring(timestamp))\n local nextHash = redis.call('HGETALL', nextJobKey)\n local out = {'NEXT_HASH', jobId, nextJobId, nextEntryId}\n for i = 1, #nextHash do\n out[#out + 1] = nextHash[i]\n end\n return out\nend)\n\nredis.register_function('glidemq_fail', function(keys, args)\n local streamKey = keys[1]\n local failedKey = keys[2]\n local scheduledKey = keys[3]\n local eventsKey = keys[4]\n local jobKey = keys[5]\n local metricsKey = keys[6]\n local jobId = args[1]\n assert(string.sub(jobKey, -(4 + #jobId)) == 'job:' .. jobId, 'unexpected key format: ' .. jobKey)\n local entryId = args[2]\n local failedReason = args[3]\n local timestamp = tonumber(args[4])\n local maxAttempts = tonumber(args[5]) or 0\n local backoffDelay = tonumber(args[6]) or 0\n local group = args[7]\n local removeMode = args[8] or '0'\n local removeCount = tonumber(args[9]) or 0\n local removeAge = tonumber(args[10]) or 0\n local broadcastMode = args[11] or '0'\n local processedOn = tonumber(redis.call('HGET', jobKey, 'processedOn')) or timestamp\n if entryId ~= '' then redis.call('XACK', streamKey, group, entryId) end\n if entryId ~= '' and broadcastMode ~= '1' then redis.call('XDEL', streamKey, entryId) end\n local attemptsMade\n if broadcastMode == '1' then\n local subKey = jobKey .. ':sub:' .. group\n attemptsMade = redis.call('HINCRBY', subKey, 'a', 1)\n redis.call('EXPIRE', subKey, 86400)\n else\n attemptsMade = redis.call('HINCRBY', jobKey, 'attemptsMade', 1)\n end\n if maxAttempts > 0 and attemptsMade < maxAttempts then\n -- Advance fallback chain if the job has fallbacks configured\n local optsRaw = redis.call('HGET', jobKey, 'opts')\n if optsRaw and string.find(optsRaw, '\"fallbacks\"') then\n local fbIdx = tonumber(redis.call('HGET', jobKey, 'fallbackIndex') or '0') or 0\n redis.call('HSET', jobKey, 'fallbackIndex', tostring(fbIdx + 1))\n end\n local retryAt = timestamp + backoffDelay\n local priority = tonumber(redis.call('HGET', jobKey, 'priority')) or 0\n local score = priority * PRIORITY_SHIFT + retryAt\n redis.call('ZADD', scheduledKey, score, jobId)\n redis.call('HSET', jobKey,\n 'state', 'delayed',\n 'failedReason', failedReason,\n 'processedOn', tostring(timestamp)\n )\n -- Only release group slot if not an ordering-key job (ordering jobs hold the slot through retries)\n local failOrdSeq = tonumber(redis.call('HGET', jobKey, 'orderingSeq')) or 0\n if failOrdSeq <= 0 then\n releaseGroupSlotAndPromote(jobKey, jobId, timestamp)\n end\n emitEvent(eventsKey, 'retrying', jobId, {\n 'failedReason', failedReason,\n 'attemptsMade', tostring(attemptsMade),\n 'delay', tostring(backoffDelay)\n })\n if entryId == '' then redis.call('DECR', string.sub(jobKey, 1, #jobKey - #('job:' .. jobId)) .. 'list-active') end\n return 'retrying'\n else\n redis.call('ZADD', failedKey, timestamp, jobId)\n redis.call('HSET', jobKey,\n 'state', 'failed',\n 'failedReason', failedReason,\n 'finishedOn', tostring(timestamp),\n 'processedOn', tostring(timestamp)\n )\n markOrderingDone(jobKey, jobId)\n releaseGroupSlotAndPromote(jobKey, jobId, timestamp)\n emitEvent(eventsKey, 'failed', jobId, {'failedReason', failedReason})\n recordMetrics(metricsKey, timestamp, timestamp - processedOn)\n local prefix = string.sub(jobKey, 1, #jobKey - #('job:' .. jobId))\n -- In broadcast mode, skip job hash deletion: the job must persist for all subscriptions\n if broadcastMode ~= '1' then\n if removeMode == 'true' then\n redis.call('ZREM', failedKey, jobId)\n redis.call('DEL', jobKey)\n elseif removeMode == 'count' and removeCount > 0 then\n local total = redis.call('ZCARD', failedKey)\n if total > removeCount then\n local excess = redis.call('ZRANGE', failedKey, 0, math.min(total - removeCount, 1000) - 1)\n if #excess > 0 then removeExcessJobs(failedKey, prefix, excess) end\n end\n elseif removeMode == 'age_count' then\n if removeAge > 0 then\n local cutoff = timestamp - (removeAge * 1000)\n local old = redis.call('ZRANGEBYSCORE', failedKey, '0', string.format('%.0f', cutoff), 'LIMIT', 0, 1000)\n if #old > 0 then removeExcessJobs(failedKey, prefix, old) end\n end\n if removeCount > 0 then\n local total = redis.call('ZCARD', failedKey)\n if total > removeCount then\n local excess = redis.call('ZRANGE', failedKey, 0, math.min(total - removeCount, 1000) - 1)\n if #excess > 0 then removeExcessJobs(failedKey, prefix, excess) end\n end\n end\n end\n end\n if entryId == '' then redis.call('DECR', prefix .. 'list-active') end\n return 'failed'\n end\nend)\n\nredis.register_function('glidemq_reclaimStalled', function(keys, args)\n local streamKey = keys[1]\n local eventsKey = keys[2]\n local group = args[1]\n local consumer = args[2]\n local minIdleMs = tonumber(args[3])\n local maxStalledCount = tonumber(args[4]) or 1\n local timestamp = tonumber(args[5])\n local failedKey = args[6]\n local broadcastMode = args[7] or '0'\n local result = redis.call('XAUTOCLAIM', streamKey, group, consumer, minIdleMs, '0-0')\n local entries = result[2]\n if not entries or #entries == 0 then\n return 0\n end\n local prefix = string.sub(streamKey, 1, #streamKey - 6)\n local count = 0\n for i = 1, #entries do\n local entry = entries[i]\n local entryId = entry[1]\n local fields = entry[2]\n local jobId = nil\n if type(fields) == 'table' then\n for j = 1, #fields, 2 do\n if fields[j] == 'jobId' then\n jobId = fields[j + 1]\n break\n end\n end\n end\n if jobId then\n local jobKey = prefix .. 'job:' .. jobId\n if checkExpired(jobKey, jobId, prefix, timestamp) then\n if entryId ~= '' then redis.call('XACK', streamKey, group, entryId) end\n if entryId ~= '' and broadcastMode ~= '1' then redis.call('XDEL', streamKey, entryId) end\n count = count + 1\n else\n local vals = redis.call('HMGET', jobKey, 'lastActive', 'opts')\n local lastActive = tonumber(vals[1])\n local jobLockDuration = extractLockDurationFromOpts(vals[2])\n local effectiveIdle = (jobLockDuration > 0) and jobLockDuration or minIdleMs\n if lastActive and (timestamp - lastActive) < effectiveIdle then\n count = count + 1\n else\n local failed = applyStalledLogic(jobKey, jobId, prefix, eventsKey, failedKey, maxStalledCount, timestamp)\n if failed then\n if entryId ~= '' then redis.call('XACK', streamKey, group, entryId) end\n if entryId ~= '' and broadcastMode ~= '1' then redis.call('XDEL', streamKey, entryId) end\n else\n redis.call('HSET', jobKey, 'state', 'active')\n end\n count = count + 1\n end\n end\n end\n end\n return count\nend)\n\n-- Reclaim stalled list-sourced jobs (LIFO/priority) that are invisible to XAUTOCLAIM.\n-- Uses bounded SCAN to find active list jobs with stale lastActive, then applies stall logic.\n-- KEYS: [streamKey, eventsKey]\n-- ARGS: [minIdleMs, maxStalledCount, timestamp, failedKey]\nredis.register_function('glidemq_reclaimStalledListJobs', function(keys, args)\n local streamKey = keys[1]\n local eventsKey = keys[2]\n local minIdleMs = tonumber(args[1])\n local maxStalledCount = tonumber(args[2]) or 1\n local timestamp = tonumber(args[3])\n if not minIdleMs or not timestamp then return 0 end\n local failedKey = args[4]\n local prefix = string.sub(streamKey, 1, #streamKey - 6)\n local listActiveKey = prefix .. 'list-active'\n local currentActive = tonumber(redis.call('GET', listActiveKey)) or 0\n if currentActive <= 0 then return 0 end\n local pattern = prefix .. 'job:*'\n local cursor = '0'\n local maxIter = 1000\n local iter = 0\n local count = 0\n repeat\n iter = iter + 1\n local sr = redis.call('SCAN', cursor, 'MATCH', pattern, 'COUNT', 500)\n cursor = sr[1]\n local scannedKeys = sr[2]\n for i = 1, #scannedKeys do\n local jk = scannedKeys[i]\n local state = redis.call('HGET', jk, 'state')\n if state == 'active' then\n local vals = redis.call('HMGET', jk, 'lastActive', 'lifo', 'priority', 'opts')\n local lastActive = tonumber(vals[1])\n local isListSourced = vals[2] == '1' or (tonumber(vals[3]) or 0) > 0\n local jobLockDuration = extractLockDurationFromOpts(vals[4])\n local effectiveIdle = (jobLockDuration > 0) and jobLockDuration or minIdleMs\n if isListSourced and lastActive and (timestamp - lastActive) >= effectiveIdle then\n local jobId = string.sub(jk, #prefix + 5)\n local shouldDecr = false\n if checkExpired(jk, jobId, prefix, timestamp) then\n shouldDecr = true\n else\n if applyStalledLogic(jk, jobId, prefix, eventsKey, failedKey, maxStalledCount, timestamp) then\n shouldDecr = true\n end\n end\n if shouldDecr then\n local la = tonumber(redis.call('GET', listActiveKey)) or 0\n if la > 0 then redis.call('DECR', listActiveKey) end\n end\n count = count + 1\n end\n end\n end\n until cursor == '0' or iter >= maxIter\n return count\nend)\n\nredis.register_function('glidemq_pause', function(keys, args)\n local metaKey = keys[1]\n local eventsKey = keys[2]\n redis.call('HSET', metaKey, 'paused', '1')\n emitEvent(eventsKey, 'paused', '0', nil)\n return 1\nend)\n\nredis.register_function('glidemq_resume', function(keys, args)\n local metaKey = keys[1]\n local eventsKey = keys[2]\n redis.call('HSET', metaKey, 'paused', '0')\n emitEvent(eventsKey, 'resumed', '0', nil)\n return 1\nend)\n\nredis.register_function('glidemq_dedup', function(keys, args)\n local dedupKey = keys[1]\n local idKey = keys[2]\n local streamKey = keys[3]\n local scheduledKey = keys[4]\n local eventsKey = keys[5]\n local dedupId = args[1]\n local ttlMs = tonumber(args[2]) or 0\n local mode = args[3]\n local jobName = args[4]\n local jobData = args[5]\n local jobOpts = args[6]\n local timestamp = tonumber(args[7])\n local delay = tonumber(args[8]) or 0\n local priority = tonumber(args[9]) or 0\n local parentId = args[10] or ''\n local maxAttempts = tonumber(args[11]) or 0\n local orderingKey = args[12] or ''\n local groupConcurrency = tonumber(args[13]) or 0\n local lifo = tonumber(args[21]) or 0\n local groupRateMax = tonumber(args[14]) or 0\n local groupRateDuration = tonumber(args[15]) or 0\n local tbCapacity = tonumber(args[16]) or 0\n local tbRefillRate = tonumber(args[17]) or 0\n local jobCost = tonumber(args[18]) or 0\n local ttl = tonumber(args[19]) or 0\n local customJobId = args[20] or ''\n local parentQueue = args[22] or ''\n local skipEvents = args[23] or '0'\n local prefix = string.sub(idKey, 1, #idKey - 2)\n local existing = redis.call('HGET', dedupKey, dedupId)\n if mode == 'simple' then\n if existing then\n local sep = string.find(existing, ':')\n if sep then\n local existingJobId = string.sub(existing, 1, sep - 1)\n local jobKey = prefix .. 'job:' .. existingJobId\n local state = redis.call('HGET', jobKey, 'state')\n if state and state ~= 'completed' and state ~= 'failed' then\n return 'skipped'\n end\n end\n end\n elseif mode == 'throttle' then\n if existing and ttlMs > 0 then\n local sep = string.find(existing, ':')\n if sep then\n local storedTs = tonumber(string.sub(existing, sep + 1))\n if storedTs and (timestamp - storedTs) < ttlMs then\n return 'skipped'\n end\n end\n end\n elseif mode == 'debounce' then\n if existing then\n local sep = string.find(existing, ':')\n if sep then\n local existingJobId = string.sub(existing, 1, sep - 1)\n local jobKey = prefix .. 'job:' .. existingJobId\n local state = redis.call('HGET', jobKey, 'state')\n if state == 'delayed' or state == 'prioritized' then\n local delGroupKey = redis.call('HGET', jobKey, 'groupKey')\n local delOrderingSeq = tonumber(redis.call('HGET', jobKey, 'orderingSeq')) or 0\n redis.call('ZREM', scheduledKey, existingJobId)\n markOrderingDone(jobKey, existingJobId)\n if delGroupKey and delGroupKey ~= '' and delOrderingSeq > 0 then\n local groupHashKey = prefix .. 'group:' .. delGroupKey\n redis.call('HSET', groupHashKey, 'skip:' .. tostring(delOrderingSeq), '1')\n end\n redis.call('DEL', jobKey)\n if skipEvents ~= '1' then emitEvent(eventsKey, 'removed', existingJobId, nil) end\n elseif state and state ~= 'completed' and state ~= 'failed' then\n return 'skipped'\n end\n end\n end\n end\n local jobIdStr\n local jobKey\n if customJobId ~= '' then\n jobKey = prefix .. 'job:' .. customJobId\n if redis.call('EXISTS', jobKey) == 1 then\n return 'duplicate'\n end\n jobIdStr = customJobId\n advanceIdCounter(idKey, customJobId)\n else\n local jobId = redis.call('INCR', idKey)\n jobIdStr = tostring(jobId)\n jobKey = prefix .. 'job:' .. jobIdStr\n end\n local useGroupConcurrency = (orderingKey ~= '')\n local orderingSeq = 0\n if orderingKey ~= '' then\n local orderingMetaKey = prefix .. 'ordering'\n orderingSeq = redis.call('HINCRBY', orderingMetaKey, orderingKey, 1)\n end\n if useGroupConcurrency then\n if groupConcurrency < 1 then groupConcurrency = 1 end\n local groupHashKey = prefix .. 'group:' .. orderingKey\n local curMax = tonumber(redis.call('HGET', groupHashKey, 'maxConcurrency')) or 0\n if curMax ~= groupConcurrency then\n redis.call('HSET', groupHashKey, 'maxConcurrency', tostring(groupConcurrency))\n end\n -- Initialize nextSeq for ordered promotion\n if orderingSeq > 0 and redis.call('HEXISTS', groupHashKey, 'nextSeq') == 0 then\n redis.call('HSET', groupHashKey, 'nextSeq', '1')\n end\n if groupRateMax > 0 then\n local curRateMax = tonumber(redis.call('HGET', groupHashKey, 'rateMax')) or 0\n if curRateMax ~= groupRateMax then\n redis.call('HSET', groupHashKey, 'rateMax', tostring(groupRateMax))\n end\n local curRateDuration = tonumber(redis.call('HGET', groupHashKey, 'rateDuration')) or 0\n if curRateDuration ~= groupRateDuration then\n redis.call('HSET', groupHashKey, 'rateDuration', tostring(groupRateDuration))\n end\n else\n local oldRateMax = tonumber(redis.call('HGET', groupHashKey, 'rateMax')) or 0\n if oldRateMax > 0 then\n redis.call('HDEL', groupHashKey, 'rateMax', 'rateDuration', 'rateWindowStart', 'rateCount')\n end\n end\n -- Upsert token bucket fields on group hash\n if tbCapacity > 0 then\n local curTbCap = tonumber(redis.call('HGET', groupHashKey, 'tbCapacity')) or 0\n if curTbCap ~= tbCapacity then\n redis.call('HSET', groupHashKey, 'tbCapacity', tostring(tbCapacity))\n end\n local curTbRate = tonumber(redis.call('HGET', groupHashKey, 'tbRefillRate')) or 0\n if curTbRate ~= tbRefillRate then\n redis.call('HSET', groupHashKey, 'tbRefillRate', tostring(tbRefillRate))\n end\n -- Initialize tokens on first setup\n if curTbCap == 0 then\n redis.call('HSET', groupHashKey,\n 'tbTokens', tostring(tbCapacity),\n 'tbLastRefill', tostring(timestamp),\n 'tbRefillRemainder', '0')\n end\n -- Validate cost (explicit or default 1000 millitokens) against capacity\n local effectiveCost = (jobCost > 0) and jobCost or 1000\n if effectiveCost > tbCapacity then\n return 'ERR:COST_EXCEEDS_CAPACITY'\n end\n else\n -- Clear stale tb fields\n local oldTbCap = tonumber(redis.call('HGET', groupHashKey, 'tbCapacity')) or 0\n if oldTbCap > 0 then\n redis.call('HDEL', groupHashKey, 'tbCapacity', 'tbRefillRate', 'tbTokens', 'tbLastRefill', 'tbRefillRemainder')\n end\n end\n end\n local hashFields = {\n 'id', jobIdStr,\n 'name', jobName,\n 'data', jobData,\n 'opts', jobOpts,\n 'timestamp', tostring(timestamp),\n 'attemptsMade', '0',\n 'delay', tostring(delay),\n 'priority', tostring(priority),\n 'maxAttempts', tostring(maxAttempts)\n }\n if useGroupConcurrency then\n hashFields[#hashFields + 1] = 'groupKey'\n hashFields[#hashFields + 1] = orderingKey\n if orderingSeq > 0 then\n hashFields[#hashFields + 1] = 'orderingSeq'\n hashFields[#hashFields + 1] = tostring(orderingSeq)\n end\n end\n if jobCost > 0 then\n hashFields[#hashFields + 1] = 'cost'\n hashFields[#hashFields + 1] = tostring(jobCost)\n end\n if ttl > 0 then\n hashFields[#hashFields + 1] = 'expireAt'\n hashFields[#hashFields + 1] = tostring(timestamp + ttl)\n end\n if parentId ~= '' then\n hashFields[#hashFields + 1] = 'parentId'\n hashFields[#hashFields + 1] = parentId\n if parentQueue ~= '' then\n hashFields[#hashFields + 1] = 'parentQueue'\n hashFields[#hashFields + 1] = parentQueue\n end\n end\n if lifo > 0 then\n hashFields[#hashFields + 1] = 'lifo'\n hashFields[#hashFields + 1] = '1'\n end\n if delay > 0 or priority > 0 then\n hashFields[#hashFields + 1] = 'state'\n hashFields[#hashFields + 1] = delay > 0 and 'delayed' or 'prioritized'\n else\n hashFields[#hashFields + 1] = 'state'\n hashFields[#hashFields + 1] = 'waiting'\n end\n redis.call('HSET', jobKey, unpack(hashFields))\n -- Register child in parent's deps set when parentDepsKey is provided (keys[6])\n if parentId ~= '' and parentQueue ~= '' and #keys >= 6 then\n local parentDepsKey = keys[6]\n local queuePrefix = string.sub(prefix, 1, #prefix - 1)\n local depsMember = queuePrefix .. ':' .. jobIdStr\n redis.call('SADD', parentDepsKey, depsMember)\n end\n if delay > 0 then\n local score = priority * PRIORITY_SHIFT + (timestamp + delay)\n redis.call('ZADD', scheduledKey, score, jobIdStr)\n elseif priority > 0 then\n local score = priority * PRIORITY_SHIFT\n redis.call('ZADD', scheduledKey, score, jobIdStr)\n elseif lifo > 0 then\n local lifoKey = prefix .. 'lifo'\n redis.call('RPUSH', lifoKey, jobIdStr)\n else\n xaddJob(streamKey, jobIdStr, jobName)\n end\n redis.call('HSET', dedupKey, dedupId, jobIdStr .. ':' .. tostring(timestamp))\n if skipEvents ~= '1' then emitEvent(eventsKey, 'added', jobIdStr, {'name', jobName}) end\n return jobIdStr\nend)\n\nredis.register_function('glidemq_rateLimit', function(keys, args)\n local rateKey = keys[1]\n local metaKey = keys[2]\n local maxPerWindow = tonumber(args[1])\n local windowDuration = tonumber(args[2])\n local now = tonumber(args[3])\n -- Fallback: read rate limit config from meta if not provided inline\n if maxPerWindow <= 0 then\n maxPerWindow = tonumber(redis.call('HGET', metaKey, 'rateLimitMax')) or 0\n windowDuration = tonumber(redis.call('HGET', metaKey, 'rateLimitDuration')) or 0\n if maxPerWindow <= 0 then return 0 end\n end\n local windowStart = tonumber(redis.call('HGET', rateKey, 'windowStart')) or 0\n local count = tonumber(redis.call('HGET', rateKey, 'count')) or 0\n if now - windowStart >= windowDuration then\n redis.call('HSET', rateKey, 'windowStart', tostring(now), 'count', '1')\n return 0\n end\n if count >= maxPerWindow then\n local delayMs = windowDuration - (now - windowStart)\n return delayMs\n end\n redis.call('HSET', rateKey, 'count', tostring(count + 1))\n return 0\nend)\n\nredis.register_function('glidemq_promoteRateLimited', function(keys, args)\n local rateLimitedKey = keys[1]\n local streamKey = keys[2]\n local now = tonumber(args[1])\n -- Derive prefix from the server-validated key instead of caller-supplied arg\n local prefix = string.sub(rateLimitedKey, 1, #rateLimitedKey - #'ratelimited')\n local expired = redis.call('ZRANGEBYSCORE', rateLimitedKey, '0', string.format('%.0f', now), 'LIMIT', 0, 100)\n if not expired or #expired == 0 then return 0 end\n local promoted = 0\n for i = 1, #expired do\n local gk = expired[i]\n redis.call('ZREM', rateLimitedKey, gk)\n local groupHashKey = prefix .. 'group:' .. gk\n local waitListKey = prefix .. 'groupq:' .. gk\n -- Load all group fields in one call for rate limit + token bucket checks\n local prGrpFields = redis.call('HGETALL', groupHashKey)\n local prGrp = {}\n for pf = 1, #prGrpFields, 2 do prGrp[prGrpFields[pf]] = prGrpFields[pf + 1] end\n local rateMax = tonumber(prGrp.rateMax) or 0\n local maxConc = tonumber(prGrp.maxConcurrency) or 0\n local active = tonumber(prGrp.active) or 0\n -- Token bucket pre-check: peek head job cost before promoting\n local prTbCap = tonumber(prGrp.tbCapacity) or 0\n local tbCheckPassed = true\n if prTbCap > 0 then\n local prTbTokens = tbRefill(groupHashKey, prGrp, now)\n local headMembers = redis.call('ZRANGE', waitListKey, 0, 0)\n local headJobId = headMembers[1]\n if headJobId then\n local headJobKey = prefix .. 'job:' .. headJobId\n -- Tombstone guard\n if redis.call('EXISTS', headJobKey) == 0 then\n redis.call('ZREM', waitListKey, headJobId)\n tbCheckPassed = false\n end\n if tbCheckPassed then\n local headCost = tonumber(redis.call('HGET', headJobKey, 'cost')) or 1000\n -- DLQ guard: cost > capacity\n if headCost > prTbCap then\n redis.call('ZREM', waitListKey, headJobId)\n redis.call('ZADD', prefix .. 'failed', now, headJobId)\n redis.call('HSET', headJobKey,\n 'state', 'failed',\n 'failedReason', 'cost exceeds token bucket capacity',\n 'finishedOn', tostring(now))\n emitEvent(prefix .. 'events', 'failed', headJobId, {'failedReason', 'cost exceeds token bucket capacity'})\n tbCheckPassed = false\n end\n if tbCheckPassed and prTbTokens < headCost then\n -- Not enough tokens: re-register with calculated delay\n local prTbRate = math.max(tonumber(prGrp.tbRefillRate) or 0, 1)\n local prTbDelay = math.ceil((headCost - prTbTokens) * 1000 / prTbRate)\n redis.call('ZADD', rateLimitedKey, now + prTbDelay, gk)\n tbCheckPassed = false\n end\n end\n end\n end\n if tbCheckPassed then\n -- Promote up to min(rateMax, available concurrency) jobs.\n -- Do NOT touch rateCount/rateWindowStart here - moveToActive handles\n -- window reset and counting when the worker picks up the promoted jobs.\n local canPromote = 1000\n if rateMax > 0 then\n canPromote = math.min(canPromote, rateMax)\n end\n if maxConc > 0 then\n canPromote = math.min(canPromote, math.max(0, maxConc - active))\n end\n local prNextSeq = tonumber(prGrp.nextSeq) or 0\n if prNextSeq > 0 then\n local prAdv = advancePastSkips(groupHashKey, prNextSeq)\n if prAdv > prNextSeq then\n redis.call('HSET', groupHashKey, 'nextSeq', tostring(prAdv))\n prNextSeq = prAdv\n end\n end\n local groupPromoted = 0\n local prIter = 0\n local prMaxIter = canPromote + 20\n while groupPromoted < canPromote and prIter < prMaxIter do\n prIter = prIter + 1\n local zpResult = redis.call('ZPOPMIN', waitListKey, 1)\n local nextJobId = zpResult[1]\n if not nextJobId then break end\n local nextJobKey = prefix .. 'job:' .. nextJobId\n local nextState = redis.call('HGET', nextJobKey, 'state')\n if nextState ~= 'group-waiting' then\n -- Stale: skip\n elseif checkExpired(nextJobKey, nextJobId, prefix, now) then\n -- Expired: skip\n else\n if prNextSeq > 0 then\n local jobSeq = tonumber(redis.call('HGET', nextJobKey, 'orderingSeq')) or 0\n if jobSeq > prNextSeq then\n redis.call('ZADD', waitListKey, jobSeq, nextJobId)\n break\n end\n end\n xaddJob(streamKey, nextJobId, redis.call('HGET', nextJobKey, 'name'))\n redis.call('HSET', nextJobKey, 'state', 'waiting')\n promoted = promoted + 1\n groupPromoted = groupPromoted + 1\n if prNextSeq > 0 then prNextSeq = prNextSeq + 1 end\n end\n end\n end\n end\n return promoted\nend)\n\nredis.register_function('glidemq_checkConcurrency', function(keys, args)\n local metaKey = keys[1]\n local streamKey = keys[2]\n local listActiveKey = keys[3]\n local group = args[1]\n local gc = tonumber(redis.call('HGET', metaKey, 'globalConcurrency')) or 0\n if gc <= 0 then\n return -1\n end\n local ok_cc, pending = pcall(redis.call, 'XPENDING', streamKey, group)\n local pendingCount = (ok_cc and pending and tonumber(pending[1])) or 0\n local listActive = tonumber(redis.call('GET', listActiveKey)) or 0\n local remaining = gc - pendingCount - listActive\n if remaining <= 0 then\n return 0\n end\n return remaining\nend)\n\n-- Atomically check global concurrency capacity, then RPOP up to count jobs from a list and INCRBY list-active counter.\n-- Returns an array of popped jobIds (may be empty if at capacity or list is empty).\n-- KEYS: [metaKey, streamKey, listActiveKey, listKey]\n-- ARGS: [group, count]\nredis.register_function('glidemq_rpopAndReserve', function(keys, args)\n local metaKey = keys[1]\n local streamKey = keys[2]\n local listActiveKey = keys[3]\n local listKey = keys[4]\n local group = args[1]\n local requested = tonumber(args[2]) or 1\n local maxPop = requested\n local gc = tonumber(redis.call('HGET', metaKey, 'globalConcurrency')) or 0\n if gc > 0 then\n local ok_ra, pending = pcall(redis.call, 'XPENDING', streamKey, group)\n local pendingCount = (ok_ra and pending and tonumber(pending[1])) or 0\n local listActive = tonumber(redis.call('GET', listActiveKey)) or 0\n local available = gc - pendingCount - listActive\n if available <= 0 then\n return {}\n end\n if available < maxPop then\n maxPop = available\n end\n end\n local results = {}\n for i = 1, maxPop do\n local jobId = redis.call('RPOP', listKey)\n if not jobId then break end\n results[#results + 1] = jobId\n end\n if #results > 0 then\n redis.call('INCRBY', listActiveKey, #results)\n end\n return results\nend)\n\nredis.register_function('glidemq_moveToActive', function(keys, args)\n local jobKey = keys[1]\n local streamKey = keys[2] or ''\n local timestamp = args[1]\n local entryId = args[2] or ''\n local group = args[3] or ''\n local jobId = args[4] or ''\n local broadcastMode = args[5] or '0'\n local ts = tonumber(timestamp) or 0\n local timestampStr = tostring(ts)\n local fields = redis.call('HGETALL', jobKey)\n if not fields or #fields == 0 then\n return ''\n end\n local revoked = ''\n local expireAt = 0\n local curState = ''\n local orderingKey = ''\n local orderingSeq = ''\n local groupKey = ''\n local costVal = ''\n local stateValueIndex = nil\n local processedOnValueIndex = nil\n local lastActiveValueIndex = nil\n for f = 1, #fields, 2 do\n local field = fields[f]\n local value = fields[f + 1]\n if field == 'revoked' then\n revoked = value\n elseif field == 'expireAt' then\n expireAt = tonumber(value) or 0\n elseif field == 'state' then\n curState = value\n stateValueIndex = f + 1\n elseif field == 'orderingKey' then\n orderingKey = value\n elseif field == 'orderingSeq' then\n orderingSeq = value\n elseif field == 'groupKey' then\n groupKey = value\n elseif field == 'cost' then\n costVal = value\n elseif field == 'processedOn' then\n processedOnValueIndex = f + 1\n elseif field == 'lastActive' then\n lastActiveValueIndex = f + 1\n end\n end\n if revoked == '1' then\n return 'REVOKED'\n end\n local prefix = string.sub(jobKey, 1, #jobKey - #('job:' .. jobId))\n if expireAt > 0 and ts > expireAt then\n expireJob(jobKey, jobId, prefix, ts, curState, orderingKey, orderingSeq, groupKey)\n if streamKey ~= '' and entryId ~= '' and group ~= '' then\n redis.call('XACK', streamKey, group, entryId)\n if broadcastMode ~= '1' then redis.call('XDEL', streamKey, entryId) end\n end\n return 'EXPIRED'\n end\n if groupKey and groupKey ~= '' then\n local groupHashKey = prefix .. 'group:' .. groupKey\n -- Load all group fields in one call\n local grpFields = redis.call('HGETALL', groupHashKey)\n local grp = {}\n for f = 1, #grpFields, 2 do grp[grpFields[f]] = grpFields[f + 1] end\n local maxConc = tonumber(grp.maxConcurrency) or 0\n local active = tonumber(grp.active) or 0\n local waitListKey = prefix .. 'groupq:' .. groupKey\n -- Ordering gate: park future jobs (seq > nextSeq) in sorted groupq.\n -- Returning step-jobs (seq <= nextSeq) are allowed through.\n local jobOrderingSeq = tonumber(redis.call('HGET', jobKey, 'orderingSeq')) or 0\n local isReturningStepJob = false\n if jobOrderingSeq > 0 then\n local nextSeq = tonumber(grp.nextSeq) or 0\n if nextSeq > 0 then\n local mAdv = advancePastSkips(groupHashKey, nextSeq)\n if mAdv > nextSeq then\n redis.call('HSET', groupHashKey, 'nextSeq', tostring(mAdv))\n nextSeq = mAdv\n end\n end\n if nextSeq > 0 and jobOrderingSeq > nextSeq then\n if streamKey ~= '' and entryId ~= '' and group ~= '' then\n redis.call('XACK', streamKey, group, entryId)\n if broadcastMode ~= '1' then redis.call('XDEL', streamKey, entryId) end\n end\n redis.call('ZADD', waitListKey, jobOrderingSeq, jobId)\n redis.call('HSET', jobKey, 'state', 'group-waiting')\n return 'GROUP_ORDERED'\n end\n isReturningStepJob = (nextSeq > 0 and jobOrderingSeq < nextSeq)\n end\n -- Concurrency gate (skip for returning step-jobs that already hold the slot)\n if maxConc > 0 and active >= maxConc and not isReturningStepJob then\n if streamKey ~= '' and entryId ~= '' and group ~= '' then\n redis.call('XACK', streamKey, group, entryId)\n if broadcastMode ~= '1' then redis.call('XDEL', streamKey, entryId) end\n end\n redis.call('ZADD', waitListKey, groupqScore(jobKey, jobId), jobId)\n redis.call('HSET', jobKey, 'state', 'group-waiting')\n return 'GROUP_FULL'\n end\n -- Token bucket gate (read-only)\n local tbCapacity = tonumber(grp.tbCapacity) or 0\n local tbBlocked = false\n local tbDelay = 0\n local tbTokens = 0\n local jobCostVal = 0\n if tbCapacity > 0 then\n tbTokens = tbRefill(groupHashKey, grp, ts)\n jobCostVal = tonumber(costVal) or 1000\n -- DLQ guard: cost > capacity\n if jobCostVal > tbCapacity then\n if streamKey ~= '' and entryId ~= '' and group ~= '' then\n redis.call('XACK', streamKey, group, entryId)\n if broadcastMode ~= '1' then redis.call('XDEL', streamKey, entryId) end\n end\n redis.call('ZADD', prefix .. 'failed', ts, jobId)\n redis.call('HSET', jobKey,\n 'state', 'failed',\n 'failedReason', 'cost exceeds token bucket capacity',\n 'finishedOn', timestampStr)\n emitEvent(prefix .. 'events', 'failed', jobId, {'failedReason', 'cost exceeds token bucket capacity'})\n return 'ERR:COST_EXCEEDS_CAPACITY'\n end\n if tbTokens < jobCostVal then\n tbBlocked = true\n local tbRefillRateVal = tonumber(grp.tbRefillRate) or 0\n if tbRefillRateVal <= 0 then tbRefillRateVal = 1 end\n tbDelay = math.ceil((jobCostVal - tbTokens) * 1000 / tbRefillRateVal)\n end\n end\n -- Sliding window gate (read-only)\n local rateMax = tonumber(grp.rateMax) or 0\n local rlBlocked = false\n local rlDelay = 0\n if rateMax > 0 then\n local rateDuration = tonumber(grp.rateDuration) or 0\n local rateWindowStart = tonumber(grp.rateWindowStart) or 0\n local rateCount = tonumber(grp.rateCount) or 0\n local now = ts\n if rateDuration > 0 and now - rateWindowStart < rateDuration and rateCount >= rateMax then\n rlBlocked = true\n rlDelay = (rateWindowStart + rateDuration) - now\n end\n end\n -- If ANY gate blocked: park + register (skip for returning step-jobs)\n if (tbBlocked or rlBlocked) and not isReturningStepJob then\n if streamKey ~= '' and entryId ~= '' and group ~= '' then\n redis.call('XACK', streamKey, group, entryId)\n if broadcastMode ~= '1' then redis.call('XDEL', streamKey, entryId) end\n end\n local waitListKey = prefix .. 'groupq:' .. groupKey\n redis.call('ZADD', waitListKey, groupqScore(jobKey, jobId), jobId)\n redis.call('HSET', jobKey, 'state', 'group-waiting')\n local maxDelay = math.max(tbDelay, rlDelay)\n local rateLimitedKey = prefix .. 'ratelimited'\n redis.call('ZADD', rateLimitedKey, ts + maxDelay, groupKey)\n if tbBlocked then return 'GROUP_TOKEN_LIMITED' end\n return 'GROUP_RATE_LIMITED'\n end\n -- All gates passed: mutate state\n if tbCapacity > 0 then\n redis.call('HINCRBY', groupHashKey, 'tbTokens', -jobCostVal)\n end\n if rateMax > 0 then\n local rateDuration = tonumber(grp.rateDuration) or 0\n if rateDuration > 0 then\n local rateWindowStart = tonumber(grp.rateWindowStart) or 0\n local now = ts\n if now - rateWindowStart >= rateDuration then\n redis.call('HSET', groupHashKey, 'rateWindowStart', tostring(now), 'rateCount', '1')\n else\n redis.call('HINCRBY', groupHashKey, 'rateCount', 1)\n end\n end\n end\n if not isReturningStepJob then\n redis.call('HINCRBY', groupHashKey, 'active', 1)\n end\n if jobOrderingSeq > 0 then\n if not isReturningStepJob then\n redis.call('HSET', groupHashKey, 'nextSeq', tostring(jobOrderingSeq + 1))\n end\n -- Remove from groupq in case job was parked earlier and re-delivered via priority list\n redis.call('ZREM', waitListKey, jobId)\n end\n end\n redis.call('HSET', jobKey, 'state', 'active', 'processedOn', timestampStr, 'lastActive', timestampStr)\n if stateValueIndex ~= nil then\n fields[stateValueIndex] = 'active'\n else\n fields[#fields + 1] = 'state'\n fields[#fields + 1] = 'active'\n end\n if processedOnValueIndex ~= nil then\n fields[processedOnValueIndex] = timestampStr\n else\n fields[#fields + 1] = 'processedOn'\n fields[#fields + 1] = timestampStr\n end\n if lastActiveValueIndex ~= nil then\n fields[lastActiveValueIndex] = timestampStr\n else\n fields[#fields + 1] = 'lastActive'\n fields[#fields + 1] = timestampStr\n end\n return fields\nend)\n\nredis.register_function('glidemq_deferActive', function(keys, args)\n local streamKey = keys[1]\n local jobKey = keys[2]\n local listActiveKey = keys[3] or ''\n local jobId = args[1]\n local entryId = args[2]\n local group = args[3]\n local broadcastMode = args[4] or '0'\n local exists = redis.call('EXISTS', jobKey)\n if entryId ~= '' then redis.call('XACK', streamKey, group, entryId) end\n if entryId ~= '' and broadcastMode ~= '1' then redis.call('XDEL', streamKey, entryId) end\n -- List-sourced jobs (entryId='') were counted in list-active; DECR to stay balanced.\n -- Guard > 0: healListActive does not correct negative drift.\n if entryId == '' and listActiveKey ~= '' then\n local la = tonumber(redis.call('GET', listActiveKey)) or 0\n if la > 0 then\n redis.call('DECR', listActiveKey)\n end\n end\n if exists == 0 then\n return 0\n end\n xaddJob(streamKey, jobId, redis.call('HGET', jobKey, 'name'))\n redis.call('HSET', jobKey, 'state', 'waiting')\n return 1\nend)\n\nredis.register_function('glidemq_addFlow', function(keys, args)\n local parentIdKey = keys[1]\n local parentStreamKey = keys[2]\n local parentScheduledKey = keys[3]\n local parentEventsKey = keys[4]\n local parentName = args[1]\n local parentData = args[2]\n local parentOpts = args[3]\n local timestamp = tonumber(args[4])\n local parentDelay = tonumber(args[5]) or 0\n local parentPriority = tonumber(args[6]) or 0\n local parentMaxAttempts = tonumber(args[7]) or 0\n local numChildren = tonumber(args[8])\n local parentCustomId = args[9] or ''\n local parentPrefix = string.sub(parentIdKey, 1, #parentIdKey - 2)\n local parentJobIdStr\n local parentJobKey\n if parentCustomId ~= '' then\n parentJobKey = parentPrefix .. 'job:' .. parentCustomId\n if redis.call('EXISTS', parentJobKey) == 1 then\n return cjson.encode({'duplicate'})\n end\n parentJobIdStr = parentCustomId\n advanceIdCounter(parentIdKey, parentCustomId)\n else\n local parentJobId = redis.call('INCR', parentIdKey)\n parentJobIdStr = tostring(parentJobId)\n parentJobKey = parentPrefix .. 'job:' .. parentJobIdStr\n end\n -- Pre-validate all children's custom IDs for duplicates before any writes\n local seenChildKeys = {}\n for i = 1, numChildren do\n local base = 9 + (i - 1) * 9\n local preChildCustomId = args[base + 9] or ''\n if preChildCustomId ~= '' then\n local ckBase = 4 + (i - 1) * 4\n local preChildIdKey = keys[ckBase + 1]\n local preChildPrefix = string.sub(preChildIdKey, 1, #preChildIdKey - 2)\n local preChildJobKey = preChildPrefix .. 'job:' .. preChildCustomId\n if preChildJobKey == parentJobKey or seenChildKeys[preChildJobKey] then\n return cjson.encode({'duplicate'})\n end\n seenChildKeys[preChildJobKey] = true\n if redis.call('EXISTS', preChildJobKey) == 1 then\n return cjson.encode({'duplicate'})\n end\n end\n end\n local depsKey = parentPrefix .. 'deps:' .. parentJobIdStr\n local parentOrderingKey = extractOrderingKeyFromOpts(parentOpts)\n local parentGroupConc = extractGroupConcurrencyFromOpts(parentOpts)\n local parentRateMax, parentRateDuration = extractGroupRateLimitFromOpts(parentOpts)\n local parentTbCapacity, parentTbRefillRate = extractTokenBucketFromOpts(parentOpts)\n local parentCost = extractCostFromOpts(parentOpts)\n local parentUseGroup = (parentOrderingKey ~= '')\n local parentOrderingSeq = 0\n if parentOrderingKey ~= '' then\n local parentOrderingMetaKey = parentPrefix .. 'ordering'\n parentOrderingSeq = redis.call('HINCRBY', parentOrderingMetaKey, parentOrderingKey, 1)\n end\n local parentHash = {\n 'id', parentJobIdStr,\n 'name', parentName,\n 'data', parentData,\n 'opts', parentOpts,\n 'timestamp', tostring(timestamp),\n 'attemptsMade', '0',\n 'delay', tostring(parentDelay),\n 'priority', tostring(parentPriority),\n 'maxAttempts', tostring(parentMaxAttempts),\n 'state', 'waiting-children'\n }\n if parentUseGroup then\n parentHash[#parentHash + 1] = 'groupKey'\n parentHash[#parentHash + 1] = parentOrderingKey\n if parentOrderingSeq > 0 then\n parentHash[#parentHash + 1] = 'orderingSeq'\n parentHash[#parentHash + 1] = tostring(parentOrderingSeq)\n end\n if parentGroupConc < 1 then parentGroupConc = 1 end\n local groupHashKey = parentPrefix .. 'group:' .. parentOrderingKey\n redis.call('HSET', groupHashKey, 'maxConcurrency', tostring(parentGroupConc))\n redis.call('HSETNX', groupHashKey, 'active', '0')\n if parentOrderingSeq > 0 and redis.call('HEXISTS', groupHashKey, 'nextSeq') == 0 then\n redis.call('HSET', groupHashKey, 'nextSeq', '1')\n end\n if parentRateMax > 0 then\n redis.call('HSET', groupHashKey, 'rateMax', tostring(parentRateMax))\n redis.call('HSET', groupHashKey, 'rateDuration', tostring(parentRateDuration))\n end\n if parentTbCapacity > 0 then\n if parentCost > 0 and parentCost > parentTbCapacity then\n return 'ERR:COST_EXCEEDS_CAPACITY'\n end\n redis.call('HSET', groupHashKey, 'tbCapacity', tostring(parentTbCapacity), 'tbRefillRate', tostring(parentTbRefillRate))\n redis.call('HSETNX', groupHashKey, 'tbTokens', tostring(parentTbCapacity))\n redis.call('HSETNX', groupHashKey, 'tbLastRefill', tostring(timestamp))\n redis.call('HSETNX', groupHashKey, 'tbRefillRemainder', '0')\n end\n end\n if parentCost > 0 then\n parentHash[#parentHash + 1] = 'cost'\n parentHash[#parentHash + 1] = tostring(parentCost)\n end\n local parentTtl = extractTtlFromOpts(parentOpts)\n if parentTtl > 0 then\n parentHash[#parentHash + 1] = 'expireAt'\n parentHash[#parentHash + 1] = tostring(timestamp + parentTtl)\n end\n redis.call('HSET', parentJobKey, unpack(parentHash))\n -- Pre-validate all children's cost vs capacity before any child writes\n local childArgOffset = 9\n local childKeyOffset = 4\n for i = 1, numChildren do\n local base = childArgOffset + (i - 1) * 9\n local preChildOpts = args[base + 3]\n local preChildTbCap, _ = extractTokenBucketFromOpts(preChildOpts)\n if preChildTbCap > 0 then\n local preChildCost = extractCostFromOpts(preChildOpts)\n local preEffective = (preChildCost > 0) and preChildCost or 1000\n if preEffective > preChildTbCap then\n return 'ERR:COST_EXCEEDS_CAPACITY'\n end\n end\n end\n local childIds = {}\n for i = 1, numChildren do\n local base = childArgOffset + (i - 1) * 9\n local childName = args[base + 1]\n local childData = args[base + 2]\n local childOpts = args[base + 3]\n local childDelay = tonumber(args[base + 4]) or 0\n local childPriority = tonumber(args[base + 5]) or 0\n local childMaxAttempts = tonumber(args[base + 6]) or 0\n local childQueuePrefix = args[base + 7]\n local childParentQueue = args[base + 8]\n local childCustomId = args[base + 9] or ''\n local ckBase = childKeyOffset + (i - 1) * 4\n local childIdKey = keys[ckBase + 1]\n local childStreamKey = keys[ckBase + 2]\n local childScheduledKey = keys[ckBase + 3]\n local childEventsKey = keys[ckBase + 4]\n local childPrefix = string.sub(childIdKey, 1, #childIdKey - 2)\n local childJobIdStr\n local childJobKey\n if childCustomId ~= '' then\n childJobKey = childPrefix .. 'job:' .. childCustomId\n childJobIdStr = childCustomId\n advanceIdCounter(childIdKey, childCustomId)\n else\n local childJobId = redis.call('INCR', childIdKey)\n childJobIdStr = tostring(childJobId)\n childJobKey = childPrefix .. 'job:' .. childJobIdStr\n end\n local childOrderingKey = extractOrderingKeyFromOpts(childOpts)\n local childLifo = extractLifoFromOpts(childOpts)\n local childGroupConc = extractGroupConcurrencyFromOpts(childOpts)\n local childRateMax, childRateDuration = extractGroupRateLimitFromOpts(childOpts)\n local childTbCapacity, childTbRefillRate = extractTokenBucketFromOpts(childOpts)\n local childCost = extractCostFromOpts(childOpts)\n local childUseGroup = (childOrderingKey ~= '')\n local childOrderingSeq = 0\n if childOrderingKey ~= '' then\n local childOrderingMetaKey = childPrefix .. 'ordering'\n childOrderingSeq = redis.call('HINCRBY', childOrderingMetaKey, childOrderingKey, 1)\n end\n local childHash = {\n 'id', childJobIdStr,\n 'name', childName,\n 'data', childData,\n 'opts', childOpts,\n 'timestamp', tostring(timestamp),\n 'attemptsMade', '0',\n 'delay', tostring(childDelay),\n 'priority', tostring(childPriority),\n 'maxAttempts', tostring(childMaxAttempts),\n 'parentId', parentJobIdStr,\n 'parentQueue', childParentQueue\n }\n if childUseGroup then\n childHash[#childHash + 1] = 'groupKey'\n childHash[#childHash + 1] = childOrderingKey\n if childOrderingSeq > 0 then\n childHash[#childHash + 1] = 'orderingSeq'\n childHash[#childHash + 1] = tostring(childOrderingSeq)\n end\n if childGroupConc < 1 then childGroupConc = 1 end\n local childGroupHashKey = childPrefix .. 'group:' .. childOrderingKey\n redis.call('HSETNX', childGroupHashKey, 'maxConcurrency', tostring(childGroupConc))\n redis.call('HSETNX', childGroupHashKey, 'active', '0')\n if childOrderingSeq > 0 and redis.call('HEXISTS', childGroupHashKey, 'nextSeq') == 0 then\n redis.call('HSET', childGroupHashKey, 'nextSeq', '1')\n end\n if childRateMax > 0 then\n redis.call('HSET', childGroupHashKey, 'rateMax', tostring(childRateMax))\n redis.call('HSET', childGroupHashKey, 'rateDuration', tostring(childRateDuration))\n end\n if childTbCapacity > 0 then\n redis.call('HSET', childGroupHashKey, 'tbCapacity', tostring(childTbCapacity), 'tbRefillRate', tostring(childTbRefillRate))\n redis.call('HSETNX', childGroupHashKey, 'tbTokens', tostring(childTbCapacity))\n redis.call('HSETNX', childGroupHashKey, 'tbLastRefill', tostring(timestamp))\n redis.call('HSETNX', childGroupHashKey, 'tbRefillRemainder', '0')\n end\n end\n if childCost > 0 then\n childHash[#childHash + 1] = 'cost'\n childHash[#childHash + 1] = tostring(childCost)\n end\n local childTtl = extractTtlFromOpts(childOpts)\n if childTtl > 0 then\n childHash[#childHash + 1] = 'expireAt'\n childHash[#childHash + 1] = tostring(timestamp + childTtl)\n end\n if childLifo > 0 then\n childHash[#childHash + 1] = 'lifo'\n childHash[#childHash + 1] = '1'\n end\n if childDelay > 0 or childPriority > 0 then\n childHash[#childHash + 1] = 'state'\n childHash[#childHash + 1] = childDelay > 0 and 'delayed' or 'prioritized'\n else\n childHash[#childHash + 1] = 'state'\n childHash[#childHash + 1] = 'waiting'\n end\n redis.call('HSET', childJobKey, unpack(childHash))\n local depsMember = childQueuePrefix .. ':' .. childJobIdStr\n redis.call('SADD', depsKey, depsMember)\n if childDelay > 0 then\n local score = childPriority * PRIORITY_SHIFT + (timestamp + childDelay)\n redis.call('ZADD', childScheduledKey, score, childJobIdStr)\n elseif childPriority > 0 then\n local score = childPriority * PRIORITY_SHIFT\n redis.call('ZADD', childScheduledKey, score, childJobIdStr)\n elseif childLifo > 0 then\n redis.call('RPUSH', childPrefix .. 'lifo', childJobIdStr)\n else\n xaddJob(childStreamKey, childJobIdStr, childName)\n end\n emitEvent(childEventsKey, 'added', childJobIdStr, {'name', childName})\n childIds[#childIds + 1] = childJobIdStr\n end\n local extraDepsOffset = childArgOffset + numChildren * 9\n local numExtraDeps = tonumber(args[extraDepsOffset + 1]) or 0\n for i = 1, numExtraDeps do\n local extraMember = args[extraDepsOffset + 1 + i]\n redis.call('SADD', depsKey, extraMember)\n end\n emitEvent(parentEventsKey, 'added', parentJobIdStr, {'name', parentName})\n local result = {parentJobIdStr}\n for i = 1, #childIds do\n result[#result + 1] = childIds[i]\n end\n return cjson.encode(result)\nend)\n\nredis.register_function('glidemq_completeChild', function(keys, args)\n local depsKey = keys[1]\n local parentJobKey = keys[2]\n local parentStreamKey = keys[3]\n local parentEventsKey = keys[4]\n local depsMember = args[1]\n local parentId = args[2]\n local depMarker = 'depdone:' .. depsMember\n if redis.call('HSETNX', parentJobKey, depMarker, '1') == 0 then\n local doneCount = tonumber(redis.call('HGET', parentJobKey, 'depsCompleted')) or 0\n local totalDeps = redis.call('SCARD', depsKey)\n return totalDeps - doneCount\n end\n local doneCount = redis.call('HINCRBY', parentJobKey, 'depsCompleted', 1)\n local totalDeps = redis.call('SCARD', depsKey)\n local remaining = totalDeps - doneCount\n if remaining <= 0 then\n local parentState = redis.call('HGET', parentJobKey, 'state')\n if parentState == 'waiting-children' then\n redis.call('HSET', parentJobKey, 'state', 'waiting')\n xaddJob(parentStreamKey, parentId, redis.call('HGET', parentJobKey, 'name'))\n emitEvent(parentEventsKey, 'active', parentId, nil)\n end\n end\n return remaining\nend)\n\nredis.register_function('glidemq_registerParent', function(keys, args)\n -- Register an additional parent for an existing child job (DAG multi-parent).\n -- Keys: [childJobKey, childParentsKey, parentDepsKey, parentJobKey, parentStreamKey, parentEventsKey]\n -- Args: [childJobId, parentId, parentQueue, depsMember]\n local childJobKey = keys[1]\n local childParentsKey = keys[2]\n local parentDepsKey = keys[3]\n local parentJobKey = keys[4]\n local parentStreamKey = keys[5]\n local parentEventsKey = keys[6]\n local childJobId = args[1]\n local parentId = args[2]\n local parentQueue = args[3]\n local depsMember = args[4]\n -- Verify child exists\n if redis.call('EXISTS', childJobKey) == 0 then\n return 'error:child_not_found'\n end\n -- Add parent entry to child's parents SET (idempotent)\n redis.call('SADD', childParentsKey, parentQueue .. ':' .. parentId)\n -- Add child as dependency in parent's deps SET (idempotent)\n redis.call('SADD', parentDepsKey, depsMember)\n -- Race condition check: if child already completed, trigger parent notification immediately\n local childState = redis.call('HGET', childJobKey, 'state')\n if childState == 'completed' then\n local depMarker = 'depdone:' .. depsMember\n if redis.call('HSETNX', parentJobKey, depMarker, '1') == 1 then\n local doneCount = redis.call('HINCRBY', parentJobKey, 'depsCompleted', 1)\n local totalDeps = redis.call('SCARD', parentDepsKey)\n if totalDeps - doneCount <= 0 then\n local parentState = redis.call('HGET', parentJobKey, 'state')\n if parentState == 'waiting-children' then\n redis.call('HSET', parentJobKey, 'state', 'waiting')\n xaddJob(parentStreamKey, parentId, redis.call('HGET', parentJobKey, 'name'))\n emitEvent(parentEventsKey, 'active', parentId, nil)\n end\n end\n end\n return 'already_completed'\n end\n return 'ok'\nend)\n\nredis.register_function('glidemq_removeJob', function(keys, args)\n local jobKey = keys[1]\n local streamKey = keys[2]\n local scheduledKey = keys[3]\n local completedKey = keys[4]\n local failedKey = keys[5]\n local eventsKey = keys[6]\n local logKey = keys[7]\n local jobId = args[1]\n local exists = redis.call('EXISTS', jobKey)\n if exists == 0 then\n return 0\n end\n local state = redis.call('HGET', jobKey, 'state')\n local groupKey = redis.call('HGET', jobKey, 'groupKey')\n if groupKey and groupKey ~= '' then\n if state == 'active' then\n releaseGroupSlotAndPromote(jobKey, jobId, 0)\n elseif state == 'group-waiting' then\n local prefix = string.sub(jobKey, 1, #jobKey - #('job:' .. jobId))\n local waitListKey = prefix .. 'groupq:' .. groupKey\n redis.call('ZREM', waitListKey, jobId)\n end\n end\n -- DECR list-active if the job was active and list-sourced (LIFO or priority-list)\n if state == 'active' then\n local jobLifo = redis.call('HGET', jobKey, 'lifo')\n local jobPriority = tonumber(redis.call('HGET', jobKey, 'priority')) or 0\n if jobLifo == '1' or jobPriority > 0 then\n local prefix_r = string.sub(jobKey, 1, #jobKey - #('job:' .. jobId))\n redis.call('DECR', prefix_r .. 'list-active')\n end\n end\n redis.call('ZREM', scheduledKey, jobId)\n redis.call('ZREM', completedKey, jobId)\n redis.call('ZREM', failedKey, jobId)\n markOrderingDone(jobKey, jobId)\n -- Clean up DAG parents SET, per-job streaming channel, and signals\n local prefix = string.sub(jobKey, 1, #jobKey - #('job:' .. jobId))\n local parentsKey = prefix .. 'parents:' .. jobId\n local jstreamKey = prefix .. 'jstream:' .. jobId\n local signalsKey = prefix .. 'signals:' .. jobId\n local suspendedKey = prefix .. 'suspended'\n redis.call('DEL', parentsKey)\n redis.call('DEL', jstreamKey)\n redis.call('DEL', signalsKey)\n redis.call('ZREM', suspendedKey, jobId)\n redis.call('DEL', jobKey)\n redis.call('DEL', logKey)\n emitEvent(eventsKey, 'removed', jobId, nil)\n return 1\nend)\n\nredis.register_function('glidemq_clean', function(keys, args)\n local setKey = keys[1]\n local eventsKey = keys[2]\n local idKey = keys[3]\n local cutoff = tonumber(args[1])\n local limit = tonumber(args[2])\n if not limit or limit <= 0 then return {} end\n local prefix = string.sub(idKey, 1, #idKey - 2)\n local ids = redis.call('ZRANGEBYSCORE', setKey, '-inf', string.format('%.0f', cutoff), 'LIMIT', 0, limit)\n if #ids == 0 then\n return {}\n end\n for i = 1, #ids do\n redis.call('DEL', prefix .. 'job:' .. ids[i], prefix .. 'log:' .. ids[i], prefix .. 'deps:' .. ids[i], prefix .. 'parents:' .. ids[i], prefix .. 'jstream:' .. ids[i], prefix .. 'signals:' .. ids[i])\n end\n for i = 1, #ids, 1000 do\n redis.call('ZREM', setKey, unpack(ids, i, math.min(i + 999, #ids)))\n end\n emitEvent(eventsKey, 'cleaned', tostring(#ids), nil)\n return ids\nend)\n\nredis.register_function('glidemq_revoke', function(keys, args)\n local jobKey = keys[1]\n local streamKey = keys[2]\n local scheduledKey = keys[3]\n local failedKey = keys[4]\n local eventsKey = keys[5]\n local jobId = args[1]\n local timestamp = tonumber(args[2])\n local group = args[3]\n local exists = redis.call('EXISTS', jobKey)\n if exists == 0 then\n return 'not_found'\n end\n redis.call('HSET', jobKey, 'revoked', '1')\n local state = redis.call('HGET', jobKey, 'state')\n if state == 'group-waiting' then\n local gk = redis.call('HGET', jobKey, 'groupKey')\n if gk and gk ~= '' then\n local prefix = string.sub(jobKey, 1, #jobKey - #('job:' .. jobId))\n local waitListKey = prefix .. 'groupq:' .. gk\n redis.call('ZREM', waitListKey, jobId)\n end\n redis.call('ZADD', failedKey, timestamp, jobId)\n redis.call('HSET', jobKey,\n 'state', 'failed',\n 'failedReason', 'revoked',\n 'finishedOn', tostring(timestamp)\n )\n emitEvent(eventsKey, 'revoked', jobId, nil)\n return 'revoked'\n end\n if state == 'waiting' or state == 'delayed' or state == 'prioritized' then\n redis.call('ZREM', scheduledKey, jobId)\n local cursor = '-'\n local found = false\n while not found do\n local entries = redis.call('XRANGE', streamKey, cursor, '+', 'COUNT', 1000)\n if #entries == 0 then break end\n for i = 1, #entries do\n local entryId = entries[i][1]\n local fields = entries[i][2]\n for j = 1, #fields, 2 do\n if fields[j] == 'jobId' and fields[j+1] == jobId then\n if entryId ~= '' then redis.call('XACK', streamKey, group, entryId) end\n if entryId ~= '' then redis.call('XDEL', streamKey, entryId) end\n found = true\n break\n end\n end\n if found then break end\n end\n if not found then\n local lastId = entries[#entries][1]\n local dashPos = lastId:find('-')\n cursor = lastId:sub(1, dashPos) .. tostring(tonumber(lastId:sub(dashPos + 1)) + 1)\n end\n end\n redis.call('ZADD', failedKey, timestamp, jobId)\n redis.call('HSET', jobKey,\n 'state', 'failed',\n 'failedReason', 'revoked',\n 'finishedOn', tostring(timestamp)\n )\n markOrderingDone(jobKey, jobId)\n emitEvent(eventsKey, 'revoked', jobId, nil)\n return 'revoked'\n end\n emitEvent(eventsKey, 'revoked', jobId, nil)\n return 'flagged'\nend)\n\nredis.register_function('glidemq_changePriority', function(keys, args)\n local jobKey = keys[1]\n local streamKey = keys[2]\n local scheduledKey = keys[3]\n local eventsKey = keys[4]\n local jobId = args[1]\n local newPriority = tonumber(args[2])\n if newPriority == nil or newPriority < 0 then\n return 'error:invalid_priority'\n end\n local group = args[3]\n local exists = redis.call('EXISTS', jobKey)\n if exists == 0 then\n return 'error:not_found'\n end\n local state = redis.call('HGET', jobKey, 'state')\n if state == 'waiting' then\n if newPriority == 0 then\n return 'no_op'\n end\n local cursor = '-'\n local found = false\n while not found do\n local entries = redis.call('XRANGE', streamKey, cursor, '+', 'COUNT', 1000)\n if #entries == 0 then break end\n for i = 1, #entries do\n local entryId = entries[i][1]\n local fields = entries[i][2]\n for j = 1, #fields, 2 do\n if fields[j] == 'jobId' and fields[j+1] == jobId then\n pcall(redis.call, 'XACK', streamKey, group, entryId)\n if entryId ~= '' then redis.call('XDEL', streamKey, entryId) end\n found = true\n break\n end\n end\n if found then break end\n end\n if not found then\n local lastId = entries[#entries][1]\n local dashPos = lastId:find('-')\n cursor = lastId:sub(1, dashPos) .. tostring(tonumber(lastId:sub(dashPos + 1)) + 1)\n end\n end\n if not found then\n return 'error:not_in_stream'\n end\n redis.call('ZADD', scheduledKey, string.format('%.0f', newPriority * PRIORITY_SHIFT), jobId)\n redis.call('HSET', jobKey, 'state', 'prioritized', 'priority', tostring(newPriority))\n emitEvent(eventsKey, 'priority-changed', jobId, {'priority', tostring(newPriority)})\n return 'ok'\n elseif state == 'prioritized' then\n if newPriority == 0 then\n redis.call('ZREM', scheduledKey, jobId)\n xaddJob(streamKey, jobId, redis.call('HGET', jobKey, 'name'))\n redis.call('HSET', jobKey, 'state', 'waiting', 'priority', '0')\n else\n redis.call('ZADD', scheduledKey, string.format('%.0f', newPriority * PRIORITY_SHIFT), jobId)\n redis.call('HSET', jobKey, 'priority', tostring(newPriority))\n end\n emitEvent(eventsKey, 'priority-changed', jobId, {'priority', tostring(newPriority)})\n return 'ok'\n elseif state == 'delayed' then\n local rawScore = redis.call('ZSCORE', scheduledKey, jobId)\n if rawScore == false then\n return 'error:not_in_scheduled'\n end\n local oldScore = tonumber(rawScore) or 0\n local oldTimestamp = oldScore % PRIORITY_SHIFT\n local newScore = newPriority * PRIORITY_SHIFT + oldTimestamp\n redis.call('ZREM', scheduledKey, jobId)\n redis.call('ZADD', scheduledKey, string.format('%.0f', newScore), jobId)\n redis.call('HSET', jobKey, 'priority', tostring(newPriority))\n emitEvent(eventsKey, 'priority-changed', jobId, {'priority', tostring(newPriority)})\n return 'ok'\n else\n return 'error:invalid_state'\n end\nend)\n\nredis.register_function('glidemq_changeDelay', function(keys, args)\n local jobKey = keys[1]\n local streamKey = keys[2]\n local scheduledKey = keys[3]\n local eventsKey = keys[4]\n local jobId = args[1]\n local newDelay = tonumber(args[2])\n if newDelay == nil or newDelay < 0 then\n return 'error:invalid_delay'\n end\n local now = tonumber(args[3])\n local group = args[4]\n local exists = redis.call('EXISTS', jobKey)\n if exists == 0 then\n return 'error:not_found'\n end\n local state = redis.call('HGET', jobKey, 'state')\n if state == 'delayed' then\n if newDelay == 0 then\n local rawScore = redis.call('ZSCORE', scheduledKey, jobId)\n if rawScore == false then\n return 'error:not_in_scheduled'\n end\n local oldScore = tonumber(rawScore) or 0\n local priority = math.floor(oldScore / PRIORITY_SHIFT)\n if priority > 0 then\n redis.call('ZADD', scheduledKey, 'XX', string.format('%.0f', priority * PRIORITY_SHIFT), jobId)\n redis.call('HSET', jobKey, 'state', 'prioritized', 'delay', '0')\n else\n redis.call('ZREM', scheduledKey, jobId)\n xaddJob(streamKey, jobId, redis.call('HGET', jobKey, 'name'))\n redis.call('HSET', jobKey, 'state', 'waiting', 'delay', '0')\n end\n else\n local rawScore = redis.call('ZSCORE', scheduledKey, jobId)\n if rawScore == false then\n return 'error:not_in_scheduled'\n end\n local oldScore = tonumber(rawScore) or 0\n local priority = math.floor(oldScore / PRIORITY_SHIFT)\n local newScore = priority * PRIORITY_SHIFT + (now + newDelay)\n redis.call('ZADD', scheduledKey, 'XX', string.format('%.0f', newScore), jobId)\n redis.call('HSET', jobKey, 'delay', tostring(newDelay))\n end\n emitEvent(eventsKey, 'delay-changed', jobId, {'delay', tostring(newDelay)})\n return 'ok'\n elseif state == 'waiting' then\n if newDelay == 0 then\n return 'no_op'\n end\n local priority = tonumber(redis.call('HGET', jobKey, 'priority')) or 0\n local cursor = '-'\n local found = false\n while not found do\n local entries = redis.call('XRANGE', streamKey, cursor, '+', 'COUNT', 1000)\n if #entries == 0 then break end\n for i = 1, #entries do\n local entryId = entries[i][1]\n local fields = entries[i][2]\n for j = 1, #fields, 2 do\n if fields[j] == 'jobId' and fields[j+1] == jobId then\n pcall(redis.call, 'XACK', streamKey, group, entryId)\n if entryId ~= '' then redis.call('XDEL', streamKey, entryId) end\n found = true\n break\n end\n end\n if found then break end\n end\n if not found then\n cursor = '(' .. entries[#entries][1]\n end\n end\n if not found then\n return 'error:not_in_stream'\n end\n local newScore = priority * PRIORITY_SHIFT + (now + newDelay)\n redis.call('ZADD', scheduledKey, string.format('%.0f', newScore), jobId)\n redis.call('HSET', jobKey, 'state', 'delayed', 'delay', tostring(newDelay))\n emitEvent(eventsKey, 'delay-changed', jobId, {'delay', tostring(newDelay)})\n return 'ok'\n elseif state == 'prioritized' then\n if newDelay == 0 then\n return 'no_op'\n end\n local rawScore = redis.call('ZSCORE', scheduledKey, jobId)\n if rawScore == false then\n return 'error:not_in_scheduled'\n end\n local oldScore = tonumber(rawScore) or 0\n local priority = math.floor(oldScore / PRIORITY_SHIFT)\n local newScore = priority * PRIORITY_SHIFT + (now + newDelay)\n redis.call('ZADD', scheduledKey, 'XX', string.format('%.0f', newScore), jobId)\n redis.call('HSET', jobKey, 'state', 'delayed', 'delay', tostring(newDelay))\n emitEvent(eventsKey, 'delay-changed', jobId, {'delay', tostring(newDelay)})\n return 'ok'\n else\n return 'error:invalid_state'\n end\nend)\n\nredis.register_function('glidemq_promoteJob', function(keys, args)\n local jobKey = keys[1]\n local streamKey = keys[2]\n local scheduledKey = keys[3]\n local eventsKey = keys[4]\n local jobId = args[1]\n local exists = redis.call('EXISTS', jobKey)\n if exists == 0 then\n return 'error:not_found'\n end\n local state = redis.call('HGET', jobKey, 'state')\n if state ~= 'delayed' then\n return 'error:not_delayed'\n end\n redis.call('ZREM', scheduledKey, jobId)\n local jobPriority = tonumber(redis.call('HGET', jobKey, 'priority')) or 0\n local jobLifo = redis.call('HGET', jobKey, 'lifo')\n local prefix = string.sub(jobKey, 1, #jobKey - #('job:' .. jobId))\n if jobLifo == '1' then\n redis.call('RPUSH', prefix .. 'lifo', jobId)\n elseif jobPriority > 0 then\n redis.call('LPUSH', prefix .. 'priority', jobId)\n else\n xaddJob(streamKey, jobId, redis.call('HGET', jobKey, 'name'))\n end\n redis.call('HSET', jobKey, 'state', 'waiting', 'delay', '0')\n emitEvent(eventsKey, 'promoted', jobId, nil)\n return 'ok'\nend)\n\nredis.register_function('glidemq_moveActiveToDelayed', function(keys, args)\n local jobKey = keys[1]\n local streamKey = keys[2]\n local scheduledKey = keys[3]\n local eventsKey = keys[4]\n local jobId = args[1]\n local entryId = args[2]\n local now = tonumber(args[3]) or 0\n local delayedUntil = tonumber(args[4]) or now\n local group = args[5]\n local nextData = args[6]\n local broadcastMode = args[7] or '0'\n\n if redis.call('EXISTS', jobKey) == 0 then\n return 'error:not_found'\n end\n\n local state = redis.call('HGET', jobKey, 'state')\n if state ~= 'active' then\n return 'error:not_active'\n end\n\n if delayedUntil < now then\n delayedUntil = now\n end\n\n local priority = tonumber(redis.call('HGET', jobKey, 'priority')) or 0\n local delay = delayedUntil - now\n local score = priority * PRIORITY_SHIFT + delayedUntil\n\n pcall(redis.call, 'XACK', streamKey, group, entryId)\n if entryId ~= '' and broadcastMode ~= '1' then redis.call('XDEL', streamKey, entryId) end\n redis.call('ZADD', scheduledKey, string.format('%.0f', score), jobId)\n if nextData and nextData ~= '' then\n redis.call('HSET', jobKey, 'data', nextData, 'state', 'delayed', 'delay', tostring(delay))\n else\n redis.call('HSET', jobKey, 'state', 'delayed', 'delay', tostring(delay))\n end\n if entryId == '' then redis.call('DECR', string.sub(jobKey, 1, #jobKey - #('job:' .. jobId)) .. 'list-active') end\n -- Only release group slot if this is NOT an ordering-key step-job.\n -- Ordering-key jobs hold the slot until full completion to preserve per-key order.\n local jobOrdSeq = tonumber(redis.call('HGET', jobKey, 'orderingSeq')) or 0\n if jobOrdSeq <= 0 then\n releaseGroupSlotAndPromote(jobKey, jobId, now, nil)\n end\n emitEvent(eventsKey, 'delay-changed', jobId, {'delay', tostring(delay)})\n return 'ok'\nend)\n\nredis.register_function('glidemq_moveToWaitingChildren', function(keys, args)\n local jobKey = keys[1]\n local streamKey = keys[2]\n local eventsKey = keys[3]\n local jobId = args[1]\n local entryId = args[2]\n local group = args[3]\n local now = tonumber(args[4]) or 0\n local broadcastMode = args[5] or '0'\n\n local state = redis.call('HGET', jobKey, 'state')\n if not state then\n return 'error:not_found'\n end\n if state ~= 'active' then\n return 'error:not_active'\n end\n\n pcall(redis.call, 'XACK', streamKey, group, entryId)\n if entryId ~= '' and broadcastMode ~= '1' then redis.call('XDEL', streamKey, entryId) end\n redis.call('HSET', jobKey, 'state', 'waiting-children')\n\n local wcOrdSeq = tonumber(redis.call('HGET', jobKey, 'orderingSeq')) or 0\n if wcOrdSeq <= 0 then\n releaseGroupSlotAndPromote(jobKey, jobId, now)\n end\n\n -- Race condition check: children may have already completed before this call\n local prefix = string.sub(jobKey, 1, #jobKey - #('job:' .. jobId))\n local depsKey = prefix .. 'deps:' .. jobId\n local totalDeps = redis.call('SCARD', depsKey)\n if totalDeps > 0 then\n local depsCompleted = tonumber(redis.call('HGET', jobKey, 'depsCompleted')) or 0\n if depsCompleted >= totalDeps then\n redis.call('HSET', jobKey, 'state', 'waiting')\n xaddJob(streamKey, jobId, redis.call('HGET', jobKey, 'name'))\n emitEvent(eventsKey, 'active', jobId, nil)\n if entryId == '' then redis.call('DECR', prefix .. 'list-active') end\n return 'completed'\n end\n end\n\n if entryId == '' then redis.call('DECR', prefix .. 'list-active') end\n emitEvent(eventsKey, 'waiting-children', jobId, nil)\n return 'ok'\nend)\n\nredis.register_function('glidemq_rateLimitGroup', function(keys, args)\n local jobKey = keys[1]\n local streamKey = keys[2]\n local jobId = args[1]\n local entryId = args[2]\n local group = args[3]\n local duration = tonumber(args[4]) or 0\n local timestamp = tonumber(args[5]) or 0\n local broadcastMode = args[6] or '0'\n local currentJob = args[7] or 'requeue'\n local requeuePosition = args[8] or 'front'\n local extend = args[9] or 'max'\n\n local state = redis.call('HGET', jobKey, 'state')\n if state ~= 'active' then return 'error:not_active' end\n\n local groupKey = redis.call('HGET', jobKey, 'groupKey')\n if not groupKey or groupKey == '' then return 'error:no_group' end\n\n local prefix = string.sub(jobKey, 1, #jobKey - #('job:' .. jobId))\n local groupHashKey = prefix .. 'group:' .. groupKey\n local waitListKey = prefix .. 'groupq:' .. groupKey\n local rateLimitedKey = prefix .. 'ratelimited'\n local eventsKey = prefix .. 'events'\n\n if entryId ~= '' then pcall(redis.call, 'XACK', streamKey, group, entryId) end\n if entryId ~= '' and broadcastMode ~= '1' then redis.call('XDEL', streamKey, entryId) end\n if entryId == '' then redis.call('DECR', prefix .. 'list-active') end\n\n local active = tonumber(redis.call('HGET', groupHashKey, 'active')) or 0\n if active > 0 then redis.call('HSET', groupHashKey, 'active', tostring(active - 1)) end\n\n if currentJob == 'requeue' then\n local orderingSeq = tonumber(redis.call('HGET', jobKey, 'orderingSeq')) or 0\n local score\n if requeuePosition == 'front' then\n score = orderingSeq > 0 and orderingSeq or 0\n else\n local lastEntry = redis.call('ZRANGE', waitListKey, -1, -1, 'WITHSCORES')\n if lastEntry and #lastEntry >= 2 then\n score = tonumber(lastEntry[2]) + 1\n else\n score = orderingSeq > 0 and orderingSeq or (tonumber(jobId) or 0)\n end\n end\n redis.call('ZADD', waitListKey, score, jobId)\n redis.call('HSET', jobKey, 'state', 'group-waiting')\n else\n redis.call('ZADD', prefix .. 'failed', timestamp, jobId)\n local processedOn = tonumber(redis.call('HGET', jobKey, 'processedOn')) or timestamp\n redis.call('HSET', jobKey, 'state', 'failed', 'failedReason', 'group rate limited', 'finishedOn', tostring(timestamp), 'processedOn', tostring(processedOn))\n emitEvent(eventsKey, 'failed', jobId, {'failedReason', 'group rate limited'})\n local metricsKey = prefix .. 'metrics:failed'\n recordMetrics(metricsKey, timestamp, timestamp - processedOn)\n end\n\n local resumeAt = timestamp + duration\n if extend == 'max' then\n local existing = tonumber(redis.call('ZSCORE', rateLimitedKey, groupKey))\n if existing and existing > resumeAt then resumeAt = existing end\n end\n redis.call('ZADD', rateLimitedKey, resumeAt, groupKey)\n\n emitEvent(eventsKey, 'group-rate-limited', jobId, {\n 'groupKey', groupKey, 'duration', tostring(duration), 'resumeAt', tostring(resumeAt)\n })\n return tostring(resumeAt)\nend)\n\nredis.register_function('glidemq_rateLimitGroupExternal', function(keys, args)\n local rateLimitedKey = keys[1]\n local eventsKey = keys[2]\n local groupKey = args[1]\n local duration = tonumber(args[2]) or 0\n local timestamp = tonumber(args[3]) or 0\n local extend = args[4] or 'max'\n\n local resumeAt = timestamp + duration\n if extend == 'max' then\n local existing = tonumber(redis.call('ZSCORE', rateLimitedKey, groupKey))\n if existing and existing > resumeAt then resumeAt = existing end\n end\n redis.call('ZADD', rateLimitedKey, resumeAt, groupKey)\n\n emitEvent(eventsKey, 'group-rate-limited', '', {\n 'groupKey', groupKey, 'duration', tostring(duration), 'resumeAt', tostring(resumeAt)\n })\n return resumeAt\nend)\n\nredis.register_function('glidemq_searchByName', function(keys, args)\n local stateKey = keys[1]\n local stateType = args[1]\n local nameFilter = args[2]\n local limit = tonumber(args[3]) or 100\n local prefix = args[4]\n local matched = {}\n if stateType == 'zset' then\n local members = redis.call('ZRANGE', stateKey, 0, -1)\n for i = 1, #members do\n if #matched >= limit then break end\n local jobId = members[i]\n local jobKey = prefix .. 'job:' .. jobId\n local name = redis.call('HGET', jobKey, 'name')\n if name == nameFilter then\n matched[#matched + 1] = jobId\n end\n end\n elseif stateType == 'stream' then\n local cursor = '-'\n while #matched < limit do\n local entries = redis.call('XRANGE', stateKey, cursor, '+', 'COUNT', 1000)\n if #entries == 0 then break end\n for i = 1, #entries do\n if #matched >= limit then break end\n local fields = entries[i][2]\n local jobId = nil\n for j = 1, #fields, 2 do\n if fields[j] == 'jobId' then\n jobId = fields[j + 1]\n break\n end\n end\n if jobId then\n local jobKey = prefix .. 'job:' .. jobId\n local name = redis.call('HGET', jobKey, 'name')\n if name == nameFilter then\n matched[#matched + 1] = jobId\n end\n end\n end\n local lastId = entries[#entries][1]\n local dashPos = lastId:find('-')\n cursor = lastId:sub(1, dashPos) .. tostring(tonumber(lastId:sub(dashPos + 1)) + 1)\n end\n end\n return matched\nend)\n\nredis.register_function('glidemq_drain', function(keys, args)\n local streamKey = keys[1]\n local scheduledKey = keys[2]\n local eventsKey = keys[3]\n local idKey = keys[4]\n local lifoKey = keys[5]\n local priorityKey = keys[6]\n local drainDelayed = args[1] == '1'\n local group = args[2]\n local prefix = string.sub(idKey, 1, #idKey - 2)\n local removed = 0\n\n -- Build set of active entry IDs from PEL via paginated XPENDING\n local activeSet = {}\n local ok, pending = pcall(redis.call, 'XPENDING', streamKey, group, '-', '+', '10000')\n if ok and pending and #pending > 0 then\n for i = 1, #pending do\n activeSet[pending[i][1]] = true\n end\n -- Page through remaining PEL entries if there were exactly 10000\n while #pending == 10000 do\n local lastId = pending[#pending][1]\n local dashPos = lastId:find('-')\n local seq = tonumber(lastId:sub(dashPos + 1))\n local nextStart = lastId:sub(1, dashPos) .. tostring(seq + 1)\n ok, pending = pcall(redis.call, 'XPENDING', streamKey, group, nextStart, '+', '10000')\n if ok and pending and #pending > 0 then\n for i = 1, #pending do\n activeSet[pending[i][1]] = true\n end\n else\n break\n end\n end\n end\n\n -- Paginated XRANGE to avoid loading entire stream into memory\n local cursor = '-'\n while true do\n local entries = redis.call('XRANGE', streamKey, cursor, '+', 'COUNT', 1000)\n if #entries == 0 then break end\n\n local toDelete = {}\n for i = 1, #entries do\n local entryId = entries[i][1]\n if not activeSet[entryId] then\n toDelete[#toDelete + 1] = entryId\n local fields = entries[i][2]\n for j = 1, #fields, 2 do\n if fields[j] == 'jobId' and fields[j + 1] ~= '' then\n local jobId = fields[j + 1]\n redis.call('DEL', prefix .. 'job:' .. jobId, prefix .. 'log:' .. jobId, prefix .. 'deps:' .. jobId, prefix .. 'jstream:' .. jobId, prefix .. 'signals:' .. jobId)\n removed = removed + 1\n break\n end\n end\n end\n end\n if #toDelete > 0 then\n for i = 1, #toDelete, 1000 do\n redis.call('XDEL', streamKey, unpack(toDelete, i, math.min(i + 999, #toDelete)))\n end\n end\n\n -- Advance cursor past the last entry\n local lastId = entries[#entries][1]\n local dashPos = lastId:find('-')\n local seq = tonumber(lastId:sub(dashPos + 1))\n cursor = lastId:sub(1, dashPos) .. tostring(seq + 1)\n end\n\n -- Optionally drain delayed/scheduled jobs\n if drainDelayed then\n local offset = 0\n while true do\n local scheduled = redis.call('ZRANGE', scheduledKey, offset, offset + 999)\n if #scheduled == 0 then break end\n local batch = {}\n for j = 1, #scheduled do\n local jobId = scheduled[j]\n batch[#batch + 1] = prefix .. 'job:' .. jobId\n batch[#batch + 1] = prefix .. 'log:' .. jobId\n batch[#batch + 1] = prefix .. 'deps:' .. jobId\n batch[#batch + 1] = prefix .. 'jstream:' .. jobId\n batch[#batch + 1] = prefix .. 'signals:' .. jobId\n end\n redis.call('DEL', unpack(batch))\n removed = removed + #scheduled\n offset = offset + 1000\n end\n redis.call('DEL', scheduledKey)\n end\n\n -- Drain LIFO list: get all waiting job IDs, delete their hashes, then delete the list\n if lifoKey and lifoKey ~= '' then\n local lifoIds = redis.call('LRANGE', lifoKey, 0, -1)\n for i = 1, #lifoIds do\n local jobId = lifoIds[i]\n redis.call('DEL', prefix .. 'job:' .. jobId, prefix .. 'log:' .. jobId, prefix .. 'deps:' .. jobId, prefix .. 'jstream:' .. jobId, prefix .. 'signals:' .. jobId)\n removed = removed + 1\n end\n redis.call('DEL', lifoKey)\n end\n\n -- Drain priority list: get all waiting job IDs, delete their hashes, then delete the list\n if priorityKey and priorityKey ~= '' then\n local priorityIds = redis.call('LRANGE', priorityKey, 0, -1)\n for i = 1, #priorityIds do\n local jobId = priorityIds[i]\n redis.call('DEL', prefix .. 'job:' .. jobId, prefix .. 'log:' .. jobId, prefix .. 'deps:' .. jobId, prefix .. 'jstream:' .. jobId, prefix .. 'signals:' .. jobId)\n removed = removed + 1\n end\n redis.call('DEL', priorityKey)\n end\n\n if removed > 0 then\n emitEvent(eventsKey, 'drained', tostring(removed), nil)\n end\n return removed\nend)\n\nredis.register_function('glidemq_retryJobs', function(keys, args)\n local failedKey = keys[1]\n local scheduledKey = keys[2]\n local eventsKey = keys[3]\n local idKey = keys[4]\n local count = tonumber(args[1]) or 0\n local timestamp = tonumber(args[2])\n if not timestamp then return redis.error_reply('ERR invalid timestamp') end\n local prefix = string.sub(idKey, 1, #idKey - 2)\n local retried = 0\n\n while true do\n if count > 0 and retried >= count then break end\n local batchSize = 1000\n if count > 0 then\n batchSize = math.min(1000, count - retried)\n end\n local ids = redis.call('ZRANGE', failedKey, 0, batchSize - 1)\n if #ids == 0 then break end\n redis.call('ZREM', failedKey, unpack(ids))\n for i = 1, #ids do\n local jobId = ids[i]\n local jobKey = prefix .. 'job:' .. jobId\n if redis.call('EXISTS', jobKey) == 1 then\n local priority = tonumber(redis.call('HGET', jobKey, 'priority')) or 0\n local score = priority * PRIORITY_SHIFT + timestamp\n redis.call('ZADD', scheduledKey, score, jobId)\n redis.call('HSET', jobKey,\n 'state', 'delayed',\n 'attemptsMade', '0',\n 'failedReason', '',\n 'finishedOn', ''\n )\n retried = retried + 1\n end\n end\n end\n if retried > 0 then\n emitEvent(eventsKey, 'retried', tostring(retried), nil)\n end\n return retried\nend)\n\nredis.register_function('glidemq_healListActive', function(keys, args)\n local idKey = keys[1]\n local prefix = string.sub(idKey, 1, #idKey - 2)\n local listActiveKey = prefix .. 'list-active'\n local counter = tonumber(redis.call('GET', listActiveKey)) or 0\n if counter <= 0 then\n return 0\n end\n local pattern = prefix .. 'job:*'\n local cursor = '0'\n local actual = 0\n local maxIter = 500\n local iter = 0\n repeat\n iter = iter + 1\n local scanResult = redis.call('SCAN', cursor, 'MATCH', pattern, 'COUNT', 100)\n cursor = scanResult[1]\n local scannedKeys = scanResult[2]\n for i = 1, #scannedKeys do\n local jk = scannedKeys[i]\n local state = redis.call('HGET', jk, 'state')\n if state == 'active' then\n local lifo = redis.call('HGET', jk, 'lifo')\n if lifo == '1' then\n actual = actual + 1\n else\n local pri = tonumber(redis.call('HGET', jk, 'priority')) or 0\n if pri > 0 then\n actual = actual + 1\n end\n end\n end\n end\n until cursor == '0' or iter >= maxIter\n local drift = counter - actual\n if drift > 0 then\n redis.call('DECRBY', listActiveKey, drift)\n end\n return drift\nend)\n\nredis.register_function('glidemq_popLists', function(keys, args)\n local priorityKey = keys[1]\n local lifoKey = keys[2]\n local count = tonumber(args[1]) or 1\n local results = {}\n for i = 1, count do\n local id = redis.call('RPOP', priorityKey)\n if not id then break end\n results[#results + 1] = id\n end\n if #results > 0 then return results end\n for i = 1, count do\n local id = redis.call('RPOP', lifoKey)\n if not id then break end\n results[#results + 1] = id\n end\n return results\nend)\n\nredis.register_function('glidemq_suspend', function(keys, args)\n local jobKey = keys[1]\n local streamKey = keys[2]\n local eventsKey = keys[3]\n local suspendedKey = keys[4]\n local jobId = args[1]\n local entryId = args[2]\n local group = args[3]\n local now = tonumber(args[4]) or 0\n local reason = args[5] or ''\n local timeout = tonumber(args[6]) or 0\n local broadcastMode = args[7] or '0'\n\n local state = redis.call('HGET', jobKey, 'state')\n if not state then return 'error:not_found' end\n if state ~= 'active' then return 'error:not_active' end\n\n pcall(redis.call, 'XACK', streamKey, group, entryId)\n if entryId ~= '' and broadcastMode ~= '1' then redis.call('XDEL', streamKey, entryId) end\n\n local fields = {'state', 'suspended', 'suspendedAt', tostring(now)}\n if reason ~= '' then\n fields[#fields + 1] = 'suspendReason'\n fields[#fields + 1] = reason\n end\n if timeout > 0 then\n fields[#fields + 1] = 'suspendTimeout'\n fields[#fields + 1] = tostring(timeout)\n end\n redis.call('HSET', jobKey, unpack(fields))\n\n local deadline = timeout > 0 and (now + timeout) or 9999999999999\n redis.call('ZADD', suspendedKey, deadline, jobId)\n\n local ordSeq = tonumber(redis.call('HGET', jobKey, 'orderingSeq')) or 0\n if ordSeq <= 0 then\n releaseGroupSlotAndPromote(jobKey, jobId, now)\n end\n\n if entryId == '' then\n local prefix = string.sub(jobKey, 1, #jobKey - #('job:' .. jobId))\n redis.call('DECR', prefix .. 'list-active')\n end\n\n emitEvent(eventsKey, 'suspended', jobId, nil)\n return 'ok'\nend)\n\nredis.register_function('glidemq_signal', function(keys, args)\n local jobKey = keys[1]\n local streamKey = keys[2]\n local eventsKey = keys[3]\n local suspendedKey = keys[4]\n local signalsKey = keys[5]\n local jobId = args[1]\n local signalName = args[2]\n local signalData = args[3] or ''\n local now = tonumber(args[4]) or 0\n\n local state = redis.call('HGET', jobKey, 'state')\n if not state or state ~= 'suspended' then return 'not_suspended' end\n\n local signalJson = cjson.encode({name = signalName, data = signalData, receivedAt = now})\n redis.call('RPUSH', signalsKey, signalJson)\n\n local rawSignals = redis.call('LRANGE', signalsKey, 0, -1)\n local signalsArr = {}\n for i, s in ipairs(rawSignals) do signalsArr[i] = cjson.decode(s) end\n redis.call('HSET', jobKey, 'signals', cjson.encode(signalsArr))\n\n redis.call('HSET', jobKey, 'state', 'waiting')\n redis.call('ZREM', suspendedKey, jobId)\n\n local jobName = redis.call('HGET', jobKey, 'name') or ''\n xaddJob(streamKey, jobId, jobName)\n\n emitEvent(eventsKey, 'resumed', jobId, {'signal', signalName})\n return 'ok'\nend)\n\nredis.register_function('glidemq_sweepSuspended', function(keys, args)\n local suspendedKey = keys[1]\n local eventsKey = keys[2]\n local now = tonumber(args[1]) or 0\n local keyPrefix = args[2] or ''\n\n local expired = redis.call('ZRANGEBYSCORE', suspendedKey, '0', string.format('%.0f', now), 'LIMIT', 0, 100)\n if not expired or #expired == 0 then return 0 end\n\n local count = 0\n for _, jobId in ipairs(expired) do\n local id = tostring(jobId)\n local jobKey = keyPrefix .. 'job:' .. id\n local jState = redis.call('HGET', jobKey, 'state')\n if jState == 'suspended' then\n redis.call('HSET', jobKey, 'state', 'failed', 'failedReason', 'Suspend timeout exceeded')\n emitEvent(eventsKey, 'failed', id, {'failedReason', 'Suspend timeout exceeded'})\n count = count + 1\n end\n redis.call('ZREM', suspendedKey, id)\n end\n return count\nend)\n\nredis.register_function('glidemq_checkBudget', function(keys, args)\n local budgetKey = keys[1]\n local exists = redis.call('EXISTS', budgetKey)\n if exists == 0 then return 'no_budget' end\n local exceeded = redis.call('HGET', budgetKey, 'exceeded')\n if exceeded == '1' then return 'exceeded' end\n return 'ok'\nend)\n\nredis.register_function('glidemq_recordUsageAndCheckBudget', function(keys, args)\n local budgetKey = keys[1]\n local tokensJson = args[1]\n local costsJson = args[2]\n local weightedTotal = tonumber(args[3]) or 0\n local totalCost = tonumber(args[4]) or 0\n local maxTokensJson = args[5]\n local maxCostsJson = args[6]\n\n local exists = redis.call('EXISTS', budgetKey)\n if exists == 0 then return 'no_budget' end\n\n -- Increment weighted total and total cost\n if weightedTotal > 0 then redis.call('HINCRBYFLOAT', budgetKey, 'usedTokens', weightedTotal) end\n if totalCost > 0 then redis.call('HINCRBYFLOAT', budgetKey, 'usedCost', totalCost) end\n\n -- Increment per-category token counters\n local ok1, tokens = pcall(cjson.decode, tokensJson)\n if ok1 and type(tokens) == 'table' then\n for cat, val in pairs(tokens) do\n if tonumber(val) and tonumber(val) > 0 then\n redis.call('HINCRBYFLOAT', budgetKey, 'usedTokens:' .. cat, val)\n end\n end\n end\n\n -- Increment per-category cost counters\n local ok2, costs = pcall(cjson.decode, costsJson)\n if ok2 and type(costs) == 'table' then\n for cat, val in pairs(costs) do\n if tonumber(val) and tonumber(val) > 0 then\n redis.call('HINCRBYFLOAT', budgetKey, 'usedCost:' .. cat, val)\n end\n end\n end\n\n -- Check weighted total tokens cap\n local maxTotalTokens = tonumber(redis.call('HGET', budgetKey, 'maxTotalTokens')) or 0\n local usedTokens = tonumber(redis.call('HGET', budgetKey, 'usedTokens')) or 0\n if maxTotalTokens > 0 and usedTokens > maxTotalTokens then\n redis.call('HSET', budgetKey, 'exceeded', '1')\n return 'exceeded'\n end\n\n -- Check total cost cap\n local maxTotalCost = tonumber(redis.call('HGET', budgetKey, 'maxTotalCost')) or 0\n local usedCost = tonumber(redis.call('HGET', budgetKey, 'usedCost')) or 0\n if maxTotalCost > 0 and usedCost > maxTotalCost then\n redis.call('HSET', budgetKey, 'exceeded', '1')\n return 'exceeded'\n end\n\n -- Check per-category token limits\n local ok3, maxTokens = pcall(cjson.decode, maxTokensJson)\n if ok3 and type(maxTokens) == 'table' then\n for cat, limit in pairs(maxTokens) do\n local used = tonumber(redis.call('HGET', budgetKey, 'usedTokens:' .. cat)) or 0\n if tonumber(limit) and tonumber(limit) > 0 and used > tonumber(limit) then\n redis.call('HSET', budgetKey, 'exceeded', '1')\n return 'exceeded'\n end\n end\n end\n\n -- Check per-category cost limits\n local ok4, maxCosts = pcall(cjson.decode, maxCostsJson)\n if ok4 and type(maxCosts) == 'table' then\n for cat, limit in pairs(maxCosts) do\n local used = tonumber(redis.call('HGET', budgetKey, 'usedCost:' .. cat)) or 0\n if tonumber(limit) and tonumber(limit) > 0 and used > tonumber(limit) then\n redis.call('HSET', budgetKey, 'exceeded', '1')\n return 'exceeded'\n end\n end\n end\n\n return 'ok'\nend)\n";
7
7
  export type QueueKeys = ReturnType<typeof import('../utils').buildKeys>;
8
8
  /**
9
9
  * Build the keys and args arrays for glidemq_addJob, shared by addJob() and Batch callers.
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/functions/index.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/functions/index.ts"],"names":[],"mappings":"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"}
@@ -84,7 +84,8 @@ exports.LIBRARY_NAME = 'glidemq';
84
84
  // Version 78: glidemq_fail increments fallbackIndex on retry when job has fallbacks configured.
85
85
  // Version 79: Budget middleware: glidemq_checkBudget, glidemq_recordUsageAndCheckBudget.
86
86
  // Version 80: Budget v2: per-category token/cost tracking, weighted totals, per-category limits.
87
- exports.LIBRARY_VERSION = '80';
87
+ // Version 81: Fix debounce + ordering nextSeq deadlock: advancePastSkips() helper, skip markers in all ordering gates.
88
+ exports.LIBRARY_VERSION = '81';
88
89
  // Consumer group name used by workers
89
90
  exports.CONSUMER_GROUP = 'workers';
90
91
  // Embedded Lua library source loaded via FUNCTION LOAD.
@@ -148,6 +149,16 @@ local function markOrderingDone(jobKey, jobId, hintOrderingKey, hintOrderingSeq)
148
149
  end
149
150
  end
150
151
 
152
+ -- Advance nextSeq past any skip markers left by debounce on ordered jobs.
153
+ -- Skip markers are hash fields 'skip:<seq>' set when debounce deletes an ordered job.
154
+ -- Returns the advanced nextSeq. Cleans up markers via HDEL as it goes.
155
+ local function advancePastSkips(groupHashKey, nextSeq)
156
+ while redis.call('HDEL', groupHashKey, 'skip:' .. tostring(nextSeq)) == 1 do
157
+ nextSeq = nextSeq + 1
158
+ end
159
+ return nextSeq
160
+ end
161
+
151
162
  -- Refill token bucket using remainder accumulator for precision.
152
163
  -- tbRefillRate is in millitokens/second. Returns current millitokens after refill.
153
164
  -- Side effect: updates tbTokens, tbLastRefill, tbRefillRemainder on the group hash.
@@ -319,6 +330,13 @@ local function releaseGroupSlotAndPromote(jobKey, jobId, now, hintGroupKey)
319
330
  end
320
331
  local streamKey = prefix .. 'stream'
321
332
  local nextSeq = tonumber(g.nextSeq) or 0
333
+ if nextSeq > 0 then
334
+ local advanced = advancePastSkips(groupHashKey, nextSeq)
335
+ if advanced > nextSeq then
336
+ redis.call('HSET', groupHashKey, 'nextSeq', tostring(advanced))
337
+ nextSeq = advanced
338
+ end
339
+ end
322
340
  local promoted = 0
323
341
  local maxIter = available + 20
324
342
  local iter = 0
@@ -1124,6 +1142,13 @@ redis.register_function('glidemq_completeAndFetchNext', function(keys, args)
1124
1142
  for pf = 1, #priGrpFields, 2 do priGrp[priGrpFields[pf]] = priGrpFields[pf + 1] end
1125
1143
  local priOrdSeq = tonumber(redis.call('HGET', priJobKey, 'orderingSeq')) or 0
1126
1144
  local priNextSeq = tonumber(priGrp.nextSeq) or 0
1145
+ if priNextSeq > 0 then
1146
+ local priAdv = advancePastSkips(priGroupHashKey, priNextSeq)
1147
+ if priAdv > priNextSeq then
1148
+ redis.call('HSET', priGroupHashKey, 'nextSeq', tostring(priAdv))
1149
+ priNextSeq = priAdv
1150
+ end
1151
+ end
1127
1152
  local priMaxConc = tonumber(priGrp.maxConcurrency) or 0
1128
1153
  local priActive = tonumber(priGrp.active) or 0
1129
1154
  local priWaitListKey = prefix .. 'groupq:' .. priGroupKey
@@ -1259,6 +1284,13 @@ redis.register_function('glidemq_completeAndFetchNext', function(keys, args)
1259
1284
  local nextReturning = false
1260
1285
  if nextJobOrderingSeq > 0 then
1261
1286
  local nextExpectedSeq = tonumber(nGrp.nextSeq) or 0
1287
+ if nextExpectedSeq > 0 then
1288
+ local relAdv = advancePastSkips(nextGroupHashKey, nextExpectedSeq)
1289
+ if relAdv > nextExpectedSeq then
1290
+ redis.call('HSET', nextGroupHashKey, 'nextSeq', tostring(relAdv))
1291
+ nextExpectedSeq = relAdv
1292
+ end
1293
+ end
1262
1294
  if nextExpectedSeq > 0 and nextJobOrderingSeq > nextExpectedSeq then
1263
1295
  redis.call('XACK', streamKey, group, nextEntryId)
1264
1296
  redis.call('XDEL', streamKey, nextEntryId)
@@ -1657,8 +1689,14 @@ redis.register_function('glidemq_dedup', function(keys, args)
1657
1689
  local jobKey = prefix .. 'job:' .. existingJobId
1658
1690
  local state = redis.call('HGET', jobKey, 'state')
1659
1691
  if state == 'delayed' or state == 'prioritized' then
1692
+ local delGroupKey = redis.call('HGET', jobKey, 'groupKey')
1693
+ local delOrderingSeq = tonumber(redis.call('HGET', jobKey, 'orderingSeq')) or 0
1660
1694
  redis.call('ZREM', scheduledKey, existingJobId)
1661
1695
  markOrderingDone(jobKey, existingJobId)
1696
+ if delGroupKey and delGroupKey ~= '' and delOrderingSeq > 0 then
1697
+ local groupHashKey = prefix .. 'group:' .. delGroupKey
1698
+ redis.call('HSET', groupHashKey, 'skip:' .. tostring(delOrderingSeq), '1')
1699
+ end
1662
1700
  redis.call('DEL', jobKey)
1663
1701
  if skipEvents ~= '1' then emitEvent(eventsKey, 'removed', existingJobId, nil) end
1664
1702
  elseif state and state ~= 'completed' and state ~= 'failed' then
@@ -1910,6 +1948,13 @@ redis.register_function('glidemq_promoteRateLimited', function(keys, args)
1910
1948
  canPromote = math.min(canPromote, math.max(0, maxConc - active))
1911
1949
  end
1912
1950
  local prNextSeq = tonumber(prGrp.nextSeq) or 0
1951
+ if prNextSeq > 0 then
1952
+ local prAdv = advancePastSkips(groupHashKey, prNextSeq)
1953
+ if prAdv > prNextSeq then
1954
+ redis.call('HSET', groupHashKey, 'nextSeq', tostring(prAdv))
1955
+ prNextSeq = prAdv
1956
+ end
1957
+ end
1913
1958
  local groupPromoted = 0
1914
1959
  local prIter = 0
1915
1960
  local prMaxIter = canPromote + 20
@@ -2075,6 +2120,13 @@ redis.register_function('glidemq_moveToActive', function(keys, args)
2075
2120
  local isReturningStepJob = false
2076
2121
  if jobOrderingSeq > 0 then
2077
2122
  local nextSeq = tonumber(grp.nextSeq) or 0
2123
+ if nextSeq > 0 then
2124
+ local mAdv = advancePastSkips(groupHashKey, nextSeq)
2125
+ if mAdv > nextSeq then
2126
+ redis.call('HSET', groupHashKey, 'nextSeq', tostring(mAdv))
2127
+ nextSeq = mAdv
2128
+ end
2129
+ end
2078
2130
  if nextSeq > 0 and jobOrderingSeq > nextSeq then
2079
2131
  if streamKey ~= '' and entryId ~= '' and group ~= '' then
2080
2132
  redis.call('XACK', streamKey, group, entryId)
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/functions/index.ts"],"names":[],"mappings":";;;AA88GA,gCAuDC;AAED,wBAqDC;AAMD,sBA0DC;AAMD,0BAGC;AASD,8BAOC;AAED,0BAGC;AAED,wBAGC;AAED,8BAGC;AA+BD,kCAqCC;AAwBD,oDA8GC;AAOD,0BAgCC;AAMD,wCAwBC;AAMD,wDAaC;AAKD,sBAEC;AAKD,wBAEC;AAMD,8BAaC;AAOD,4CAGC;AAOD,wCAeC;AAMD,4BAIC;AAiBD,oCAsDC;AAOD,gDAGC;AAQD,wCA6BC;AAOD,wDAcC;AAOD,kCAWC;AAMD,8BAOC;AAOD,8BAeC;AAQD,gCAYC;AAWD,8BAOC;AAQD,8BAaC;AAOD,wCAaC;AAOD,kCAaC;AAOD,gCAGC;AAMD,kDAmBC;AAOD,sDAaC;AAQD,oCAkBC;AAMD,0BA6DC;AAMD,sCAYC;AAOD,wCAsBC;AAOD,wCAGC;AAMD,gCAeC;AAMD,8BAcC;AAMD,wCAYC;AAKD,kCAGC;AAOD,8DAuBC;AA/lJY,QAAA,YAAY,GAAG,SAAS,CAAC;AACtC,yEAAyE;AACzE,4GAA4G;AAC5G,wGAAwG;AACxG,0GAA0G;AAC1G,qGAAqG;AACrG,+FAA+F;AAC/F,qFAAqF;AACrF,wKAAwK;AACxK,wFAAwF;AACxF,wGAAwG;AACxG,4GAA4G;AAC5G,iFAAiF;AACjF,sHAAsH;AACtH,6EAA6E;AAC7E,6EAA6E;AAC7E,uGAAuG;AACvG,yFAAyF;AACzF,mGAAmG;AACnG,2HAA2H;AAC3H,0EAA0E;AAC1E,sHAAsH;AACtH,oHAAoH;AACpH,+IAA+I;AAC/I,uGAAuG;AACvG,8GAA8G;AAC9G,sIAAsI;AACtI,0IAA0I;AAC1I,0GAA0G;AAC1G,0FAA0F;AAC1F,wGAAwG;AACxG,gKAAgK;AAChK,8FAA8F;AAC9F,oGAAoG;AACpG,yFAAyF;AACzF,uIAAuI;AACvI,gGAAgG;AAChG,yFAAyF;AACzF,iGAAiG;AACpF,QAAA,eAAe,GAAG,IAAI,CAAC;AAEpC,sCAAsC;AACzB,QAAA,cAAc,GAAG,SAAS,CAAC;AAExC,wDAAwD;AACxD,0EAA0E;AAC7D,QAAA,cAAc,GAAG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;YA0blB,uBAAe;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAw9F1B,CAAC;AAMF,iCAAiC;AAEjC;;GAEG;AACH,SAAgB,UAAU,CACxB,CAAY,EACZ,OAAe,EACf,IAAY,EACZ,IAAY,EACZ,SAAiB,EACjB,KAAa,EACb,QAAgB,EAChB,QAAgB,EAChB,WAAmB,EACnB,cAAsB,EAAE,EACxB,mBAA2B,CAAC,EAC5B,eAAuB,CAAC,EACxB,oBAA4B,CAAC,EAC7B,aAAqB,CAAC,EACtB,eAAuB,CAAC,EACxB,UAAkB,CAAC,EACnB,MAAc,CAAC,EACf,cAAsB,EAAE,EACxB,OAAe,CAAC,EAChB,cAAsB,EAAE,EACxB,gBAAwB,EAAE,EAC1B,gBAAwB,EAAE,EAC1B,aAAsB,KAAK;IAE3B,MAAM,IAAI,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,CAAC,SAAS,EAAE,CAAC,CAAC,MAAM,CAAC,CAAC;IACrD,IAAI,aAAa,EAAE,CAAC;QAClB,IAAI,CAAC,IAAI,CAAC,aAAa,CAAC,CAAC;IAC3B,CAAC;IACD,OAAO;QACL,IAAI;QACJ,IAAI,EAAE;YACJ,OAAO;YACP,IAAI;YACJ,IAAI;YACJ,SAAS,CAAC,QAAQ,EAAE;YACpB,KAAK,CAAC,QAAQ,EAAE;YAChB,QAAQ,CAAC,QAAQ,EAAE;YACnB,QAAQ;YACR,WAAW,CAAC,QAAQ,EAAE;YACtB,WAAW;YACX,gBAAgB,CAAC,QAAQ,EAAE;YAC3B,YAAY,CAAC,QAAQ,EAAE;YACvB,iBAAiB,CAAC,QAAQ,EAAE;YAC5B,UAAU,CAAC,QAAQ,EAAE;YACrB,YAAY,CAAC,QAAQ,EAAE;YACvB,OAAO,CAAC,QAAQ,EAAE;YAClB,GAAG,CAAC,QAAQ,EAAE;YACd,WAAW;YACX,IAAI,CAAC,QAAQ,EAAE;YACf,WAAW;YACX,aAAa;YACb,UAAU,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,GAAG;SACvB;KACF,CAAC;AACJ,CAAC;AAEM,KAAK,UAAU,MAAM,CAC1B,MAAc,EACd,CAAY,EACZ,OAAe,EACf,IAAY,EACZ,IAAY,EACZ,SAAiB,EACjB,KAAa,EACb,QAAgB,EAChB,QAAgB,EAChB,WAAmB,EACnB,cAAsB,EAAE,EACxB,mBAA2B,CAAC,EAC5B,eAAuB,CAAC,EACxB,oBAA4B,CAAC,EAC7B,aAAqB,CAAC,EACtB,eAAuB,CAAC,EACxB,UAAkB,CAAC,EACnB,MAAc,CAAC,EACf,cAAsB,EAAE,EACxB,OAAe,CAAC,EAChB,cAAsB,EAAE,EACxB,gBAAwB,EAAE,EAC1B,gBAAwB,EAAE,EAC1B,aAAsB,KAAK;IAE3B,MAAM,EAAE,IAAI,EAAE,IAAI,EAAE,GAAG,UAAU,CAC/B,CAAC,EACD,OAAO,EACP,IAAI,EACJ,IAAI,EACJ,SAAS,EACT,KAAK,EACL,QAAQ,EACR,QAAQ,EACR,WAAW,EACX,WAAW,EACX,gBAAgB,EAChB,YAAY,EACZ,iBAAiB,EACjB,UAAU,EACV,YAAY,EACZ,OAAO,EACP,GAAG,EACH,WAAW,EACX,IAAI,EACJ,WAAW,EACX,aAAa,EACb,aAAa,EACb,UAAU,CACX,CAAC;IACF,MAAM,MAAM,GAAG,MAAM,MAAM,CAAC,KAAK,CAAC,gBAAgB,EAAE,IAAI,EAAE,IAAI,CAAC,CAAC;IAChE,OAAO,MAAgB,CAAC;AAC1B,CAAC;AAED;;;GAGG;AACI,KAAK,UAAU,KAAK,CACzB,MAAc,EACd,CAAY,EACZ,OAAe,EACf,KAAa,EACb,IAAY,EACZ,OAAe,EACf,IAAY,EACZ,IAAY,EACZ,SAAiB,EACjB,KAAa,EACb,QAAgB,EAChB,QAAgB,EAChB,WAAmB,EACnB,cAAsB,EAAE,EACxB,mBAA2B,CAAC,EAC5B,eAAuB,CAAC,EACxB,oBAA4B,CAAC,EAC7B,aAAqB,CAAC,EACtB,eAAuB,CAAC,EACxB,UAAkB,CAAC,EACnB,SAAiB,CAAC,EAClB,cAAsB,EAAE,EACxB,OAAe,CAAC,EAChB,cAAsB,EAAE,EACxB,gBAAwB,EAAE,EAC1B,aAAsB,KAAK;IAE3B,MAAM,IAAI,GAAG,CAAC,CAAC,CAAC,KAAK,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,CAAC,SAAS,EAAE,CAAC,CAAC,MAAM,CAAC,CAAC;IAC9D,IAAI,aAAa,EAAE,CAAC;QAClB,IAAI,CAAC,IAAI,CAAC,aAAa,CAAC,CAAC;IAC3B,CAAC;IACD,MAAM,MAAM,GAAG,MAAM,MAAM,CAAC,KAAK,CAAC,eAAe,EAAE,IAAI,EAAE;QACvD,OAAO;QACP,KAAK,CAAC,QAAQ,EAAE;QAChB,IAAI;QACJ,OAAO;QACP,IAAI;QACJ,IAAI;QACJ,SAAS,CAAC,QAAQ,EAAE;QACpB,KAAK,CAAC,QAAQ,EAAE;QAChB,QAAQ,CAAC,QAAQ,EAAE;QACnB,QAAQ;QACR,WAAW,CAAC,QAAQ,EAAE;QACtB,WAAW;QACX,gBAAgB,CAAC,QAAQ,EAAE;QAC3B,YAAY,CAAC,QAAQ,EAAE;QACvB,iBAAiB,CAAC,QAAQ,EAAE;QAC5B,UAAU,CAAC,QAAQ,EAAE;QACrB,YAAY,CAAC,QAAQ,EAAE;QACvB,OAAO,CAAC,QAAQ,EAAE;QAClB,MAAM,CAAC,QAAQ,EAAE;QACjB,WAAW;QACX,IAAI,CAAC,QAAQ,EAAE;QACf,WAAW;QACX,UAAU,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,GAAG;KACvB,CAAC,CAAC;IACH,OAAO,MAAgB,CAAC;AAC1B,CAAC;AAED;;;GAGG;AACI,KAAK,UAAU,OAAO,CAAC,MAAc,EAAE,CAAY,EAAE,SAAiB;IAC3E,MAAM,MAAM,GAAG,MAAM,MAAM,CAAC,KAAK,CAAC,iBAAiB,EAAE,CAAC,CAAC,CAAC,SAAS,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,CAAC,MAAM,CAAC,EAAE,CAAC,SAAS,CAAC,QAAQ,EAAE,CAAC,CAAC,CAAC;IAChH,OAAO,MAAgB,CAAC;AAC1B,CAAC;AAED;;;;;;GAMG;AACI,KAAK,UAAU,SAAS,CAAC,MAAc,EAAE,CAAY;IAC1D,MAAM,MAAM,GAAG,MAAM,MAAM,CAAC,KAAK,CAAC,iBAAiB,EAAE,CAAC,CAAC,CAAC,SAAS,EAAE,CAAC,CAAC,WAAW,CAAC,EAAE,EAAE,CAAC,CAAC;IACvF,MAAM,EAAE,GAAG,MAAM,CAAC,MAAM,CAAC,CAAC;IAC1B,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,EAAE,CAAC,IAAI,EAAE,GAAG,CAAC,EAAE,CAAC;QACnC,OAAO,IAAI,CAAC;IACd,CAAC;IACD,OAAO,EAAE,CAAC;AACZ,CAAC;AAEM,KAAK,UAAU,OAAO,CAAC,MAAc,EAAE,OAAe,EAAE,KAAa,EAAE,KAAa;IACzF,MAAM,MAAM,GAAG,MAAM,MAAM,CAAC,KAAK,CAAC,iBAAiB,EAAE,CAAC,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,KAAK,CAAC,QAAQ,EAAE,CAAC,CAAC,CAAC;IAC3F,OAAO,MAAM,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;AAC9B,CAAC;AAEM,KAAK,UAAU,MAAM,CAAC,MAAc,EAAE,OAAe,EAAE,KAAa;IACzE,MAAM,MAAM,GAAG,MAAM,MAAM,CAAC,KAAK,CAAC,gBAAgB,EAAE,CAAC,OAAO,CAAC,EAAE,CAAC,KAAK,CAAC,CAAC,CAAC;IACxE,OAAO,MAAM,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;AAC9B,CAAC;AAEM,KAAK,UAAU,SAAS,CAAC,MAAc,EAAE,OAAe,EAAE,KAAa,EAAE,KAAa;IAC3F,MAAM,MAAM,GAAG,MAAM,MAAM,CAAC,KAAK,CAAC,mBAAmB,EAAE,CAAC,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,KAAK,CAAC,QAAQ,EAAE,CAAC,CAAC,CAAC;IAC7F,OAAO,MAAM,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;AAC9B,CAAC;AAED;;GAEG;AACH,MAAM,cAAc,GAAG,EAAE,IAAI,EAAE,GAAG,EAAE,KAAK,EAAE,CAAC,EAAE,GAAG,EAAE,CAAC,EAAW,CAAC;AAChE,MAAM,cAAc,GAAG,EAAE,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,CAAC,EAAE,GAAG,EAAE,CAAC,EAAW,CAAC;AAEnE,SAAS,eAAe,CAAC,GAAuD;IAK9E,IAAI,GAAG,KAAK,IAAI,EAAE,CAAC;QACjB,OAAO,cAAc,CAAC;IACxB,CAAC;IACD,IAAI,OAAO,GAAG,KAAK,QAAQ,EAAE,CAAC;QAC5B,OAAO,EAAE,IAAI,EAAE,OAAO,EAAE,KAAK,EAAE,GAAG,EAAE,GAAG,EAAE,CAAC,EAAE,CAAC;IAC/C,CAAC;IACD,IAAI,GAAG,IAAI,OAAO,GAAG,KAAK,QAAQ,EAAE,CAAC;QACnC,OAAO,EAAE,IAAI,EAAE,WAAW,EAAE,KAAK,EAAE,GAAG,CAAC,KAAK,IAAI,CAAC,EAAE,GAAG,EAAE,GAAG,CAAC,GAAG,IAAI,CAAC,EAAE,CAAC;IACzE,CAAC;IACD,OAAO,cAAc,CAAC;AACxB,CAAC;AAED;;;;;GAKG;AACI,KAAK,UAAU,WAAW,CAC/B,MAAc,EACd,CAAY,EACZ,KAAa,EACb,OAAe,EACf,WAAmB,EACnB,SAAiB,EACjB,QAAgB,sBAAc,EAC9B,gBAAoE,EACpE,UAA4E,EAC5E,aAAuB;IAEvB,MAAM,EAAE,IAAI,EAAE,KAAK,EAAE,GAAG,EAAE,GAAG,eAAe,CAAC,gBAAgB,CAAC,CAAC;IAE/D,MAAM,IAAI,GAAa,CAAC,CAAC,CAAC,MAAM,EAAE,CAAC,CAAC,SAAS,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,CAAC,GAAG,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC,gBAAgB,CAAC,CAAC;IAC3F,MAAM,IAAI,GAAa;QACrB,KAAK;QACL,OAAO;QACP,WAAW;QACX,SAAS,CAAC,QAAQ,EAAE;QACpB,KAAK;QACL,IAAI;QACJ,KAAK,CAAC,QAAQ,EAAE;QAChB,GAAG,CAAC,QAAQ,EAAE;KACf,CAAC;IAEF,IAAI,UAAU,EAAE,CAAC;QACf,MAAM,EAAE,GAAG,UAAU,CAAC,UAAU,CAAC;QACjC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,IAAI,CAAC,UAAU,CAAC,QAAQ,CAAC,EAAE,EAAE,CAAC,GAAG,CAAC,UAAU,CAAC,QAAQ,CAAC,EAAE,EAAE,CAAC,MAAM,EAAE,EAAE,CAAC,MAAM,CAAC,CAAC;QAC3F,IAAI,CAAC,IAAI,CAAC,UAAU,CAAC,UAAU,EAAE,UAAU,CAAC,QAAQ,CAAC,CAAC;IACxD,CAAC;SAAM,CAAC;QACN,IAAI,CAAC,IAAI,CAAC,EAAE,EAAE,EAAE,CAAC,CAAC;IACpB,CAAC;IAED,IAAI,aAAa;QAAE,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;IAElC,OAAO,MAAM,CAAC,KAAK,CAAC,kBAAkB,EAAE,IAAI,EAAE,IAAI,CAAC,CAAC;AACtD,CAAC;AAwBM,KAAK,UAAU,oBAAoB,CACxC,MAAc,EACd,CAAY,EACZ,KAAa,EACb,OAAe,EACf,WAAmB,EACnB,SAAiB,EACjB,KAAa,EACb,QAAgB,EAChB,gBAAoE,EACpE,UAA4E,EAC5E,KAA6B,EAC7B,aAAuB,EACvB,WAAoB,EACpB,UAAoB,EACpB,UAAoB,EACpB,WAAqB;IAErB,MAAM,EAAE,IAAI,EAAE,KAAK,EAAE,GAAG,EAAE,GAAG,eAAe,CAAC,gBAAgB,CAAC,CAAC;IAE/D,MAAM,IAAI,GAAa,CAAC,CAAC,CAAC,MAAM,EAAE,CAAC,CAAC,SAAS,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,CAAC,GAAG,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC,gBAAgB,CAAC,CAAC;IAC3F,MAAM,IAAI,GAAa;QACrB,KAAK;QACL,OAAO;QACP,WAAW;QACX,SAAS,CAAC,QAAQ,EAAE;QACpB,KAAK;QACL,QAAQ;QACR,IAAI;QACJ,KAAK,CAAC,QAAQ,EAAE;QAChB,GAAG,CAAC,QAAQ,EAAE;KACf,CAAC;IAEF,IAAI,UAAU,EAAE,CAAC;QACf,MAAM,EAAE,GAAG,UAAU,CAAC,UAAU,CAAC;QACjC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,IAAI,CAAC,UAAU,CAAC,QAAQ,CAAC,EAAE,EAAE,CAAC,GAAG,CAAC,UAAU,CAAC,QAAQ,CAAC,EAAE,EAAE,CAAC,MAAM,EAAE,EAAE,CAAC,MAAM,CAAC,CAAC;QAC3F,IAAI,CAAC,IAAI,CAAC,UAAU,CAAC,UAAU,EAAE,UAAU,CAAC,QAAQ,CAAC,CAAC;IACxD,CAAC;SAAM,CAAC;QACN,IAAI,CAAC,IAAI,CAAC,EAAE,EAAE,EAAE,CAAC,CAAC;IACpB,CAAC;IAED,MAAM,eAAe,GACnB,KAAK,EAAE,WAAW,IAAI,IAAI,IAAI,MAAM,CAAC,QAAQ,CAAC,KAAK,CAAC,WAAW,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,KAAK,CAAC,WAAW,CAAC,CAAC,QAAQ,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;IACnH,oGAAoG;IACpG,IAAI,CAAC,IAAI,CACP,KAAK,EAAE,WAAW,KAAK,SAAS,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,KAAK,CAAC,WAAW,EAC3D,eAAe,EACf,KAAK,EAAE,QAAQ,KAAK,SAAS,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,KAAK,CAAC,QAAQ,CACtD,CAAC;IACF,IAAI,CAAC,IAAI,CAAC,aAAa,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC;IACrC,IAAI,CAAC,IAAI,CAAC,WAAW,IAAI,IAAI,CAAC,CAAC,CAAC,WAAW,CAAC,QAAQ,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC;IAC7D,IAAI,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC;IAClC,IAAI,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC;IAClC,IAAI,CAAC,IAAI,CAAC,WAAW,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC;IAEnC,MAAM,GAAG,GAAG,MAAM,MAAM,CAAC,KAAK,CAAC,8BAA8B,EAAE,IAAI,EAAE,IAAI,CAAC,CAAC;IAE3E,8CAA8C;IAC9C,IAAI,KAAK,CAAC,OAAO,CAAC,GAAG,CAAC,EAAE,CAAC;QACvB,MAAM,GAAG,GAAG,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC;QAC3B,IAAI,GAAG,KAAK,WAAW,EAAE,CAAC;YACxB,OAAO,EAAE,SAAS,EAAE,GAAG,CAAC,CAAC,CAAC,IAAI,IAAI,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,KAAK,EAAE,IAAI,EAAE,KAAK,EAAE,CAAC;QAC7E,CAAC;QACD,IAAI,GAAG,KAAK,cAAc,EAAE,CAAC;YAC3B,OAAO;gBACL,SAAS,EAAE,GAAG,CAAC,CAAC,CAAC,IAAI,IAAI,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,KAAK;gBAClD,IAAI,EAAE,SAAS;gBACf,SAAS,EAAE,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC;gBACzB,WAAW,EAAE,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC;aAC5B,CAAC;QACJ,CAAC;QACD,IAAI,GAAG,KAAK,WAAW,EAAE,CAAC;YACxB,MAAM,IAAI,GAA2B,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC;YACzD,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,CAAC,GAAG,GAAG,CAAC,MAAM,EAAE,CAAC,IAAI,CAAC,EAAE,CAAC;gBAC3C,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,GAAG,MAAM,CAAC,GAAG,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC;YAC5C,CAAC;YACD,OAAO;gBACL,SAAS,EAAE,GAAG,CAAC,CAAC,CAAC,IAAI,IAAI,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,KAAK;gBAClD,IAAI,EAAE,IAAI;gBACV,SAAS,EAAE,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC;gBACzB,WAAW,EAAE,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC;aAC5B,CAAC;QACJ,CAAC;QACD,MAAM,IAAI,KAAK,CAAC,gDAAgD,GAAG,EAAE,CAAC,CAAC;IACzE,CAAC;IAED,iEAAiE;IACjE,MAAM,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC;IACvC,IAAI,CAAC,MAAM,CAAC,IAAI,IAAI,MAAM,CAAC,IAAI,KAAK,KAAK,EAAE,CAAC;QAC1C,OAAO,EAAE,SAAS,EAAE,MAAM,CAAC,SAAS,EAAE,IAAI,EAAE,KAAK,EAAE,CAAC;IACtD,CAAC;IACD,IAAI,MAAM,CAAC,IAAI,KAAK,SAAS,EAAE,CAAC;QAC9B,OAAO;YACL,SAAS,EAAE,MAAM,CAAC,SAAS;YAC3B,IAAI,EAAE,SAAS;YACf,SAAS,EAAE,MAAM,CAAC,SAAS;YAC3B,WAAW,EAAE,MAAM,CAAC,WAAW;SAChC,CAAC;IACJ,CAAC;IACD,MAAM,UAAU,GAAG,MAAM,CAAC,IAAgB,CAAC;IAC3C,MAAM,IAAI,GAA2B,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC;IACzD,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,UAAU,CAAC,MAAM,EAAE,CAAC,IAAI,CAAC,EAAE,CAAC;QAC9C,IAAI,CAAC,MAAM,CAAC,UAAU,CAAC,CAAC,CAAC,CAAC,CAAC,GAAG,MAAM,CAAC,UAAU,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC;IAC1D,CAAC;IACD,OAAO;QACL,SAAS,EAAE,MAAM,CAAC,SAAS;QAC3B,IAAI,EAAE,IAAI;QACV,SAAS,EAAE,MAAM,CAAC,SAAS;QAC3B,WAAW,EAAE,MAAM,CAAC,WAAW;KAChC,CAAC;AACJ,CAAC;AAED;;;;GAIG;AACI,KAAK,UAAU,OAAO,CAC3B,MAAc,EACd,CAAY,EACZ,KAAa,EACb,OAAe,EACf,YAAoB,EACpB,SAAiB,EACjB,WAAmB,EACnB,YAAoB,EACpB,QAAgB,sBAAc,EAC9B,YAAgE,EAChE,aAAuB;IAEvB,MAAM,EAAE,IAAI,EAAE,KAAK,EAAE,GAAG,EAAE,GAAG,eAAe,CAAC,YAAY,CAAC,CAAC;IAC3D,MAAM,MAAM,GAAG,MAAM,MAAM,CAAC,KAAK,CAC/B,cAAc,EACd,CAAC,CAAC,CAAC,MAAM,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,CAAC,SAAS,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,CAAC,GAAG,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC,aAAa,CAAC,EAC1E;QACE,KAAK;QACL,OAAO;QACP,YAAY;QACZ,SAAS,CAAC,QAAQ,EAAE;QACpB,WAAW,CAAC,QAAQ,EAAE;QACtB,YAAY,CAAC,QAAQ,EAAE;QACvB,KAAK;QACL,IAAI;QACJ,KAAK,CAAC,QAAQ,EAAE;QAChB,GAAG,CAAC,QAAQ,EAAE;QACd,GAAG,CAAC,aAAa,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC;KAChC,CACF,CAAC;IACF,OAAO,MAAgB,CAAC;AAC1B,CAAC;AAED;;;GAGG;AACI,KAAK,UAAU,cAAc,CAClC,MAAc,EACd,CAAY,EACZ,QAAgB,EAChB,SAAiB,EACjB,eAAuB,EACvB,SAAiB,EACjB,QAAgB,sBAAc,EAC9B,aAAuB;IAEvB,MAAM,MAAM,GAAG,MAAM,MAAM,CAAC,KAAK,CAC/B,wBAAwB,EACxB,CAAC,CAAC,CAAC,MAAM,EAAE,CAAC,CAAC,MAAM,CAAC,EACpB;QACE,KAAK;QACL,QAAQ;QACR,SAAS,CAAC,QAAQ,EAAE;QACpB,eAAe,CAAC,QAAQ,EAAE;QAC1B,SAAS,CAAC,QAAQ,EAAE;QACpB,CAAC,CAAC,MAAM;QACR,GAAG,CAAC,aAAa,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC;KAChC,CACF,CAAC;IACF,OAAO,MAAgB,CAAC;AAC1B,CAAC;AAED;;;GAGG;AACI,KAAK,UAAU,sBAAsB,CAC1C,MAAc,EACd,CAAY,EACZ,SAAiB,EACjB,eAAuB,EACvB,SAAiB;IAEjB,MAAM,MAAM,GAAG,MAAM,MAAM,CAAC,KAAK,CAC/B,gCAAgC,EAChC,CAAC,CAAC,CAAC,MAAM,EAAE,CAAC,CAAC,MAAM,CAAC,EACpB,CAAC,SAAS,CAAC,QAAQ,EAAE,EAAE,eAAe,CAAC,QAAQ,EAAE,EAAE,SAAS,CAAC,QAAQ,EAAE,EAAE,CAAC,CAAC,MAAM,CAAC,CACnF,CAAC;IACF,OAAO,MAAgB,CAAC;AAC1B,CAAC;AAED;;GAEG;AACI,KAAK,UAAU,KAAK,CAAC,MAAc,EAAE,CAAY;IACtD,MAAM,MAAM,CAAC,KAAK,CAAC,eAAe,EAAE,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC,MAAM,CAAC,EAAE,EAAE,CAAC,CAAC;AAC9D,CAAC;AAED;;GAEG;AACI,KAAK,UAAU,MAAM,CAAC,MAAc,EAAE,CAAY;IACvD,MAAM,MAAM,CAAC,KAAK,CAAC,gBAAgB,EAAE,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC,MAAM,CAAC,EAAE,EAAE,CAAC,CAAC;AAC/D,CAAC;AAED;;;GAGG;AACI,KAAK,UAAU,SAAS,CAC7B,MAAc,EACd,CAAY,EACZ,YAAoB,EACpB,cAAsB,EACtB,SAAiB;IAEjB,MAAM,MAAM,GAAG,MAAM,MAAM,CAAC,KAAK,CAC/B,mBAAmB,EACnB,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC,IAAI,CAAC,EAChB,CAAC,YAAY,CAAC,QAAQ,EAAE,EAAE,cAAc,CAAC,QAAQ,EAAE,EAAE,SAAS,CAAC,QAAQ,EAAE,CAAC,CAC3E,CAAC;IACF,OAAO,MAAgB,CAAC;AAC1B,CAAC;AAED;;;;GAIG;AACI,KAAK,UAAU,gBAAgB,CAAC,MAAc,EAAE,CAAY,EAAE,QAAgB,sBAAc;IACjG,MAAM,MAAM,GAAG,MAAM,MAAM,CAAC,KAAK,CAAC,0BAA0B,EAAE,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,CAAC,UAAU,CAAC,EAAE,CAAC,KAAK,CAAC,CAAC,CAAC;IACzG,OAAO,MAAgB,CAAC;AAC1B,CAAC;AAED;;;;GAIG;AACI,KAAK,UAAU,cAAc,CAClC,MAAc,EACd,CAAY,EACZ,OAAe,EACf,QAAgB,sBAAc,EAC9B,QAAgB,CAAC;IAEjB,MAAM,SAAS,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,KAAK,CAAC,KAAK,CAAC,IAAI,CAAC,EAAE,IAAI,CAAC,CAAC,CAAC;IACtE,MAAM,MAAM,GAAG,MAAM,MAAM,CAAC,KAAK,CAC/B,wBAAwB,EACxB,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,CAAC,UAAU,EAAE,OAAO,CAAC,EACzC,CAAC,KAAK,EAAE,SAAS,CAAC,QAAQ,EAAE,CAAC,CAC9B,CAAC;IACF,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,MAAM,CAAC,IAAI,MAAM,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,EAAE,CAAC;IAC7D,OAAO,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC;AACtC,CAAC;AAED;;;GAGG;AACI,KAAK,UAAU,QAAQ,CAAC,MAAc,EAAE,CAAY,EAAE,KAAa;IACxE,MAAM,MAAM,GAAG,MAAM,MAAM,CAAC,KAAK,CAAC,kBAAkB,EAAE,CAAC,CAAC,CAAC,QAAQ,EAAE,CAAC,CAAC,IAAI,CAAC,EAAE,CAAC,KAAK,CAAC,QAAQ,EAAE,CAAC,CAAC,CAAC;IAChG,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,MAAM,CAAC,IAAI,MAAM,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,EAAE,CAAC;IAC7D,OAAO,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC;AACtC,CAAC;AAED;;;;;;;;;;;;;;GAcG;AACI,KAAK,UAAU,YAAY,CAChC,MAAc,EACd,CAAY,EACZ,KAAa,EACb,SAAiB,EACjB,YAAoB,EAAE,EACtB,UAAkB,EAAE,EACpB,QAAgB,EAAE,EAClB,aAAuB;IAYvB,MAAM,IAAI,GAAa,CAAC,CAAC,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC,CAAC;IACtC,MAAM,IAAI,GAAa,CAAC,SAAS,CAAC,QAAQ,EAAE,CAAC,CAAC;IAC9C,IAAI,SAAS,EAAE,CAAC;QACd,IAAI,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC;QACrB,IAAI,CAAC,IAAI,CAAC,OAAO,EAAE,KAAK,EAAE,KAAK,CAAC,CAAC;QACjC,IAAI,aAAa;YAAE,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;IACpC,CAAC;IACD,MAAM,MAAM,GAAG,MAAM,MAAM,CAAC,KAAK,CAAC,sBAAsB,EAAE,IAAI,EAAE,IAAI,CAAC,CAAC;IAEtE,IAAI,KAAK,CAAC,OAAO,CAAC,MAAM,CAAC,EAAE,CAAC;QAC1B,IAAI,MAAM,CAAC,MAAM,KAAK,CAAC;YAAE,OAAO,IAAI,CAAC;QACrC,MAAM,IAAI,GAA2B,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC;QACzD,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,CAAC,GAAG,MAAM,CAAC,MAAM,EAAE,CAAC,IAAI,CAAC,EAAE,CAAC;YAC9C,IAAI,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC,GAAG,MAAM,CAAC,MAAM,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC;QAClD,CAAC;QACD,OAAO,IAAI,CAAC;IACd,CAAC;IAED,MAAM,GAAG,GAAG,MAAM,CAAC,MAAM,CAAC,CAAC;IAC3B,IAAI,GAAG,KAAK,EAAE,IAAI,GAAG,KAAK,MAAM;QAAE,OAAO,IAAI,CAAC;IAC9C,IAAI,GAAG,KAAK,SAAS;QAAE,OAAO,SAAS,CAAC;IACxC,IAAI,GAAG,KAAK,SAAS;QAAE,OAAO,SAAS,CAAC;IACxC,IAAI,GAAG,KAAK,YAAY;QAAE,OAAO,YAAY,CAAC;IAC9C,IAAI,GAAG,KAAK,oBAAoB;QAAE,OAAO,oBAAoB,CAAC;IAC9D,IAAI,GAAG,KAAK,qBAAqB;QAAE,OAAO,qBAAqB,CAAC;IAChE,IAAI,GAAG,KAAK,eAAe;QAAE,OAAO,eAAe,CAAC;IACpD,IAAI,GAAG,KAAK,2BAA2B;QAAE,OAAO,2BAA2B,CAAC;IAC5E,6DAA6D;IAC7D,MAAM,GAAG,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,CAAa,CAAC;IACxC,MAAM,IAAI,GAA2B,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC;IACzD,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,GAAG,CAAC,MAAM,EAAE,CAAC,IAAI,CAAC,EAAE,CAAC;QACvC,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,GAAG,MAAM,CAAC,GAAG,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC;IAC5C,CAAC;IACD,OAAO,IAAI,CAAC;AACd,CAAC;AAED;;;;GAIG;AACI,KAAK,UAAU,kBAAkB,CAAC,MAAc,EAAE,CAAY,EAAE,SAAiB;IACtF,MAAM,MAAM,GAAG,MAAM,MAAM,CAAC,KAAK,CAAC,4BAA4B,EAAE,CAAC,CAAC,CAAC,WAAW,EAAE,CAAC,CAAC,MAAM,CAAC,EAAE,CAAC,SAAS,CAAC,QAAQ,EAAE,CAAC,CAAC,CAAC;IACnH,OAAO,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC;AAC7B,CAAC;AAED;;;;;GAKG;AACI,KAAK,UAAU,cAAc,CAClC,MAAc,EACd,CAAY,EACZ,KAAa,EACb,OAAe,EACf,QAAgB,EAChB,SAAiB,EACjB,KAAa,EACb,aAAiC,SAAS,EAC1C,kBAAoC,OAAO,EAC3C,SAA4B,KAAK,EACjC,aAAuB;IAEvB,MAAM,MAAM,GAAG,MAAM,MAAM,CAAC,KAAK,CAC/B,wBAAwB,EACxB,CAAC,CAAC,CAAC,GAAG,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC,MAAM,CAAC,EACxB;QACE,KAAK;QACL,OAAO;QACP,KAAK;QACL,QAAQ,CAAC,QAAQ,EAAE;QACnB,SAAS,CAAC,QAAQ,EAAE;QACpB,aAAa,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,GAAG;QACzB,UAAU;QACV,eAAe;QACf,MAAM;KACP,CACF,CAAC;IACF,OAAO,MAAM,CAAC,MAAM,CAAC,CAAC;AACxB,CAAC;AAED;;;;GAIG;AACI,KAAK,UAAU,sBAAsB,CAC1C,MAAc,EACd,CAAY,EACZ,QAAgB,EAChB,QAAgB,EAChB,SAAiB,EACjB,SAA4B,KAAK;IAEjC,MAAM,MAAM,GAAG,MAAM,MAAM,CAAC,KAAK,CAC/B,gCAAgC,EAChC,CAAC,CAAC,CAAC,WAAW,EAAE,CAAC,CAAC,MAAM,CAAC,EACzB,CAAC,QAAQ,EAAE,QAAQ,CAAC,QAAQ,EAAE,EAAE,SAAS,CAAC,QAAQ,EAAE,EAAE,MAAM,CAAC,CAC9D,CAAC;IACF,OAAO,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC;AAC7B,CAAC;AAED;;;;GAIG;AACI,KAAK,UAAU,WAAW,CAC/B,MAAc,EACd,CAAY,EACZ,KAAa,EACb,OAAe,EACf,QAAgB,sBAAc,EAC9B,aAAuB;IAEvB,MAAM,IAAI,GAAG,CAAC,KAAK,EAAE,OAAO,EAAE,KAAK,CAAC,CAAC;IACrC,IAAI,aAAa;QAAE,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;IAClC,MAAM,MAAM,CAAC,KAAK,CAAC,qBAAqB,EAAE,CAAC,CAAC,CAAC,MAAM,EAAE,CAAC,CAAC,GAAG,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC,UAAU,CAAC,EAAE,IAAI,CAAC,CAAC;AAC1F,CAAC;AAED;;;GAGG;AACI,KAAK,UAAU,SAAS,CAAC,MAAc,EAAE,CAAY,EAAE,KAAa;IACzE,MAAM,MAAM,GAAG,MAAM,MAAM,CAAC,KAAK,CAC/B,mBAAmB,EACnB,CAAC,CAAC,CAAC,GAAG,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,CAAC,SAAS,EAAE,CAAC,CAAC,SAAS,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC,EACpF,CAAC,KAAK,CAAC,CACR,CAAC;IACF,OAAO,MAAgB,CAAC;AAC1B,CAAC;AAED;;;;GAIG;AACI,KAAK,UAAU,SAAS,CAC7B,MAAc,EACd,CAAY,EACZ,IAA4B,EAC5B,KAAa,EACb,KAAa,EACb,SAAiB;IAEjB,IAAI,IAAI,KAAK,WAAW,IAAI,IAAI,KAAK,QAAQ,EAAE,CAAC;QAC9C,MAAM,IAAI,SAAS,CAAC,oDAAoD,IAAI,GAAG,CAAC,CAAC;IACnF,CAAC;IACD,MAAM,MAAM,GAAG,SAAS,GAAG,KAAK,CAAC;IACjC,MAAM,MAAM,GAAG,IAAI,KAAK,WAAW,CAAC,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC;IAC7D,MAAM,MAAM,GAAG,MAAM,MAAM,CAAC,KAAK,CAAC,eAAe,EAAE,CAAC,MAAM,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,MAAM,CAAC,QAAQ,EAAE,EAAE,KAAK,CAAC,QAAQ,EAAE,CAAC,CAAC,CAAC;IACpH,OAAO,KAAK,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC;AACnE,CAAC;AAED;;;;;GAKG;AACI,KAAK,UAAU,UAAU,CAC9B,MAAc,EACd,CAAY,EACZ,OAAgB,EAChB,QAAgB,sBAAc;IAE9B,MAAM,MAAM,GAAG,MAAM,MAAM,CAAC,KAAK,CAC/B,eAAe,EACf,CAAC,CAAC,CAAC,MAAM,EAAE,CAAC,CAAC,SAAS,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC,QAAQ,CAAC,EAC3D,CAAC,OAAO,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,GAAG,EAAE,KAAK,CAAC,CAC7B,CAAC;IACF,OAAO,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC;AAC7B,CAAC;AAED;;;;;;;;GAQG;AACI,KAAK,UAAU,SAAS,CAAC,MAAc,EAAE,CAAY,EAAE,KAAa,EAAE,SAAiB;IAC5F,MAAM,MAAM,GAAG,MAAM,MAAM,CAAC,KAAK,CAC/B,mBAAmB,EACnB,CAAC,CAAC,CAAC,MAAM,EAAE,CAAC,CAAC,SAAS,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,CAAC,EAAE,CAAC,EACvC,CAAC,KAAK,CAAC,QAAQ,EAAE,EAAE,SAAS,CAAC,QAAQ,EAAE,CAAC,CACzC,CAAC;IACF,OAAO,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC;AAC7B,CAAC;AAED;;;;;GAKG;AACI,KAAK,UAAU,SAAS,CAC7B,MAAc,EACd,CAAY,EACZ,KAAa,EACb,SAAiB,EACjB,QAAgB,sBAAc;IAE9B,MAAM,MAAM,GAAG,MAAM,MAAM,CAAC,KAAK,CAC/B,gBAAgB,EAChB,CAAC,CAAC,CAAC,GAAG,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,CAAC,SAAS,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,CAAC,MAAM,CAAC,EACzD,CAAC,KAAK,EAAE,SAAS,CAAC,QAAQ,EAAE,EAAE,KAAK,CAAC,CACrC,CAAC;IACF,OAAO,MAAgB,CAAC;AAC1B,CAAC;AAED;;;;GAIG;AACI,KAAK,UAAU,cAAc,CAClC,MAAc,EACd,CAAY,EACZ,KAAa,EACb,WAAmB,EACnB,QAAgB,sBAAc;IAE9B,MAAM,MAAM,GAAG,MAAM,MAAM,CAAC,KAAK,CAC/B,wBAAwB,EACxB,CAAC,CAAC,CAAC,GAAG,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,CAAC,SAAS,EAAE,CAAC,CAAC,MAAM,CAAC,EAC/C,CAAC,KAAK,EAAE,WAAW,CAAC,QAAQ,EAAE,EAAE,KAAK,CAAC,CACvC,CAAC;IACF,OAAO,MAAgB,CAAC;AAC1B,CAAC;AAED;;;;GAIG;AACI,KAAK,UAAU,WAAW,CAC/B,MAAc,EACd,CAAY,EACZ,KAAa,EACb,QAAgB,EAChB,QAAgB,sBAAc;IAE9B,MAAM,MAAM,GAAG,MAAM,MAAM,CAAC,KAAK,CAC/B,qBAAqB,EACrB,CAAC,CAAC,CAAC,GAAG,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,CAAC,SAAS,EAAE,CAAC,CAAC,MAAM,CAAC,EAC/C,CAAC,KAAK,EAAE,QAAQ,CAAC,QAAQ,EAAE,EAAE,IAAI,CAAC,GAAG,EAAE,CAAC,QAAQ,EAAE,EAAE,KAAK,CAAC,CAC3D,CAAC;IACF,OAAO,MAAgB,CAAC;AAC1B,CAAC;AAED;;;;GAIG;AACI,KAAK,UAAU,UAAU,CAAC,MAAc,EAAE,CAAY,EAAE,KAAa;IAC1E,MAAM,MAAM,GAAG,MAAM,MAAM,CAAC,KAAK,CAAC,oBAAoB,EAAE,CAAC,CAAC,CAAC,GAAG,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,CAAC,SAAS,EAAE,CAAC,CAAC,MAAM,CAAC,EAAE,CAAC,KAAK,CAAC,CAAC,CAAC;IAClH,OAAO,MAAgB,CAAC;AAC1B,CAAC;AAED;;;GAGG;AACI,KAAK,UAAU,mBAAmB,CACvC,MAAc,EACd,CAAY,EACZ,KAAa,EACb,OAAe,EACf,YAAoB,EACpB,cAAuB,EACvB,YAAoB,IAAI,CAAC,GAAG,EAAE,EAC9B,QAAgB,sBAAc,EAC9B,aAAuB;IAEvB,MAAM,IAAI,GAAG,CAAC,KAAK,EAAE,OAAO,EAAE,SAAS,CAAC,QAAQ,EAAE,EAAE,YAAY,CAAC,QAAQ,EAAE,EAAE,KAAK,EAAE,cAAc,IAAI,EAAE,CAAC,CAAC;IAC1G,IAAI,aAAa;QAAE,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;IAClC,MAAM,MAAM,GAAG,MAAM,MAAM,CAAC,KAAK,CAC/B,6BAA6B,EAC7B,CAAC,CAAC,CAAC,GAAG,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,CAAC,SAAS,EAAE,CAAC,CAAC,MAAM,CAAC,EAC/C,IAAI,CACL,CAAC;IACF,OAAO,MAAgB,CAAC;AAC1B,CAAC;AAED;;;;GAIG;AACI,KAAK,UAAU,qBAAqB,CACzC,MAAc,EACd,CAAY,EACZ,KAAa,EACb,OAAe,EACf,QAAgB,sBAAc,EAC9B,YAAoB,IAAI,CAAC,GAAG,EAAE,EAC9B,aAAuB;IAEvB,MAAM,IAAI,GAAG,CAAC,KAAK,EAAE,OAAO,EAAE,KAAK,EAAE,SAAS,CAAC,QAAQ,EAAE,CAAC,CAAC;IAC3D,IAAI,aAAa;QAAE,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;IAClC,MAAM,MAAM,GAAG,MAAM,MAAM,CAAC,KAAK,CAAC,+BAA+B,EAAE,CAAC,CAAC,CAAC,GAAG,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,CAAC,MAAM,CAAC,EAAE,IAAI,CAAC,CAAC;IAC7G,OAAO,MAAgB,CAAC;AAC1B,CAAC;AAED;;;;;GAKG;AACI,KAAK,UAAU,YAAY,CAChC,MAAc,EACd,QAAgB,EAChB,SAA4B,EAC5B,UAAkB,EAClB,KAAa,EACb,SAAiB;IAEjB,MAAM,MAAM,GAAG,MAAM,MAAM,CAAC,KAAK,CAC/B,sBAAsB,EACtB,CAAC,QAAQ,CAAC,EACV,CAAC,SAAS,EAAE,UAAU,EAAE,KAAK,CAAC,QAAQ,EAAE,EAAE,SAAS,CAAC,CACrD,CAAC;IACF,IAAI,CAAC,MAAM;QAAE,OAAO,EAAE,CAAC;IACvB,IAAI,KAAK,CAAC,OAAO,CAAC,MAAM,CAAC,EAAE,CAAC;QAC1B,OAAO,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC;IACtC,CAAC;IACD,OAAO,EAAE,CAAC;AACZ,CAAC;AAED;;;GAGG;AACI,KAAK,UAAU,OAAO,CAC3B,MAAc,EACd,UAAqB,EACrB,UAAkB,EAClB,UAAkB,EAClB,UAAkB,EAClB,SAAiB,EACjB,WAAmB,EACnB,cAAsB,EACtB,iBAAyB,EACzB,QAWG,EACH,YAAsB,EAAE,EACxB,iBAAyB,EAAE;IAE3B,MAAM,IAAI,GAAa,CAAC,UAAU,CAAC,EAAE,EAAE,UAAU,CAAC,MAAM,EAAE,UAAU,CAAC,SAAS,EAAE,UAAU,CAAC,MAAM,CAAC,CAAC;IACnG,MAAM,IAAI,GAAa;QACrB,UAAU;QACV,UAAU;QACV,UAAU;QACV,SAAS,CAAC,QAAQ,EAAE;QACpB,WAAW,CAAC,QAAQ,EAAE;QACtB,cAAc,CAAC,QAAQ,EAAE;QACzB,iBAAiB,CAAC,QAAQ,EAAE;QAC5B,QAAQ,CAAC,MAAM,CAAC,QAAQ,EAAE;QAC1B,cAAc;KACf,CAAC;IAEF,KAAK,MAAM,KAAK,IAAI,QAAQ,EAAE,CAAC;QAC7B,IAAI,CAAC,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,EAAE,EAAE,KAAK,CAAC,IAAI,CAAC,MAAM,EAAE,KAAK,CAAC,IAAI,CAAC,SAAS,EAAE,KAAK,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;QACrF,IAAI,CAAC,IAAI,CACP,KAAK,CAAC,IAAI,EACV,KAAK,CAAC,IAAI,EACV,KAAK,CAAC,IAAI,EACV,KAAK,CAAC,KAAK,CAAC,QAAQ,EAAE,EACtB,KAAK,CAAC,QAAQ,CAAC,QAAQ,EAAE,EACzB,KAAK,CAAC,WAAW,CAAC,QAAQ,EAAE,EAC5B,KAAK,CAAC,WAAW,EACjB,KAAK,CAAC,eAAe,EACrB,KAAK,CAAC,QAAQ,CACf,CAAC;IACJ,CAAC;IAED,2EAA2E;IAC3E,IAAI,CAAC,IAAI,CAAC,SAAS,CAAC,MAAM,CAAC,QAAQ,EAAE,CAAC,CAAC;IACvC,KAAK,MAAM,GAAG,IAAI,SAAS,EAAE,CAAC;QAC5B,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;IACjB,CAAC;IAED,MAAM,MAAM,GAAG,MAAM,MAAM,CAAC,KAAK,CAAC,iBAAiB,EAAE,IAAI,EAAE,IAAI,CAAC,CAAC;IACjE,OAAO,IAAI,CAAC,KAAK,CAAC,MAAgB,CAAa,CAAC;AAClD,CAAC;AAED;;;GAGG;AACI,KAAK,UAAU,aAAa,CACjC,MAAc,EACd,UAAqB,EACrB,QAAgB,EAChB,UAAkB;IAElB,MAAM,MAAM,GAAG,MAAM,MAAM,CAAC,KAAK,CAC/B,uBAAuB,EACvB,CAAC,UAAU,CAAC,IAAI,CAAC,QAAQ,CAAC,EAAE,UAAU,CAAC,GAAG,CAAC,QAAQ,CAAC,EAAE,UAAU,CAAC,MAAM,EAAE,UAAU,CAAC,MAAM,CAAC,EAC3F,CAAC,UAAU,EAAE,QAAQ,CAAC,CACvB,CAAC;IACF,OAAO,MAAgB,CAAC;AAC1B,CAAC;AAED;;;;GAIG;AACI,KAAK,UAAU,cAAc,CAClC,MAAc,EACd,SAAoB,EACpB,UAAkB,EAClB,QAAgB,EAChB,WAAmB,EACnB,UAAqB,EACrB,UAAkB;IAElB,MAAM,MAAM,GAAG,MAAM,MAAM,CAAC,KAAK,CAC/B,wBAAwB,EACxB;QACE,SAAS,CAAC,GAAG,CAAC,UAAU,CAAC;QACzB,SAAS,CAAC,OAAO,CAAC,UAAU,CAAC;QAC7B,UAAU,CAAC,IAAI,CAAC,QAAQ,CAAC;QACzB,UAAU,CAAC,GAAG,CAAC,QAAQ,CAAC;QACxB,UAAU,CAAC,MAAM;QACjB,UAAU,CAAC,MAAM;KAClB,EACD,CAAC,UAAU,EAAE,QAAQ,EAAE,WAAW,EAAE,UAAU,CAAC,CAChD,CAAC;IACF,OAAO,MAAgB,CAAC;AAC1B,CAAC;AAED;;;;GAIG;AACI,KAAK,UAAU,cAAc,CAAC,MAAc,EAAE,IAAe;IAClE,MAAM,MAAM,GAAG,MAAM,MAAM,CAAC,KAAK,CAAC,wBAAwB,EAAE,CAAC,IAAI,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC;IAC3E,OAAO,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC;AAC7B,CAAC;AAED;;;GAGG;AACI,KAAK,UAAU,UAAU,CAC9B,MAAc,EACd,CAAY,EACZ,KAAa,EACb,OAAe,EACf,KAAa,EACb,SAAiB,EACjB,MAAe,EACf,OAAgB,EAChB,aAAuB;IAEvB,MAAM,IAAI,GAAG,CAAC,KAAK,EAAE,OAAO,EAAE,KAAK,EAAE,SAAS,CAAC,QAAQ,EAAE,EAAE,MAAM,IAAI,EAAE,EAAE,CAAC,OAAO,IAAI,CAAC,CAAC,CAAC,QAAQ,EAAE,CAAC,CAAC;IACpG,IAAI,aAAa;QAAE,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;IAClC,MAAM,MAAM,GAAG,MAAM,MAAM,CAAC,KAAK,CAAC,iBAAiB,EAAE,CAAC,CAAC,CAAC,GAAG,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,CAAC,SAAS,CAAC,EAAE,IAAI,CAAC,CAAC;IAC5G,OAAO,MAAgB,CAAC;AAC1B,CAAC;AAED;;;GAGG;AACI,KAAK,UAAU,SAAS,CAC7B,MAAc,EACd,CAAY,EACZ,KAAa,EACb,UAAkB,EAClB,UAAkB,EAClB,SAAiB;IAEjB,MAAM,MAAM,GAAG,MAAM,MAAM,CAAC,KAAK,CAC/B,gBAAgB,EAChB,CAAC,CAAC,CAAC,GAAG,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,CAAC,SAAS,EAAE,CAAC,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC,EACjE,CAAC,KAAK,EAAE,UAAU,EAAE,UAAU,EAAE,SAAS,CAAC,QAAQ,EAAE,CAAC,CACtD,CAAC;IACF,OAAO,MAAgB,CAAC;AAC1B,CAAC;AAED;;;GAGG;AACI,KAAK,UAAU,cAAc,CAClC,MAAc,EACd,CAAY,EACZ,SAAiB,EACjB,SAAiB;IAEjB,MAAM,MAAM,GAAG,MAAM,MAAM,CAAC,KAAK,CAC/B,wBAAwB,EACxB,CAAC,CAAC,CAAC,SAAS,EAAE,CAAC,CAAC,MAAM,CAAC,EACvB,CAAC,SAAS,CAAC,QAAQ,EAAE,EAAE,SAAS,CAAC,CAClC,CAAC;IACF,OAAO,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC;AAC7B,CAAC;AAED;;GAEG;AACI,KAAK,UAAU,WAAW,CAAC,MAAc,EAAE,SAAiB;IACjE,MAAM,MAAM,GAAG,MAAM,MAAM,CAAC,KAAK,CAAC,qBAAqB,EAAE,CAAC,SAAS,CAAC,EAAE,EAAE,CAAC,CAAC;IAC1E,OAAO,MAAgB,CAAC;AAC1B,CAAC;AAED;;;;GAIG;AACI,KAAK,UAAU,yBAAyB,CAC7C,MAAc,EACd,SAAiB,EACjB,MAA8B,EAC9B,KAA6B,EAC7B,aAAqB,EACrB,SAAiB,EACjB,SAAiC,EACjC,QAAgC;IAEhC,MAAM,MAAM,GAAG,MAAM,MAAM,CAAC,KAAK,CAC/B,mCAAmC,EACnC,CAAC,SAAS,CAAC,EACX;QACE,IAAI,CAAC,SAAS,CAAC,MAAM,CAAC;QACtB,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC;QACrB,aAAa,CAAC,QAAQ,EAAE;QACxB,SAAS,CAAC,QAAQ,EAAE;QACpB,IAAI,CAAC,SAAS,CAAC,SAAS,CAAC;QACzB,IAAI,CAAC,SAAS,CAAC,QAAQ,CAAC;KACzB,CACF,CAAC;IACF,OAAO,MAAgB,CAAC;AAC1B,CAAC"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/functions/index.ts"],"names":[],"mappings":";;;AAkgHA,gCAuDC;AAED,wBAqDC;AAMD,sBA0DC;AAMD,0BAGC;AASD,8BAOC;AAED,0BAGC;AAED,wBAGC;AAED,8BAGC;AA+BD,kCAqCC;AAwBD,oDA8GC;AAOD,0BAgCC;AAMD,wCAwBC;AAMD,wDAaC;AAKD,sBAEC;AAKD,wBAEC;AAMD,8BAaC;AAOD,4CAGC;AAOD,wCAeC;AAMD,4BAIC;AAiBD,oCAsDC;AAOD,gDAGC;AAQD,wCA6BC;AAOD,wDAcC;AAOD,kCAWC;AAMD,8BAOC;AAOD,8BAeC;AAQD,gCAYC;AAWD,8BAOC;AAQD,8BAaC;AAOD,wCAaC;AAOD,kCAaC;AAOD,gCAGC;AAMD,kDAmBC;AAOD,sDAaC;AAQD,oCAkBC;AAMD,0BA6DC;AAMD,sCAYC;AAOD,wCAsBC;AAOD,wCAGC;AAMD,gCAeC;AAMD,8BAcC;AAMD,wCAYC;AAKD,kCAGC;AAOD,8DAuBC;AAnpJY,QAAA,YAAY,GAAG,SAAS,CAAC;AACtC,yEAAyE;AACzE,4GAA4G;AAC5G,wGAAwG;AACxG,0GAA0G;AAC1G,qGAAqG;AACrG,+FAA+F;AAC/F,qFAAqF;AACrF,wKAAwK;AACxK,wFAAwF;AACxF,wGAAwG;AACxG,4GAA4G;AAC5G,iFAAiF;AACjF,sHAAsH;AACtH,6EAA6E;AAC7E,6EAA6E;AAC7E,uGAAuG;AACvG,yFAAyF;AACzF,mGAAmG;AACnG,2HAA2H;AAC3H,0EAA0E;AAC1E,sHAAsH;AACtH,oHAAoH;AACpH,+IAA+I;AAC/I,uGAAuG;AACvG,8GAA8G;AAC9G,sIAAsI;AACtI,0IAA0I;AAC1I,0GAA0G;AAC1G,0FAA0F;AAC1F,wGAAwG;AACxG,gKAAgK;AAChK,8FAA8F;AAC9F,oGAAoG;AACpG,yFAAyF;AACzF,uIAAuI;AACvI,gGAAgG;AAChG,yFAAyF;AACzF,iGAAiG;AACjG,uHAAuH;AAC1G,QAAA,eAAe,GAAG,IAAI,CAAC;AAEpC,sCAAsC;AACzB,QAAA,cAAc,GAAG,SAAS,CAAC;AAExC,wDAAwD;AACxD,0EAA0E;AAC7D,QAAA,cAAc,GAAG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;YA2clB,uBAAe;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "glide-mq",
3
- "version": "0.15.0",
3
+ "version": "0.15.1",
4
4
  "description": "High-performance message queue for Node.js with AI-native primitives - built on Valkey/Redis with Rust NAPI bindings",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",