verae-middleware/src/services/timestampService.js

169 lines
5.2 KiB
JavaScript

/**
* @fileoverview Timestamp create/status operations with billing + job enqueue.
* @module services/timestampService
*/
import { config } from '../config.js';
import { veraeClient } from '../clients/veraeClient.js';
import { enqueueJob } from '../store/jobWatchers.js';
import { checkEntitlement, recordUsage } from './entitlementService.js';
import { createDebugger } from '../debug/logger.js';
import { getTraceId } from '../debug/trace-context.js';
const log = createDebugger('jobs');
/**
* Enqueue async job watch via NATS or in-process store.
* @param {object} ctx
* @param {string} jobId
*/
async function enqueueWatchForJob(ctx, jobId) {
const traceId = getTraceId() ?? undefined;
if (config.natsEnabled) {
const { enqueueWatch } = await import('../nats/publishers.js');
await enqueueWatch({
tenantId: ctx.tenantId,
jobId,
// Prefer re-login in worker; include token for MVP simplicity when mock
veraeToken: ctx.veraeToken,
maxAttempts: config.jobPollMaxAttempts,
intervalMs: config.jobPollIntervalMs,
traceId,
});
log.debug('watch enqueued on NATS', { jobId, tenantId: ctx.tenantId });
return;
}
enqueueJob({
tenantId: ctx.tenantId,
jobId,
veraeToken: ctx.veraeToken,
traceId,
});
}
/**
* @param {object} ctx - Auth context with tenantId, veraeToken
* @param {{ data?: string, hashAlg?: string, sha256?: string, publicMetadata?: object, privateMetadata?: object }} body
* @returns {Promise<{ jobId: string, sha256?: string, existing?: boolean }>}
*/
export async function createTimestamp(ctx, body) {
checkEntitlement(ctx.tenantId, 'timestamp');
const result = await veraeClient.createTimestamp(ctx.veraeToken, body);
recordUsage(ctx.tenantId, 'timestamp');
await enqueueWatchForJob(ctx, result.jobId);
log.debug('timestamp created', { jobId: result.jobId, tenantId: ctx.tenantId });
return result;
}
/**
* @param {object} ctx
* @param {{ data: string, hashAlg?: string }} body
* @returns {Promise<object>} StatusResponse
*/
export async function createTimestampAndWait(ctx, body) {
const created = await createTimestamp(ctx, body);
if (config.natsEnabled) {
const { waitForJobEvent } = await import('../nats/wait.js');
const event = await waitForJobEvent(created.jobId, config.waitTimeoutMs);
if (event?.status) {
recordUsage(ctx.tenantId, 'status');
log.debug('wait via NATS event', {
jobId: created.jobId,
event: event.event,
traceId: event.traceId,
});
return event.status;
}
const status = await veraeClient.getStatus(ctx.veraeToken, created.jobId);
recordUsage(ctx.tenantId, 'status');
if (status.status === 'pending') {
return { id: created.jobId, jobId: created.jobId, status: 'pending' };
}
return status;
}
try {
const status = await veraeClient.waitForJob(ctx.veraeToken, created.jobId, {
maxAttempts: config.jobPollMaxAttempts,
intervalMs: config.jobPollIntervalMs,
});
recordUsage(ctx.tenantId, 'status');
return status;
} catch {
const status = await veraeClient.getStatus(ctx.veraeToken, created.jobId);
recordUsage(ctx.tenantId, 'status');
return { id: created.jobId, jobId: created.jobId, status: status.status ?? 'pending' };
}
}
/**
* @param {object} ctx
* @param {{ items: Array<{ data: string, hashAlg?: string }> }} body
*/
export async function createBatchTimestamp(ctx, body) {
const itemCount = body.items?.length ?? 0;
checkEntitlement(ctx.tenantId, 'batch_timestamp', { amount: itemCount });
const result = await veraeClient.createBatchTimestamp(ctx.veraeToken, body);
recordUsage(ctx.tenantId, 'batch_timestamp', { amount: itemCount });
for (const jobId of result.jobIds ?? []) {
await enqueueWatchForJob(ctx, jobId);
}
return result;
}
/**
* @param {object} ctx
* @param {string} jobId
*/
export async function getJobStatus(ctx, jobId) {
const status = await veraeClient.getStatus(ctx.veraeToken, jobId);
recordUsage(ctx.tenantId, 'status');
return status;
}
/**
* @param {object} ctx
* @param {{ jobIds: string[] }} body
*/
export async function getBatchJobStatus(ctx, body) {
const status = await veraeClient.getBatchStatus(ctx.veraeToken, body);
recordUsage(ctx.tenantId, 'status', { amount: body.jobIds?.length ?? 1 });
return status;
}
/**
* @param {object} ctx
* @param {string} jobId
*/
export async function getJobVerification(ctx, jobId) {
const status = await veraeClient.getJobVerification(ctx.veraeToken, jobId);
recordUsage(ctx.tenantId, 'status');
return status;
}
/**
* @param {object} ctx
* @param {string} sha256
* @param {{ includeAttached?: boolean, includeTree?: boolean }} [opts]
*/
export async function lookupHash(ctx, sha256, opts = {}) {
const result = await veraeClient.lookupHash(ctx.veraeToken, sha256);
recordUsage(ctx.tenantId, 'status');
if (!opts.includeAttached && !opts.includeTree) return result;
return {
...result,
includeAttached: Boolean(opts.includeAttached),
includeTree: Boolean(opts.includeTree),
note:
opts.includeTree && !result?.exists
? 'Chain miss: middleware should broadcast verae.archive.query kinds=["tree"] (wired in simulator; live aggregator hook TBD).'
: undefined,
};
}