3.5 KiB
helpers.ts — small utilities used by event-bus-service.ts
Purpose
Holds the pure helpers extracted from the main service body so the service itself stays under the 300-line cap and each helper can be unit-tested in isolation. The helpers cover id generation, payload hashing, error projection, subscription projection, emit-option assembly, and OTel metric emission.
Features
nextSubscriptionId— collision-resistant id generator.stableHash— deterministic-enough payload hash for default idempotency keying.toErrorReport— converts upstream listener errors into the wire-level shape advertised onPublishResultRecord.errors.projectSubscription— internal entry → public read-only view.buildEmitOptions— sparse builder forEventBus.emitAsyncoptions.recordPublishMetric— one-call OTel emission for every publish.
Usage: Basic
import {
nextSubscriptionId,
stableHash,
} from "@suaveplan/api-plugin-event-bus/services/event-bus-service";
const id = nextSubscriptionId();
const key = stableHash({ x: 1 });
Usage: Advanced
import {
buildEmitOptions,
recordPublishMetric,
toErrorReport,
} from "@suaveplan/api-plugin-event-bus/services/event-bus-service";
const emitOptions = buildEmitOptions({
signal: AbortSignal.timeout(2000),
timeoutMs: 2000,
idempotencyKey: "k1",
});
const t0 = Date.now();
try {
// ... emit
recordPublishMetric("order.created", "success", Date.now() - t0);
} catch (err) {
recordPublishMetric("order.created", "error", Date.now() - t0);
const report = toErrorReport(err);
console.error(report.code, report.message);
}
API Reference
nextSubscriptionId(): string
Returns `sub_${n}_${base36}` ids; the base36 suffix keeps two
collocated service instances from colliding when the module-level
counter desyncs across workers.
stableHash(payload: unknown): string
JSON-stringifies the payload (falling back to String(payload) on
throw). Used as the default idempotency-key derivation when a
subscription is registered without an explicit idempotencyKey
function.
toErrorReport(err: unknown): PublishErrorReport
Projects a listener error into { code, message }. Preserves
EventsError.code; falls back to EVENT_BUS.LISTENER_FAILED.
projectSubscription(entry): SubscriptionRecordView
Strips internal fields and conditionally omits tenantId when
absent.
buildEmitOptions(opts): EmitAsyncOptions
Sparse builder — only sets fields the caller explicitly provided.
recordPublishMetric(topic, outcome, durationMs): void
Increments event_bus.published_total{outcome,topic_root} and
records the publish duration on event_bus.publish_duration_ms.
Implementation Notes
nextSubscriptionId's counter resets on module reload (test isolation) and per-process. Production collisions are guarded by the random suffix, which gives ~36⁸ ≈ 2.8e12 ids per counter value.stableHashusesJSON.stringifywhich is not key-order stable across all engines for objects with non-enumerable properties. Callers requiring canonical hashing supply an explicitidempotencyKeyfunction at subscribe time.toErrorReport'sEVENT_BUS.LISTENER_FAILEDenvelope is a surface-level code — it is NOT registered inEVENT_BUS_CODESbecause nothing in the package throws it (it only appears inPublishResultRecord.errors).projectSubscriptionreturns areadonlyshape so consumers cannot mutate the record they got fromlistSubscriptions.