Queue

La queue livre une charge utile (payload) à un worker. Elle est at-least-once et fire-and-forget : il n'y a aucun stockage de résultat et aucun statut par message. Un message est mis en file, puis le handler HTTP d'un push-worker est invoqué pour le traiter.

Enqueue

import { createSdk } from "@bext-stack/framework";

const sdk = createSdk("<app-id>");
const { id } = await sdk.queue.push("emails", { to: "a@b.com" }, /* delaySecs */ 30);

Ou en HTTP :

curl -s http://127.0.0.1/__bext/sdk/queue/push \
  -H "X-Bext-App-Id: <app-id>" \
  -H "Content-Type: application/json" \
  -d '{"queue":"emails","payload":{"to":"a@b.com"},"delay_seconds":30}'

Endpoints

Restreints par X-Bext-App-Id. POST sauf mention GET :

Endpoint Corps / notes Retourne
/queue/push { queue, payload, delay_seconds? } { id }
/queue/pull récupérer des messages
/queue/ack acquitter un message
/queue/stats GET statistiques de la queue
/queue/list GET queues
/queue/dead/list lister les messages en dead-letter
/queue/dead/retry réessayer les messages en dead-letter
/queue/dead/purge purger les messages en dead-letter
/queue/worker/register { queue, handler_url, concurrency?, visibility_timeout_secs?, max_attempts? }
/queue/worker/unregister supprimer un worker
/queue/worker/list lister les workers enregistrés

Contrat du handler de push-worker

Enregistrez un push-worker avec un handler_url. Enregistrez-le comme l'URL du vhost public de l'application — par exemple https://<app>.inklura.fr/api/queue/<name> — et non une adresse loopback :

curl -s http://127.0.0.1/__bext/sdk/queue/worker/register \
  -H "X-Bext-App-Id: <app-id>" \
  -H "Content-Type: application/json" \
  -d '{
    "queue": "emails",
    "handler_url": "https://<app>.inklura.fr/api/queue/emails",
    "concurrency": 4,
    "visibility_timeout_secs": 60,
    "max_attempts": 5
  }'

Le dispatcher envoie ce corps en POST à votre handler_url :

{ "id": "<message-id>", "queue": "emails", "payload": { }, "attempts": 1 }

Le statut de réponse de votre handler décide de l'issue :

Réponse Signification
2xx ack — message traité
408, 429, 5xx retry — redélivré plus tard
tout autre 4xx dead-letter — déplacé vers la queue morte

Implémentez le handler sous forme de route.ts :

export async function POST(request: Request) {
  const { id, payload, attempts } = await request.json();
  try {
    await sendEmail(payload);
    return new Response(null, { status: 200 }); // ack
  } catch (e) {
    return new Response(String(e), { status: 500 }); // retry
  }
}

Sémantique à prendre en compte dans la conception

  • At-least-once — un handler peut recevoir le même message plus d'une fois. Rendez les handlers idempotents (par ex. dédupliquez sur id).
  • Fire-and-forget — celui qui met en file obtient un id mais rien d'autre ; il n'existe aucun moyen intégré de demander « le message X a-t-il réussi ? ».
  • Dead-letter — les messages qui épuisent leurs retries (ou renvoient un 4xx non réessayable) atterrissent dans la queue morte ; inspectez / réessayez / purgez avec /queue/dead/*.

Superposez un enregistrement de job quand vous avez besoin d'un statut

Comme il n'y a pas de store de résultats, les applications qui ont besoin des issues conservent un enregistrement de job durable en KV — des clés comme job:<id> mises à jour par le handler (queuedrunningcomplete / fail).

Queue vs Tasks

Si vous avez besoin de traitements de longue durée avec statut et progression intégrés, utilisez plutôt le task-executor chaud — voir Tasks & Planificateur. La queue est idéale pour une livraison à haut volume, fire-and-forget, vers un handler public.

Et ensuite

Page Ce qu'elle couvre
Tasks & Planificateur Traitements longs avec statut/progression ; cron
KV Store Le motif d'enregistrement de job durable
Queue Workers Exploiter et observer les workers