{"_id":"effect-mq","_rev":"11-ba0b7c534726b50399d70ccfb91449ff","name":"effect-mq","dist-tags":{"latest":"0.7.0"},"versions":{"0.1.0":{"name":"effect-mq","version":"0.1.0","keywords":["effect","queue","jobs","background-jobs","worker","postgres","drizzle"],"author":{"name":"Adam Rankin"},"license":"MIT","_id":"effect-mq@0.1.0","maintainers":[{"name":"rankincodes","email":"atrankin134@gmail.com"}],"homepage":"https://github.com/TeamWarp/effect-mq#readme","bugs":{"url":"https://github.com/TeamWarp/effect-mq/issues"},"dist":{"shasum":"e06ab46af48d0aa59d9677271c35cf2d47130d9d","tarball":"https://registry.npmjs.org/effect-mq/-/effect-mq-0.1.0.tgz","fileCount":53,"integrity":"sha512-JYem72fq3xZ2PkD6Q7JTkrz7tYkq2pTSuaVZbnuOILejnAv3cNhg5rRpVYTg25Jc+qXHuJiVe5xIWhFAyCdGrg==","signatures":[{"sig":"MEYCIQC+4phCbvxO0BRgimr5x/C2g0lUzKOwP3tlIK4ZaTVJtQIhAJpSsIiQUhzHttC0XyzlXmLyiBcOlEYzo07ooeJw3u6Z","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":397676},"type":"module","exports":{".":"./src/index.ts","./drizzle":"./src/drizzle/index.ts","./testing":"./src/testing/index.ts","./package.json":"./package.json"},"gitHead":"1940cdbf7610ce7e5d6bf2049ce42b27c52b8f26","scripts":{"test":"vitest run --project effect-mq","build":"rm -rf dist && tsc -p tsconfig.build.json","check":"tsc --noEmit -p tsconfig.json","prepack":"bun run build && node scripts/prepack.mjs","postpack":"node scripts/postpack.mjs"},"_npmUser":{"name":"rankincodes","email":"atrankin134@gmail.com"},"repository":{"url":"git+https://github.com/TeamWarp/effect-mq.git","type":"git","directory":"packages/effect-mq"},"_npmVersion":"11.5.1","description":"Effect-native background jobs: schema-first definitions, storage-agnostic queue core, worker runtime, and a Postgres store through drizzle.","directories":{},"sideEffects":false,"_nodeVersion":"24.5.0","publishConfig":{"access":"public","provenance":true},"_hasShrinkwrap":false,"devDependencies":{"pg":"catalog:","effect":"catalog:","@types/pg":"catalog:","typescript":"catalog:","drizzle-orm":"catalog:","@effect/sql-pg":"catalog:","@effect/vitest":"catalog:"},"peerDependencies":{"effect":">=4.0.0-rc <5","drizzle-orm":">=1.0.0-rc <2","@effect/sql-pg":">=4.0.0-rc <5","@effect/vitest":">=4.0.0-rc <5"},"peerDependenciesMeta":{"drizzle-orm":{"optional":true},"@effect/sql-pg":{"optional":true},"@effect/vitest":{"optional":true}},"_npmOperationalInternal":{"tmp":"tmp/effect-mq_0.1.0_1787261196618_0.964706628043843","host":"s3://npm-registry-packages-npm-production"}},"0.2.0":{"name":"effect-mq","version":"0.2.0","keywords":["effect","queue","jobs","background-jobs","worker","postgres","drizzle"],"author":{"name":"Adam Rankin"},"license":"MIT","_id":"effect-mq@0.2.0","maintainers":[{"name":"rankincodes","email":"atrankin134@gmail.com"}],"homepage":"https://github.com/TeamWarp/effect-mq#readme","bugs":{"url":"https://github.com/TeamWarp/effect-mq/issues"},"dist":{"shasum":"a50dc689210c8694ce76ff26f2d7ed10b02c32d6","tarball":"https://registry.npmjs.org/effect-mq/-/effect-mq-0.2.0.tgz","fileCount":68,"integrity":"sha512-HdA4PWSkE+OczgvQEUlR421ap6+BYLoFRR7q9/NS0xO0p7qsew3Rstc3yd1VqZVh+CrZMGMVf2HNYE2fBpoLsQ==","signatures":[{"sig":"MEYCIQC4sQaxvUOd/DzzLzFfiADJLIxS+7rdvIXbK13wfstv0gIhAKtasALITdUUkrYt+EFio3yzQHEvNcQZzB+htXczE53s","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"attestations":{"url":"https://registry.npmjs.org/-/npm/v1/attestations/effect-mq@0.2.0","provenance":{"predicateType":"https://slsa.dev/provenance/v1"}},"unpackedSize":755057},"type":"module","exports":{".":"./src/index.ts","./redis":"./src/redis/index.ts","./testing":"./src/testing/index.ts","./package.json":"./package.json","./drizzle-postgres":"./src/drizzle-postgres/index.ts"},"gitHead":"82cec735dd514ff7aa467901a758b56e755d9816","scripts":{"test":"vitest run --project effect-mq","build":"rm -rf dist && tsc -p tsconfig.build.json","check":"tsc --noEmit -p tsconfig.json","prepack":"bun run build && node scripts/prepack.mjs","postpack":"node scripts/postpack.mjs"},"_npmUser":{"name":"rankincodes","email":"atrankin134@gmail.com"},"repository":{"url":"git+https://github.com/TeamWarp/effect-mq.git","type":"git","directory":"packages/effect-mq"},"_npmVersion":"11.17.0","description":"Effect-native background jobs: schema-first definitions, storage-agnostic queue core, worker runtime, and a Postgres store through drizzle.","directories":{},"sideEffects":false,"_nodeVersion":"24.19.0","publishConfig":{"access":"public","provenance":true},"_hasShrinkwrap":false,"devDependencies":{"pg":"catalog:","redis":"catalog:","effect":"catalog:","@types/pg":"catalog:","typescript":"catalog:","drizzle-orm":"catalog:","@effect/sql-pg":"catalog:","@effect/vitest":"catalog:","@effect/platform-node":"catalog:"},"peerDependencies":{"effect":">=4.0.0-rc <5","drizzle-orm":">=1.0.0-rc <2","@effect/sql-pg":">=4.0.0-rc <5","@effect/vitest":">=4.0.0-rc <5"},"peerDependenciesMeta":{"drizzle-orm":{"optional":true},"@effect/sql-pg":{"optional":true},"@effect/vitest":{"optional":true}},"_npmOperationalInternal":{"tmp":"tmp/effect-mq_0.2.0_1787331796895_0.3911047493701123","host":"s3://npm-registry-packages-npm-production"}},"0.3.0":{"name":"effect-mq","version":"0.3.0","keywords":["effect","queue","jobs","background-jobs","worker","postgres","drizzle"],"author":{"name":"Adam Rankin"},"license":"MIT","_id":"effect-mq@0.3.0","maintainers":[{"name":"rankincodes","email":"atrankin134@gmail.com"}],"homepage":"https://github.com/TeamWarp/effect-mq#readme","bugs":{"url":"https://github.com/TeamWarp/effect-mq/issues"},"dist":{"shasum":"49a41a1f0cfba26469dd7d6680a8588edfddaf73","tarball":"https://registry.npmjs.org/effect-mq/-/effect-mq-0.3.0.tgz","fileCount":68,"integrity":"sha512-C27VQqVeSzCdru3F4X6k0hDcBFGdnRfFFVAHvp51O0V6g97HaXTlgvxzn6h58j4JSFeAxBhNOsnHkGrWUfpW5Q==","signatures":[{"sig":"MEQCIC/+KzJuZ8m9T0wHnQMfqrRYewz3HSIy7BCKM1ZXo6Z0AiAA13mIdtkhuNF9ytt/XIuQbTZ5YUxhuKdj8ioe8CTM5Q==","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"attestations":{"url":"https://registry.npmjs.org/-/npm/v1/attestations/effect-mq@0.3.0","provenance":{"predicateType":"https://slsa.dev/provenance/v1"}},"unpackedSize":917864},"type":"module","exports":{".":"./src/index.ts","./redis":"./src/redis/index.ts","./testing":"./src/testing/index.ts","./package.json":"./package.json","./drizzle-postgres":"./src/drizzle-postgres/index.ts"},"gitHead":"36d8987665eaa0ff03d31729f256210a6a52fb9d","scripts":{"test":"vitest run --project effect-mq","build":"rm -rf dist && tsc -p tsconfig.build.json","check":"tsc --noEmit -p tsconfig.json","prepack":"bun run build && node scripts/prepack.mjs","postpack":"node scripts/postpack.mjs"},"_npmUser":{"name":"rankincodes","email":"atrankin134@gmail.com"},"repository":{"url":"git+https://github.com/TeamWarp/effect-mq.git","type":"git","directory":"packages/effect-mq"},"_npmVersion":"11.17.0","description":"Effect-native background jobs: schema-first definitions, storage-agnostic queue core, worker runtime, and a Postgres store through drizzle.","directories":{},"sideEffects":false,"_nodeVersion":"24.19.0","publishConfig":{"access":"public","provenance":true},"_hasShrinkwrap":false,"devDependencies":{"pg":"catalog:","redis":"catalog:","effect":"catalog:","@types/pg":"catalog:","typescript":"catalog:","drizzle-orm":"catalog:","@effect/sql-pg":"catalog:","@effect/vitest":"catalog:","@effect/platform-node":"catalog:"},"peerDependencies":{"effect":">=4.0.0-rc <5","drizzle-orm":">=1.0.0-rc <2","@effect/sql-pg":">=4.0.0-rc <5","@effect/vitest":">=4.0.0-rc <5"},"peerDependenciesMeta":{"drizzle-orm":{"optional":true},"@effect/sql-pg":{"optional":true},"@effect/vitest":{"optional":true}},"_npmOperationalInternal":{"tmp":"tmp/effect-mq_0.3.0_1787341105814_0.3190299136643806","host":"s3://npm-registry-packages-npm-production"}},"0.3.1":{"name":"effect-mq","version":"0.3.1","keywords":["effect","queue","jobs","background-jobs","worker","postgres","drizzle"],"author":{"name":"Adam Rankin"},"license":"MIT","_id":"effect-mq@0.3.1","maintainers":[{"name":"rankincodes","email":"atrankin134@gmail.com"}],"homepage":"https://github.com/TeamWarp/effect-mq#readme","bugs":{"url":"https://github.com/TeamWarp/effect-mq/issues"},"dist":{"shasum":"44bd5da756b507258165c8aad7827cb5eb5b26cd","tarball":"https://registry.npmjs.org/effect-mq/-/effect-mq-0.3.1.tgz","fileCount":73,"integrity":"sha512-F4qmqhRONNMlwq8xZLYiSabQitesNruJP2lyfICcyvGyPAmOB9ZYIs0ZhGgHI7QZiGzP3TcWZvcHk3ga+0ACLA==","signatures":[{"sig":"MEYCIQDHtCa+yrL4yp19A0+JNZwjMrX+3VMwDGjOn+LgqBXh5gIhAIQz43llkX13+eo0EiJtXylBdKzyicH8DQ7Ye+q7j8SY","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"attestations":{"url":"https://registry.npmjs.org/-/npm/v1/attestations/effect-mq@0.3.1","provenance":{"predicateType":"https://slsa.dev/provenance/v1"}},"unpackedSize":947364},"type":"module","exports":{".":"./src/index.ts","./redis":"./src/redis/index.ts","./testing":"./src/testing/index.ts","./package.json":"./package.json","./drizzle-postgres":"./src/drizzle-postgres/index.ts"},"gitHead":"7a482c43fe7cc6764c0daa668999c8073060cd7d","scripts":{"test":"vitest run --project effect-mq","build":"rm -rf dist && tsc -p tsconfig.build.json","check":"tsc --noEmit -p tsconfig.json","prepack":"bun run build && node scripts/prepack.mjs","postpack":"node scripts/postpack.mjs"},"_npmUser":{"name":"rankincodes","email":"atrankin134@gmail.com"},"repository":{"url":"git+https://github.com/TeamWarp/effect-mq.git","type":"git","directory":"packages/effect-mq"},"_npmVersion":"11.17.0","description":"Effect-native background jobs: schema-first definitions, storage-agnostic queue core, worker runtime, and a Postgres store through drizzle.","directories":{},"sideEffects":false,"_nodeVersion":"24.19.0","publishConfig":{"access":"public","provenance":true},"_hasShrinkwrap":false,"devDependencies":{"pg":"catalog:","redis":"catalog:","effect":"catalog:","@types/pg":"catalog:","typescript":"catalog:","drizzle-orm":"catalog:","@effect/sql-pg":"catalog:","@effect/vitest":"catalog:","@effect/platform-node":"catalog:"},"peerDependencies":{"effect":">=4.0.0-rc <5","drizzle-orm":">=1.0.0-rc <2","@effect/sql-pg":">=4.0.0-rc <5","@effect/vitest":">=4.0.0-rc <5"},"peerDependenciesMeta":{"drizzle-orm":{"optional":true},"@effect/sql-pg":{"optional":true},"@effect/vitest":{"optional":true}},"_npmOperationalInternal":{"tmp":"tmp/effect-mq_0.3.1_1787353417905_0.47202115067067263","host":"s3://npm-registry-packages-npm-production"}},"0.3.2":{"name":"effect-mq","version":"0.3.2","keywords":["effect","queue","jobs","background-jobs","worker","postgres","drizzle"],"author":{"name":"Adam Rankin"},"license":"MIT","_id":"effect-mq@0.3.2","maintainers":[{"name":"rankincodes","email":"atrankin134@gmail.com"}],"homepage":"https://github.com/TeamWarp/effect-mq#readme","bugs":{"url":"https://github.com/TeamWarp/effect-mq/issues"},"dist":{"shasum":"7abf16ac56661dc704d906b5e6aad6a40c6ee034","tarball":"https://registry.npmjs.org/effect-mq/-/effect-mq-0.3.2.tgz","fileCount":78,"integrity":"sha512-neJxDoFB6mLtXI0i1/L+rMO//ujXP4PbaGEHO3z9cZrwLc8Fw8ONFor5MQ+APyWSnC5YQuu/6clDki6YJorPnA==","signatures":[{"sig":"MEQCIFB+L3qa/w02lK0eOuejAALj1ZCiR7rOywrSrLHx5f24AiBsC+V43qT602AOCKQcwGAhpXHLOXIE3rNkiLgYSKby5w==","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"attestations":{"url":"https://registry.npmjs.org/-/npm/v1/attestations/effect-mq@0.3.2","provenance":{"predicateType":"https://slsa.dev/provenance/v1"}},"unpackedSize":964562},"type":"module","exports":{".":"./src/index.ts","./redis":"./src/redis/index.ts","./testing":"./src/testing/index.ts","./package.json":"./package.json","./drizzle-postgres":"./src/drizzle-postgres/index.ts"},"gitHead":"dc6dc61805be4aac07c512a055200e037e30d78f","scripts":{"test":"vitest run --project effect-mq","build":"rm -rf dist && tsc -p tsconfig.build.json","check":"tsc --noEmit -p tsconfig.json","prepack":"bun run build && node scripts/prepack.mjs","postpack":"node scripts/postpack.mjs"},"_npmUser":{"name":"rankincodes","email":"atrankin134@gmail.com"},"repository":{"url":"git+https://github.com/TeamWarp/effect-mq.git","type":"git","directory":"packages/effect-mq"},"_npmVersion":"11.17.0","description":"Effect-native background jobs: schema-first definitions, storage-agnostic queue core, worker runtime, and a Postgres store through drizzle.","directories":{},"sideEffects":false,"_nodeVersion":"24.19.0","publishConfig":{"access":"public","provenance":true},"_hasShrinkwrap":false,"devDependencies":{"pg":"catalog:","redis":"catalog:","effect":"catalog:","@types/pg":"catalog:","typescript":"catalog:","drizzle-orm":"catalog:","@effect/sql-pg":"catalog:","@effect/vitest":"catalog:","@effect/platform-node":"catalog:"},"peerDependencies":{"effect":">=4.0.0-rc <5","drizzle-orm":">=1.0.0-rc <2","@effect/sql-pg":">=4.0.0-rc <5","@effect/vitest":">=4.0.0-rc <5"},"peerDependenciesMeta":{"drizzle-orm":{"optional":true},"@effect/sql-pg":{"optional":true},"@effect/vitest":{"optional":true}},"_npmOperationalInternal":{"tmp":"tmp/effect-mq_0.3.2_1787357021557_0.870376140276609","host":"s3://npm-registry-packages-npm-production"}},"0.4.0":{"name":"effect-mq","version":"0.4.0","keywords":["effect","queue","jobs","background-jobs","worker","postgres","drizzle"],"author":{"name":"Adam Rankin"},"license":"MIT","_id":"effect-mq@0.4.0","maintainers":[{"name":"rankincodes","email":"atrankin134@gmail.com"}],"homepage":"https://github.com/TeamWarp/effect-mq#readme","bugs":{"url":"https://github.com/TeamWarp/effect-mq/issues"},"dist":{"shasum":"db1ed71eab4620d6de4ed93f28b2915eaccec26e","tarball":"https://registry.npmjs.org/effect-mq/-/effect-mq-0.4.0.tgz","fileCount":78,"integrity":"sha512-UWgGW9FwM8bH0D8tjUeGMb9enVf0q1SSfWR3McIWavp2trGxawCvgpmUDZP8Zte6Irc0fiMZwgqkv2/chdqtIA==","signatures":[{"sig":"MEUCIGqZcLUAV/12+V8jNSpXEdIToewB1laPIh2qX+sd/XVkAiEApmIpC2x5eqEdENN50Sv7bADU/ySstOsF6ai+9Ckqkyg=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"attestations":{"url":"https://registry.npmjs.org/-/npm/v1/attestations/effect-mq@0.4.0","provenance":{"predicateType":"https://slsa.dev/provenance/v1"}},"unpackedSize":1125286},"type":"module","exports":{".":"./src/index.ts","./redis":"./src/redis/index.ts","./testing":"./src/testing/index.ts","./package.json":"./package.json","./drizzle-postgres":"./src/drizzle-postgres/index.ts"},"gitHead":"d8d497d640efe0c997d7d36968d1da36fd3c1904","scripts":{"test":"vitest run --project effect-mq","build":"rm -rf dist && tsc -p tsconfig.build.json","check":"tsc --noEmit -p tsconfig.json","prepack":"bun run build && node scripts/prepack.mjs","postpack":"node scripts/postpack.mjs"},"_npmUser":{"name":"rankincodes","email":"atrankin134@gmail.com"},"repository":{"url":"git+https://github.com/TeamWarp/effect-mq.git","type":"git","directory":"packages/effect-mq"},"_npmVersion":"11.17.0","description":"Effect-native background jobs: schema-first definitions, storage-agnostic queue core, worker runtime, and a Postgres store through drizzle.","directories":{},"sideEffects":false,"_nodeVersion":"24.19.0","publishConfig":{"access":"public","provenance":true},"_hasShrinkwrap":false,"devDependencies":{"pg":"catalog:","redis":"catalog:","effect":"catalog:","@types/pg":"catalog:","typescript":"catalog:","drizzle-orm":"catalog:","@effect/sql-pg":"catalog:","@effect/vitest":"catalog:","@effect/platform-node":"catalog:"},"peerDependencies":{"effect":">=4.0.0-rc <5","drizzle-orm":">=1.0.0-rc <2","@effect/sql-pg":">=4.0.0-rc <5","@effect/vitest":">=4.0.0-rc <5"},"peerDependenciesMeta":{"drizzle-orm":{"optional":true},"@effect/sql-pg":{"optional":true},"@effect/vitest":{"optional":true}},"_npmOperationalInternal":{"tmp":"tmp/effect-mq_0.4.0_1787519992184_0.7348450105378663","host":"s3://npm-registry-packages-npm-production"}},"0.4.1":{"name":"effect-mq","version":"0.4.1","keywords":["effect","queue","jobs","background-jobs","worker","postgres","drizzle"],"author":{"name":"Adam Rankin"},"license":"MIT","_id":"effect-mq@0.4.1","maintainers":[{"name":"rankincodes","email":"atrankin134@gmail.com"}],"homepage":"https://github.com/TeamWarp/effect-mq#readme","bugs":{"url":"https://github.com/TeamWarp/effect-mq/issues"},"dist":{"shasum":"1322d880b37cb459e2862062c3ac4ec909dce532","tarball":"https://registry.npmjs.org/effect-mq/-/effect-mq-0.4.1.tgz","fileCount":78,"integrity":"sha512-5t2jzYckitwYum8Lb0+z2jLXCAffFkaADoDmGSuUCiM7As0X1hvW6liwjQyfb4+fwNnrrKTigCduxkAF4jfdQA==","signatures":[{"sig":"MEQCIHTmA3ce+jMmeRAKh1uI0HX8ZzybVFJ//vUXy/6aabkTAiBgbKLjBU84JnIed2fSlYPR9DKhC/S7MpbXLmcJ6VPXIg==","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"attestations":{"url":"https://registry.npmjs.org/-/npm/v1/attestations/effect-mq@0.4.1","provenance":{"predicateType":"https://slsa.dev/provenance/v1"}},"unpackedSize":1127478},"type":"module","exports":{".":"./src/index.ts","./redis":"./src/redis/index.ts","./testing":"./src/testing/index.ts","./package.json":"./package.json","./drizzle-postgres":"./src/drizzle-postgres/index.ts"},"gitHead":"bc095a628598794eb5d5db4b6161092d40ff8544","scripts":{"test":"vitest run --project effect-mq","build":"rm -rf dist && tsc -p tsconfig.build.json","check":"tsc --noEmit -p tsconfig.json","prepack":"bun run build && node scripts/prepack.mjs","postpack":"node scripts/postpack.mjs"},"_npmUser":{"name":"rankincodes","email":"atrankin134@gmail.com"},"repository":{"url":"git+https://github.com/TeamWarp/effect-mq.git","type":"git","directory":"packages/effect-mq"},"_npmVersion":"11.17.0","description":"Effect-native background jobs: schema-first definitions, storage-agnostic queue core, worker runtime, and a Postgres store through drizzle.","directories":{},"sideEffects":false,"_nodeVersion":"24.19.0","publishConfig":{"access":"public","provenance":true},"_hasShrinkwrap":false,"devDependencies":{"pg":"catalog:","redis":"catalog:","effect":"catalog:","@types/pg":"catalog:","typescript":"catalog:","drizzle-orm":"catalog:","@effect/sql-pg":"catalog:","@effect/vitest":"catalog:","@effect/platform-node":"catalog:"},"peerDependencies":{"effect":">=4.0.0-rc <5","drizzle-orm":">=1.0.0-rc <2","@effect/sql-pg":">=4.0.0-rc <5","@effect/vitest":">=4.0.0-rc <5"},"peerDependenciesMeta":{"drizzle-orm":{"optional":true},"@effect/sql-pg":{"optional":true},"@effect/vitest":{"optional":true}},"_npmOperationalInternal":{"tmp":"tmp/effect-mq_0.4.1_1787521964504_0.03758372992533365","host":"s3://npm-registry-packages-npm-production"}},"0.4.2":{"name":"effect-mq","version":"0.4.2","keywords":["effect","queue","jobs","background-jobs","worker","postgres","drizzle"],"author":{"name":"Adam Rankin"},"license":"MIT","_id":"effect-mq@0.4.2","maintainers":[{"name":"rankincodes","email":"atrankin134@gmail.com"}],"homepage":"https://www.effect-mq.com","bugs":{"url":"https://github.com/TeamWarp/effect-mq/issues"},"dist":{"shasum":"df73f438de39241a7a2a1d7b481f8c97364ac8a8","tarball":"https://registry.npmjs.org/effect-mq/-/effect-mq-0.4.2.tgz","fileCount":78,"integrity":"sha512-m+vyEwh6LZ1jSCVsV8VO2PI5ApyrTdAP8lz/2/uV2a5ll9T40ZBUB40M23T6dVwzW+Ky13ajo0pS6BKmA6Bz6w==","signatures":[{"sig":"MEQCIBo0el7va2hFvsVEjpJdIsL+w5Aj1nJg7Ty3QnXyacQzAiB0TslnhUmx2+HFgwWVBnZs4iFnA6Eb7V+C0mqlKialcQ==","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"attestations":{"url":"https://registry.npmjs.org/-/npm/v1/attestations/effect-mq@0.4.2","provenance":{"predicateType":"https://slsa.dev/provenance/v1"}},"unpackedSize":1140714},"type":"module","exports":{".":"./src/index.ts","./redis":"./src/redis/index.ts","./testing":"./src/testing/index.ts","./package.json":"./package.json","./drizzle-postgres":"./src/drizzle-postgres/index.ts"},"gitHead":"215d7da9665f2aa2560ac3d3b6cf3a50672b726f","scripts":{"test":"vitest run --project effect-mq","build":"rm -rf dist && tsc -p tsconfig.build.json","check":"tsc --noEmit -p tsconfig.json","prepack":"bun run build && node scripts/prepack.mjs","postpack":"node scripts/postpack.mjs"},"_npmUser":{"name":"rankincodes","email":"atrankin134@gmail.com"},"repository":{"url":"git+https://github.com/TeamWarp/effect-mq.git","type":"git","directory":"packages/effect-mq"},"_npmVersion":"11.17.0","description":"Effect-native background jobs: schema-first definitions, storage-agnostic queue core, worker runtime, and a Postgres store through drizzle.","directories":{},"sideEffects":false,"_nodeVersion":"24.19.0","publishConfig":{"access":"public","provenance":true},"_hasShrinkwrap":false,"devDependencies":{"pg":"catalog:","redis":"catalog:","effect":"catalog:","@types/pg":"catalog:","typescript":"catalog:","drizzle-orm":"catalog:","@effect/sql-pg":"catalog:","@effect/vitest":"catalog:","@effect/platform-node":"catalog:"},"peerDependencies":{"effect":">=4.0.0-rc <5","drizzle-orm":">=1.0.0-rc <2","@effect/sql-pg":">=4.0.0-rc <5","@effect/vitest":">=4.0.0-rc <5"},"peerDependenciesMeta":{"drizzle-orm":{"optional":true},"@effect/sql-pg":{"optional":true},"@effect/vitest":{"optional":true}},"_npmOperationalInternal":{"tmp":"tmp/effect-mq_0.4.2_1787598299696_0.9859093160804988","host":"s3://npm-registry-packages-npm-production"}},"0.5.0":{"name":"effect-mq","version":"0.5.0","keywords":["effect","queue","jobs","background-jobs","worker","postgres","drizzle"],"author":{"name":"Adam Rankin"},"license":"MIT","_id":"effect-mq@0.5.0","maintainers":[{"name":"rankincodes","email":"atrankin134@gmail.com"}],"homepage":"https://www.effect-mq.com","bugs":{"url":"https://github.com/TeamWarp/effect-mq/issues"},"dist":{"shasum":"79543b7788b84e1cfe2daa51588f55202a1a967e","tarball":"https://registry.npmjs.org/effect-mq/-/effect-mq-0.5.0.tgz","fileCount":83,"integrity":"sha512-qjINRsE3cIp56DfgIYDdICACjHgGvD6CjtYK07QLUi7QHkEEriJEOnFdpP5BXy6fjYY5EmIe4E0As4GR9Vi1TA==","signatures":[{"sig":"MEYCIQDwZPZn1Puk08Wg+oaFgBOia69y6VjDahmgYr4FvKYcCwIhAP+mPRhBoO4Dtt/JLcP4vVAqXkQKejIypM3yKX5oHehZ","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"attestations":{"url":"https://registry.npmjs.org/-/npm/v1/attestations/effect-mq@0.5.0","provenance":{"predicateType":"https://slsa.dev/provenance/v1"}},"unpackedSize":1175534},"type":"module","exports":{".":"./src/index.ts","./redis":"./src/redis/index.ts","./testing":"./src/testing/index.ts","./package.json":"./package.json","./drizzle-postgres":"./src/drizzle-postgres/index.ts"},"gitHead":"2eeeca0458079a6b7e0e43037b6ec4e698214911","scripts":{"test":"vitest run --project effect-mq","build":"rm -rf dist && tsc -p tsconfig.build.json","check":"tsc --noEmit -p tsconfig.json","prepack":"bun run build && node scripts/prepack.mjs","postpack":"node scripts/postpack.mjs"},"_npmUser":{"name":"rankincodes","email":"atrankin134@gmail.com"},"repository":{"url":"git+https://github.com/TeamWarp/effect-mq.git","type":"git","directory":"packages/effect-mq"},"_npmVersion":"11.17.0","description":"Effect-native background jobs: schema-first definitions, storage-agnostic queue core, worker runtime, and a Postgres store through drizzle.","directories":{},"sideEffects":false,"_nodeVersion":"24.19.0","publishConfig":{"access":"public","provenance":true},"_hasShrinkwrap":false,"devDependencies":{"pg":"catalog:","redis":"catalog:","effect":"catalog:","@types/pg":"catalog:","typescript":"catalog:","drizzle-orm":"catalog:","@effect/sql-pg":"catalog:","@effect/vitest":"catalog:","@effect/platform-node":"catalog:"},"peerDependencies":{"effect":">=4.0.0-rc <5","drizzle-orm":">=1.0.0-rc <2","@effect/sql-pg":">=4.0.0-rc <5","@effect/vitest":">=4.0.0-rc <5"},"peerDependenciesMeta":{"drizzle-orm":{"optional":true},"@effect/sql-pg":{"optional":true},"@effect/vitest":{"optional":true}},"_npmOperationalInternal":{"tmp":"tmp/effect-mq_0.5.0_1787603779911_0.6106425406988705","host":"s3://npm-registry-packages-npm-production"}},"0.6.0":{"name":"effect-mq","version":"0.6.0","keywords":["effect","queue","jobs","background-jobs","worker","postgres","drizzle"],"author":{"name":"Adam Rankin"},"license":"MIT","_id":"effect-mq@0.6.0","maintainers":[{"name":"rankincodes","email":"atrankin134@gmail.com"}],"homepage":"https://www.effect-mq.com","bugs":{"url":"https://github.com/TeamWarp/effect-mq/issues"},"dist":{"shasum":"f5403f0cf828651a90ce28de6de8f56f70e112a2","tarball":"https://registry.npmjs.org/effect-mq/-/effect-mq-0.6.0.tgz","fileCount":88,"integrity":"sha512-GEqZKzwCYgLxNTTkE20l+aPGk0jO2IhHaHvbWmxhk1z/60G3KCCSLUHcmLPMOPY0DWvgI60C/3u8hggSSgNSXQ==","signatures":[{"sig":"MEQCIF8zk+b6ta1r/fhBR6yZWwdM0u2q67Rfc25JrBrWjZBhAiB4qjEItTM7/G0FgyeygY/S3WOc2tIuOM0Q3gRKF3aQfw==","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"attestations":{"url":"https://registry.npmjs.org/-/npm/v1/attestations/effect-mq@0.6.0","provenance":{"predicateType":"https://slsa.dev/provenance/v1"}},"unpackedSize":1754050},"type":"module","exports":{".":"./src/index.ts","./redis":"./src/redis/index.ts","./testing":"./src/testing/index.ts","./package.json":"./package.json","./drizzle-postgres":"./src/drizzle-postgres/index.ts"},"gitHead":"ac5fb30ff1850a04bb646f59009f287b317cb827","scripts":{"test":"vitest run --project effect-mq","build":"rm -rf dist && tsc -p tsconfig.build.json","check":"tsc --noEmit -p tsconfig.json","prepack":"bun run build && node scripts/prepack.mjs","postpack":"node scripts/postpack.mjs"},"_npmUser":{"name":"rankincodes","email":"atrankin134@gmail.com"},"repository":{"url":"git+https://github.com/TeamWarp/effect-mq.git","type":"git","directory":"packages/effect-mq"},"_npmVersion":"11.17.0","description":"Effect-native background jobs: schema-first definitions, storage-agnostic queue core, worker runtime, and a Postgres store through drizzle.","directories":{},"sideEffects":false,"_nodeVersion":"24.19.0","publishConfig":{"access":"public","provenance":true},"_hasShrinkwrap":false,"devDependencies":{"pg":"catalog:","redis":"catalog:","effect":"catalog:","@types/pg":"catalog:","typescript":"catalog:","drizzle-orm":"catalog:","@effect/sql-pg":"catalog:","@effect/vitest":"catalog:","@effect/platform-node":"catalog:"},"peerDependencies":{"effect":">=4.0.0-rc <5","drizzle-orm":">=1.0.0-rc <2","@effect/sql-pg":">=4.0.0-rc <5","@effect/vitest":">=4.0.0-rc <5"},"peerDependenciesMeta":{"drizzle-orm":{"optional":true},"@effect/sql-pg":{"optional":true},"@effect/vitest":{"optional":true}},"_npmOperationalInternal":{"tmp":"tmp/effect-mq_0.6.0_1787670308370_0.7977540747513507","host":"s3://npm-registry-packages-npm-production"}},"0.7.0":{"name":"effect-mq","version":"0.7.0","description":"Effect-native background jobs: schema-first definitions, storage-agnostic queue core, worker runtime, and a Postgres store through drizzle.","license":"MIT","author":{"name":"Adam Rankin"},"homepage":"https://www.effect-mq.com","repository":{"type":"git","url":"git+https://github.com/TeamWarp/effect-mq.git","directory":"packages/effect-mq"},"bugs":{"url":"https://github.com/TeamWarp/effect-mq/issues"},"keywords":["effect","queue","jobs","background-jobs","worker","postgres","drizzle"],"type":"module","sideEffects":false,"exports":{"./package.json":"./package.json",".":"./src/index.ts","./drizzle-postgres":"./src/drizzle-postgres/index.ts","./testing":"./src/testing/index.ts","./redis":"./src/redis/index.ts"},"publishConfig":{"access":"public","provenance":true},"scripts":{"check":"tsc --noEmit -p tsconfig.json","build":"rm -rf dist && tsc -p tsconfig.build.json","test":"vitest run --project effect-mq","prepack":"bun run build && node scripts/prepack.mjs","postpack":"node scripts/postpack.mjs"},"peerDependencies":{"@effect/sql-pg":">=4.0.0-rc <5","@effect/vitest":">=4.0.0-rc <5","drizzle-orm":">=1.0.0-rc <2","effect":">=4.0.0-rc <5"},"peerDependenciesMeta":{"@effect/sql-pg":{"optional":true},"@effect/vitest":{"optional":true},"drizzle-orm":{"optional":true}},"devDependencies":{"@effect/sql-pg":"catalog:","@effect/vitest":"catalog:","@types/pg":"catalog:","drizzle-orm":"catalog:","effect":"catalog:","pg":"catalog:","typescript":"catalog:","@effect/platform-node":"catalog:","redis":"catalog:"},"gitHead":"5fea694188ee33c132bed27e1f97c40533276511","_id":"effect-mq@0.7.0","_nodeVersion":"24.19.0","_npmVersion":"11.17.0","dist":{"integrity":"sha512-MljABw5vOCjqaKN11DGvBnfVoDfE6Jwbdidikxgkjo9JDk797E7vrYSB0ASbxPKCiev1BGWOFyYHXPb8eXmVuw==","shasum":"fd46c84886b22a1ebcea10a4b7aab11cf14d2257","tarball":"https://registry.npmjs.org/effect-mq/-/effect-mq-0.7.0.tgz","fileCount":88,"unpackedSize":1830585,"attestations":{"url":"https://registry.npmjs.org/-/npm/v1/attestations/effect-mq@0.7.0","provenance":{"predicateType":"https://slsa.dev/provenance/v1"}},"signatures":[{"keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U","sig":"MEYCIQD62at/8RNAC35njwcyD5w1VewO7nEFoIs3xKwRQpBX2wIhALpB3mlFqSEnwRxAUEeMykEzfyuhEytcilBemKnlpkuD"}]},"_npmUser":{"name":"rankincodes","email":"atrankin134@gmail.com"},"directories":{},"maintainers":[{"name":"rankincodes","email":"atrankin134@gmail.com"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages-npm-production","tmp":"tmp/effect-mq_0.7.0_1787676027771_0.35672137115974323"},"_hasShrinkwrap":false}},"time":{"created":"2026-08-20T21:26:36.412Z","modified":"2026-08-25T16:40:28.277Z","0.1.0":"2026-08-20T21:26:36.785Z","0.2.0":"2026-08-21T17:03:17.032Z","0.3.0":"2026-08-21T19:38:25.972Z","0.3.1":"2026-08-21T23:03:38.070Z","0.3.2":"2026-08-22T00:03:41.718Z","0.4.0":"2026-08-23T21:19:52.325Z","0.4.1":"2026-08-23T21:52:44.658Z","0.4.2":"2026-08-24T19:04:59.897Z","0.5.0":"2026-08-24T20:36:20.067Z","0.6.0":"2026-08-25T15:05:08.514Z","0.7.0":"2026-08-25T16:40:27.962Z"},"bugs":{"url":"https://github.com/TeamWarp/effect-mq/issues"},"author":{"name":"Adam Rankin"},"license":"MIT","homepage":"https://www.effect-mq.com","keywords":["effect","queue","jobs","background-jobs","worker","postgres","drizzle"],"repository":{"type":"git","url":"git+https://github.com/TeamWarp/effect-mq.git","directory":"packages/effect-mq"},"description":"Effect-native background jobs: schema-first definitions, storage-agnostic queue core, worker runtime, and a Postgres store through drizzle.","maintainers":[{"name":"rankincodes","email":"atrankin134@gmail.com"}],"readme":"# effect-mq\n\nEffect-native background jobs. Schema-first job definitions, a storage-agnostic\nqueue core, a worker runtime, and a Postgres store that lives inside your\ndrizzle schema — inspired by BullMQ's semantics and `effect/workflow`'s DX.\nBuilt on Effect v4.\n\n**Documentation: [effect-mq.com](https://www.effect-mq.com)**\n\n```\nbun add effect-mq effect            # or npm / pnpm / yarn\n```\n\nOne package, tree-shakeable modules:\n\n| Import | Contents | Extra peers |\n| --- | --- | --- |\n| `effect-mq` | `Job`, `JobStore`, `MemoryJobStore`, `Worker` | — |\n| `effect-mq/drizzle-postgres` | drizzle-postgres schema factories + the Postgres `JobStore` | `drizzle-orm` (v1), `@effect/sql-pg` |\n| `effect-mq/redis` | the Redis `JobStore` (Lua-script atomicity) | a `Redis` service (`@effect/platform-node`/`-bun`) |\n| `effect-mq/testing` | `TestJobStore` (assert enqueues in unit tests) + the driver conformance suite | `@effect/vitest` (conformance only) |\n\n## Five-minute tour\n\n```ts\nimport { Job, MemoryJobStore, Worker } from \"effect-mq\"\nimport { Effect, Layer, Schema } from \"effect\"\n\n// 1. Define a job — shared by producers and runners.\nclass SendEmail extends Job.make(\"SendEmail\", {\n  payload: { to: Schema.String, subject: Schema.String },\n  success: Schema.String,\n  idempotencyKey: ({ to, subject }) => `${to}:${subject}`,\n  metadata: ({ to }) => ({ to }),                    // indexed, queryable\n  queue: \"email\",\n  defaults: { attempts: 3, backoff: { type: \"exponential\", delay: \"1 second\" } }\n}) {}\n\n// 2. Produce. Only the store is required — no worker anywhere in sight.\nconst producer = Effect.gen(function*() {\n  const jobId = yield* SendEmail.enqueue({ to: \"ada@example.com\", subject: \"hi\" })\n  //    ^ JobId — deterministic here, thanks to idempotencyKey\n\n  // ...or enqueue-and-wait for the typed result:\n  const messageId = yield* SendEmail.execute(\n    { to: \"grace@example.com\", subject: \"now\" },\n    { delay: \"5 seconds\", priority: 2 }\n  )\n})\n\n// 3. Run. Workers are layers — deploy them in the same process or across\n//    machines against shared storage.\nconst RunnerLive = SendEmail.toLayer(\n  (payload) => Effect.map(Worker.CurrentJob, ({ jobId }) => `message-${jobId}`),\n  { concurrency: 5 }\n).pipe(\n  Layer.provideMerge(Worker.layer()),\n  Layer.provideMerge(MemoryJobStore.layer)   // swap for Postgres below\n)\n```\n\nWhat you get out of the box:\n\n- **At-least-once execution** — token-guarded locks, heartbeat renewal, and a\n  stalled-job sweeper that recovers work from crashed workers.\n- **Durable retries** — a failed attempt is written back to the store and\n  re-claimed after its backoff by *any* worker; no lock or worker slot is held\n  while waiting.\n- **A full run ledger** — every run (success, retry, failure, stall) persists\n  as an `AttemptRecord`; `Job.attempts(id)` decodes them back to typed exits.\n- **Idempotency** — `idempotencyKey` makes enqueue a no-op while a job with\n  that key exists, and makes the job id *computable from business data* — the\n  natural join key between the queue and your own domain tables.\n- **A dashboard data layer** — `store.list({ name, states, metadata, cursor })`,\n  `Job.poll`, `Job.retry(id)` (failed → fresh attempt budget, ledger intact),\n  and per-job retention via `keep: { count, age }`.\n- **Graceful shutdown** — interrupting a worker releases in-flight jobs back\n  to `waiting` without consuming an attempt.\n- **Repeatable jobs** — durable cron/interval schedules\n  (`MyJob.schedule(key, { cron })`); each occurrence is claimed and enqueued\n  in one atomic store op, so ticks are exactly-once across any number of\n  workers.\n- **Batch enqueue** — `MyJob.enqueueMany(payloads, options?)` inserts a\n  whole batch of plain items in one store round trip per chunk (multi-row\n  `INSERT` / one Lua script), with per-item idempotency and dedup semantics\n  intact (dedup-keyed items fall back to individual enqueues).\n- **Admin verbs** — `cancel` (including *running* jobs, whose handler fiber is\n  interrupted cross-process), `promote` (delayed → now), and queue-level\n  `pause`/`resume`.\n- **Handler timeouts** — `defaults: { timeout: \"30 seconds\" }` interrupts the\n  handler cleanly and routes through normal retry accounting; something BullMQ\n  can't do to a running processor.\n- **Unrecoverable errors** — `Job.unrecoverable(error)` or a `retryable`\n  predicate skips the remaining retry budget when retrying can't help.\n- **Deduplication** — pending-dedup, throttle, debounce, and\n  replace-while-delayed via a dedup key that never touches your job ids.\n\n## Batch enqueue\n\nFan-out inserts a whole batch of plain items in **one store round trip per\nchunk** — a multi-row `INSERT` on Postgres, one Lua script on Redis (drivers\nchunk very large batches):\n\n```ts\nconst ids = yield* GenerateInvoice.enqueueMany(\n  companies.map((company) => ({ companyId: company.id })),\n  { queue: \"billing\", at: nextBillingRun }   // shared options\n)\n```\n\nIds come back aligned with the payloads. Every item keeps full single-enqueue\nsemantics — `idempotencyKey`, `dedupe`, and `metadata` callbacks run per\npayload, and duplicates are silent no-ops returning the existing id (items\nthat derive a dedup key run through the single-enqueue path individually, in\norder). Options apply batch-wide; per-job `jobId`/`dedupe` are excluded at\nthe type level. The batch is intentionally *not* one transaction: a\nmid-batch store failure can leave a subset applied, which is safe under\nat-least-once — re-running a batch with deterministic ids skips what already\nlanded (store-assigned ids may re-insert).\n\n## Repeatable jobs\n\nSchedules are durable rows in the store — not process-local timers — so they\nsurvive restarts and coordinate across workers. Each occurrence is claimed\nand its job (id `sched/<key>/<slot>`) enqueued in **one atomic store op** —\na compare-and-swap on the schedule's next occurrence — so every occurrence\nfires exactly once no matter how many workers sweep, and no matter how\naggressively history retention prunes old tick jobs:\n\n```ts\n// Create or replace (same key = replace; upsert is idempotent to deploy).\nyield* SendDigest.schedule(\"daily\", {\n  cron: \"0 9 * * *\",              // or: every: \"10 minutes\"\n  tz: \"America/New_York\",         // IANA zone, cron only\n  payload: { edition: \"morning\" }\n})\n\nyield* SendDigest.unschedule(\"daily\")\n```\n\n`cron` fires at the next matching occurrence; `every` first fires one interval\nfrom now and stays on its original grid. If workers are down over several\noccurrences, missed slots collapse into one run (the next sweep enqueues the\noverdue slot once, then advances past `now`). Options mirror `enqueue`:\n`metadata`, `priority`, `attempts`, `backoff`, `keep`, `timeout`.\n\n## Parent-child flows\n\nA flow fans a parent job out into N children, parks the parent until every\nchild settles, then resumes it with their typed results. Children can live\non a **different store** than the parent (a cron parent in Postgres fanning\nout 10k idempotent sends into Redis, collecting the outcomes back):\n\n```ts\nimport { Flow } from \"effect-mq\"\n\nconst DigestFlow = Flow.make(\"daily-digest\", {\n  parent: SendDigest,            // Postgres\n  children: [SendEmail],         // Redis\n  onChildFailure: \"continue\"     // or \"fail\": first failure settles the flow\n})\n\n// The parent worker runs two phases (requires parent AND child stores):\nconst DigestWorker = DigestFlow.toLayer({\n  fanOut: (payload) =>\n    Effect.map(Users.active, (users) =>\n      Flow.children(SendEmail, users.map((user) => ({\n        key: user.id,                       // unique in the flow = idempotency\n        payload: { userId: user.id }\n      })))),\n  collect: (payload, results) =>\n    Effect.succeed({ sent: results.counts.completed, failed: results.counts.failed })\n})\n\n// Workers that run the children declare the flow so their relay can push\n// results to the parent store the moment each child acks:\nWorker.layer({ store: EmailStore, flows: [DigestFlow] })\n```\n\nThe parent's store owns the flow (manifest, per-child results, outcome\ncounters), so \"settle exactly once\" is single-store atomic. Cross-store,\nevery terminal child transition appends its report to the child store's\n**outbox** in the same atomic operation; worker relays push those in\nbatches (children keep completing through parent-store outages) and a\nreconciliation sweeper repairs anything the push path misses, from storage\nalone. Flows nest (a child can be another flow's parent), and `collect`\nreads results as plain `counts`, materialized buckets, or a paged `Stream`.\nDocs: [Parent-child flows](https://www.effect-mq.com/guide/flows).\n\n## Timeouts, cancellation, and unrecoverable errors\n\nBecause handlers are Effect fibers, the runtime can *actually stop them* —\nthese verbs interrupt cleanly (finalizers run) instead of abandoning work:\n\n```ts\nclass GenerateInvoice extends Job.make(\"generate-invoice\", {\n  payload: { invoiceId: Schema.String },\n  error: InvoiceError,\n  defaults: { attempts: 5, timeout: \"2 minutes\" },  // per-run limit\n  retryable: (e) => e.reason !== \"invoice-voided\"   // skip retries when futile\n}) {}\n\n// Inside a handler, mark a specific failure as not worth retrying:\nEffect.fail(Job.unrecoverable(new InvoiceError({ reason: \"customer deleted\" })))\n\n// From anywhere (a dashboard, another process):\nyield* GenerateInvoice.cancel(jobId)   // waiting/delayed: terminal immediately;\n                                       // running: the worker's next heartbeat\n                                       // interrupts the handler fiber\nyield* GenerateInvoice.promote(jobId)  // delayed -> runnable now\n\n// Store-level (definition-free) equivalents for generic dashboards:\nconst store = yield* JobStore.JobStore\nyield* store.cancel(jobId)\nyield* store.pause(QueueName(\"email\"))   // claims stop; producers unaffected\nyield* store.resume(QueueName(\"email\"))  // wakes idle workers immediately\n```\n\nA timed-out run records a `TimeoutError` defect in the attempt ledger and\nconsumes an attempt like any other failure. A cancelled job lands in the\nterminal `cancelled` state with a `cancelled` ledger entry; `awaitResult`\ntreats it as a defect (`JobCancelledError`), not a typed failure.\n\n## History retention\n\nTwo layers of control, both splittable by terminal state (completed jobs are\nusually noise, failed ones evidence):\n\n- **Per job**: `keep` prunes terminal records per name+state — flat\n  `{ count, age }` applies to all states, or split per state:\n\n```ts\nkeep: { count: 100 }                                              // all states\nkeep: { completed: { age: \"1 day\" }, failed: { age: \"30 days\" } } // split\n```\n\n- **Per store**: a retention ceiling swept periodically in the background —\n  one duration or a per-state split (an absent state has no ceiling; the\n  timer touches its rows only when they carry their own `keep.age`):\n\n```ts\nMemoryJobStore.layerWith({ historyTtl: \"7 days\" })\nDrizzleJobStore.layer({ ...tables, historyTtl: { completed: \"1 day\", failed: \"90 days\" } })\n```\n\nThe sweep honours `min(per-row keep.age, ceiling)`, so a job name that goes\nquiet is still pruned on the timer, not only when its group is next acked.\n\n## Tracing\n\nProducer → handler traces connect **across processes and storage**: the\nenqueue span's context (`traceId`/`spanId`/`sampled`) is persisted on the\njob record, and the worker wraps every handler run in a span whose parent\nis that external context (`Tracer.externalSpan`) — your invite handler's\nspan appears as a child of the HTTP request that scheduled it, even when\nthey ran hours apart on different machines. Run spans are named\n`` `${name}.run` `` by default (configurable via\n`Worker.layer({ handlerSpanName: (ctx) => ... })`) and carry\n`effectMqJobId`, `effectMqQueue`, and `effectMqAttempt` attributes.\n\nHow the handler span attaches follows the delay: **immediate enqueues\ncontinue the producer trace** (parent-child — the email handler sits inside\nthe signup request's waterfall), while **explicitly delayed/`at`-scheduled\njobs start their own trace with a causal link** back to the producer (a\nfive-day-wide parent-child trace renders badly and defeats tail sampling).\nThe policy keys off scheduling *intent* captured at enqueue — queue backlog\nnever changes your trace shapes — and every retry attempt of a job keeps\nits mode. Override per worker with\n`Worker.layer({ traceLinking: \"auto\" | \"parent\" | \"link\" | \"none\" })`. All\nproducer verbs (`enqueue`, `cancel`, `schedule`, ...) already run in their\nown spans. Wire up any Effect tracer/exporter; without one, the overhead is\nnegligible. Poll-loop iterations are deliberately unspanned — the handler\nrun is the meaningful trace unit; per-claim spans would flood your backend.\n\n## Metrics\n\nWorkers and producers emit Effect `Metric` instruments (exported as the\n`Metrics` module). They are **process-local operational signal, not\npersisted state** — wire up any Effect-compatible exporter (the Otlp modules\nfrom `effect/unstable/observability`, `@effect/opentelemetry`, Prometheus)\nand retention lives in your metrics backend. The *durable* analogues stay in\nthe store: `store.counts()` for live depth, the attempt ledger for per-run\nhistory — both queryable forever.\n\n| Metric | Type | Tags |\n| --- | --- | --- |\n| `effect_mq_jobs_enqueued` | counter | `name`, `queue`, `duplicate` |\n| `effect_mq_job_runs` | counter | `name`, `queue`, `outcome` |\n| `effect_mq_job_run_duration_ms` | histogram | `name`, `queue`, `outcome` |\n| `effect_mq_job_wait_duration_ms` | histogram | `name`, `queue` |\n| `effect_mq_claims` | counter | `queue`, `result` (claimed/empty) |\n| `effect_mq_jobs_in_flight` | gauge | `queue` |\n| `effect_mq_queue_depth` | gauge | `queue`, `state` (opt-in sampler) |\n| `effect_mq_locks_lost` / `effect_mq_cancel_interrupts` | counter | — |\n| `effect_mq_stalled_recovered` | counter | `outcome` |\n| `effect_mq_schedule_ticks` | counter | `name` |\n\n`job_wait_duration_ms` is the queue-latency headline: time between a job\nbecoming runnable and its claim. Depth sampling costs one `counts()` query\nper queue per tick, so it is opt-in:\n`Worker.layer({ queueMetricsInterval: \"15 seconds\" })`.\n\n## Custom job ids\n\nStore-assigned ids default to a compact sequence (`j-<n>`). Bring your own\ngenerator when you want globally unique or prefixed ids:\n\n```ts\nimport { ulid } from \"ulid\"\n\nDrizzleJobStore.layer({\n  jobs, attempts, schedules, queues,\n  idGenerator: ({ name }) => `${name}_${ulid()}`   // sync or Effect-returning\n})\n```\n\nThe generator only runs for store-assigned ids — `idempotencyKey` ids and\nrepeatable-schedule tick ids stay deterministic (exactly-once depends on\nthem). Collisions are retried a bounded number of times, then the enqueue\nfails; bring real entropy.\n\n## Deduplication\n\nDedup is a **separate key**, not id derivation — your ids (explicit,\n`idGenerator`, or store-assigned) are never rewritten. Keys are scoped per\njob name and picked per definition or per enqueue:\n\n```ts\nclass SyncBenefits extends Job.make(\"sync-benefits\", {\n  payload: { employerId: Schema.String },\n  dedupe: ({ employerId }) => employerId          // string shorthand = { key }\n}) {}\n\n// Per-enqueue, with modes:\nyield* SyncBenefits.enqueue(payload, { dedupe: { key: \"emp-1\", ttl: \"1 minute\" } })\n```\n\nFour behaviors, composable from three fields:\n\n| Options | Behavior |\n| --- | --- |\n| `{ key }` | dedupe while the keyed job is pending; finishing frees the key |\n| `{ key, ttl }` | throttle: at most one job per window, even after completion |\n| `{ key, ttl, extend: true }` | debounce: every dropped enqueue pushes the window out |\n| `{ key, replace: true }` | while the keyed job is still delayed, the newest payload/metadata/priority/attempts/backoff/keep/timeout/delay replace it (same id; a `ttl` window re-arms) |\n\nA deduplicated enqueue returns the keyed job's id (`duplicate: true` at the\nstore level). `idempotencyKey` still exists and is different on purpose: it\nmakes the job id *itself* deterministic (permanent identity, joinable from\nyour domain tables), while `dedupe` is temporal policy with its own lifecycle.\n\nKeys also power the schedule/reschedule/cancel lifecycle for one-shot future\nwork — no job-id bookkeeping in your business logic:\n\n```ts\nclass SendInvite extends Job.make(\"send-invite\", {\n  payload: { employeeId: Schema.String },\n  dedupe: ({ employeeId }) => ({ key: employeeId, replace: true })\n}) {}\n\n// Schedule for a wall-clock instant (any DateTime.Input — zero duration math):\nyield* SendInvite.enqueue({ employeeId }, {\n  at: DateTime.makeZonedUnsafe(\n    { year: 2026, month: 8, day: 24, hours: 9 },\n    { timeZone: \"America/New_York\", adjustForTimeZone: true }\n  )\n})\n\n// Reschedule: the same idempotent call with a new time (replace moves it).\nyield* SendInvite.enqueue({ employeeId }, { at: nextDay })\n\n// They are not onboarding after all — cancel whatever is pending, if anything:\nconst wasPending = yield* SendInvite.cancelByKey(employeeId)\n```\n\n`delay` and `at` are mutually exclusive (a compile error via the option\nunion); an `at` in the past runs immediately.\n\nPostgres users: dedup adds one table and one jobs column — add\n`export const jobDedupe = mqDedupe()` to your schema and `drizzle-kit\ngenerate` diffs both (the table and the new `dedupe_key` column) into one\nmigration. Memory and Redis need nothing.\n\n## Postgres through drizzle\n\nThe Postgres store runs on drizzle v1's Effect driver\n(`drizzle-orm/effect-postgres`, built on `@effect/sql-pg` — works on Node and\nBun). Claims use `FOR UPDATE SKIP LOCKED`; wake-ups use LISTEN/NOTIFY, so\nworkers in other processes pick jobs up promptly.\n\n```\nbun add drizzle-orm@rc @effect/sql-pg\n```\n\n### 1. Put the tables in your drizzle schema\n\nThe factories are the single source of truth for the table layout. Re-export\nthem from your schema file and **your drizzle-kit pipeline owns the\nmigrations** — no library-run DDL, no parallel migration system:\n\n```ts\n// db/schema.ts\nimport {\n  mqDedupe,\n  mqFlowChildren,\n  mqFlowOutbox,\n  mqJobAttempts,\n  mqJobs,\n  mqQueueControl,\n  mqSchedules\n} from \"effect-mq/drizzle-postgres\"\n\n// The `name` column is typed to your job tags (derived, not hand-written):\ntype JobNames = typeof GenerateInvoice._tag | typeof SendEmail._tag\n\nexport const jobs = mqJobs<JobNames>()            // default table: effect_mq_jobs\nexport const jobAttempts = mqJobAttempts(jobs)    // default: effect_mq_job_attempts\nexport const jobSchedules = mqSchedules()         // default: effect_mq_schedules\nexport const jobQueues = mqQueueControl()         // default: effect_mq_queue_control\nexport const jobDedupe = mqDedupe()               // default: effect_mq_dedupe\nexport const jobFlowChildren = mqFlowChildren()   // default: effect_mq_flow_children\nexport const jobFlowOutbox = mqFlowOutbox()       // default: effect_mq_flow_outbox\n```\n\nNeed more indexes (the built-ins cover claiming, listing, metadata\ncontainment, and `name`/`state`/`finishedAt` history)? Every factory takes an\n`extraConfig` callback — the same shape as drizzle's third `pgTable` argument —\nappended after the built-in indexes and owned by your migrations like\neverything else:\n\n```ts\nexport const jobs = mqJobs<JobNames>(\"effect_mq_jobs\", {\n  extraConfig: (t) => [index(\"jobs_name_recent_idx\").on(t.name, t.enqueuedAt.desc())]\n})\n```\n\n### Custom columns\n\nNeed real columns — a tenant id you can FK, RLS-scope, and join — populated\nat job creation instead of patched in from job logic? `extend` the jobs\ntable; at enqueue the store fills each extended column from the job's\n`metadata` entry with the same TS key (the definition's\n`metadata: (payload) => ...` is your creation-time hook), NULL when absent:\n\n```ts\nclass SyncPayments extends Job.make(\"sync-payments\", {\n  payload: { companyId: Schema.String, objectId: Schema.String },\n  metadata: ({ companyId, objectId }) => ({ companyId, objectId })\n}) {}\n\nexport const jobs = mqJobs<JobNames>(\"effect_mq_jobs\", {\n  extend: {\n    companyId: text(\"company_id\").notNull(),\n    objectId: text(\"object_id\")\n  },\n  extraConfig: (t) => [index(\"jobs_company_idx\").on(t.companyId, t.state)]\n})\n\n// Coercion/renames when the metadata convention isn't enough:\nDrizzleJobStore.layer({ ...tables, extraValues: ({ metadata }) => ({ companyId: metadata.companyId }) })\n```\n\n`db.select().from(jobs).where(eq(jobs.companyId, tenant))` is fully typed; a\ndedupe `replace` rewrites the extended columns with the latest values; a\nmissing metadata key on a `NOT NULL` column fails the enqueue loudly. Memory\nand Redis need nothing — there the metadata projection is already the\nqueryable surface (`store.list({ metadata: { companyId } })`).\n\n```\ndrizzle-kit generate   # emits the CREATE TABLE migration next to your others\ndrizzle-kit migrate\n```\n\nWhen a future effect-mq version changes the layout, the factory changes and\n`drizzle-kit generate` diffs it — you get a normal, reviewable migration.\n\n### 2. Provide the store layer\n\n```ts\nimport { DrizzleJobStore } from \"effect-mq/drizzle-postgres\"\nimport { PgClient } from \"@effect/sql-pg\"\nimport { Layer, Redacted } from \"effect\"\nimport {\n  jobAttempts,\n  jobDedupe,\n  jobFlowChildren,\n  jobFlowOutbox,\n  jobQueues,\n  jobs,\n  jobSchedules\n} from \"./db/schema.ts\"\n\nconst JobStoreLive = DrizzleJobStore.layer({\n  jobs,\n  attempts: jobAttempts,\n  schedules: jobSchedules,\n  queues: jobQueues,\n  dedupe: jobDedupe,\n  flowChildren: jobFlowChildren,\n  flowOutbox: jobFlowOutbox\n}).pipe(\n  Layer.provide(PgClient.layer({ url: Redacted.make(process.env.DATABASE_URL!) }))\n)\n```\n\nThe layer probes the tables at startup and fails fast with a clear message if\nmigrations haven't run (`validate: false` defers that to first use).\n\n### 3. Query it like any other table\n\nProduct UIs read the tables directly with drizzle — fully typed:\n\n```ts\ndb.select().from(jobs).where(and(\n  eq(jobs.name, \"generate-invoice\"),                 // a typo is a compile error\n  sql`${jobs.metadata} @> ${{ customerId }}::jsonb`  // GIN-indexed containment\n))\n\ndb.select().from(jobAttempts)\n  .innerJoin(jobs, eq(jobAttempts.jobId, jobs.id))\n  .where(eq(jobAttempts.outcome, \"failed\"))\n```\n\n**Reads yes, writes no.** Mutations must go through the store —\n`MyJob.retry(id)`, `store.remove(id)` — so lock tokens, attempt accounting,\nand wake-up notifications stay coherent.\n\nWorker tip: `awaitWake` is LISTEN/NOTIFY-driven and **queue-filtered** (the\nNOTIFY payload names the queue, so an enqueue wakes only the takers watching\nthat queue — many queues don't amplify into many claims). The worker's\n`pollInterval` is just the fallback; the 5s default is fine.\n\n## Redis store\n\nThe Redis store (`effect-mq/redis`) implements every `JobStore` operation as\none atomic Lua script, so it is safe across any number of producer and worker\nprocesses. It builds on Effect's client-agnostic `Redis` service — provide it\nfrom your platform package (no extra peers beyond what you already run):\n\n```ts\nimport { RedisJobStore } from \"effect-mq/redis\"\nimport { NodeRedis } from \"@effect/platform-node\"   // node-redis under the hood\n// import { BunRedis } from \"@effect/platform-bun\"  // Bun.redis under the hood\nimport { Layer } from \"effect\"\n\nconst JobStoreLive = RedisJobStore.layer({\n  prefix: \"myapp-jobs\",          // key namespace (default \"effect-mq\")\n  historyTtl: \"30 days\"          // optional retention ceiling\n}).pipe(\n  Layer.provide(NodeRedis.layer({ url: process.env.REDIS_URL }))\n)\n```\n\nWake-ups ride pub/sub (`<prefix>:wake`) with queue-filtered messages, so\nidle workers in other processes pick new jobs up promptly and an enqueue\nwakes only the takers watching its queue; the worker's `pollInterval` is the\nfallback. Notes:\n\n- Keys are plain-prefixed (no hash tags) — point it at a single Redis /\n  Valkey node or a cluster-unaware proxy, not Redis Cluster.\n- `list` filters scan server-side in Lua: fine for dashboards, not for\n  millions of terminal rows — set `historyTtl`/`keep` accordingly. `counts`\n  is O(1) (maintained counters).\n- The same conformance suite that runs against Postgres runs against a real\n  Redis in this repo, TestClock included (scripts take time via ARGV).\n\n## Multiple stores on different infrastructure\n\nBind jobs to *named stores* so business-critical runs live in Postgres while\ndisposable ones live elsewhere — enforced by the type system:\n\n```ts\nimport { Job, JobStore, MemoryJobStore, Worker } from \"effect-mq\"\n\nconst Durable = JobStore.named(\"durable\")       // -> Postgres in prod\nconst Ephemeral = JobStore.named(\"ephemeral\")   // -> Redis or memory\n\nclass GenerateInvoice extends Job.make(\"generate-invoice\", {\n  payload: { invoiceId: Schema.String },\n  idempotencyKey: ({ invoiceId }) => invoiceId,\n  store: Durable\n}) {}\n\n// Forgetting the Durable layer is now a COMPILE error at every enqueue site.\n// Workers bind to one store:\nconst durableWorkers = GenerateInvoice.toLayer(handler).pipe(\n  Layer.provide(Worker.layer({ store: Durable }))     // local provide: several\n)                                                     // workers can coexist\n```\n\nA **queue** is an ordering/concurrency domain *within* a store\n(`Job.make({ queue })`, `Worker.layer({ queues: { email: { concurrency: 5 } } })`);\na **store** is an infrastructure/durability domain hosting many queues.\n\n## Metadata vs. your own tables\n\nTwo kinds of \"business context\", two homes:\n\n- **Ops UI** (\"list invoice runs for customer X, retry that one\"): use the\n  `metadata` projection — a flat `Record<string, string>` derived from the\n  payload, indexed by every driver, filterable via `store.list` or raw SQL.\n- **Domain history** (\"what did this invoice run actually produce\"): your own\n  table, joined by the *deterministic* job id from `idempotencyKey`. The queue's\n  retention (`keep`) can then prune freely while your business history lives\n  forever. Don't let queue infrastructure own business data lifecycles.\n\n## Secrets and redaction\n\nPayloads and results are persisted **schema-encoded** — nothing reaches a\nstore un-encoded. For `Schema.Redacted` fields, Effect's semantics apply:\n\n- `Schema.Redacted(Schema.String)` **round-trips**: handlers receive a real\n  `Redacted` value (safe to log — it prints `<redacted>`), but the underlying\n  value *is* stored in the payload/exit JSON. Redaction protects logs and\n  inspection, not the database at rest.\n- `Schema.Redacted(inner, { disallowJsonEncode: true })` **refuses\n  persistence**: enqueueing such a payload dies with `Cannot serialize\n  Redacted` before anything reaches the store. Use it for values that must\n  never be written down; pass them to handlers via context/services instead.\n\nBoth behaviors are pinned by tests.\n\n## Reference: knobs at a glance\n\nEverything a job definition, an enqueue, and a worker can be tuned with:\n\n**`Job.make(name, options)`** — `payload` (schema or struct fields),\n`success`/`error` schemas, `idempotencyKey(payload)`, `dedupe(payload)`,\n`metadata(payload)`, `retryable(error)`, `queue`, `store`, and `defaults`\n(any per-enqueue option below).\n\n**Per enqueue** (`enqueue`/`execute` options) — `jobId`, `queue`,\n`metadata`, `dedupe`, `delay` OR `at` (absolute `DateTime.Input`; exclusive\nby type), `priority` (higher first), `attempts`, `backoff`\n(`fixed`/`exponential`), `keep` (`count`/`age`), `timeout`.\n\n**Job verbs** — `enqueue`, `enqueueMany` (a whole batch, one store round\ntrip per chunk; same options minus per-job `jobId`/`dedupe`), `execute` (enqueue +\nawait the typed result), `poll`, `awaitResult`, `attempts` (the decoded run\nledger), `retry`, `cancel`, `cancelByKey` (by dedup key, idempotent),\n`promote`, `schedule`/`unschedule`, `toLayer` (register the handler).\n\n**`Worker.layer(options)`** — all durations take `Duration.Input`:\n\n| Option | Default | Meaning |\n| --- | --- | --- |\n| `store` | default `JobStore` | which named store this worker claims from |\n| `concurrency` | 1 | taker fibers per queue |\n| `queues` | — | per-queue overrides: `{ email: { concurrency: 5 } }` |\n| `lockDuration` | 30s | how long a claim's lock lasts before it counts as stalled |\n| `lockRenewInterval` | half of `lockDuration` | heartbeat cadence (also delivers cross-process cancels) |\n| `stalledInterval` | 30s | how often to sweep for stalled jobs |\n| `maxStalledCount` | 1 | stalls tolerated before a job is failed outright |\n| `pollInterval` | 5s | idle fallback when no wake-up arrives (wake-ups are queue-filtered and push-based, so the default is fine even on Postgres) |\n| `scheduleSweepInterval` | 15s | how often to tick due repeatable-job schedules |\n| `queueMetricsInterval` | off | sample `store.counts()` per queue into the depth gauge |\n| `handlerSpanName` | `` `${name}.run` `` | name of the span wrapping each handler run |\n| `traceLinking` | `auto` | parent for immediate jobs, causal link for delayed ones (`parent`/`link`/`none` force a mode) |\n| `onJobFailure` | — | callback after each failed run is acked; runs isolated |\n| `flows` | — | flows whose children this worker runs (lets its relay push results) |\n| `flowSweepInterval` | 30s | flow sweeper cadence + the relay's fallback drain cadence |\n| `id` | random | identifier used in lock tokens |\n\n**Store construction** — every driver accepts `idGenerator`, `historyTtl`,\nand `historySweepInterval`; drizzle-postgres additionally takes the table\ninstances (+ `validate`), Redis a key `prefix`.\n\n## Testing your app\n\nUnit-test that services enqueue correctly — no worker, no boilerplate, and\npayloads come back **decoded through the job's schema** (so `Redacted`,\n`DateTime`, and branded values are real instances, not stored JSON):\n\n```ts\nimport { TestJobStore } from \"effect-mq/testing\"\n\nit.effect(\"signup enqueues a welcome email\", () =>\n  Effect.gen(function*() {\n    yield* SignupService.register({ email: \"ada@example.com\" })\n\n    const emails = yield* TestJobStore.enqueuedOf(SendEmail)\n    expect(emails).toHaveLength(1)\n    expect(emails[0]?.payload.to).toBe(\"ada@example.com\")\n    expect(emails[0]?.state).toBe(\"waiting\")\n  }).pipe(Effect.provide(TestJobStore.layer)))\n```\n\n`TestJobStore.layer` provides a fresh in-memory store as both the default\n`JobStore` (for the code under test) and the inspection service; jobs just\naccumulate in `waiting`/`delayed` since nothing claims them. Named stores\nuse `TestJobStore.layerFor(Durable)`. Records surface scheduling detail\n(`state`, `priority`, `runAt`, `metadata`, `dedupeKey`), and the raw store\nis exposed for simulating claims/acks. No `@effect/vitest` required — it is\nplain Effect, so it works with any test runner.\n\n## Writing a storage driver\n\nImplement the `JobStore` service (one atomic seam: `enqueue`/`enqueueMany`,\n`claim`, `ack`, `release`, `extendLocks`, `recoverStalled`, `awaitWake`,\n`getJob`, `getAttempts`, `list`, `retry`, `counts`, `remove`, `cancel`,\n`promote`, `pause`/`resume`/`pausedQueues`, `cancelByDedupe`, the schedule\nops `upsertSchedule`/`removeSchedule`/`listSchedules`/`dueSchedules`/\n`tickSchedule`/`advanceSchedule`, and the flow ops `recordChildResults`/\n`listChildResults`/`flowSweepWork`/`markChildrenCascaded`/`peekOutbox`/\n`deleteOutbox`) and run the conformance suite against it:\n\n```ts\nimport { jobStoreConformance } from \"effect-mq/testing\"\n\njobStoreConformance(\"MyDriver\", () => MyDriver.layer)\n```\n\nTwo rules make the suite work against real storage: derive **all time from\nthe Effect `Clock`** (pass `now` as a bind parameter — never SQL `now()`), so\ntests run under `TestClock`; and keep every operation atomic. The in-memory\ndriver (`MemoryJobStore`) is the reference implementation, and the Postgres\nsuite in this repo runs the same conformance tests against a real database.\n\n## Roadmap\n\nNext up: drizzle schema customization (column renames, native id and\ntimestamp column types, a typed queue registry), a cross-process event\nstream, and global queue concurrency/rate limits. Full prioritized list:\n[ROADMAP.md](https://github.com/TeamWarp/effect-mq/blob/main/ROADMAP.md);\nrelease history:\n[CHANGELOG.md](https://github.com/TeamWarp/effect-mq/blob/main/CHANGELOG.md).\n\n## License\n\nMIT\n","readmeFilename":"README.md"}