hono-idempotency 0.7.2 → 0.8.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/chunk-C2JZZSOP.js +9 -0
- package/dist/chunk-C2JZZSOP.js.map +1 -0
- package/dist/index.cjs +49 -12
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +2 -2
- package/dist/index.d.ts +2 -2
- package/dist/index.js +47 -12
- package/dist/index.js.map +1 -1
- package/dist/stores/cloudflare-d1.cjs +28 -8
- package/dist/stores/cloudflare-d1.cjs.map +1 -1
- package/dist/stores/cloudflare-d1.d.cts +1 -1
- package/dist/stores/cloudflare-d1.d.ts +1 -1
- package/dist/stores/cloudflare-d1.js +27 -8
- package/dist/stores/cloudflare-d1.js.map +1 -1
- package/dist/stores/cloudflare-kv.cjs +32 -8
- package/dist/stores/cloudflare-kv.cjs.map +1 -1
- package/dist/stores/cloudflare-kv.d.cts +1 -1
- package/dist/stores/cloudflare-kv.d.ts +1 -1
- package/dist/stores/cloudflare-kv.js +31 -8
- package/dist/stores/cloudflare-kv.js.map +1 -1
- package/dist/stores/durable-objects.cjs +10 -2
- package/dist/stores/durable-objects.cjs.map +1 -1
- package/dist/stores/durable-objects.d.cts +1 -1
- package/dist/stores/durable-objects.d.ts +1 -1
- package/dist/stores/durable-objects.js +9 -2
- package/dist/stores/durable-objects.js.map +1 -1
- package/dist/stores/memory.cjs +16 -3
- package/dist/stores/memory.cjs.map +1 -1
- package/dist/stores/memory.d.cts +1 -1
- package/dist/stores/memory.d.ts +1 -1
- package/dist/stores/memory.js +15 -3
- package/dist/stores/memory.js.map +1 -1
- package/dist/stores/redis.cjs +33 -5
- package/dist/stores/redis.cjs.map +1 -1
- package/dist/stores/redis.d.cts +1 -1
- package/dist/stores/redis.d.ts +1 -1
- package/dist/stores/redis.js +32 -5
- package/dist/stores/redis.js.map +1 -1
- package/dist/{types-C_OW_leh.d.cts → types-7IwFeI0l.d.cts} +6 -2
- package/dist/{types-C_OW_leh.d.ts → types-7IwFeI0l.d.ts} +6 -2
- package/package.json +2 -2
package/dist/stores/redis.js
CHANGED
|
@@ -1,3 +1,7 @@
|
|
|
1
|
+
import {
|
|
2
|
+
RECORD_STATUS_COMPLETED
|
|
3
|
+
} from "../chunk-C2JZZSOP.js";
|
|
4
|
+
|
|
1
5
|
// src/stores/redis.ts
|
|
2
6
|
var DEFAULT_TTL = 86400;
|
|
3
7
|
function redisStore(options) {
|
|
@@ -6,19 +10,42 @@ function redisStore(options) {
|
|
|
6
10
|
async get(key) {
|
|
7
11
|
const raw = await client.get(key);
|
|
8
12
|
if (!raw) return void 0;
|
|
9
|
-
|
|
13
|
+
try {
|
|
14
|
+
return JSON.parse(raw);
|
|
15
|
+
} catch {
|
|
16
|
+
return void 0;
|
|
17
|
+
}
|
|
10
18
|
},
|
|
11
19
|
async lock(key, record) {
|
|
12
|
-
|
|
20
|
+
let serialized;
|
|
21
|
+
try {
|
|
22
|
+
serialized = JSON.stringify(record);
|
|
23
|
+
} catch {
|
|
24
|
+
return false;
|
|
25
|
+
}
|
|
26
|
+
const result = await client.set(key, serialized, { NX: true, EX: ttl });
|
|
13
27
|
return result === "OK";
|
|
14
28
|
},
|
|
15
29
|
async complete(key, response) {
|
|
16
30
|
const raw = await client.get(key);
|
|
17
31
|
if (!raw) return;
|
|
18
|
-
|
|
19
|
-
|
|
32
|
+
let record;
|
|
33
|
+
try {
|
|
34
|
+
record = JSON.parse(raw);
|
|
35
|
+
} catch {
|
|
36
|
+
return;
|
|
37
|
+
}
|
|
38
|
+
record.status = RECORD_STATUS_COMPLETED;
|
|
20
39
|
record.response = response;
|
|
21
|
-
|
|
40
|
+
const elapsed = Math.floor((Date.now() - record.createdAt) / 1e3);
|
|
41
|
+
const remaining = Math.max(1, ttl - elapsed);
|
|
42
|
+
let serialized;
|
|
43
|
+
try {
|
|
44
|
+
serialized = JSON.stringify(record);
|
|
45
|
+
} catch {
|
|
46
|
+
return;
|
|
47
|
+
}
|
|
48
|
+
await client.set(key, serialized, { EX: remaining });
|
|
22
49
|
},
|
|
23
50
|
async delete(key) {
|
|
24
51
|
await client.del(key);
|
package/dist/stores/redis.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/stores/redis.ts"],"sourcesContent":["import type
|
|
1
|
+
{"version":3,"sources":["../../src/stores/redis.ts"],"sourcesContent":["import { type IdempotencyRecord, RECORD_STATUS_COMPLETED, type StoredResponse } from \"../types.js\";\nimport type { IdempotencyStore } from \"./types.js\";\n\nconst DEFAULT_TTL = 86400; // 24 hours in seconds\n\n/** Minimal Redis client subset compatible with ioredis, node-redis, and @upstash/redis. */\nexport interface RedisClientLike {\n\tget(key: string): Promise<string | null>;\n\tset(key: string, value: string, options?: { NX?: boolean; EX?: number }): Promise<string | null>;\n\tdel(...keys: string[]): Promise<number>;\n}\n\nexport interface RedisStoreOptions {\n\t/** Redis client instance (ioredis, node-redis, or @upstash/redis). */\n\tclient: RedisClientLike;\n\t/** TTL in seconds (default: 86400 = 24h). Passed as Redis EX option. */\n\tttl?: number;\n}\n\nexport function redisStore(options: RedisStoreOptions): IdempotencyStore {\n\tconst { client, ttl = DEFAULT_TTL } = options;\n\n\treturn {\n\t\tasync get(key) {\n\t\t\tconst raw = await client.get(key);\n\t\t\tif (!raw) return undefined;\n\t\t\ttry {\n\t\t\t\treturn JSON.parse(raw) as IdempotencyRecord;\n\t\t\t} catch {\n\t\t\t\treturn undefined;\n\t\t\t}\n\t\t},\n\n\t\tasync lock(key, record) {\n\t\t\tlet serialized: string;\n\t\t\ttry {\n\t\t\t\tserialized = JSON.stringify(record);\n\t\t\t} catch {\n\t\t\t\treturn false;\n\t\t\t}\n\t\t\tconst result = await client.set(key, serialized, { NX: true, EX: ttl });\n\t\t\treturn result === \"OK\";\n\t\t},\n\n\t\tasync complete(key, response) {\n\t\t\tconst raw = await client.get(key);\n\t\t\tif (!raw) return;\n\t\t\tlet record: IdempotencyRecord;\n\t\t\ttry {\n\t\t\t\trecord = JSON.parse(raw) as IdempotencyRecord;\n\t\t\t} catch {\n\t\t\t\treturn;\n\t\t\t}\n\t\t\trecord.status = RECORD_STATUS_COMPLETED;\n\t\t\trecord.response = response;\n\t\t\tconst elapsed = Math.floor((Date.now() - record.createdAt) / 1000);\n\t\t\tconst remaining = Math.max(1, ttl - elapsed);\n\t\t\tlet serialized: string;\n\t\t\ttry {\n\t\t\t\tserialized = JSON.stringify(record);\n\t\t\t} catch {\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tawait client.set(key, serialized, { EX: remaining });\n\t\t},\n\n\t\tasync delete(key) {\n\t\t\tawait client.del(key);\n\t\t},\n\n\t\tasync purge() {\n\t\t\t// Redis handles expiration automatically via EX — no manual purge needed\n\t\t\treturn 0;\n\t\t},\n\t};\n}\n"],"mappings":";;;;;AAGA,IAAM,cAAc;AAgBb,SAAS,WAAW,SAA8C;AACxE,QAAM,EAAE,QAAQ,MAAM,YAAY,IAAI;AAEtC,SAAO;AAAA,IACN,MAAM,IAAI,KAAK;AACd,YAAM,MAAM,MAAM,OAAO,IAAI,GAAG;AAChC,UAAI,CAAC,IAAK,QAAO;AACjB,UAAI;AACH,eAAO,KAAK,MAAM,GAAG;AAAA,MACtB,QAAQ;AACP,eAAO;AAAA,MACR;AAAA,IACD;AAAA,IAEA,MAAM,KAAK,KAAK,QAAQ;AACvB,UAAI;AACJ,UAAI;AACH,qBAAa,KAAK,UAAU,MAAM;AAAA,MACnC,QAAQ;AACP,eAAO;AAAA,MACR;AACA,YAAM,SAAS,MAAM,OAAO,IAAI,KAAK,YAAY,EAAE,IAAI,MAAM,IAAI,IAAI,CAAC;AACtE,aAAO,WAAW;AAAA,IACnB;AAAA,IAEA,MAAM,SAAS,KAAK,UAAU;AAC7B,YAAM,MAAM,MAAM,OAAO,IAAI,GAAG;AAChC,UAAI,CAAC,IAAK;AACV,UAAI;AACJ,UAAI;AACH,iBAAS,KAAK,MAAM,GAAG;AAAA,MACxB,QAAQ;AACP;AAAA,MACD;AACA,aAAO,SAAS;AAChB,aAAO,WAAW;AAClB,YAAM,UAAU,KAAK,OAAO,KAAK,IAAI,IAAI,OAAO,aAAa,GAAI;AACjE,YAAM,YAAY,KAAK,IAAI,GAAG,MAAM,OAAO;AAC3C,UAAI;AACJ,UAAI;AACH,qBAAa,KAAK,UAAU,MAAM;AAAA,MACnC,QAAQ;AACP;AAAA,MACD;AACA,YAAM,OAAO,IAAI,KAAK,YAAY,EAAE,IAAI,UAAU,CAAC;AAAA,IACpD;AAAA,IAEA,MAAM,OAAO,KAAK;AACjB,YAAM,OAAO,IAAI,GAAG;AAAA,IACrB;AAAA,IAEA,MAAM,QAAQ;AAEb,aAAO;AAAA,IACR;AAAA,EACD;AACD;","names":[]}
|
|
@@ -8,6 +8,8 @@ interface ProblemDetail {
|
|
|
8
8
|
detail: string;
|
|
9
9
|
code: IdempotencyErrorCode;
|
|
10
10
|
}
|
|
11
|
+
/** Ensures status is a valid HTTP status code (200-599), defaults to 500. */
|
|
12
|
+
declare function clampHttpStatus(status: number): number;
|
|
11
13
|
declare function problemResponse(problem: ProblemDetail, extraHeaders?: Record<string, string>): Response;
|
|
12
14
|
declare const IdempotencyErrors: {
|
|
13
15
|
readonly missingKey: () => ProblemDetail;
|
|
@@ -50,6 +52,8 @@ interface IdempotencyStore {
|
|
|
50
52
|
purge(): Promise<number>;
|
|
51
53
|
}
|
|
52
54
|
|
|
55
|
+
declare const RECORD_STATUS_PROCESSING: "processing";
|
|
56
|
+
declare const RECORD_STATUS_COMPLETED: "completed";
|
|
53
57
|
interface IdempotencyEnv extends Env {
|
|
54
58
|
Variables: {
|
|
55
59
|
idempotencyKey: string | undefined;
|
|
@@ -74,7 +78,7 @@ interface IdempotencyOptions {
|
|
|
74
78
|
required?: boolean;
|
|
75
79
|
methods?: string[];
|
|
76
80
|
maxKeyLength?: number;
|
|
77
|
-
/** Maximum request body size in bytes.
|
|
81
|
+
/** Maximum request body size in bytes. Pre-checked via Content-Length header, then enforced against actual body byte length. */
|
|
78
82
|
maxBodySize?: number;
|
|
79
83
|
/** Should be a lightweight, side-effect-free predicate. Avoid reading the request body. */
|
|
80
84
|
skipRequest?: (c: Context) => boolean | Promise<boolean>;
|
|
@@ -95,4 +99,4 @@ interface IdempotencyOptions {
|
|
|
95
99
|
onCacheMiss?: (key: string, c: Context) => void | Promise<void>;
|
|
96
100
|
}
|
|
97
101
|
|
|
98
|
-
export { type IdempotencyOptions as I, type ProblemDetail as P, type StoredResponse as S, type IdempotencyEnv as a, type IdempotencyErrorCode as b, IdempotencyErrors as c, type IdempotencyRecord as d, type IdempotencyStore as e, problemResponse as p };
|
|
102
|
+
export { type IdempotencyOptions as I, type ProblemDetail as P, RECORD_STATUS_COMPLETED as R, type StoredResponse as S, type IdempotencyEnv as a, type IdempotencyErrorCode as b, IdempotencyErrors as c, type IdempotencyRecord as d, type IdempotencyStore as e, RECORD_STATUS_PROCESSING as f, clampHttpStatus as g, problemResponse as p };
|
|
@@ -8,6 +8,8 @@ interface ProblemDetail {
|
|
|
8
8
|
detail: string;
|
|
9
9
|
code: IdempotencyErrorCode;
|
|
10
10
|
}
|
|
11
|
+
/** Ensures status is a valid HTTP status code (200-599), defaults to 500. */
|
|
12
|
+
declare function clampHttpStatus(status: number): number;
|
|
11
13
|
declare function problemResponse(problem: ProblemDetail, extraHeaders?: Record<string, string>): Response;
|
|
12
14
|
declare const IdempotencyErrors: {
|
|
13
15
|
readonly missingKey: () => ProblemDetail;
|
|
@@ -50,6 +52,8 @@ interface IdempotencyStore {
|
|
|
50
52
|
purge(): Promise<number>;
|
|
51
53
|
}
|
|
52
54
|
|
|
55
|
+
declare const RECORD_STATUS_PROCESSING: "processing";
|
|
56
|
+
declare const RECORD_STATUS_COMPLETED: "completed";
|
|
53
57
|
interface IdempotencyEnv extends Env {
|
|
54
58
|
Variables: {
|
|
55
59
|
idempotencyKey: string | undefined;
|
|
@@ -74,7 +78,7 @@ interface IdempotencyOptions {
|
|
|
74
78
|
required?: boolean;
|
|
75
79
|
methods?: string[];
|
|
76
80
|
maxKeyLength?: number;
|
|
77
|
-
/** Maximum request body size in bytes.
|
|
81
|
+
/** Maximum request body size in bytes. Pre-checked via Content-Length header, then enforced against actual body byte length. */
|
|
78
82
|
maxBodySize?: number;
|
|
79
83
|
/** Should be a lightweight, side-effect-free predicate. Avoid reading the request body. */
|
|
80
84
|
skipRequest?: (c: Context) => boolean | Promise<boolean>;
|
|
@@ -95,4 +99,4 @@ interface IdempotencyOptions {
|
|
|
95
99
|
onCacheMiss?: (key: string, c: Context) => void | Promise<void>;
|
|
96
100
|
}
|
|
97
101
|
|
|
98
|
-
export { type IdempotencyOptions as I, type ProblemDetail as P, type StoredResponse as S, type IdempotencyEnv as a, type IdempotencyErrorCode as b, IdempotencyErrors as c, type IdempotencyRecord as d, type IdempotencyStore as e, problemResponse as p };
|
|
102
|
+
export { type IdempotencyOptions as I, type ProblemDetail as P, RECORD_STATUS_COMPLETED as R, type StoredResponse as S, type IdempotencyEnv as a, type IdempotencyErrorCode as b, IdempotencyErrors as c, type IdempotencyRecord as d, type IdempotencyStore as e, RECORD_STATUS_PROCESSING as f, clampHttpStatus as g, problemResponse as p };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "hono-idempotency",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.8.1",
|
|
4
4
|
"description": "Stripe-style Idempotency-Key middleware for Hono. IETF draft-ietf-httpapi-idempotency-key-header compliant.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.cjs",
|
|
@@ -112,7 +112,7 @@
|
|
|
112
112
|
"@changesets/cli": "2.29.8",
|
|
113
113
|
"@vitest/coverage-v8": "3.2.4",
|
|
114
114
|
"hono": "^4.7.0",
|
|
115
|
-
"hono-problem-details": "0.1.
|
|
115
|
+
"hono-problem-details": "0.1.4",
|
|
116
116
|
"lefthook": "2.1.1",
|
|
117
117
|
"tsup": "^8.0.0",
|
|
118
118
|
"typescript": "^5.7.0",
|