'use strict'; /* * Outbox inter-processo. * * I daemon possono produrre eventi senza importare ws-server. * Solo il processo server consuma questi eventi e li inoltra * al WebSocket reale. */ let ensurePromise = null; async function ensureServerEventOutbox(db) { if (!ensurePromise) { ensurePromise = (async () => { const exists = await db.schema.hasTable('server_event_outbox'); if (!exists) { await db.schema.createTable('server_event_outbox', (t) => { t.increments('id').primary(); t.string('channel').notNullable(); t.string('user').notNullable(); t.text('payload').notNullable(); t.string('status') .notNullable() .defaultTo('pending'); t.integer('attempts') .notNullable() .defaultTo(0); t.text('last_error').nullable(); t.datetime('created_at') .notNullable() .defaultTo(db.fn.now()); t.datetime('updated_at') .notNullable() .defaultTo(db.fn.now()); t.datetime('processed_at').nullable(); t.index( ['status', 'id'], 'server_event_outbox_status_id_idx' ); }); console.log( '[ServerEventOutbox] tabella server_event_outbox creata' ); } })().catch((err) => { ensurePromise = null; throw err; }); } return ensurePromise; } async function enqueueServerEvent( db, { channel, user, payload, } ) { if (!channel || !user || !payload) { throw new Error( 'enqueueServerEvent richiede channel, user e payload' ); } await ensureServerEventOutbox(db); const now = new Date().toISOString(); await db('server_event_outbox').insert({ channel, user, payload: JSON.stringify(payload), status: 'pending', attempts: 0, last_error: null, created_at: now, updated_at: now, processed_at: null, }); } /* * C'e' un solo consumer nel processo server. * Non serve tenere il job in processing durante il broadcast: * viene selezionato pending e marcato done soltanto dopo * un broadcast completato senza eccezioni. */ async function getNextPendingServerEvent(db) { await ensureServerEventOutbox(db); return db('server_event_outbox') .where({ status: 'pending' }) .orderBy('id', 'asc') .first(); } async function completeServerEvent(db, id) { const now = new Date().toISOString(); return db('server_event_outbox') .where({ id, status: 'pending', }) .update({ status: 'done', processed_at: now, updated_at: now, last_error: null, }); } async function failServerEvent(db, id, error) { const now = new Date().toISOString(); return db('server_event_outbox') .where({ id, status: 'pending', }) .update({ attempts: db.raw('attempts + 1'), last_error: String(error || 'Errore sconosciuto'), updated_at: now, }); } module.exports = { ensureServerEventOutbox, enqueueServerEvent, getNextPendingServerEvent, completeServerEvent, failServerEvent, };