diff --git a/docs/readme.md b/docs/readme.md index 349f8b9..e993e35 100644 --- a/docs/readme.md +++ b/docs/readme.md @@ -983,10 +983,12 @@ Allowed policy values: | Policy | Description | | - | - | -| standard | (Default) Supports all standard features such as deferral, priority, and throttling | -| short | All standard features, but only allows 1 job to be queued, unlimited active. Can be extended with `singletonKey` | -| singleton | All standard features, but only allows 1 job to be active, unlimited queued. Can be extended with `singletonKey` | -| stately | Combination of short and singleton: Only allows 1 job per state, queued and/or active. Can be extended with `singletonKey` | +| `standard` | (Default) Supports all standard features such as deferral, priority, and throttling | +| `short` | All standard features, but only allows 1 job to be queued, unlimited active. Can be extended with `singletonKey` | +| `singleton` | All standard features, but only allows 1 job to be active, unlimited queued. Can be extended with `singletonKey` | +| `stately` | Combination of short and singleton: Only allows 1 job per state, queued and/or active. Can be extended with `singletonKey` | + +> `stately` queues are special in how retries are handled. By definition, stately queues will not allow multiple jobs to occupy `retry` state. Once a job exists in `retry`, failing another `active` job will bypass the retry mechanism and force the job to `failed`. If this job requires retries, consider a custom retry implementation using a dead letter queue. ## `updateQueue(name, options)` diff --git a/package-lock.json b/package-lock.json index d0aca18..0597231 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,24 +1,24 @@ { "name": "pg-boss", - "version": "10.1.2", + "version": "10.1.3", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "pg-boss", - "version": "10.1.2", + "version": "10.1.3", "license": "MIT", "dependencies": { - "cron-parser": "^4.0.0", - "pg": "^8.5.1", + "cron-parser": "^4.9.0", + "pg": "^8.12.0", "serialize-error": "^8.1.0" }, "devDependencies": { - "@types/node": "^20.3.3", - "luxon": "^3.0.1", - "mocha": "^10.0.0", + "@types/node": "^20.16.5", + "luxon": "^3.5.0", + "mocha": "^10.7.3", "nyc": "^17.0.0", - "standard": "^17.0.0" + "standard": "^17.1.2" }, "engines": { "node": ">=20" @@ -105,12 +105,12 @@ } }, "node_modules/@babel/generator": { - "version": "7.25.5", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.25.5.tgz", - "integrity": "sha512-abd43wyLfbWoxC6ahM8xTkqLpGB2iWBVyuKC9/srhFunCd1SDNrV1s72bBpK4hLj8KLzHBBcOblvLQZBNw9r3w==", + "version": "7.25.6", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.25.6.tgz", + "integrity": "sha512-VPC82gr1seXOpkjAAKoLhP50vx4vGNlF4msF64dSFq1P8RfB+QAuJWGHPXXPc8QyfVWwwB/TNNU4+ayZmHNbZw==", "dev": true, "dependencies": { - "@babel/types": "^7.25.4", + "@babel/types": "^7.25.6", "@jridgewell/gen-mapping": "^0.3.5", "@jridgewell/trace-mapping": "^0.3.25", "jsesc": "^2.5.1" @@ -216,13 +216,13 @@ } }, "node_modules/@babel/helpers": { - "version": "7.25.0", - "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.25.0.tgz", - "integrity": "sha512-MjgLZ42aCm0oGjJj8CtSM3DB8NOOf8h2l7DCTePJs29u+v7yO/RBX9nShlKMgFnRks/Q4tBAe7Hxnov9VkGwLw==", + "version": "7.25.6", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.25.6.tgz", + "integrity": "sha512-Xg0tn4HcfTijTwfDwYlvVCl43V6h4KyVVX2aEm4qdO/PC6L2YvzLHFdmxhoeSA3eslcE6+ZVXHgWwopXYLNq4Q==", "dev": true, "dependencies": { "@babel/template": "^7.25.0", - "@babel/types": "^7.25.0" + "@babel/types": "^7.25.6" }, "engines": { "node": ">=6.9.0" @@ -315,12 +315,12 @@ } }, "node_modules/@babel/parser": { - "version": "7.25.4", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.25.4.tgz", - "integrity": "sha512-nq+eWrOgdtu3jG5Os4TQP3x3cLA8hR8TvJNjD8vnPa20WGycimcparWnLK4jJhElTK6SDyuJo1weMKO/5LpmLA==", + "version": "7.25.6", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.25.6.tgz", + "integrity": "sha512-trGdfBdbD0l1ZPmcJ83eNxB9rbEax4ALFTF7fN386TMYbeCQbyme5cOEXQhbGXKebwGaB/J52w1mrklMcbgy6Q==", "dev": true, "dependencies": { - "@babel/types": "^7.25.4" + "@babel/types": "^7.25.6" }, "bin": { "parser": "bin/babel-parser.js" @@ -344,16 +344,16 @@ } }, "node_modules/@babel/traverse": { - "version": "7.25.4", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.25.4.tgz", - "integrity": "sha512-VJ4XsrD+nOvlXyLzmLzUs/0qjFS4sK30te5yEFlvbbUNEgKaVb2BHZUpAL+ttLPQAHNrsI3zZisbfha5Cvr8vg==", + "version": "7.25.6", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.25.6.tgz", + "integrity": "sha512-9Vrcx5ZW6UwK5tvqsj0nGpp/XzqthkT0dqIc9g1AdtygFToNtTF67XzYS//dm+SAK9cp3B9R4ZO/46p63SCjlQ==", "dev": true, "dependencies": { "@babel/code-frame": "^7.24.7", - "@babel/generator": "^7.25.4", - "@babel/parser": "^7.25.4", + "@babel/generator": "^7.25.6", + "@babel/parser": "^7.25.6", "@babel/template": "^7.25.0", - "@babel/types": "^7.25.4", + "@babel/types": "^7.25.6", "debug": "^4.3.1", "globals": "^11.1.0" }, @@ -362,9 +362,9 @@ } }, "node_modules/@babel/types": { - "version": "7.25.4", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.25.4.tgz", - "integrity": "sha512-zQ1ijeeCXVEh+aNL0RlmkPkG8HUiDcU2pzQQFjtbntgAczRASFzj4H+6+bV+dy1ntKR14I/DypeuRG1uma98iQ==", + "version": "7.25.6", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.25.6.tgz", + "integrity": "sha512-/l42B1qxpG6RdfYf343Uw1vmDjeNhneUXtzhojE7pDgfpEypmRhI6j1kr17XCVv4Cgl9HdAiQY2x0GwKm7rWCw==", "dev": true, "dependencies": { "@babel/helper-string-parser": "^7.24.8", @@ -719,6 +719,12 @@ "node": ">= 8" } }, + "node_modules/@rtsao/scc": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@rtsao/scc/-/scc-1.1.0.tgz", + "integrity": "sha512-zt6OdqaDoOnJ1ZYsCYGt9YmWzDXl4vQdKTyJev62gFhRGKdx7mcT54V9KIjg+d2wi9EXsPvAPKe7i7WjfVWB8g==", + "dev": true + }, "node_modules/@types/json5": { "version": "0.0.29", "resolved": "https://registry.npmjs.org/@types/json5/-/json5-0.0.29.tgz", @@ -726,9 +732,9 @@ "dev": true }, "node_modules/@types/node": { - "version": "20.16.1", - "resolved": "https://registry.npmjs.org/@types/node/-/node-20.16.1.tgz", - "integrity": "sha512-zJDo7wEadFtSyNz5QITDfRcrhqDvQI1xQNQ0VoizPjM/dVAODqqIUWbJPkvsxmTI0MYRGRikcdjMPhOssnPejQ==", + "version": "20.16.5", + "resolved": "https://registry.npmjs.org/@types/node/-/node-20.16.5.tgz", + "integrity": "sha512-VwYCweNo3ERajwy0IUlqqcyZ8/A7Zwa9ZP3MnENWcB11AejO+tLy3pu850goUW2FC/IJMdZUfKpX/yxL1gymCA==", "dev": true, "dependencies": { "undici-types": "~6.19.2" @@ -1164,9 +1170,9 @@ } }, "node_modules/caniuse-lite": { - "version": "1.0.30001653", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001653.tgz", - "integrity": "sha512-XGWQVB8wFQ2+9NZwZ10GxTYC5hk0Fa+q8cSkr0tgvMhYhMHP/QC+WTgrePMDBWiWc/pV+1ik82Al20XOK25Gcw==", + "version": "1.0.30001660", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001660.tgz", + "integrity": "sha512-GacvNTTuATm26qC74pt+ad1fW15mlQ/zuTzzY1ZoIzECTP8HURDfF43kNxPgf7H1jmelCBQTTbBNxdSXOA7Bqg==", "dev": true, "funding": [ { @@ -1368,12 +1374,12 @@ } }, "node_modules/debug": { - "version": "4.3.6", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.6.tgz", - "integrity": "sha512-O/09Bd4Z1fBrU4VzkhFqVgpPzaGbw6Sm9FEkBT1A/YBXQFGuuSxa1dN2nxgxS34JmKXqYx8CZAwEVoJFImUXIg==", + "version": "4.3.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz", + "integrity": "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==", "dev": true, "dependencies": { - "ms": "2.1.2" + "ms": "^2.1.3" }, "engines": { "node": ">=6.0" @@ -1384,12 +1390,6 @@ } } }, - "node_modules/debug/node_modules/ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", - "dev": true - }, "node_modules/decamelize": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz", @@ -1476,9 +1476,9 @@ } }, "node_modules/electron-to-chromium": { - "version": "1.5.13", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.13.tgz", - "integrity": "sha512-lbBcvtIJ4J6sS4tb5TLp1b4LyfCdMkwStzXPyAgVgTRAsep4bvrAGaBOP7ZJtQMNJpSQ9SqG4brWOroNaQtm7Q==", + "version": "1.5.23", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.23.tgz", + "integrity": "sha512-mBhODedOXg4v5QWwl21DjM5amzjmI1zw9EPrPK/5Wx7C8jt33bpZNrC7OhHUG3pxRtbLpr3W2dXT+Ph1SsfRZA==", "dev": true }, "node_modules/emoji-regex": { @@ -1661,9 +1661,9 @@ "dev": true }, "node_modules/escalade": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.2.tgz", - "integrity": "sha512-ErCHMCae19vR8vQGe50xIsVomy19rg6gFu3+r3jkEO46suLMWBksvVyoGgQV+jOfl84ZSOSlmv6Gxa89PmTGmA==", + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", + "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", "dev": true, "engines": { "node": ">=6" @@ -1810,9 +1810,9 @@ } }, "node_modules/eslint-module-utils": { - "version": "2.8.2", - "resolved": "https://registry.npmjs.org/eslint-module-utils/-/eslint-module-utils-2.8.2.tgz", - "integrity": "sha512-3XnC5fDyc8M4J2E8pt8pmSVRX2M+5yWMCfI/kDZwauQeFgzQOuhcRBFKjTeJagqgk4sFKxe1mvNVnaWwImx/Tg==", + "version": "2.11.0", + "resolved": "https://registry.npmjs.org/eslint-module-utils/-/eslint-module-utils-2.11.0.tgz", + "integrity": "sha512-gbBE5Hitek/oG6MUVj6sFuzEjA/ClzNflVrLovHi/JgLdC7fiN5gLAY1WIPW1a0V5I999MnsrvVrCOGmmVqDBQ==", "dev": true, "dependencies": { "debug": "^3.2.7" @@ -1879,26 +1879,27 @@ } }, "node_modules/eslint-plugin-import": { - "version": "2.29.1", - "resolved": "https://registry.npmjs.org/eslint-plugin-import/-/eslint-plugin-import-2.29.1.tgz", - "integrity": "sha512-BbPC0cuExzhiMo4Ff1BTVwHpjjv28C5R+btTOGaCRC7UEz801up0JadwkeSk5Ued6TG34uaczuVuH6qyy5YUxw==", + "version": "2.30.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-import/-/eslint-plugin-import-2.30.0.tgz", + "integrity": "sha512-/mHNE9jINJfiD2EKkg1BKyPyUk4zdnT54YgbOgfjSakWT5oyX/qQLVNTkehyfpcMxZXMy1zyonZ2v7hZTX43Yw==", "dev": true, "dependencies": { - "array-includes": "^3.1.7", - "array.prototype.findlastindex": "^1.2.3", + "@rtsao/scc": "^1.1.0", + "array-includes": "^3.1.8", + "array.prototype.findlastindex": "^1.2.5", "array.prototype.flat": "^1.3.2", "array.prototype.flatmap": "^1.3.2", "debug": "^3.2.7", "doctrine": "^2.1.0", "eslint-import-resolver-node": "^0.3.9", - "eslint-module-utils": "^2.8.0", - "hasown": "^2.0.0", - "is-core-module": "^2.13.1", + "eslint-module-utils": "^2.9.0", + "hasown": "^2.0.2", + "is-core-module": "^2.15.1", "is-glob": "^4.0.3", "minimatch": "^3.1.2", - "object.fromentries": "^2.0.7", - "object.groupby": "^1.0.1", - "object.values": "^1.1.7", + "object.fromentries": "^2.0.8", + "object.groupby": "^1.0.3", + "object.values": "^1.2.0", "semver": "^6.3.1", "tsconfig-paths": "^3.15.0" }, @@ -2024,9 +2025,9 @@ } }, "node_modules/eslint-plugin-react": { - "version": "7.35.0", - "resolved": "https://registry.npmjs.org/eslint-plugin-react/-/eslint-plugin-react-7.35.0.tgz", - "integrity": "sha512-v501SSMOWv8gerHkk+IIQBkcGRGrO2nfybfj5pLxuJNFTPxxA3PSryhXTK+9pNbtkggheDdsC0E9Q8CuPk6JKA==", + "version": "7.36.1", + "resolved": "https://registry.npmjs.org/eslint-plugin-react/-/eslint-plugin-react-7.36.1.tgz", + "integrity": "sha512-/qwbqNXZoq+VP30s1d4Nc1C5GTxjJQjk4Jzs4Wq2qzxFM7dSmuG2UkIjg2USMLh3A/aVcUNrK7v0J5U1XEGGwA==", "dev": true, "dependencies": { "array-includes": "^3.1.8", @@ -4309,9 +4310,9 @@ } }, "node_modules/picocolors": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.1.tgz", - "integrity": "sha512-anP1Z8qwhkbmu7MFP5iTt+wQKXgwzf7zTyGlcdzabySa9vd0Xt392U0rVmz9poOaBj0uHJKyyo9/upk0HrEQew==", + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.0.tgz", + "integrity": "sha512-TQ92mBOW0l3LeMeyLV6mzy/kWr8lkd/hp3mTg7wYK7zJhuBStmGMBG0BdeDZS/dZx1IukaX6Bk11zcln25o1Aw==", "dev": true }, "node_modules/picomatch": { @@ -5026,9 +5027,9 @@ "dev": true }, "node_modules/standard": { - "version": "17.1.0", - "resolved": "https://registry.npmjs.org/standard/-/standard-17.1.0.tgz", - "integrity": "sha512-jaDqlNSzLtWYW4lvQmU0EnxWMUGQiwHasZl5ZEIwx3S/ijZDjZOzs1y1QqKwKs5vqnFpGtizo4NOYX2s0Voq/g==", + "version": "17.1.2", + "resolved": "https://registry.npmjs.org/standard/-/standard-17.1.2.tgz", + "integrity": "sha512-WLm12WoXveKkvnPnPnaFUUHuOB2cUdAsJ4AiGHL2G0UNMrcRAWY2WriQaV8IQ3oRmYr0AWUbLNr94ekYFAHOrA==", "dev": true, "funding": [ { @@ -5051,8 +5052,8 @@ "eslint-plugin-import": "^2.27.5", "eslint-plugin-n": "^15.7.0", "eslint-plugin-promise": "^6.1.1", - "eslint-plugin-react": "^7.32.2", - "standard-engine": "^15.0.0", + "eslint-plugin-react": "^7.36.1", + "standard-engine": "^15.1.0", "version-guard": "^1.1.1" }, "bin": { @@ -5540,9 +5541,9 @@ } }, "node_modules/version-guard": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/version-guard/-/version-guard-1.1.2.tgz", - "integrity": "sha512-D8d+YxCUpoqtCnQzDxm6SF7DLU3gr2535T4khAtMq4osBahsQnmSxuwXFdrbAdDGG8Uokzfis/jvyeFPdmlc7w==", + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/version-guard/-/version-guard-1.1.3.tgz", + "integrity": "sha512-JwPr6erhX53EWH/HCSzfy1tTFrtPXUe927wdM1jqBBeYp1OM+qPHjWbsvv6pIBduqdgxxS+ScfG7S28pzyr2DQ==", "dev": true, "engines": { "node": ">=0.10.48" diff --git a/package.json b/package.json index b970e74..be3812c 100644 --- a/package.json +++ b/package.json @@ -1,22 +1,22 @@ { "name": "pg-boss", - "version": "10.1.2", + "version": "10.1.3", "description": "Queueing jobs in Postgres from Node.js like a boss", "main": "./src/index.js", "engines": { "node": ">=20" }, "dependencies": { - "cron-parser": "^4.0.0", - "pg": "^8.5.1", + "cron-parser": "^4.9.0", + "pg": "^8.12.0", "serialize-error": "^8.1.0" }, "devDependencies": { - "@types/node": "^20.3.3", - "luxon": "^3.0.1", - "mocha": "^10.0.0", + "@types/node": "^20.16.5", + "luxon": "^3.5.0", + "mocha": "^10.7.3", "nyc": "^17.0.0", - "standard": "^17.0.0" + "standard": "^17.1.2" }, "scripts": { "test": "standard && mocha", diff --git a/src/plans.js b/src/plans.js index bbb24b3..941dc0b 100644 --- a/src/plans.js +++ b/src/plans.js @@ -194,7 +194,7 @@ function createTableJob (schema) { keep_until timestamp with time zone NOT NULL default now() + interval '14 days', output jsonb, dead_letter text, - policy text + policy text ) PARTITION BY LIST (name) ` } @@ -555,28 +555,126 @@ function failJobsByTimeout (schema) { function failJobs (schema, where, output) { return ` - WITH results AS ( - UPDATE ${schema}.job SET - state = CASE + WITH deleted_jobs AS ( + DELETE FROM ${schema}.job + WHERE ${where} + RETURNING * + ), + retried_jobs AS ( + INSERT INTO ${schema}.job ( + id, + name, + priority, + data, + state, + retry_limit, + retry_count, + retry_delay, + retry_backoff, + start_after, + started_on, + singleton_key, + singleton_on, + expire_in, + created_on, + completed_on, + keep_until, + dead_letter, + policy, + output + ) + SELECT + id, + name, + priority, + data, + CASE WHEN retry_count < retry_limit THEN '${JOB_STATES.retry}'::${schema}.job_state ELSE '${JOB_STATES.failed}'::${schema}.job_state - END, - completed_on = CASE - WHEN retry_count < retry_limit THEN NULL - ELSE now() - END, - start_after = CASE + END as state, + retry_limit, + retry_count, + retry_delay, + retry_backoff, + CASE WHEN retry_count = retry_limit THEN start_after WHEN NOT retry_backoff THEN now() + retry_delay * interval '1' ELSE now() + ( retry_delay * 2 ^ LEAST(16, retry_count + 1) / 2 + retry_delay * 2 ^ LEAST(16, retry_count + 1) / 2 * random() ) * interval '1' - END, - output = ${output} - WHERE ${where} + END as start_after, + started_on, + singleton_key, + singleton_on, + expire_in, + created_on, + CASE + WHEN retry_count < retry_limit THEN NULL + ELSE now() + END as completed_on, + keep_until, + dead_letter, + policy, + ${output} + FROM deleted_jobs + ON CONFLICT DO NOTHING + RETURNING * + ), + failed_jobs as ( + INSERT INTO ${schema}.job ( + id, + name, + priority, + data, + state, + retry_limit, + retry_count, + retry_delay, + retry_backoff, + start_after, + started_on, + singleton_key, + singleton_on, + expire_in, + created_on, + completed_on, + keep_until, + dead_letter, + policy, + output + ) + SELECT + id, + name, + priority, + data, + '${JOB_STATES.failed}'::${schema}.job_state as state, + retry_limit, + retry_count, + retry_delay, + retry_backoff, + start_after, + started_on, + singleton_key, + singleton_on, + expire_in, + created_on, + now() as completed_on, + keep_until, + dead_letter, + policy, + ${output} + FROM deleted_jobs + WHERE id NOT IN (SELECT id from retried_jobs) RETURNING * - ), dlq_jobs as ( + ), + results as ( + SELECT * FROM retried_jobs + UNION ALL + SELECT * FROM failed_jobs + ), + dlq_jobs as ( INSERT INTO ${schema}.job (name, data, output, retry_limit, keep_until) SELECT dead_letter, diff --git a/test/queueTest.js b/test/queueTest.js index 7e154a2..ce1bdfa 100644 --- a/test/queueTest.js +++ b/test/queueTest.js @@ -350,6 +350,38 @@ describe('queues', function () { assert(!blockedSecondActive) }) + it('stately policy fails a job without retry when others are active', async function () { + const boss = this.test.boss = await helper.start({ ...this.test.bossConfig, noDefault: true }) + const queue = this.test.bossConfig.schema + const deadLetter = queue + '_dlq' + + await boss.createQueue(deadLetter) + await boss.createQueue(queue, { policy: 'stately', deadLetter, retryLimit: 3 }) + + const jobId1 = await boss.send(queue, null, { expireInSeconds: 1 }) + await boss.fetch(queue) + await boss.fail(queue, jobId1) + const job1Data = await boss.getJobById(queue, jobId1) + assert.strictEqual(job1Data.state, 'retry') + + // higher priority new job should be active next + const jobId2 = await boss.send(queue, null, { priority: 1, expireInSeconds: 1 }) + await boss.fetch(queue) + + const jobId3 = await boss.send(queue) + assert(jobId3) + + await boss.fail(queue, jobId2) + + const job2Data = await boss.getJobById(queue, jobId2) + + assert.strictEqual(job2Data.state, 'failed') + + const [job2Dlq] = await boss.fetch(deadLetter) + + assert(job2Dlq) + }) + it('stately policy should be extended with singletonKey', async function () { const boss = this.test.boss = await helper.start({ ...this.test.bossConfig, noDefault: true }) const queue = this.test.bossConfig.schema