first commit

This commit is contained in:
2026-05-29 08:20:56 +01:00
commit 949299f58d
6368 changed files with 527176 additions and 0 deletions

View File

@@ -0,0 +1,110 @@
# `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 on `PublishResultRecord.errors`.
- `projectSubscription` — internal entry → public read-only view.
- `buildEmitOptions` — sparse builder for `EventBus.emitAsync`
options.
- `recordPublishMetric` — one-call OTel emission for every publish.
## Usage: Basic
```ts
import {
nextSubscriptionId,
stableHash,
} from "@suaveplan/api-plugin-event-bus/services/event-bus-service";
const id = nextSubscriptionId();
const key = stableHash({ x: 1 });
```
## Usage: Advanced
```ts
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.
- `stableHash` uses `JSON.stringify` which is not key-order stable
across all engines for objects with non-enumerable properties.
Callers requiring canonical hashing supply an explicit
`idempotencyKey` function at subscribe time.
- `toErrorReport`'s `EVENT_BUS.LISTENER_FAILED` envelope is a
surface-level code — it is NOT registered in `EVENT_BUS_CODES`
because nothing in the package throws it (it only appears in
`PublishResultRecord.errors`).
- `projectSubscription` returns a `readonly` shape so consumers
cannot mutate the record they got from `listSubscriptions`.