Skip to content

feat: add idempotency keys to delivery creation and escrow funding (#145) - #155

Merged
Tybravo merged 1 commit into
SwiftChainn:mainfrom
mmotunrayo:feat/idempotency-keys
Aug 28, 2026
Merged

feat: add idempotency keys to delivery creation and escrow funding (#145)#155
Tybravo merged 1 commit into
SwiftChainn:mainfrom
mmotunrayo:feat/idempotency-keys

Conversation

@mmotunrayo

@mmotunrayo mmotunrayo commented Aug 28, 2026

Copy link
Copy Markdown
Contributor

Closes #145


What changed

New files

  • src/middlewares/idempotency.ts
    requireIdempotencyKey Express middleware that enforces the Idempotency-Key header on protected POST endpoints. Intercepts res.json to capture response bodies and replays cached responses for duplicate requests.

  • src/services/idempotency.service.ts
    IdempotencyService with dual-store strategy:

    • Redis (primary) when REDIS_URL is configured; uses SET NX for atomic first-write protection against concurrent duplicates.
    • MongoDB (fallback) when Redis is absent; uses findOneAndUpdate with $setOnInsert for the same atomicity guarantee.

    Exposes get / markProcessing / markCompleted / markFailed methods.

  • src/models/IdempotencyRecord.ts
    Mongoose model with a composite unique index on (key, endpoint) and a TTL index on expiresAt for automatic record expiry.

  • src/config/redis.ts
    Lazy ioredis singleton with exponential back-off retry and graceful degradation — when REDIS_URL is absent or Redis is unreachable the app continues using the MongoDB fallback store.

Modified files

  • src/routes/delivery.routes.ts
    Added requireIdempotencyKey before the POST / handler so every delivery creation request must carry an Idempotency-Key header.

  • src/routes/escrow.routes.ts
    Added POST /fund endpoint protected by requireIdempotencyKey + validateRequest(fundEscrowBodySchema). Mounted escrow routes in src/routes/index.ts at /api/v1/escrow.

  • src/routes/index.ts
    Mounted escrow routes so /api/v1/escrow/* is now reachable.

  • src/controllers/escrow.controller.ts
    Added fund() handler that delegates to EscrowService.fund().

  • src/services/escrow.service.ts
    Added fund() method as the HTTP-layer entry point for escrow funding; delegates to existing recordEscrowFunded() for write logic and re-fetches the persisted document for the response.

  • src/validators/escrowValidator.ts
    Added fundEscrowBodySchema (Zod v4) for request-body validation of the POST /fund endpoint.

  • src/models/Escrow.ts
    Fixed pre-existing duplicate-schema merge bug that caused 37 TypeScript errors; retained the EscrowLockStatus / transactions schema that the service layer depends on.

  • src/config/env.ts
    Added REDIS_URL (optional) and IDEMPOTENCY_TTL_SECONDS (default 86400 s) to envSchema and EnvConfig interface.

  • package.json / package-lock.json
    Added [email protected] runtime dependency.

  • .env.example
    Documented REDIS_URL and IDEMPOTENCY_TTL_SECONDS variables.

Idempotency behaviour

Scenario Response
Missing Idempotency-Key header 422 Unprocessable Entity
First request Processed normally; response cached under key
Duplicate (original completed) 200/201 replayed from cache, no DB writes
Duplicate (original in-flight) 409 Conflict
Key TTL Configurable via IDEMPOTENCY_TTL_SECONDS (default 24 h)
#closes 145

…wiftChainn#145)

Closes SwiftChainn#145

## What changed

### New files
- src/middlewares/idempotency.ts
  requireIdempotencyKey Express middleware that enforces the
  Idempotency-Key header on protected POST endpoints. Intercepts
  res.json to capture response bodies and replays cached responses
  for duplicate requests.

- src/services/idempotency.service.ts
  IdempotencyService with dual-store strategy:
    - Redis (primary)  when REDIS_URL is configured; uses SET NX for
      atomic first-write protection against concurrent duplicates.
    - MongoDB (fallback) when Redis is absent; uses findOneAndUpdate
      with  for the same atomicity guarantee.
  Exposes get / markProcessing / markCompleted / markFailed methods.

- src/models/IdempotencyRecord.ts
  Mongoose model with a composite unique index on (key, endpoint) and
  a TTL index on expiresAt for automatic record expiry.

- src/config/redis.ts
  Lazy ioredis singleton with exponential back-off retry and graceful
  degradation — when REDIS_URL is absent or Redis is unreachable the
  app continues using the MongoDB fallback store.

### Modified files
- src/routes/delivery.routes.ts
  Added requireIdempotencyKey before the POST / handler so every
  delivery creation request must carry an Idempotency-Key header.

- src/routes/escrow.routes.ts
  Added POST /fund endpoint protected by requireIdempotencyKey +
  validateRequest(fundEscrowBodySchema). Mounted escrow routes in
  src/routes/index.ts at /api/v1/escrow.

- src/routes/index.ts
  Mounted escrow routes so /api/v1/escrow/* is now reachable.

- src/controllers/escrow.controller.ts
  Added fund() handler that delegates to EscrowService.fund().

- src/services/escrow.service.ts
  Added fund() method as the HTTP-layer entry point for escrow
  funding; delegates to existing recordEscrowFunded() for write
  logic and re-fetches the persisted document for the response.

- src/validators/escrowValidator.ts
  Added fundEscrowBodySchema (Zod v4) for request-body validation
  of the POST /fund endpoint.

- src/models/Escrow.ts
  Fixed pre-existing duplicate-schema merge bug that caused 37
  TypeScript errors; retained the EscrowLockStatus / transactions
  schema that the service layer depends on.

- src/config/env.ts
  Added REDIS_URL (optional) and IDEMPOTENCY_TTL_SECONDS (default
  86400 s) to envSchema and EnvConfig interface.

- package.json / package-lock.json
  Added [email protected] runtime dependency.

- .env.example
  Documented REDIS_URL and IDEMPOTENCY_TTL_SECONDS variables.

## Idempotency behaviour
- Missing header       -> 422 Unprocessable Entity
- First request        -> processed normally; response cached under key
- Duplicate (complete) -> 200/201 replayed from cache, no DB writes
- Duplicate (in-flight)-> 409 Conflict
- Key TTL              -> configurable via IDEMPOTENCY_TTL_SECONDS (default 24 h)
@drips-wave

drips-wave Bot commented Aug 28, 2026

Copy link
Copy Markdown

@mmotunrayo Great news! 🎉 Based on an automated assessment of this PR, the linked Wave issue(s) no longer count against your application limits.

You can now already apply to more issues while waiting for a review of this PR. Keep up the great work! 🚀

Learn more about application limits

@Tybravo
Tybravo merged commit 665a2b7 into SwiftChainn:main Aug 28, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Enhancement] Add Idempotency keys to Delivery Creation and Escrow Funding endpoints

2 participants