콘텐츠로 이동

JobRunStore — @gj-kit/nest-operations-jobs

@gj-kit/nest-operations-jobs/core에서 공개하는 interface입니다. package version 0.1.1의 release declaration을 그대로 표시합니다.

import { JobRunStore } from '@gj-kit/nest-operations-jobs/core';
/**
* Persistence port for job runs. The library owns no schema; a host maps these
* five operations onto its own table.
*
* The concurrency obligations below are part of the contract, and
* `jobRunStoreContractCases()` from the `./testing` subpath checks them.
*
* - **S1 single-claimer** — for one `overlapKey` at most one `RUNNING` row exists
* at any instant. `claim` is an atomic compare-and-set, never "read, then
* insert if absent". The loser returns `null` rather than throwing. The only
* exception convertible to `null` is the overlap uniqueness violation; every
* other constraint violation, connection error or serialisation failure must
* be rethrown so the runner reports `ERR_JOB_STORE`. Implement with a
* **partial** unique index (`WHERE status = 'RUNNING'`) and narrow the caught
* violation by constraint name — swallowing every uniqueness violation turns a
* permanently blocked job into a stream of green SKIPPED responses.
* - **S2 monotonic heartbeat** — `heartbeat` advances the liveness watermark to
* the store's own current time and returns `true` only while the row is
* `RUNNING`. The watermark never moves backwards. A row that is no longer
* `RUNNING` is left untouched and answered `false`.
* - **S3 idempotent completion** — `complete` writes only on a
* `RUNNING -> terminal` transition and returns `true`. An already-settled row
* is left untouched and answered `false`. Terminal is final.
* - **S4 atomic reap** — `reapStale` transitions matching stale `RUNNING` rows to
* `TIMED_OUT` in a single statement and returns only the count it actually
* moved, so two concurrent reapers never double-count. Releasing the overlap
* key is immediate.
* - **S5 run id uniqueness** — ids returned by `claim` and `recordSkipped` are
* globally unique and immutable for the row's lifetime.
* - **S6 clock axis split** — recording instants (`startedAt`, `at`,
* `finishedAt`) come from the runner's injected clock and are stored verbatim.
* Liveness instants (the heartbeat watermark and the stale cutoff) come from
* the store's own clock only. `claim` must initialise the watermark from that
* clock; a null watermark is never reapable and would hold the overlap key
* forever.
* - **S7 input/summary round trip** — `input` and `summary` are stored as
* JSON-round-trippable values; a value that cannot be stored raises rather
* than being silently dropped.
*/
interface JobRunStore {
/** Atomically take the overlap key. `null` means another run holds it. */
claim(request: JobRunClaimRequest): Promise<JobRunClaim | null>;
/** `false` means the run is no longer RUNNING and the claim is gone. */
heartbeat(request: JobRunHeartbeatRequest): Promise<boolean>;
/** `false` means the run was already settled; the stored outcome is unchanged. */
complete(request: JobRunCompleteRequest): Promise<boolean>;
/** Record a run that never executed. Returns the new run's id. */
recordSkipped(request: JobRunSkippedRequest): Promise<JobRunClaim>;
/** Abandon stale RUNNING rows as TIMED_OUT. Returns how many this call moved. */
reapStale(request: JobRunReapRequest): Promise<number>;
}

이 선언은 매개변수, optionality, 제네릭, 반환값, 공개 union/type 계약의 정본입니다. 호출 전 필요한 환경·권한·오류 경계는 패키지 Golden path와 이 subpath의 import 조건을 함께 확인하세요.

  • 패키지: @gj-kit/nest-operations-jobs
  • 버전: 0.1.1
  • 공개 entry: ./core
  • 소스: GitHub

Persistence port for job runs. The library owns no schema; a host maps these five operations onto its own table.

The concurrency obligations below are part of the contract, and jobRunStoreContractCases() from the ./testing subpath checks them.

  • S1 single-claimer — for one overlapKey at most one RUNNING row exists at any instant. claim is an atomic compare-and-set, never “read, then insert if absent”. The loser returns null rather than throwing. The only exception convertible to null is the overlap uniqueness violation; every other constraint violation, connection error or serialisation failure must be rethrown so the runner reports ERR_JOB_STORE. Implement with a partial unique index (WHERE status = 'RUNNING') and narrow the caught violation by constraint name — swallowing every uniqueness violation turns a permanently blocked job into a stream of green SKIPPED responses.
  • S2 monotonic heartbeatheartbeat advances the liveness watermark to the store’s own current time and returns true only while the row is RUNNING. The watermark never moves backwards. A row that is no longer RUNNING is left untouched and answered false.
  • S3 idempotent completioncomplete writes only on a RUNNING -> terminal transition and returns true. An already-settled row is left untouched and answered false. Terminal is final.
  • S4 atomic reapreapStale transitions matching stale RUNNING rows to TIMED_OUT in a single statement and returns only the count it actually moved, so two concurrent reapers never double-count. Releasing the overlap key is immediate.
  • S5 run id uniqueness — ids returned by claim and recordSkipped are globally unique and immutable for the row’s lifetime.
  • S6 clock axis split — recording instants (startedAt, at, finishedAt) come from the runner’s injected clock and are stored verbatim. Liveness instants (the heartbeat watermark and the stale cutoff) come from the store’s own clock only. claim must initialise the watermark from that clock; a null watermark is never reapable and would hold the overlap key forever.
  • S7 input/summary round tripinput and summary are stored as JSON-round-trippable values; a value that cannot be stored raises rather than being silently dropped.