Skip to content

@dukkan.one/app-sdk/idempotency

Idempotency-Key helpers, canonical JSON as the platform hashes it, and the inbox contract the receiver deduplicates on.

Package
@dukkan.one/app-sdk@0.3.0
API revision
2026-09-10
Import
import { … } from "@dukkan.one/app-sdk/idempotency"
On this page

Functions

canonicalJson#

function canonicalJson(value: unknown): string

Canonical JSON exactly as the platform hashes request bodies (packages/contracts/vectors/canonical-json.json): keys sorted at every depth, arrays in order, no whitespace. Useful to detect, client-side, that two payloads would collide on one key.

deterministicIdempotencyKey#

function deterministicIdempotencyKey(namespace: string, ...parts: Array<string | number>): Promise<string>

Deterministic key from your own entity identity, so a crashed process that re-runs the same job sends the same key and gets the recorded result instead of a second refund: deterministicIdempotencyKey("refund", orderId, jobId). Namespaced and hashed, so the parts can be any length and never leak.

randomIdempotencyKey#

function randomIdempotencyKey(): string

Random key for a write that has no natural identity (reused across the SDK's own retries).

requestHash#

function requestHash(payload: unknown): Promise<string>

sha256 of the canonical JSON of a payload: the value the platform compares when the same key arrives with a different body.

resolveIdempotencyKey#

function resolveIdempotencyKey(operation: string, options: IdempotencyOptions | undefined): Promise<string>

Picks the key for a write: an explicit key, else a deterministic key from ref, else a random one.

Classes

MemoryInboxStore#

class MemoryInboxStore implements InboxStore

new MemoryInboxStore(options?

In-memory inbox for tests and single-process tools; bounded so it never grows forever.

Members
NameTypeDescription
insertIfNewRequired(record: InboxRecord): Promise<boolean>
markFailedRequired(eventId: string, error: string): Promise<void>

Called when a handler failed AFTER the event was recorded, so the app can keep the row visible for a replay instead of losing the event behind the dedupe. Optional; the receiver never throws past the acknowledgement.

failuresRequired(): Map<string, string>

Events whose handler failed, with the error message, for replays and assertions.

sizeRequirednumber
recordsRequired(): InboxRecord[]

Interfaces

IdempotencyOptions#

interface IdempotencyOptions

How a write chooses its Idempotency-Key: an explicit key, or parts that identify the write in your own system.

Members
NameTypeDescription
keystring | undefined

Explicit key; wins over ref.

refArray<string | number> | undefined

Parts that identify the write in YOUR system (an order id plus a job id, a queue message id...). The SDK derives a stable key from them.

InboxRecord#

interface InboxRecord

One accepted delivery as the receiver hands it to the inbox: the signed event id, the install and store, the topic, and the payload.

Members
NameTypeDescription
eventIdRequiredstring
installIdRequiredstring
storeIdRequiredstring
topicRequiredstring
sequenceRequirednumber
occurredAtRequiredstring
testRequiredboolean
payloadRequiredRecord<string, unknown>
receivedAtRequiredstring

InboxStore#

interface InboxStore

Durable inbox the webhook receiver deduplicates on. insertIfNew must be atomic (a unique index on the event id in SQL, a conditional put in KV).

Members
NameTypeDescription
insertIfNewRequired(record: InboxRecord): Promise<boolean>
markFailed((eventId: string, error: string) => Promise<void>) | undefined

Called when a handler failed AFTER the event was recorded, so the app can keep the row visible for a replay instead of losing the event behind the dedupe. Optional; the receiver never throws past the acknowledgement.

Constants

IDEMPOTENCY_KEY_MAX_LENGTH#

const IDEMPOTENCY_KEY_MAX_LENGTH: 200

Idempotency-Key helpers (docs/reference/api#idempotency). The platform remembers a key for 24 hours per install: the same key with the same body replays the recorded response, the same key with a different body is a 409.