glide-mq 0.4.1 → 0.4.3
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/dist/flow-producer.d.ts.map +1 -1
- package/dist/flow-producer.js +1 -1
- package/dist/flow-producer.js.map +1 -1
- package/dist/functions/index.d.ts +10 -4
- package/dist/functions/index.d.ts.map +1 -1
- package/dist/functions/index.js +154 -6
- package/dist/functions/index.js.map +1 -1
- package/dist/job.d.ts +2 -0
- package/dist/job.d.ts.map +1 -1
- package/dist/job.js +4 -0
- package/dist/job.js.map +1 -1
- package/dist/queue.d.ts.map +1 -1
- package/dist/queue.js +50 -11
- package/dist/queue.js.map +1 -1
- package/dist/types.d.ts +7 -0
- package/dist/types.d.ts.map +1 -1
- package/dist/worker.d.ts +11 -1
- package/dist/worker.d.ts.map +1 -1
- package/dist/worker.js +46 -4
- package/dist/worker.js.map +1 -1
- package/package.json +1 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"flow-producer.d.ts","sourceRoot":"","sources":["../src/flow-producer.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,mBAAmB,EAAE,OAAO,EAAU,MAAM,SAAS,CAAC;AACpE,OAAO,EAAE,GAAG,EAAE,MAAM,OAAO,CAAC;AAM5B,MAAM,WAAW,OAAO;IACtB,GAAG,EAAE,GAAG,CAAC;IACT,QAAQ,CAAC,EAAE,OAAO,EAAE,CAAC;CACtB;AAED,qBAAa,YAAY;IACvB,OAAO,CAAC,IAAI,CAAsB;IAClC,OAAO,CAAC,MAAM,CAAuB;IACrC,OAAO,CAAC,OAAO,CAAS;gBAEZ,IAAI,EAAE,mBAAmB;IAIrC,gBAAgB;YACF,SAAS;IAYvB;;;;OAIG;IACG,GAAG,CAAC,IAAI,EAAE,OAAO,GAAG,OAAO,CAAC,OAAO,CAAC;IAe1C;;OAEG;IACG,OAAO,CAAC,KAAK,EAAE,OAAO,EAAE,GAAG,OAAO,CAAC,OAAO,EAAE,CAAC;IASnD;;;;OAIG;YACW,gBAAgB;
|
|
1
|
+
{"version":3,"file":"flow-producer.d.ts","sourceRoot":"","sources":["../src/flow-producer.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,mBAAmB,EAAE,OAAO,EAAU,MAAM,SAAS,CAAC;AACpE,OAAO,EAAE,GAAG,EAAE,MAAM,OAAO,CAAC;AAM5B,MAAM,WAAW,OAAO;IACtB,GAAG,EAAE,GAAG,CAAC;IACT,QAAQ,CAAC,EAAE,OAAO,EAAE,CAAC;CACtB;AAED,qBAAa,YAAY;IACvB,OAAO,CAAC,IAAI,CAAsB;IAClC,OAAO,CAAC,MAAM,CAAuB;IACrC,OAAO,CAAC,OAAO,CAAS;gBAEZ,IAAI,EAAE,mBAAmB;IAIrC,gBAAgB;YACF,SAAS;IAYvB;;;;OAIG;IACG,GAAG,CAAC,IAAI,EAAE,OAAO,GAAG,OAAO,CAAC,OAAO,CAAC;IAe1C;;OAEG;IACG,OAAO,CAAC,KAAK,EAAE,OAAO,EAAE,GAAG,OAAO,CAAC,OAAO,EAAE,CAAC;IASnD;;;;OAIG;YACW,gBAAgB;IA0I9B;;;OAGG;IACG,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;CAQ7B"}
|
package/dist/flow-producer.js
CHANGED
|
@@ -94,7 +94,7 @@ class FlowProducer {
|
|
|
94
94
|
const { addJob } = await Promise.resolve().then(() => __importStar(require('./functions/index')));
|
|
95
95
|
const timestamp = Date.now();
|
|
96
96
|
const opts = flow.opts ?? {};
|
|
97
|
-
const jobId = await addJob(client, parentKeys, flow.name, JSON.stringify(flow.data), JSON.stringify(opts), timestamp, opts.delay ?? 0, opts.priority ?? 0, '', opts.attempts ?? 0);
|
|
97
|
+
const jobId = await addJob(client, parentKeys, flow.name, JSON.stringify(flow.data), JSON.stringify(opts), timestamp, opts.delay ?? 0, opts.priority ?? 0, '', opts.attempts ?? 0, opts.ordering?.key ?? '');
|
|
98
98
|
const job = new job_1.Job(client, parentKeys, String(jobId), flow.name, flow.data, opts);
|
|
99
99
|
job.timestamp = timestamp;
|
|
100
100
|
return { job };
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"flow-producer.js","sourceRoot":"","sources":["../src/flow-producer.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AACA,+BAA4B;AAC5B,mCAA+C;AAC/C,6CAAmE;AACnE,6CAA4D;AAC5D,2CAAuC;AAOvC,MAAa,YAAY;IACf,IAAI,CAAsB;IAC1B,MAAM,GAAkB,IAAI,CAAC;IAC7B,OAAO,GAAG,KAAK,CAAC;IAExB,YAAY,IAAyB;QACnC,IAAI,CAAC,IAAI,GAAG,IAAI,CAAC;IACnB,CAAC;IAED,gBAAgB;IACR,KAAK,CAAC,SAAS;QACrB,IAAI,CAAC,IAAI,CAAC,MAAM,EAAE,CAAC;YACjB,IAAI,CAAC,MAAM,GAAG,MAAM,IAAA,yBAAY,EAAC,IAAI,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC;YACvD,MAAM,IAAA,kCAAqB,EACzB,IAAI,CAAC,MAAM,EACX,sBAAc,EACd,IAAI,CAAC,IAAI,CAAC,UAAU,CAAC,WAAW,IAAI,KAAK,CAC1C,CAAC;QACJ,CAAC;QACD,OAAO,IAAI,CAAC,MAAM,CAAC;IACrB,CAAC;IAED;;;;OAIG;IACH,KAAK,CAAC,GAAG,CAAC,IAAa;QACrB,OAAO,IAAA,oBAAQ,EACb,mBAAmB,EACnB;YACE,gBAAgB,EAAE,IAAI,CAAC,SAAS;YAChC,oBAAoB,EAAE,IAAI,CAAC,IAAI;YAC/B,0BAA0B,EAAE,IAAI,CAAC,QAAQ,EAAE,MAAM,IAAI,CAAC;SACvD,EACD,KAAK,IAAI,EAAE;YACT,MAAM,MAAM,GAAG,MAAM,IAAI,CAAC,SAAS,EAAE,CAAC;YACtC,OAAO,IAAI,CAAC,gBAAgB,CAAC,MAAM,EAAE,IAAI,CAAC,CAAC;QAC7C,CAAC,CACF,CAAC;IACJ,CAAC;IAED;;OAEG;IACH,KAAK,CAAC,OAAO,CAAC,KAAgB;QAC5B,MAAM,MAAM,GAAG,MAAM,IAAI,CAAC,SAAS,EAAE,CAAC;QACtC,MAAM,OAAO,GAAc,EAAE,CAAC;QAC9B,KAAK,MAAM,IAAI,IAAI,KAAK,EAAE,CAAC;YACzB,OAAO,CAAC,IAAI,CAAC,MAAM,IAAI,CAAC,gBAAgB,CAAC,MAAM,EAAE,IAAI,CAAC,CAAC,CAAC;QAC1D,CAAC;QACD,OAAO,OAAO,CAAC;IACjB,CAAC;IAED;;;;OAIG;IACK,KAAK,CAAC,gBAAgB,CAAC,MAAc,EAAE,IAAa;QAC1D,MAAM,MAAM,GAAG,IAAI,CAAC,IAAI,CAAC,MAAM,IAAI,OAAO,CAAC;QAC3C,MAAM,eAAe,GAAG,IAAI,CAAC,SAAS,CAAC;QACvC,MAAM,UAAU,GAAG,IAAA,iBAAS,EAAC,eAAe,EAAE,MAAM,CAAC,CAAC;QAEtD,qEAAqE;QACrE,IAAI,CAAC,IAAI,CAAC,QAAQ,IAAI,IAAI,CAAC,QAAQ,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;YACjD,MAAM,EAAE,MAAM,EAAE,GAAG,wDAAa,mBAAmB,GAAC,CAAC;YACrD,MAAM,SAAS,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;YAC7B,MAAM,IAAI,GAAG,IAAI,CAAC,IAAI,IAAI,EAAE,CAAC;YAC7B,MAAM,KAAK,GAAG,MAAM,MAAM,CACxB,MAAM,EACN,UAAU,EACV,IAAI,CAAC,IAAI,EACT,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,IAAI,CAAC,EACzB,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,EACpB,SAAS,EACT,IAAI,CAAC,KAAK,IAAI,CAAC,EACf,IAAI,CAAC,QAAQ,IAAI,CAAC,EAClB,EAAE,EACF,IAAI,CAAC,QAAQ,IAAI,CAAC,
|
|
1
|
+
{"version":3,"file":"flow-producer.js","sourceRoot":"","sources":["../src/flow-producer.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,20 +1,20 @@
|
|
|
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 = "
|
|
4
|
+
export declare const LIBRARY_VERSION = "15";
|
|
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\nredis.register_function('glidemq_version', function(keys, args)\n return '9'\nend)\n\nredis.register_function('glidemq_addJob', function(keys, args)\n local idKey = keys[1]\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 jobId = redis.call('INCR', idKey)\n local jobIdStr = tostring(jobId)\n local prefix = string.sub(idKey, 1, #idKey - 2)\n local jobKey = prefix .. 'job:' .. jobIdStr\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 parentId ~= '' then\n hashFields[#hashFields + 1] = 'parentId'\n hashFields[#hashFields + 1] = parentId\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 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 else\n redis.call('XADD', streamKey, '*', 'jobId', jobIdStr)\n end\n emitEvent(eventsKey, 'added', jobIdStr, {'name', jobName})\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 count = 0\n local cursorMin = 0\n while true do\n local nextEntry = redis.call('ZRANGEBYSCORE', scheduledKey, tostring(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 members = redis.call('ZRANGEBYSCORE', scheduledKey, tostring(minScore), tostring(maxDueScore))\n for i = 1, #members do\n local jobId = members[i]\n redis.call('XADD', streamKey, '*', 'jobId', jobId)\n redis.call('ZREM', scheduledKey, jobId)\n local prefix = string.sub(scheduledKey, 1, #scheduledKey - 9)\n local jobKey = prefix .. 'job:' .. jobId\n redis.call('HSET', jobKey, 'state', 'waiting')\n emitEvent(eventsKey, 'promoted', jobId, nil)\n count = count + 1\n end\n cursorMin = (priority + 1) * PRIORITY_SHIFT\n end\n return count\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 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 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 redis.call('XACK', streamKey, group, entryId)\n redis.call('XDEL', streamKey, entryId)\n redis.call('ZADD', completedKey, timestamp, jobId)\n redis.call('HSET', jobKey,\n 'state', 'completed',\n 'returnvalue', returnvalue,\n 'finishedOn', tostring(timestamp)\n )\n emitEvent(eventsKey, 'completed', jobId, {'returnvalue', returnvalue})\n local prefix = string.sub(jobKey, 1, #jobKey - #('job:' .. jobId))\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, total - removeCount - 1)\n for i = 1, #excess do\n local oldId = excess[i]\n redis.call('DEL', prefix .. 'job:' .. oldId)\n redis.call('ZREM', completedKey, oldId)\n 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', tostring(cutoff))\n for i = 1, #old do\n local oldId = old[i]\n redis.call('DEL', prefix .. 'job:' .. oldId)\n redis.call('ZREM', completedKey, oldId)\n 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, total - removeCount - 1)\n for i = 1, #excess do\n local oldId = excess[i]\n redis.call('DEL', prefix .. 'job:' .. oldId)\n redis.call('ZREM', completedKey, oldId)\n end\n end\n end\n end\n if depsMember ~= '' and parentId ~= '' and #keys >= 8 then\n local parentDepsKey = keys[5]\n local parentJobKey = keys[6]\n local parentStreamKey = keys[7]\n local parentEventsKey = keys[8]\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 redis.call('HSET', parentJobKey, 'state', 'waiting')\n redis.call('XADD', parentStreamKey, '*', 'jobId', parentId)\n emitEvent(parentEventsKey, 'active', parentId, nil)\n end\n 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 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\n -- Phase 1: Complete current job (same as glidemq_complete)\n redis.call('XACK', streamKey, group, entryId)\n redis.call('XDEL', streamKey, entryId)\n redis.call('ZADD', completedKey, timestamp, jobId)\n redis.call('HSET', jobKey,\n 'state', 'completed',\n 'returnvalue', returnvalue,\n 'finishedOn', tostring(timestamp)\n )\n emitEvent(eventsKey, 'completed', jobId, {'returnvalue', returnvalue})\n local prefix = string.sub(jobKey, 1, #jobKey - #('job:' .. jobId))\n\n -- Retention cleanup\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, total - removeCount - 1)\n for i = 1, #excess do\n redis.call('DEL', prefix .. 'job:' .. excess[i])\n redis.call('ZREM', completedKey, excess[i])\n end\n end\n end\n\n -- Parent deps\n if depsMember ~= '' and parentId ~= '' and #keys >= 8 then\n local parentDepsKey = keys[5]\n local parentJobKey = keys[6]\n local parentStreamKey = keys[7]\n local parentEventsKey = keys[8]\n local doneCount = redis.call('HINCRBY', parentJobKey, 'depsCompleted', 1)\n local totalDeps = redis.call('SCARD', parentDepsKey)\n if totalDeps - doneCount <= 0 then\n redis.call('HSET', parentJobKey, 'state', 'waiting')\n redis.call('XADD', parentStreamKey, '*', 'jobId', parentId)\n emitEvent(parentEventsKey, 'active', parentId, nil)\n end\n end\n\n -- Phase 2: Fetch next job (non-blocking XREADGROUP)\n local nextEntries = redis.call('XREADGROUP', 'GROUP', group, consumer, 'COUNT', 1, 'STREAMS', streamKey, '>')\n if not nextEntries or #nextEntries == 0 then\n return cjson.encode({completed = jobId, next = false})\n end\n local streamData = nextEntries[1]\n local entries = streamData[2]\n if not entries or #entries == 0 then\n return cjson.encode({completed = jobId, next = false})\n end\n local nextEntry = entries[1]\n local nextEntryId = nextEntry[1]\n local nextFields = nextEntry[2]\n local 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 cjson.encode({completed = jobId, next = false})\n end\n\n -- Phase 3: Activate next job (same as moveToActive)\n local nextJobKey = prefix .. 'job:' .. nextJobId\n local nextExists = redis.call('EXISTS', nextJobKey)\n if nextExists == 0 then\n return cjson.encode({completed = jobId, next = false, nextEntryId = nextEntryId})\n end\n local revoked = redis.call('HGET', nextJobKey, 'revoked')\n if revoked == '1' then\n return cjson.encode({completed = jobId, next = 'REVOKED', nextJobId = nextJobId, nextEntryId = nextEntryId})\n end\n redis.call('HSET', nextJobKey, 'state', 'active', 'processedOn', tostring(timestamp), 'lastActive', tostring(timestamp))\n local nextHash = redis.call('HGETALL', nextJobKey)\n return cjson.encode({completed = jobId, next = nextHash, nextJobId = nextJobId, nextEntryId = nextEntryId})\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 jobId = args[1]\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 redis.call('XACK', streamKey, group, entryId)\n redis.call('XDEL', streamKey, entryId)\n local attemptsMade = redis.call('HINCRBY', jobKey, 'attemptsMade', 1)\n if maxAttempts > 0 and attemptsMade < maxAttempts then\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 emitEvent(eventsKey, 'retrying', jobId, {\n 'failedReason', failedReason,\n 'attemptsMade', tostring(attemptsMade),\n 'delay', tostring(backoffDelay)\n })\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 emitEvent(eventsKey, 'failed', jobId, {'failedReason', failedReason})\n local prefix = string.sub(jobKey, 1, #jobKey - #('job:' .. jobId))\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, total - removeCount - 1)\n for i = 1, #excess do\n local oldId = excess[i]\n redis.call('DEL', prefix .. 'job:' .. oldId)\n redis.call('ZREM', failedKey, oldId)\n 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', tostring(cutoff))\n for i = 1, #old do\n local oldId = old[i]\n redis.call('DEL', prefix .. 'job:' .. oldId)\n redis.call('ZREM', failedKey, oldId)\n 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, total - removeCount - 1)\n for i = 1, #excess do\n local oldId = excess[i]\n redis.call('DEL', prefix .. 'job:' .. oldId)\n redis.call('ZREM', failedKey, oldId)\n end\n end\n end\n 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 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 local lastActive = tonumber(redis.call('HGET', jobKey, 'lastActive'))\n if lastActive and (timestamp - lastActive) < minIdleMs then\n count = count + 1\n else\n local stalledCount = redis.call('HINCRBY', jobKey, 'stalledCount', 1)\n if stalledCount > maxStalledCount then\n redis.call('XACK', streamKey, group, entryId)\n redis.call('XDEL', streamKey, entryId)\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 emitEvent(eventsKey, 'failed', jobId, {\n 'failedReason', 'job stalled more than maxStalledCount'\n })\n else\n redis.call('HSET', jobKey, 'state', 'active')\n emitEvent(eventsKey, 'stalled', jobId, nil)\n end\n count = count + 1\n end\n end\n end\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 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 redis.call('DEL', jobKey)\n emitEvent(eventsKey, 'removed', existingJobId, nil)\n elseif state and state ~= 'completed' and state ~= 'failed' then\n return 'skipped'\n end\n end\n end\n end\n local jobId = redis.call('INCR', idKey)\n local jobIdStr = tostring(jobId)\n local jobKey = prefix .. 'job:' .. jobIdStr\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 parentId ~= '' then\n hashFields[#hashFields + 1] = 'parentId'\n hashFields[#hashFields + 1] = parentId\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 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 else\n redis.call('XADD', streamKey, '*', 'jobId', jobIdStr)\n end\n redis.call('HSET', dedupKey, dedupId, jobIdStr .. ':' .. tostring(timestamp))\n emitEvent(eventsKey, 'added', jobIdStr, {'name', jobName})\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 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_checkConcurrency', function(keys, args)\n local metaKey = keys[1]\n local streamKey = keys[2]\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 pending = redis.call('XPENDING', streamKey, group)\n local pendingCount = tonumber(pending[1]) or 0\n local remaining = gc - pendingCount\n if remaining <= 0 then\n return 0\n end\n return remaining\nend)\n\nredis.register_function('glidemq_moveToActive', function(keys, args)\n local jobKey = keys[1]\n local timestamp = args[1]\n local exists = redis.call('EXISTS', jobKey)\n if exists == 0 then\n return ''\n end\n local revoked = redis.call('HGET', jobKey, 'revoked')\n if revoked == '1' then\n return 'REVOKED'\n end\n redis.call('HSET', jobKey, 'state', 'active', 'processedOn', timestamp, 'lastActive', timestamp)\n local fields = redis.call('HGETALL', jobKey)\n return cjson.encode(fields)\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 parentJobId = redis.call('INCR', parentIdKey)\n local parentJobIdStr = tostring(parentJobId)\n local parentPrefix = string.sub(parentIdKey, 1, #parentIdKey - 2)\n local parentJobKey = parentPrefix .. 'job:' .. parentJobIdStr\n local depsKey = parentPrefix .. 'deps:' .. parentJobIdStr\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 redis.call('HSET', parentJobKey, unpack(parentHash))\n local childIds = {}\n local childArgOffset = 8\n local childKeyOffset = 4\n for i = 1, numChildren do\n local base = childArgOffset + (i - 1) * 8\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 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 childJobId = redis.call('INCR', childIdKey)\n local childJobIdStr = tostring(childJobId)\n local childPrefix = string.sub(childIdKey, 1, #childIdKey - 2)\n local childJobKey = childPrefix .. 'job:' .. childJobIdStr\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 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 else\n redis.call('XADD', childStreamKey, '*', 'jobId', childJobIdStr)\n end\n emitEvent(childEventsKey, 'added', childJobIdStr, {'name', childName})\n childIds[#childIds + 1] = childJobIdStr\n end\n local extraDepsOffset = childArgOffset + numChildren * 8\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 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 redis.call('HSET', parentJobKey, 'state', 'waiting')\n redis.call('XADD', parentStreamKey, '*', 'jobId', parentId)\n emitEvent(parentEventsKey, 'active', parentId, nil)\n end\n return remaining\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 redis.call('ZREM', scheduledKey, jobId)\n redis.call('ZREM', completedKey, jobId)\n redis.call('ZREM', failedKey, 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_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 == 'waiting' or state == 'delayed' or state == 'prioritized' then\n redis.call('ZREM', scheduledKey, jobId)\n local entries = redis.call('XRANGE', streamKey, '-', '+')\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 redis.call('XACK', streamKey, group, entryId)\n redis.call('XDEL', streamKey, entryId)\n break\n end\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 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_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 entries = redis.call('XRANGE', stateKey, '-', '+')\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 end\n return matched\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)\n local orderingKey = redis.call('HGET', jobKey, 'orderingKey')\n if not orderingKey or orderingKey == '' then\n return\n end\n local orderingSeq = tonumber(redis.call('HGET', jobKey, 'orderingSeq')) or 0\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\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\nredis.register_function('glidemq_version', function(keys, args)\n return '15'\nend)\n\nredis.register_function('glidemq_addJob', function(keys, args)\n local idKey = keys[1]\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 jobId = redis.call('INCR', idKey)\n local jobIdStr = tostring(jobId)\n local prefix = string.sub(idKey, 1, #idKey - 2)\n local jobKey = prefix .. 'job:' .. jobIdStr\n local orderingSeq = 0\n if orderingKey ~= '' then\n local orderingMetaKey = prefix .. 'ordering'\n orderingSeq = redis.call('HINCRBY', orderingMetaKey, orderingKey, 1)\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 orderingKey ~= '' then\n hashFields[#hashFields + 1] = 'orderingKey'\n hashFields[#hashFields + 1] = orderingKey\n hashFields[#hashFields + 1] = 'orderingSeq'\n hashFields[#hashFields + 1] = tostring(orderingSeq)\n end\n if parentId ~= '' then\n hashFields[#hashFields + 1] = 'parentId'\n hashFields[#hashFields + 1] = parentId\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 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 else\n redis.call('XADD', streamKey, '*', 'jobId', jobIdStr)\n end\n emitEvent(eventsKey, 'added', jobIdStr, {'name', jobName})\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 redis.call('XADD', streamKey, '*', 'jobId', jobId)\n redis.call('ZREM', scheduledKey, jobId)\n local prefix = string.sub(scheduledKey, 1, #scheduledKey - 9)\n local jobKey = prefix .. 'job:' .. jobId\n redis.call('HSET', jobKey, 'state', 'waiting')\n emitEvent(eventsKey, 'promoted', jobId, nil)\n count = count + 1\n end\n cursorMin = (priority + 1) * PRIORITY_SHIFT\n end\n return count\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 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 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 redis.call('XACK', streamKey, group, entryId)\n redis.call('XDEL', streamKey, entryId)\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 emitEvent(eventsKey, 'completed', jobId, {'returnvalue', returnvalue})\n local prefix = string.sub(jobKey, 1, #jobKey - #('job:' .. jobId))\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, total - removeCount - 1)\n for i = 1, #excess do\n local oldId = excess[i]\n redis.call('DEL', prefix .. 'job:' .. oldId)\n redis.call('ZREM', completedKey, oldId)\n 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', tostring(cutoff))\n for i = 1, #old do\n local oldId = old[i]\n redis.call('DEL', prefix .. 'job:' .. oldId)\n redis.call('ZREM', completedKey, oldId)\n 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, total - removeCount - 1)\n for i = 1, #excess do\n local oldId = excess[i]\n redis.call('DEL', prefix .. 'job:' .. oldId)\n redis.call('ZREM', completedKey, oldId)\n end\n end\n end\n end\n if depsMember ~= '' and parentId ~= '' and #keys >= 8 then\n local parentDepsKey = keys[5]\n local parentJobKey = keys[6]\n local parentStreamKey = keys[7]\n local parentEventsKey = keys[8]\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 redis.call('HSET', parentJobKey, 'state', 'waiting')\n redis.call('XADD', parentStreamKey, '*', 'jobId', parentId)\n emitEvent(parentEventsKey, 'active', parentId, nil)\n end\n 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 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\n -- Phase 1: Complete current job (same as glidemq_complete)\n redis.call('XACK', streamKey, group, entryId)\n redis.call('XDEL', streamKey, entryId)\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 emitEvent(eventsKey, 'completed', jobId, {'returnvalue', returnvalue})\n local prefix = string.sub(jobKey, 1, #jobKey - #('job:' .. jobId))\n\n -- Retention cleanup\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, total - removeCount - 1)\n for i = 1, #excess do\n redis.call('DEL', prefix .. 'job:' .. excess[i])\n redis.call('ZREM', completedKey, excess[i])\n end\n end\n end\n\n -- Parent deps\n if depsMember ~= '' and parentId ~= '' and #keys >= 8 then\n local parentDepsKey = keys[5]\n local parentJobKey = keys[6]\n local parentStreamKey = keys[7]\n local parentEventsKey = keys[8]\n local doneCount = redis.call('HINCRBY', parentJobKey, 'depsCompleted', 1)\n local totalDeps = redis.call('SCARD', parentDepsKey)\n if totalDeps - doneCount <= 0 then\n redis.call('HSET', parentJobKey, 'state', 'waiting')\n redis.call('XADD', parentStreamKey, '*', 'jobId', parentId)\n emitEvent(parentEventsKey, 'active', parentId, nil)\n end\n end\n\n -- Phase 2: Fetch next job (non-blocking XREADGROUP)\n local nextEntries = redis.call('XREADGROUP', 'GROUP', group, consumer, 'COUNT', 1, 'STREAMS', streamKey, '>')\n if not nextEntries or #nextEntries == 0 then\n return cjson.encode({completed = jobId, next = false})\n end\n local streamData = nextEntries[1]\n local entries = streamData[2]\n if not entries or #entries == 0 then\n return cjson.encode({completed = jobId, next = false})\n end\n local nextEntry = entries[1]\n local nextEntryId = nextEntry[1]\n local nextFields = nextEntry[2]\n local 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 cjson.encode({completed = jobId, next = false})\n end\n\n -- Phase 3: Activate next job (same as moveToActive)\n local nextJobKey = prefix .. 'job:' .. nextJobId\n local nextExists = redis.call('EXISTS', nextJobKey)\n if nextExists == 0 then\n return cjson.encode({completed = jobId, next = false, nextEntryId = nextEntryId})\n end\n local revoked = redis.call('HGET', nextJobKey, 'revoked')\n if revoked == '1' then\n return cjson.encode({completed = jobId, next = 'REVOKED', nextJobId = nextJobId, nextEntryId = nextEntryId})\n end\n redis.call('HSET', nextJobKey, 'state', 'active', 'processedOn', tostring(timestamp), 'lastActive', tostring(timestamp))\n local nextHash = redis.call('HGETALL', nextJobKey)\n return cjson.encode({completed = jobId, next = nextHash, nextJobId = nextJobId, nextEntryId = nextEntryId})\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 jobId = args[1]\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 redis.call('XACK', streamKey, group, entryId)\n redis.call('XDEL', streamKey, entryId)\n local attemptsMade = redis.call('HINCRBY', jobKey, 'attemptsMade', 1)\n if maxAttempts > 0 and attemptsMade < maxAttempts then\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 emitEvent(eventsKey, 'retrying', jobId, {\n 'failedReason', failedReason,\n 'attemptsMade', tostring(attemptsMade),\n 'delay', tostring(backoffDelay)\n })\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 emitEvent(eventsKey, 'failed', jobId, {'failedReason', failedReason})\n local prefix = string.sub(jobKey, 1, #jobKey - #('job:' .. jobId))\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, total - removeCount - 1)\n for i = 1, #excess do\n local oldId = excess[i]\n redis.call('DEL', prefix .. 'job:' .. oldId)\n redis.call('ZREM', failedKey, oldId)\n 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', tostring(cutoff))\n for i = 1, #old do\n local oldId = old[i]\n redis.call('DEL', prefix .. 'job:' .. oldId)\n redis.call('ZREM', failedKey, oldId)\n 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, total - removeCount - 1)\n for i = 1, #excess do\n local oldId = excess[i]\n redis.call('DEL', prefix .. 'job:' .. oldId)\n redis.call('ZREM', failedKey, oldId)\n end\n end\n end\n 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 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 local lastActive = tonumber(redis.call('HGET', jobKey, 'lastActive'))\n if lastActive and (timestamp - lastActive) < minIdleMs then\n count = count + 1\n else\n local stalledCount = redis.call('HINCRBY', jobKey, 'stalledCount', 1)\n if stalledCount > maxStalledCount then\n redis.call('XACK', streamKey, group, entryId)\n redis.call('XDEL', streamKey, entryId)\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 emitEvent(eventsKey, 'failed', jobId, {\n 'failedReason', 'job stalled more than maxStalledCount'\n })\n else\n redis.call('HSET', jobKey, 'state', 'active')\n emitEvent(eventsKey, 'stalled', jobId, nil)\n end\n count = count + 1\n end\n end\n end\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 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 emitEvent(eventsKey, 'removed', existingJobId, nil)\n elseif state and state ~= 'completed' and state ~= 'failed' then\n return 'skipped'\n end\n end\n end\n end\n local jobId = redis.call('INCR', idKey)\n local jobIdStr = tostring(jobId)\n local jobKey = prefix .. 'job:' .. jobIdStr\n local orderingSeq = 0\n if orderingKey ~= '' then\n local orderingMetaKey = prefix .. 'ordering'\n orderingSeq = redis.call('HINCRBY', orderingMetaKey, orderingKey, 1)\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 orderingKey ~= '' then\n hashFields[#hashFields + 1] = 'orderingKey'\n hashFields[#hashFields + 1] = orderingKey\n hashFields[#hashFields + 1] = 'orderingSeq'\n hashFields[#hashFields + 1] = tostring(orderingSeq)\n end\n if parentId ~= '' then\n hashFields[#hashFields + 1] = 'parentId'\n hashFields[#hashFields + 1] = parentId\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 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 else\n redis.call('XADD', streamKey, '*', 'jobId', jobIdStr)\n end\n redis.call('HSET', dedupKey, dedupId, jobIdStr .. ':' .. tostring(timestamp))\n emitEvent(eventsKey, 'added', jobIdStr, {'name', jobName})\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 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_checkConcurrency', function(keys, args)\n local metaKey = keys[1]\n local streamKey = keys[2]\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 pending = redis.call('XPENDING', streamKey, group)\n local pendingCount = tonumber(pending[1]) or 0\n local remaining = gc - pendingCount\n if remaining <= 0 then\n return 0\n end\n return remaining\nend)\n\nredis.register_function('glidemq_moveToActive', function(keys, args)\n local jobKey = keys[1]\n local timestamp = args[1]\n local exists = redis.call('EXISTS', jobKey)\n if exists == 0 then\n return ''\n end\n local revoked = redis.call('HGET', jobKey, 'revoked')\n if revoked == '1' then\n return 'REVOKED'\n end\n redis.call('HSET', jobKey, 'state', 'active', 'processedOn', timestamp, 'lastActive', timestamp)\n local fields = redis.call('HGETALL', jobKey)\n return cjson.encode(fields)\nend)\n\nredis.register_function('glidemq_deferActive', function(keys, args)\n local streamKey = keys[1]\n local jobKey = keys[2]\n local jobId = args[1]\n local entryId = args[2]\n local group = args[3]\n local exists = redis.call('EXISTS', jobKey)\n redis.call('XACK', streamKey, group, entryId)\n redis.call('XDEL', streamKey, entryId)\n if exists == 0 then\n return 0\n end\n redis.call('XADD', streamKey, '*', 'jobId', jobId)\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 parentJobId = redis.call('INCR', parentIdKey)\n local parentJobIdStr = tostring(parentJobId)\n local parentPrefix = string.sub(parentIdKey, 1, #parentIdKey - 2)\n local parentJobKey = parentPrefix .. 'job:' .. parentJobIdStr\n local depsKey = parentPrefix .. 'deps:' .. parentJobIdStr\n local parentOrderingKey = extractOrderingKeyFromOpts(parentOpts)\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 parentOrderingKey ~= '' then\n parentHash[#parentHash + 1] = 'orderingKey'\n parentHash[#parentHash + 1] = parentOrderingKey\n parentHash[#parentHash + 1] = 'orderingSeq'\n parentHash[#parentHash + 1] = tostring(parentOrderingSeq)\n end\n redis.call('HSET', parentJobKey, unpack(parentHash))\n local childIds = {}\n local childArgOffset = 8\n local childKeyOffset = 4\n for i = 1, numChildren do\n local base = childArgOffset + (i - 1) * 8\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 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 childJobId = redis.call('INCR', childIdKey)\n local childJobIdStr = tostring(childJobId)\n local childPrefix = string.sub(childIdKey, 1, #childIdKey - 2)\n local childJobKey = childPrefix .. 'job:' .. childJobIdStr\n local childOrderingKey = extractOrderingKeyFromOpts(childOpts)\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 childOrderingKey ~= '' then\n childHash[#childHash + 1] = 'orderingKey'\n childHash[#childHash + 1] = childOrderingKey\n childHash[#childHash + 1] = 'orderingSeq'\n childHash[#childHash + 1] = tostring(childOrderingSeq)\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 else\n redis.call('XADD', childStreamKey, '*', 'jobId', childJobIdStr)\n end\n emitEvent(childEventsKey, 'added', childJobIdStr, {'name', childName})\n childIds[#childIds + 1] = childJobIdStr\n end\n local extraDepsOffset = childArgOffset + numChildren * 8\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 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 redis.call('HSET', parentJobKey, 'state', 'waiting')\n redis.call('XADD', parentStreamKey, '*', 'jobId', parentId)\n emitEvent(parentEventsKey, 'active', parentId, nil)\n end\n return remaining\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 redis.call('ZREM', scheduledKey, jobId)\n redis.call('ZREM', completedKey, jobId)\n redis.call('ZREM', failedKey, jobId)\n markOrderingDone(jobKey, 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_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 == 'waiting' or state == 'delayed' or state == 'prioritized' then\n redis.call('ZREM', scheduledKey, jobId)\n local entries = redis.call('XRANGE', streamKey, '-', '+')\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 redis.call('XACK', streamKey, group, entryId)\n redis.call('XDEL', streamKey, entryId)\n break\n end\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_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 entries = redis.call('XRANGE', stateKey, '-', '+')\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 end\n return matched\nend)\n";
|
|
7
7
|
export type QueueKeys = ReturnType<typeof import('../utils').buildKeys>;
|
|
8
8
|
/**
|
|
9
9
|
* Add a job to the queue atomically.
|
|
10
10
|
* Returns the new job ID (string).
|
|
11
11
|
*/
|
|
12
|
-
export declare function addJob(client: Client, k: QueueKeys, jobName: string, data: string, opts: string, timestamp: number, delay: number, priority: number, parentId: string, maxAttempts: number): Promise<string>;
|
|
12
|
+
export declare function addJob(client: Client, k: QueueKeys, jobName: string, data: string, opts: string, timestamp: number, delay: number, priority: number, parentId: string, maxAttempts: number, orderingKey?: string): Promise<string>;
|
|
13
13
|
/**
|
|
14
14
|
* Add a job with deduplication. Checks the dedup hash and either skips or adds the job.
|
|
15
15
|
* Returns "skipped" if deduplicated, otherwise the new job ID (string).
|
|
16
16
|
*/
|
|
17
|
-
export declare function dedup(client: Client, k: QueueKeys, dedupId: string, ttlMs: number, mode: string, jobName: string, data: string, opts: string, timestamp: number, delay: number, priority: number, parentId: string, maxAttempts: number): Promise<string>;
|
|
17
|
+
export declare function dedup(client: Client, k: QueueKeys, dedupId: string, ttlMs: number, mode: string, jobName: string, data: string, opts: string, timestamp: number, delay: number, priority: number, parentId: string, maxAttempts: number, orderingKey?: string): Promise<string>;
|
|
18
18
|
/**
|
|
19
19
|
* Promote delayed/prioritized jobs whose score <= now from scheduled ZSet to stream.
|
|
20
20
|
* Returns the number of jobs promoted.
|
|
@@ -101,6 +101,12 @@ export declare function checkConcurrency(client: Client, k: QueueKeys, group?: s
|
|
|
101
101
|
* Replaces: HGETALL + revoked check + HSET lastActive (3 round trips -> 1)
|
|
102
102
|
*/
|
|
103
103
|
export declare function moveToActive(client: Client, k: QueueKeys, jobId: string, timestamp: number): Promise<Record<string, string> | 'REVOKED' | null>;
|
|
104
|
+
/**
|
|
105
|
+
* Defers an active job back to waiting by acknowledging + deleting the current
|
|
106
|
+
* stream entry and re-enqueuing the same jobId to the stream tail.
|
|
107
|
+
* If the job hash no longer exists, it only removes the stream entry.
|
|
108
|
+
*/
|
|
109
|
+
export declare function deferActive(client: Client, k: QueueKeys, jobId: string, entryId: string, group?: string): Promise<void>;
|
|
104
110
|
/**
|
|
105
111
|
* Remove a job from all data structures (hash, stream, scheduled, completed, failed).
|
|
106
112
|
* Returns 1 if removed, 0 if not found.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/functions/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,UAAU,CAAC;AACvC,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,mBAAmB,CAAC;AAEzD,eAAO,MAAM,YAAY,YAAY,CAAC;AACtC,eAAO,MAAM,eAAe,
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/functions/index.ts"],"names":[],"mappings":"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"}
|
package/dist/functions/index.js
CHANGED
|
@@ -13,13 +13,14 @@ exports.resume = resume;
|
|
|
13
13
|
exports.rateLimit = rateLimit;
|
|
14
14
|
exports.checkConcurrency = checkConcurrency;
|
|
15
15
|
exports.moveToActive = moveToActive;
|
|
16
|
+
exports.deferActive = deferActive;
|
|
16
17
|
exports.removeJob = removeJob;
|
|
17
18
|
exports.revokeJob = revokeJob;
|
|
18
19
|
exports.searchByName = searchByName;
|
|
19
20
|
exports.addFlow = addFlow;
|
|
20
21
|
exports.completeChild = completeChild;
|
|
21
22
|
exports.LIBRARY_NAME = 'glidemq';
|
|
22
|
-
exports.LIBRARY_VERSION = '
|
|
23
|
+
exports.LIBRARY_VERSION = '15';
|
|
23
24
|
// Consumer group name used by workers
|
|
24
25
|
exports.CONSUMER_GROUP = 'workers';
|
|
25
26
|
// Embedded Lua library source (from glidemq.lua)
|
|
@@ -39,6 +40,61 @@ local function emitEvent(eventsKey, eventType, jobId, extraFields)
|
|
|
39
40
|
redis.call('XADD', eventsKey, 'MAXLEN', '~', '1000', '*', unpack(fields))
|
|
40
41
|
end
|
|
41
42
|
|
|
43
|
+
local function markOrderingDone(jobKey, jobId)
|
|
44
|
+
local orderingKey = redis.call('HGET', jobKey, 'orderingKey')
|
|
45
|
+
if not orderingKey or orderingKey == '' then
|
|
46
|
+
return
|
|
47
|
+
end
|
|
48
|
+
local orderingSeq = tonumber(redis.call('HGET', jobKey, 'orderingSeq')) or 0
|
|
49
|
+
if orderingSeq <= 0 then
|
|
50
|
+
return
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
local prefix = string.sub(jobKey, 1, #jobKey - #('job:' .. jobId))
|
|
54
|
+
local metaKey = prefix .. 'meta'
|
|
55
|
+
local doneField = 'orderdone:' .. orderingKey
|
|
56
|
+
local pendingKey = prefix .. 'orderdone:pending:' .. orderingKey
|
|
57
|
+
|
|
58
|
+
local lastDone = tonumber(redis.call('HGET', metaKey, doneField)) or 0
|
|
59
|
+
if orderingSeq <= lastDone then
|
|
60
|
+
redis.call('HDEL', pendingKey, tostring(orderingSeq))
|
|
61
|
+
return
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
redis.call('HSET', pendingKey, tostring(orderingSeq), '1')
|
|
65
|
+
local advanced = lastDone
|
|
66
|
+
while true do
|
|
67
|
+
local nextSeq = advanced + 1
|
|
68
|
+
if redis.call('HEXISTS', pendingKey, tostring(nextSeq)) == 0 then
|
|
69
|
+
break
|
|
70
|
+
end
|
|
71
|
+
redis.call('HDEL', pendingKey, tostring(nextSeq))
|
|
72
|
+
advanced = nextSeq
|
|
73
|
+
end
|
|
74
|
+
if advanced > lastDone then
|
|
75
|
+
redis.call('HSET', metaKey, doneField, tostring(advanced))
|
|
76
|
+
end
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
local function extractOrderingKeyFromOpts(optsJson)
|
|
80
|
+
if not optsJson or optsJson == '' then
|
|
81
|
+
return ''
|
|
82
|
+
end
|
|
83
|
+
local ok, decoded = pcall(cjson.decode, optsJson)
|
|
84
|
+
if not ok or type(decoded) ~= 'table' then
|
|
85
|
+
return ''
|
|
86
|
+
end
|
|
87
|
+
local ordering = decoded['ordering']
|
|
88
|
+
if type(ordering) ~= 'table' then
|
|
89
|
+
return ''
|
|
90
|
+
end
|
|
91
|
+
local key = ordering['key']
|
|
92
|
+
if key == nil then
|
|
93
|
+
return ''
|
|
94
|
+
end
|
|
95
|
+
return tostring(key)
|
|
96
|
+
end
|
|
97
|
+
|
|
42
98
|
redis.register_function('glidemq_version', function(keys, args)
|
|
43
99
|
return '${exports.LIBRARY_VERSION}'
|
|
44
100
|
end)
|
|
@@ -56,10 +112,16 @@ redis.register_function('glidemq_addJob', function(keys, args)
|
|
|
56
112
|
local priority = tonumber(args[6]) or 0
|
|
57
113
|
local parentId = args[7] or ''
|
|
58
114
|
local maxAttempts = tonumber(args[8]) or 0
|
|
115
|
+
local orderingKey = args[9] or ''
|
|
59
116
|
local jobId = redis.call('INCR', idKey)
|
|
60
117
|
local jobIdStr = tostring(jobId)
|
|
61
118
|
local prefix = string.sub(idKey, 1, #idKey - 2)
|
|
62
119
|
local jobKey = prefix .. 'job:' .. jobIdStr
|
|
120
|
+
local orderingSeq = 0
|
|
121
|
+
if orderingKey ~= '' then
|
|
122
|
+
local orderingMetaKey = prefix .. 'ordering'
|
|
123
|
+
orderingSeq = redis.call('HINCRBY', orderingMetaKey, orderingKey, 1)
|
|
124
|
+
end
|
|
63
125
|
local hashFields = {
|
|
64
126
|
'id', jobIdStr,
|
|
65
127
|
'name', jobName,
|
|
@@ -71,6 +133,12 @@ redis.register_function('glidemq_addJob', function(keys, args)
|
|
|
71
133
|
'priority', tostring(priority),
|
|
72
134
|
'maxAttempts', tostring(maxAttempts)
|
|
73
135
|
}
|
|
136
|
+
if orderingKey ~= '' then
|
|
137
|
+
hashFields[#hashFields + 1] = 'orderingKey'
|
|
138
|
+
hashFields[#hashFields + 1] = orderingKey
|
|
139
|
+
hashFields[#hashFields + 1] = 'orderingSeq'
|
|
140
|
+
hashFields[#hashFields + 1] = tostring(orderingSeq)
|
|
141
|
+
end
|
|
74
142
|
if parentId ~= '' then
|
|
75
143
|
hashFields[#hashFields + 1] = 'parentId'
|
|
76
144
|
hashFields[#hashFields + 1] = parentId
|
|
@@ -101,10 +169,11 @@ redis.register_function('glidemq_promote', function(keys, args)
|
|
|
101
169
|
local streamKey = keys[2]
|
|
102
170
|
local eventsKey = keys[3]
|
|
103
171
|
local now = tonumber(args[1])
|
|
172
|
+
local MAX_PROMOTIONS = 1000
|
|
104
173
|
local count = 0
|
|
105
174
|
local cursorMin = 0
|
|
106
|
-
while
|
|
107
|
-
local nextEntry = redis.call('ZRANGEBYSCORE', scheduledKey,
|
|
175
|
+
while count < MAX_PROMOTIONS do
|
|
176
|
+
local nextEntry = redis.call('ZRANGEBYSCORE', scheduledKey, string.format('%.0f', cursorMin), '+inf', 'WITHSCORES', 'LIMIT', 0, 1)
|
|
108
177
|
if not nextEntry or #nextEntry == 0 then
|
|
109
178
|
break
|
|
110
179
|
end
|
|
@@ -112,7 +181,16 @@ redis.register_function('glidemq_promote', function(keys, args)
|
|
|
112
181
|
local priority = math.floor(firstScore / PRIORITY_SHIFT)
|
|
113
182
|
local minScore = priority * PRIORITY_SHIFT
|
|
114
183
|
local maxDueScore = minScore + now
|
|
115
|
-
local
|
|
184
|
+
local remaining = MAX_PROMOTIONS - count
|
|
185
|
+
local members = redis.call(
|
|
186
|
+
'ZRANGEBYSCORE',
|
|
187
|
+
scheduledKey,
|
|
188
|
+
string.format('%.0f', minScore),
|
|
189
|
+
string.format('%.0f', maxDueScore),
|
|
190
|
+
'LIMIT',
|
|
191
|
+
0,
|
|
192
|
+
remaining
|
|
193
|
+
)
|
|
116
194
|
for i = 1, #members do
|
|
117
195
|
local jobId = members[i]
|
|
118
196
|
redis.call('XADD', streamKey, '*', 'jobId', jobId)
|
|
@@ -151,6 +229,7 @@ redis.register_function('glidemq_complete', function(keys, args)
|
|
|
151
229
|
'returnvalue', returnvalue,
|
|
152
230
|
'finishedOn', tostring(timestamp)
|
|
153
231
|
)
|
|
232
|
+
markOrderingDone(jobKey, jobId)
|
|
154
233
|
emitEvent(eventsKey, 'completed', jobId, {'returnvalue', returnvalue})
|
|
155
234
|
local prefix = string.sub(jobKey, 1, #jobKey - #('job:' .. jobId))
|
|
156
235
|
if removeMode == 'true' then
|
|
@@ -231,6 +310,7 @@ redis.register_function('glidemq_completeAndFetchNext', function(keys, args)
|
|
|
231
310
|
'returnvalue', returnvalue,
|
|
232
311
|
'finishedOn', tostring(timestamp)
|
|
233
312
|
)
|
|
313
|
+
markOrderingDone(jobKey, jobId)
|
|
234
314
|
emitEvent(eventsKey, 'completed', jobId, {'returnvalue', returnvalue})
|
|
235
315
|
local prefix = string.sub(jobKey, 1, #jobKey - #('job:' .. jobId))
|
|
236
316
|
|
|
@@ -346,6 +426,7 @@ redis.register_function('glidemq_fail', function(keys, args)
|
|
|
346
426
|
'finishedOn', tostring(timestamp),
|
|
347
427
|
'processedOn', tostring(timestamp)
|
|
348
428
|
)
|
|
429
|
+
markOrderingDone(jobKey, jobId)
|
|
349
430
|
emitEvent(eventsKey, 'failed', jobId, {'failedReason', failedReason})
|
|
350
431
|
local prefix = string.sub(jobKey, 1, #jobKey - #('job:' .. jobId))
|
|
351
432
|
if removeMode == 'true' then
|
|
@@ -432,6 +513,7 @@ redis.register_function('glidemq_reclaimStalled', function(keys, args)
|
|
|
432
513
|
'failedReason', 'job stalled more than maxStalledCount',
|
|
433
514
|
'finishedOn', tostring(timestamp)
|
|
434
515
|
)
|
|
516
|
+
markOrderingDone(jobKey, jobId)
|
|
435
517
|
emitEvent(eventsKey, 'failed', jobId, {
|
|
436
518
|
'failedReason', 'job stalled more than maxStalledCount'
|
|
437
519
|
})
|
|
@@ -479,6 +561,7 @@ redis.register_function('glidemq_dedup', function(keys, args)
|
|
|
479
561
|
local priority = tonumber(args[9]) or 0
|
|
480
562
|
local parentId = args[10] or ''
|
|
481
563
|
local maxAttempts = tonumber(args[11]) or 0
|
|
564
|
+
local orderingKey = args[12] or ''
|
|
482
565
|
local prefix = string.sub(idKey, 1, #idKey - 2)
|
|
483
566
|
local existing = redis.call('HGET', dedupKey, dedupId)
|
|
484
567
|
if mode == 'simple' then
|
|
@@ -512,6 +595,7 @@ redis.register_function('glidemq_dedup', function(keys, args)
|
|
|
512
595
|
local state = redis.call('HGET', jobKey, 'state')
|
|
513
596
|
if state == 'delayed' or state == 'prioritized' then
|
|
514
597
|
redis.call('ZREM', scheduledKey, existingJobId)
|
|
598
|
+
markOrderingDone(jobKey, existingJobId)
|
|
515
599
|
redis.call('DEL', jobKey)
|
|
516
600
|
emitEvent(eventsKey, 'removed', existingJobId, nil)
|
|
517
601
|
elseif state and state ~= 'completed' and state ~= 'failed' then
|
|
@@ -523,6 +607,11 @@ redis.register_function('glidemq_dedup', function(keys, args)
|
|
|
523
607
|
local jobId = redis.call('INCR', idKey)
|
|
524
608
|
local jobIdStr = tostring(jobId)
|
|
525
609
|
local jobKey = prefix .. 'job:' .. jobIdStr
|
|
610
|
+
local orderingSeq = 0
|
|
611
|
+
if orderingKey ~= '' then
|
|
612
|
+
local orderingMetaKey = prefix .. 'ordering'
|
|
613
|
+
orderingSeq = redis.call('HINCRBY', orderingMetaKey, orderingKey, 1)
|
|
614
|
+
end
|
|
526
615
|
local hashFields = {
|
|
527
616
|
'id', jobIdStr,
|
|
528
617
|
'name', jobName,
|
|
@@ -534,6 +623,12 @@ redis.register_function('glidemq_dedup', function(keys, args)
|
|
|
534
623
|
'priority', tostring(priority),
|
|
535
624
|
'maxAttempts', tostring(maxAttempts)
|
|
536
625
|
}
|
|
626
|
+
if orderingKey ~= '' then
|
|
627
|
+
hashFields[#hashFields + 1] = 'orderingKey'
|
|
628
|
+
hashFields[#hashFields + 1] = orderingKey
|
|
629
|
+
hashFields[#hashFields + 1] = 'orderingSeq'
|
|
630
|
+
hashFields[#hashFields + 1] = tostring(orderingSeq)
|
|
631
|
+
end
|
|
537
632
|
if parentId ~= '' then
|
|
538
633
|
hashFields[#hashFields + 1] = 'parentId'
|
|
539
634
|
hashFields[#hashFields + 1] = parentId
|
|
@@ -613,6 +708,23 @@ redis.register_function('glidemq_moveToActive', function(keys, args)
|
|
|
613
708
|
return cjson.encode(fields)
|
|
614
709
|
end)
|
|
615
710
|
|
|
711
|
+
redis.register_function('glidemq_deferActive', function(keys, args)
|
|
712
|
+
local streamKey = keys[1]
|
|
713
|
+
local jobKey = keys[2]
|
|
714
|
+
local jobId = args[1]
|
|
715
|
+
local entryId = args[2]
|
|
716
|
+
local group = args[3]
|
|
717
|
+
local exists = redis.call('EXISTS', jobKey)
|
|
718
|
+
redis.call('XACK', streamKey, group, entryId)
|
|
719
|
+
redis.call('XDEL', streamKey, entryId)
|
|
720
|
+
if exists == 0 then
|
|
721
|
+
return 0
|
|
722
|
+
end
|
|
723
|
+
redis.call('XADD', streamKey, '*', 'jobId', jobId)
|
|
724
|
+
redis.call('HSET', jobKey, 'state', 'waiting')
|
|
725
|
+
return 1
|
|
726
|
+
end)
|
|
727
|
+
|
|
616
728
|
redis.register_function('glidemq_addFlow', function(keys, args)
|
|
617
729
|
local parentIdKey = keys[1]
|
|
618
730
|
local parentStreamKey = keys[2]
|
|
@@ -631,6 +743,12 @@ redis.register_function('glidemq_addFlow', function(keys, args)
|
|
|
631
743
|
local parentPrefix = string.sub(parentIdKey, 1, #parentIdKey - 2)
|
|
632
744
|
local parentJobKey = parentPrefix .. 'job:' .. parentJobIdStr
|
|
633
745
|
local depsKey = parentPrefix .. 'deps:' .. parentJobIdStr
|
|
746
|
+
local parentOrderingKey = extractOrderingKeyFromOpts(parentOpts)
|
|
747
|
+
local parentOrderingSeq = 0
|
|
748
|
+
if parentOrderingKey ~= '' then
|
|
749
|
+
local parentOrderingMetaKey = parentPrefix .. 'ordering'
|
|
750
|
+
parentOrderingSeq = redis.call('HINCRBY', parentOrderingMetaKey, parentOrderingKey, 1)
|
|
751
|
+
end
|
|
634
752
|
local parentHash = {
|
|
635
753
|
'id', parentJobIdStr,
|
|
636
754
|
'name', parentName,
|
|
@@ -643,6 +761,12 @@ redis.register_function('glidemq_addFlow', function(keys, args)
|
|
|
643
761
|
'maxAttempts', tostring(parentMaxAttempts),
|
|
644
762
|
'state', 'waiting-children'
|
|
645
763
|
}
|
|
764
|
+
if parentOrderingKey ~= '' then
|
|
765
|
+
parentHash[#parentHash + 1] = 'orderingKey'
|
|
766
|
+
parentHash[#parentHash + 1] = parentOrderingKey
|
|
767
|
+
parentHash[#parentHash + 1] = 'orderingSeq'
|
|
768
|
+
parentHash[#parentHash + 1] = tostring(parentOrderingSeq)
|
|
769
|
+
end
|
|
646
770
|
redis.call('HSET', parentJobKey, unpack(parentHash))
|
|
647
771
|
local childIds = {}
|
|
648
772
|
local childArgOffset = 8
|
|
@@ -666,6 +790,12 @@ redis.register_function('glidemq_addFlow', function(keys, args)
|
|
|
666
790
|
local childJobIdStr = tostring(childJobId)
|
|
667
791
|
local childPrefix = string.sub(childIdKey, 1, #childIdKey - 2)
|
|
668
792
|
local childJobKey = childPrefix .. 'job:' .. childJobIdStr
|
|
793
|
+
local childOrderingKey = extractOrderingKeyFromOpts(childOpts)
|
|
794
|
+
local childOrderingSeq = 0
|
|
795
|
+
if childOrderingKey ~= '' then
|
|
796
|
+
local childOrderingMetaKey = childPrefix .. 'ordering'
|
|
797
|
+
childOrderingSeq = redis.call('HINCRBY', childOrderingMetaKey, childOrderingKey, 1)
|
|
798
|
+
end
|
|
669
799
|
local childHash = {
|
|
670
800
|
'id', childJobIdStr,
|
|
671
801
|
'name', childName,
|
|
@@ -679,6 +809,12 @@ redis.register_function('glidemq_addFlow', function(keys, args)
|
|
|
679
809
|
'parentId', parentJobIdStr,
|
|
680
810
|
'parentQueue', childParentQueue
|
|
681
811
|
}
|
|
812
|
+
if childOrderingKey ~= '' then
|
|
813
|
+
childHash[#childHash + 1] = 'orderingKey'
|
|
814
|
+
childHash[#childHash + 1] = childOrderingKey
|
|
815
|
+
childHash[#childHash + 1] = 'orderingSeq'
|
|
816
|
+
childHash[#childHash + 1] = tostring(childOrderingSeq)
|
|
817
|
+
end
|
|
682
818
|
if childDelay > 0 or childPriority > 0 then
|
|
683
819
|
childHash[#childHash + 1] = 'state'
|
|
684
820
|
childHash[#childHash + 1] = childDelay > 0 and 'delayed' or 'prioritized'
|
|
@@ -749,6 +885,7 @@ redis.register_function('glidemq_removeJob', function(keys, args)
|
|
|
749
885
|
redis.call('ZREM', scheduledKey, jobId)
|
|
750
886
|
redis.call('ZREM', completedKey, jobId)
|
|
751
887
|
redis.call('ZREM', failedKey, jobId)
|
|
888
|
+
markOrderingDone(jobKey, jobId)
|
|
752
889
|
redis.call('DEL', jobKey)
|
|
753
890
|
redis.call('DEL', logKey)
|
|
754
891
|
emitEvent(eventsKey, 'removed', jobId, nil)
|
|
@@ -790,6 +927,7 @@ redis.register_function('glidemq_revoke', function(keys, args)
|
|
|
790
927
|
'failedReason', 'revoked',
|
|
791
928
|
'finishedOn', tostring(timestamp)
|
|
792
929
|
)
|
|
930
|
+
markOrderingDone(jobKey, jobId)
|
|
793
931
|
emitEvent(eventsKey, 'revoked', jobId, nil)
|
|
794
932
|
return 'revoked'
|
|
795
933
|
end
|
|
@@ -844,7 +982,7 @@ end)
|
|
|
844
982
|
* Add a job to the queue atomically.
|
|
845
983
|
* Returns the new job ID (string).
|
|
846
984
|
*/
|
|
847
|
-
async function addJob(client, k, jobName, data, opts, timestamp, delay, priority, parentId, maxAttempts) {
|
|
985
|
+
async function addJob(client, k, jobName, data, opts, timestamp, delay, priority, parentId, maxAttempts, orderingKey = '') {
|
|
848
986
|
const result = await client.fcall('glidemq_addJob', [k.id, k.stream, k.scheduled, k.events], [
|
|
849
987
|
jobName,
|
|
850
988
|
data,
|
|
@@ -854,6 +992,7 @@ async function addJob(client, k, jobName, data, opts, timestamp, delay, priority
|
|
|
854
992
|
priority.toString(),
|
|
855
993
|
parentId,
|
|
856
994
|
maxAttempts.toString(),
|
|
995
|
+
orderingKey,
|
|
857
996
|
]);
|
|
858
997
|
return result;
|
|
859
998
|
}
|
|
@@ -861,7 +1000,7 @@ async function addJob(client, k, jobName, data, opts, timestamp, delay, priority
|
|
|
861
1000
|
* Add a job with deduplication. Checks the dedup hash and either skips or adds the job.
|
|
862
1001
|
* Returns "skipped" if deduplicated, otherwise the new job ID (string).
|
|
863
1002
|
*/
|
|
864
|
-
async function dedup(client, k, dedupId, ttlMs, mode, jobName, data, opts, timestamp, delay, priority, parentId, maxAttempts) {
|
|
1003
|
+
async function dedup(client, k, dedupId, ttlMs, mode, jobName, data, opts, timestamp, delay, priority, parentId, maxAttempts, orderingKey = '') {
|
|
865
1004
|
const result = await client.fcall('glidemq_dedup', [k.dedup, k.id, k.stream, k.scheduled, k.events], [
|
|
866
1005
|
dedupId,
|
|
867
1006
|
ttlMs.toString(),
|
|
@@ -874,6 +1013,7 @@ async function dedup(client, k, dedupId, ttlMs, mode, jobName, data, opts, times
|
|
|
874
1013
|
priority.toString(),
|
|
875
1014
|
parentId,
|
|
876
1015
|
maxAttempts.toString(),
|
|
1016
|
+
orderingKey,
|
|
877
1017
|
]);
|
|
878
1018
|
return result;
|
|
879
1019
|
}
|
|
@@ -1055,6 +1195,14 @@ async function moveToActive(client, k, jobId, timestamp) {
|
|
|
1055
1195
|
}
|
|
1056
1196
|
return hash;
|
|
1057
1197
|
}
|
|
1198
|
+
/**
|
|
1199
|
+
* Defers an active job back to waiting by acknowledging + deleting the current
|
|
1200
|
+
* stream entry and re-enqueuing the same jobId to the stream tail.
|
|
1201
|
+
* If the job hash no longer exists, it only removes the stream entry.
|
|
1202
|
+
*/
|
|
1203
|
+
async function deferActive(client, k, jobId, entryId, group = exports.CONSUMER_GROUP) {
|
|
1204
|
+
await client.fcall('glidemq_deferActive', [k.stream, k.job(jobId)], [jobId, entryId, group]);
|
|
1205
|
+
}
|
|
1058
1206
|
/**
|
|
1059
1207
|
* Remove a job from all data structures (hash, stream, scheduled, completed, failed).
|
|
1060
1208
|
* Returns 1 if removed, 0 if not found.
|