openvibers/openvibe.vip Commits
Health and readiness show the outbox's real counts: status() awaits pending()/rejected() (queries on PostgreSQL), so /api/health and /api/ready no longer serialize two Promises as {}. The API test asserts a number. npm test 11/11.
Changes
@@@ -87,7 +87,7 @@ async function createApp(opts = {}) {app.use('/embed', createEmbedRoutes({ domain, config }));app.use(cookieParser());-- app.get('/api/health', (req, res) => res.json({ ok: true, service: 'vip', version: VERSION, release: release.release, events: outbox.status() }));++ app.get('/api/health', async (req, res) => res.json({ ok: true, service: 'vip', version: VERSION, release: release.release, events: await outbox.status() }));// Valkey (ADR-035): shared, never-authoritative state (per-actor limit counters). Optional.const valkey = opts.valkey !== undefined ? opts.valkey : (config.valkey.url ? require('openvibe-sdk/valkey').createValkey({ url: config.valkey.url, prefix: config.valkey.prefix, log }) : null);const readiness = createVipReadiness({ db, keys, config, outbox, now, release: release.release, fetchImpl, valkey });@@@ -63,7 +63,8 @@ function createVipOutbox({ db, config, fetchImpl, now = () => Date.now(), log =start() { if (enabled) outbox.start(); },stop: () => outbox.stop(),kick() { if (enabled) outbox.kick(); },-- status: () => ({ enabled, pending: outbox.pending(), rejected: outbox.rejected(), last_error: lastError }),++ // pending()/rejected() are queries on PostgreSQL: awaited, or the health output serialized two Promises as {}.++ status: async () => ({ enabled, pending: await outbox.pending(), rejected: await outbox.rejected(), last_error: lastError }),};}@@@ -49,7 +49,7 @@ function createVipReadiness({ db, keys, config, outbox, now, release = null, fetconst t = now();const p = await db.prepare('SELECT COUNT(*) AS n, SUM(CASE WHEN valid_until < ? THEN 1 ELSE 0 END) AS stale FROM vip_entitlement_projection').get(t);return {-- events: outbox.status(),++ events: await outbox.status(),billing_events_accepted: config.events.webhookSecrets.length > 0,projection: { rows: p.n || 0, past_valid_until: p.stale || 0, max_age_ms: config.projection.maxAgeMs, grace_ms: config.projection.graceMs },};@@@ -18,6 +18,7 @@ const { test, run } = harness('api');test('health, release and truthful readiness', async () => {const h = await t.call('GET', '/api/health', { token: null });assert.strictEqual(h.json.service, 'vip');++ assert.strictEqual(typeof h.json.events.pending, 'number', 'outbox counts are numbers (an unawaited query serialized as {})');const rel = await t.call('GET', '/release.json', { token: null });assert.strictEqual(rel.json.service, 'vip');assert.deepStrictEqual(require('openvibe-contracts').validate('registry.release-manifest@1', rel.json).errors, []);
@@ -87,7 +87,7 @@ async function createApp(opts = {}) {
@@ -87,7 +87,7 @@ async function createApp(opts = {}) {
app.use('/embed', createEmbedRoutes({ domain, config }));
app.use('/embed', createEmbedRoutes({ domain, config }));
app.use(cookieParser());
app.use(cookieParser());
- app.get('/api/health', (req, res) => res.json({ ok: true, service: 'vip', version: VERSION, release: release.release, events: outbox.status() }));
+ app.get('/api/health', async (req, res) => res.json({ ok: true, service: 'vip', version: VERSION, release: release.release, events: await outbox.status() }));
// Valkey (ADR-035): shared, never-authoritative state (per-actor limit counters). Optional.
// Valkey (ADR-035): shared, never-authoritative state (per-actor limit counters). Optional.
const valkey = opts.valkey !== undefined ? opts.valkey : (config.valkey.url ? require('openvibe-sdk/valkey').createValkey({ url: config.valkey.url, prefix: config.valkey.prefix, log }) : null);
const valkey = opts.valkey !== undefined ? opts.valkey : (config.valkey.url ? require('openvibe-sdk/valkey').createValkey({ url: config.valkey.url, prefix: config.valkey.prefix, log }) : null);
const readiness = createVipReadiness({ db, keys, config, outbox, now, release: release.release, fetchImpl, valkey });
const readiness = createVipReadiness({ db, keys, config, outbox, now, release: release.release, fetchImpl, valkey });
@@ -63,7 +63,8 @@ function createVipOutbox({ db, config, fetchImpl, now = () => Date.now(), log =
@@ -63,7 +63,8 @@ function createVipOutbox({ db, config, fetchImpl, now = () => Date.now(), log =
start() { if (enabled) outbox.start(); },
start() { if (enabled) outbox.start(); },
stop: () => outbox.stop(),
stop: () => outbox.stop(),
kick() { if (enabled) outbox.kick(); },
kick() { if (enabled) outbox.kick(); },
- status: () => ({ enabled, pending: outbox.pending(), rejected: outbox.rejected(), last_error: lastError }),
+ // pending()/rejected() are queries on PostgreSQL: awaited, or the health output serialized two Promises as {}.
+ status: async () => ({ enabled, pending: await outbox.pending(), rejected: await outbox.rejected(), last_error: lastError }),
};
};
}
}
@@ -49,7 +49,7 @@ function createVipReadiness({ db, keys, config, outbox, now, release = null, fet
@@ -49,7 +49,7 @@ function createVipReadiness({ db, keys, config, outbox, now, release = null, fet
const t = now();
const t = now();
const p = await db.prepare('SELECT COUNT(*) AS n, SUM(CASE WHEN valid_until < ? THEN 1 ELSE 0 END) AS stale FROM vip_entitlement_projection').get(t);
const p = await db.prepare('SELECT COUNT(*) AS n, SUM(CASE WHEN valid_until < ? THEN 1 ELSE 0 END) AS stale FROM vip_entitlement_projection').get(t);
return {
return {
- events: outbox.status(),
+ events: await outbox.status(),
billing_events_accepted: config.events.webhookSecrets.length > 0,
billing_events_accepted: config.events.webhookSecrets.length > 0,
projection: { rows: p.n || 0, past_valid_until: p.stale || 0, max_age_ms: config.projection.maxAgeMs, grace_ms: config.projection.graceMs },
projection: { rows: p.n || 0, past_valid_until: p.stale || 0, max_age_ms: config.projection.maxAgeMs, grace_ms: config.projection.graceMs },
};
};
@@ -18,6 +18,7 @@ const { test, run } = harness('api');
@@ -18,6 +18,7 @@ const { test, run } = harness('api');
test('health, release and truthful readiness', async () => {
test('health, release and truthful readiness', async () => {
const h = await t.call('GET', '/api/health', { token: null });
const h = await t.call('GET', '/api/health', { token: null });
assert.strictEqual(h.json.service, 'vip');
assert.strictEqual(h.json.service, 'vip');
+ assert.strictEqual(typeof h.json.events.pending, 'number', 'outbox counts are numbers (an unawaited query serialized as {})');
const rel = await t.call('GET', '/release.json', { token: null });
const rel = await t.call('GET', '/release.json', { token: null });
assert.strictEqual(rel.json.service, 'vip');
assert.strictEqual(rel.json.service, 'vip');
assert.deepStrictEqual(require('openvibe-contracts').validate('registry.release-manifest@1', rel.json).errors, []);
assert.deepStrictEqual(require('openvibe-contracts').validate('registry.release-manifest@1', rel.json).errors, []);