gallery_server_client_s190js/db/serverEventOutbox.js
2026-10-26 11:00:14 +01:00

147 lines
3.1 KiB
JavaScript

'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,
};