Jobs

Jobs are units of background work. Every job is a C++ class that derives from Garvan::Job. They can be triggered directly, from an event listener, or over HTTP through the admin API (which is what kalpasan job:dispatch uses internally).

Job API

Override four things and provide one static factory:

// app/jobs/MyJob.h
#include "queue/Job.h"
#include "tools/JsonValue.h"

namespace AppJobs {

class MyJob : public Garvan::Job
{
public:
    MyJob() = default;
    MyJob(std::string arg) : arg_(std::move(arg)) {}

    void handle() override;                                 // do the work
    std::string jobName() const override { return "MyJob"; }
    std::string connection() const override { return "sync"; } // sync | async | database

    Garvan::JsonValue payload() const override;             // serialize for persisted queues
    static std::unique_ptr<Garvan::Job>
        fromPayload(const Garvan::JsonValue& p);            // reverse of payload()

private:
    std::string arg_;
};

} // namespace AppJobs

Registering a job

Register every user job in JobServiceProvider::register_(). Use JobRegistry::bind() directly with the short name from jobName():

// app/providers/JobServiceProvider.cpp
Garvan::JobRegistry::bind("MyJob", &AppJobs::MyJob::fromPayload);
Do not use GARVAN_REGISTER_JOB for user jobs

The macro GARVAN_REGISTER_JOB(AppJobs::MyJob) stringifies the whole token and registers the key as "AppJobs::MyJob", which then does not match jobName() or the short name kalpasan job:dispatch expects. See the fix in app/providers/JobServiceProvider.cpp:18.

Dispatching

Anywhere in your code — controller, listener, or another job:

#include "queue/JobDispatcher.h"
#include "app/jobs/MyJob.h"

Garvan::JobDispatcher::dispatch(std::make_unique<AppJobs::MyJob>("hello"));

The dispatcher routes the job to the driver named by job->connection(). Drivers are bound by service providers; today only "sync" is wired in the starter.

SyncDriver

SyncDriver runs the job inline in the calling thread. This is the default in Phase A and gives you the same call-and-return semantics as a plain function call — perfect for testing, low-throughput tasks, or pipelines triggered by an HTTP request. Async and database-backed drivers will slot into the same JobDispatcher API in later phases with no changes to your job classes.

Example: SendTestMail

The bundled AppJobs::SendTestMail (app/jobs/SendTestMail.h:21) is a real, sendable job. Its handle() opens an SMTP session with libcurl, sends an HTML email using the MAIL_* block from .env, and logs the outcome to stdout. See Mail (SMTP) for the SMTP side and Kalpasan CLI for the job:dispatch verb.