169 lines
5.2 KiB
JavaScript
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,
|
|
};
|
|
}
|