hono-idempotency 0.2.0 → 0.2.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/stores/cloudflare-d1.cjs +14 -4
- package/dist/stores/cloudflare-d1.cjs.map +1 -1
- package/dist/stores/cloudflare-d1.d.cts +3 -1
- package/dist/stores/cloudflare-d1.d.ts +3 -1
- package/dist/stores/cloudflare-d1.js +14 -4
- package/dist/stores/cloudflare-d1.js.map +1 -1
- package/package.json +1 -1
|
@@ -24,8 +24,13 @@ __export(cloudflare_d1_exports, {
|
|
|
24
24
|
});
|
|
25
25
|
module.exports = __toCommonJS(cloudflare_d1_exports);
|
|
26
26
|
var DEFAULT_TABLE = "idempotency_keys";
|
|
27
|
+
var DEFAULT_TTL = 86400;
|
|
28
|
+
var TABLE_NAME_RE = /^[a-zA-Z_][a-zA-Z0-9_]*$/;
|
|
27
29
|
function d1Store(options) {
|
|
28
|
-
const { database: db, tableName = DEFAULT_TABLE } = options;
|
|
30
|
+
const { database: db, tableName = DEFAULT_TABLE, ttl = DEFAULT_TTL } = options;
|
|
31
|
+
if (!TABLE_NAME_RE.test(tableName)) {
|
|
32
|
+
throw new Error(`Invalid table name: "${tableName}". Must match ${TABLE_NAME_RE}`);
|
|
33
|
+
}
|
|
29
34
|
let initialized = false;
|
|
30
35
|
const ensureTable = async () => {
|
|
31
36
|
if (initialized) return;
|
|
@@ -40,6 +45,9 @@ function d1Store(options) {
|
|
|
40
45
|
).run();
|
|
41
46
|
initialized = true;
|
|
42
47
|
};
|
|
48
|
+
const ttlThreshold = () => {
|
|
49
|
+
return Date.now() - ttl * 1e3;
|
|
50
|
+
};
|
|
43
51
|
const toRecord = (row) => ({
|
|
44
52
|
key: row.key,
|
|
45
53
|
fingerprint: row.fingerprint,
|
|
@@ -50,15 +58,17 @@ function d1Store(options) {
|
|
|
50
58
|
return {
|
|
51
59
|
async get(key) {
|
|
52
60
|
await ensureTable();
|
|
53
|
-
const row = await db.prepare(`SELECT * FROM ${tableName} WHERE key = ?`).bind(key).first();
|
|
61
|
+
const row = await db.prepare(`SELECT * FROM ${tableName} WHERE key = ? AND created_at >= ?`).bind(key, ttlThreshold()).first();
|
|
54
62
|
if (!row) return void 0;
|
|
55
63
|
return toRecord(row);
|
|
56
64
|
},
|
|
57
65
|
async lock(key, record) {
|
|
58
66
|
await ensureTable();
|
|
59
67
|
const result = await db.prepare(
|
|
60
|
-
`INSERT OR IGNORE INTO ${tableName} (key, fingerprint, status, response, created_at)
|
|
61
|
-
|
|
68
|
+
`INSERT OR IGNORE INTO ${tableName} (key, fingerprint, status, response, created_at)
|
|
69
|
+
SELECT ?, ?, ?, ?, ?
|
|
70
|
+
WHERE NOT EXISTS (SELECT 1 FROM ${tableName} WHERE key = ? AND created_at >= ?)`
|
|
71
|
+
).bind(key, record.fingerprint, record.status, null, record.createdAt, key, ttlThreshold()).run();
|
|
62
72
|
return result.meta.changes > 0;
|
|
63
73
|
},
|
|
64
74
|
async complete(key, response) {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/stores/cloudflare-d1.ts"],"sourcesContent":["import type { IdempotencyRecord, StoredResponse } from \"../types.js\";\nimport type { IdempotencyStore } from \"./types.js\";\n\nconst DEFAULT_TABLE = \"idempotency_keys\";\n\n/** Minimal D1Database subset used by d1Store (avoids @cloudflare/workers-types dependency). */\nexport interface D1DatabaseLike {\n\tprepare(sql: string): D1PreparedStatementLike;\n}\n\nexport interface D1PreparedStatementLike {\n\tbind(...params: unknown[]): D1PreparedStatementLike;\n\trun(): Promise<{ success: boolean; meta: { changes: number } }>;\n\tfirst(): Promise<Record<string, unknown> | null>;\n}\n\nexport interface D1StoreOptions {\n\t/** Cloudflare D1 database binding. */\n\tdatabase: D1DatabaseLike;\n\t/** Table name (default: \"idempotency_keys\"). */\n\ttableName?: string;\n}\n\nexport function d1Store(options: D1StoreOptions): IdempotencyStore {\n\tconst { database: db, tableName = DEFAULT_TABLE } = options;\n\tlet initialized = false;\n\n\tconst ensureTable = async (): Promise<void> => {\n\t\tif (initialized) return;\n\t\tawait db\n\t\t\t.prepare(\n\t\t\t\t`CREATE TABLE IF NOT EXISTS ${tableName} (\n\t\t\t\tkey TEXT PRIMARY KEY,\n\t\t\t\tfingerprint TEXT NOT NULL,\n\t\t\t\tstatus TEXT NOT NULL,\n\t\t\t\tresponse TEXT,\n\t\t\t\tcreated_at INTEGER NOT NULL\n\t\t\t)`,\n\t\t\t)\n\t\t\t.run();\n\t\tinitialized = true;\n\t};\n\n\tconst toRecord = (row: Record<string, unknown>): IdempotencyRecord => ({\n\t\tkey: row.key as string,\n\t\tfingerprint: row.fingerprint as string,\n\t\tstatus: row.status as \"processing\" | \"completed\",\n\t\tresponse: row.response ? (JSON.parse(row.response as string) as StoredResponse) : undefined,\n\t\tcreatedAt: row.created_at as number,\n\t});\n\n\treturn {\n\t\tasync get(key) {\n\t\t\tawait ensureTable();\n\t\t\tconst row = await db.prepare(`SELECT * FROM ${tableName} WHERE key = ?`).bind(key).first();\n\t\t\tif (!row) return undefined;\n\t\t\treturn toRecord(row);\n\t\t},\n\n\t\tasync lock(key, record) {\n\t\t\tawait ensureTable();\n\t\t\tconst result = await db\n\t\t\t\t.prepare(\n\t\t\t\t\t`INSERT OR IGNORE INTO ${tableName} (key, fingerprint, status, response, created_at)
|
|
1
|
+
{"version":3,"sources":["../../src/stores/cloudflare-d1.ts"],"sourcesContent":["import type { IdempotencyRecord, StoredResponse } from \"../types.js\";\nimport type { IdempotencyStore } from \"./types.js\";\n\nconst DEFAULT_TABLE = \"idempotency_keys\";\nconst DEFAULT_TTL = 86400; // 24 hours in seconds\nconst TABLE_NAME_RE = /^[a-zA-Z_][a-zA-Z0-9_]*$/;\n\n/** Minimal D1Database subset used by d1Store (avoids @cloudflare/workers-types dependency). */\nexport interface D1DatabaseLike {\n\tprepare(sql: string): D1PreparedStatementLike;\n}\n\nexport interface D1PreparedStatementLike {\n\tbind(...params: unknown[]): D1PreparedStatementLike;\n\trun(): Promise<{ success: boolean; meta: { changes: number } }>;\n\tfirst(): Promise<Record<string, unknown> | null>;\n}\n\nexport interface D1StoreOptions {\n\t/** Cloudflare D1 database binding. */\n\tdatabase: D1DatabaseLike;\n\t/** Table name (default: \"idempotency_keys\"). Must match /^[a-zA-Z_][a-zA-Z0-9_]*$/. */\n\ttableName?: string;\n\t/** TTL in seconds (default: 86400 = 24h). Expired rows are ignored by get/lock. */\n\tttl?: number;\n}\n\nexport function d1Store(options: D1StoreOptions): IdempotencyStore {\n\tconst { database: db, tableName = DEFAULT_TABLE, ttl = DEFAULT_TTL } = options;\n\n\tif (!TABLE_NAME_RE.test(tableName)) {\n\t\tthrow new Error(`Invalid table name: \"${tableName}\". Must match ${TABLE_NAME_RE}`);\n\t}\n\n\tlet initialized = false;\n\n\tconst ensureTable = async (): Promise<void> => {\n\t\tif (initialized) return;\n\t\tawait db\n\t\t\t.prepare(\n\t\t\t\t`CREATE TABLE IF NOT EXISTS ${tableName} (\n\t\t\t\tkey TEXT PRIMARY KEY,\n\t\t\t\tfingerprint TEXT NOT NULL,\n\t\t\t\tstatus TEXT NOT NULL,\n\t\t\t\tresponse TEXT,\n\t\t\t\tcreated_at INTEGER NOT NULL\n\t\t\t)`,\n\t\t\t)\n\t\t\t.run();\n\t\tinitialized = true;\n\t};\n\n\tconst ttlThreshold = (): number => {\n\t\treturn Date.now() - ttl * 1000;\n\t};\n\n\tconst toRecord = (row: Record<string, unknown>): IdempotencyRecord => ({\n\t\tkey: row.key as string,\n\t\tfingerprint: row.fingerprint as string,\n\t\tstatus: row.status as \"processing\" | \"completed\",\n\t\tresponse: row.response ? (JSON.parse(row.response as string) as StoredResponse) : undefined,\n\t\tcreatedAt: row.created_at as number,\n\t});\n\n\treturn {\n\t\tasync get(key) {\n\t\t\tawait ensureTable();\n\t\t\tconst row = await db\n\t\t\t\t.prepare(`SELECT * FROM ${tableName} WHERE key = ? AND created_at >= ?`)\n\t\t\t\t.bind(key, ttlThreshold())\n\t\t\t\t.first();\n\t\t\tif (!row) return undefined;\n\t\t\treturn toRecord(row);\n\t\t},\n\n\t\tasync lock(key, record) {\n\t\t\tawait ensureTable();\n\t\t\tconst result = await db\n\t\t\t\t.prepare(\n\t\t\t\t\t`INSERT OR IGNORE INTO ${tableName} (key, fingerprint, status, response, created_at)\n\t\t\t\t\tSELECT ?, ?, ?, ?, ?\n\t\t\t\t\tWHERE NOT EXISTS (SELECT 1 FROM ${tableName} WHERE key = ? AND created_at >= ?)`,\n\t\t\t\t)\n\t\t\t\t.bind(key, record.fingerprint, record.status, null, record.createdAt, key, ttlThreshold())\n\t\t\t\t.run();\n\t\t\treturn result.meta.changes > 0;\n\t\t},\n\n\t\tasync complete(key, response) {\n\t\t\tawait ensureTable();\n\t\t\tawait db\n\t\t\t\t.prepare(`UPDATE ${tableName} SET status = ?, response = ? WHERE key = ?`)\n\t\t\t\t.bind(\"completed\", JSON.stringify(response), key)\n\t\t\t\t.run();\n\t\t},\n\n\t\tasync delete(key) {\n\t\t\tawait ensureTable();\n\t\t\tawait db.prepare(`DELETE FROM ${tableName} WHERE key = ?`).bind(key).run();\n\t\t},\n\t};\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAGA,IAAM,gBAAgB;AACtB,IAAM,cAAc;AACpB,IAAM,gBAAgB;AAsBf,SAAS,QAAQ,SAA2C;AAClE,QAAM,EAAE,UAAU,IAAI,YAAY,eAAe,MAAM,YAAY,IAAI;AAEvE,MAAI,CAAC,cAAc,KAAK,SAAS,GAAG;AACnC,UAAM,IAAI,MAAM,wBAAwB,SAAS,iBAAiB,aAAa,EAAE;AAAA,EAClF;AAEA,MAAI,cAAc;AAElB,QAAM,cAAc,YAA2B;AAC9C,QAAI,YAAa;AACjB,UAAM,GACJ;AAAA,MACA,8BAA8B,SAAS;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,IAOxC,EACC,IAAI;AACN,kBAAc;AAAA,EACf;AAEA,QAAM,eAAe,MAAc;AAClC,WAAO,KAAK,IAAI,IAAI,MAAM;AAAA,EAC3B;AAEA,QAAM,WAAW,CAAC,SAAqD;AAAA,IACtE,KAAK,IAAI;AAAA,IACT,aAAa,IAAI;AAAA,IACjB,QAAQ,IAAI;AAAA,IACZ,UAAU,IAAI,WAAY,KAAK,MAAM,IAAI,QAAkB,IAAuB;AAAA,IAClF,WAAW,IAAI;AAAA,EAChB;AAEA,SAAO;AAAA,IACN,MAAM,IAAI,KAAK;AACd,YAAM,YAAY;AAClB,YAAM,MAAM,MAAM,GAChB,QAAQ,iBAAiB,SAAS,oCAAoC,EACtE,KAAK,KAAK,aAAa,CAAC,EACxB,MAAM;AACR,UAAI,CAAC,IAAK,QAAO;AACjB,aAAO,SAAS,GAAG;AAAA,IACpB;AAAA,IAEA,MAAM,KAAK,KAAK,QAAQ;AACvB,YAAM,YAAY;AAClB,YAAM,SAAS,MAAM,GACnB;AAAA,QACA,yBAAyB,SAAS;AAAA;AAAA,uCAEA,SAAS;AAAA,MAC5C,EACC,KAAK,KAAK,OAAO,aAAa,OAAO,QAAQ,MAAM,OAAO,WAAW,KAAK,aAAa,CAAC,EACxF,IAAI;AACN,aAAO,OAAO,KAAK,UAAU;AAAA,IAC9B;AAAA,IAEA,MAAM,SAAS,KAAK,UAAU;AAC7B,YAAM,YAAY;AAClB,YAAM,GACJ,QAAQ,UAAU,SAAS,6CAA6C,EACxE,KAAK,aAAa,KAAK,UAAU,QAAQ,GAAG,GAAG,EAC/C,IAAI;AAAA,IACP;AAAA,IAEA,MAAM,OAAO,KAAK;AACjB,YAAM,YAAY;AAClB,YAAM,GAAG,QAAQ,eAAe,SAAS,gBAAgB,EAAE,KAAK,GAAG,EAAE,IAAI;AAAA,IAC1E;AAAA,EACD;AACD;","names":[]}
|
|
@@ -18,8 +18,10 @@ interface D1PreparedStatementLike {
|
|
|
18
18
|
interface D1StoreOptions {
|
|
19
19
|
/** Cloudflare D1 database binding. */
|
|
20
20
|
database: D1DatabaseLike;
|
|
21
|
-
/** Table name (default: "idempotency_keys"). */
|
|
21
|
+
/** Table name (default: "idempotency_keys"). Must match /^[a-zA-Z_][a-zA-Z0-9_]*$/. */
|
|
22
22
|
tableName?: string;
|
|
23
|
+
/** TTL in seconds (default: 86400 = 24h). Expired rows are ignored by get/lock. */
|
|
24
|
+
ttl?: number;
|
|
23
25
|
}
|
|
24
26
|
declare function d1Store(options: D1StoreOptions): IdempotencyStore;
|
|
25
27
|
|
|
@@ -18,8 +18,10 @@ interface D1PreparedStatementLike {
|
|
|
18
18
|
interface D1StoreOptions {
|
|
19
19
|
/** Cloudflare D1 database binding. */
|
|
20
20
|
database: D1DatabaseLike;
|
|
21
|
-
/** Table name (default: "idempotency_keys"). */
|
|
21
|
+
/** Table name (default: "idempotency_keys"). Must match /^[a-zA-Z_][a-zA-Z0-9_]*$/. */
|
|
22
22
|
tableName?: string;
|
|
23
|
+
/** TTL in seconds (default: 86400 = 24h). Expired rows are ignored by get/lock. */
|
|
24
|
+
ttl?: number;
|
|
23
25
|
}
|
|
24
26
|
declare function d1Store(options: D1StoreOptions): IdempotencyStore;
|
|
25
27
|
|
|
@@ -1,7 +1,12 @@
|
|
|
1
1
|
// src/stores/cloudflare-d1.ts
|
|
2
2
|
var DEFAULT_TABLE = "idempotency_keys";
|
|
3
|
+
var DEFAULT_TTL = 86400;
|
|
4
|
+
var TABLE_NAME_RE = /^[a-zA-Z_][a-zA-Z0-9_]*$/;
|
|
3
5
|
function d1Store(options) {
|
|
4
|
-
const { database: db, tableName = DEFAULT_TABLE } = options;
|
|
6
|
+
const { database: db, tableName = DEFAULT_TABLE, ttl = DEFAULT_TTL } = options;
|
|
7
|
+
if (!TABLE_NAME_RE.test(tableName)) {
|
|
8
|
+
throw new Error(`Invalid table name: "${tableName}". Must match ${TABLE_NAME_RE}`);
|
|
9
|
+
}
|
|
5
10
|
let initialized = false;
|
|
6
11
|
const ensureTable = async () => {
|
|
7
12
|
if (initialized) return;
|
|
@@ -16,6 +21,9 @@ function d1Store(options) {
|
|
|
16
21
|
).run();
|
|
17
22
|
initialized = true;
|
|
18
23
|
};
|
|
24
|
+
const ttlThreshold = () => {
|
|
25
|
+
return Date.now() - ttl * 1e3;
|
|
26
|
+
};
|
|
19
27
|
const toRecord = (row) => ({
|
|
20
28
|
key: row.key,
|
|
21
29
|
fingerprint: row.fingerprint,
|
|
@@ -26,15 +34,17 @@ function d1Store(options) {
|
|
|
26
34
|
return {
|
|
27
35
|
async get(key) {
|
|
28
36
|
await ensureTable();
|
|
29
|
-
const row = await db.prepare(`SELECT * FROM ${tableName} WHERE key = ?`).bind(key).first();
|
|
37
|
+
const row = await db.prepare(`SELECT * FROM ${tableName} WHERE key = ? AND created_at >= ?`).bind(key, ttlThreshold()).first();
|
|
30
38
|
if (!row) return void 0;
|
|
31
39
|
return toRecord(row);
|
|
32
40
|
},
|
|
33
41
|
async lock(key, record) {
|
|
34
42
|
await ensureTable();
|
|
35
43
|
const result = await db.prepare(
|
|
36
|
-
`INSERT OR IGNORE INTO ${tableName} (key, fingerprint, status, response, created_at)
|
|
37
|
-
|
|
44
|
+
`INSERT OR IGNORE INTO ${tableName} (key, fingerprint, status, response, created_at)
|
|
45
|
+
SELECT ?, ?, ?, ?, ?
|
|
46
|
+
WHERE NOT EXISTS (SELECT 1 FROM ${tableName} WHERE key = ? AND created_at >= ?)`
|
|
47
|
+
).bind(key, record.fingerprint, record.status, null, record.createdAt, key, ttlThreshold()).run();
|
|
38
48
|
return result.meta.changes > 0;
|
|
39
49
|
},
|
|
40
50
|
async complete(key, response) {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/stores/cloudflare-d1.ts"],"sourcesContent":["import type { IdempotencyRecord, StoredResponse } from \"../types.js\";\nimport type { IdempotencyStore } from \"./types.js\";\n\nconst DEFAULT_TABLE = \"idempotency_keys\";\n\n/** Minimal D1Database subset used by d1Store (avoids @cloudflare/workers-types dependency). */\nexport interface D1DatabaseLike {\n\tprepare(sql: string): D1PreparedStatementLike;\n}\n\nexport interface D1PreparedStatementLike {\n\tbind(...params: unknown[]): D1PreparedStatementLike;\n\trun(): Promise<{ success: boolean; meta: { changes: number } }>;\n\tfirst(): Promise<Record<string, unknown> | null>;\n}\n\nexport interface D1StoreOptions {\n\t/** Cloudflare D1 database binding. */\n\tdatabase: D1DatabaseLike;\n\t/** Table name (default: \"idempotency_keys\"). */\n\ttableName?: string;\n}\n\nexport function d1Store(options: D1StoreOptions): IdempotencyStore {\n\tconst { database: db, tableName = DEFAULT_TABLE } = options;\n\tlet initialized = false;\n\n\tconst ensureTable = async (): Promise<void> => {\n\t\tif (initialized) return;\n\t\tawait db\n\t\t\t.prepare(\n\t\t\t\t`CREATE TABLE IF NOT EXISTS ${tableName} (\n\t\t\t\tkey TEXT PRIMARY KEY,\n\t\t\t\tfingerprint TEXT NOT NULL,\n\t\t\t\tstatus TEXT NOT NULL,\n\t\t\t\tresponse TEXT,\n\t\t\t\tcreated_at INTEGER NOT NULL\n\t\t\t)`,\n\t\t\t)\n\t\t\t.run();\n\t\tinitialized = true;\n\t};\n\n\tconst toRecord = (row: Record<string, unknown>): IdempotencyRecord => ({\n\t\tkey: row.key as string,\n\t\tfingerprint: row.fingerprint as string,\n\t\tstatus: row.status as \"processing\" | \"completed\",\n\t\tresponse: row.response ? (JSON.parse(row.response as string) as StoredResponse) : undefined,\n\t\tcreatedAt: row.created_at as number,\n\t});\n\n\treturn {\n\t\tasync get(key) {\n\t\t\tawait ensureTable();\n\t\t\tconst row = await db.prepare(`SELECT * FROM ${tableName} WHERE key = ?`).bind(key).first();\n\t\t\tif (!row) return undefined;\n\t\t\treturn toRecord(row);\n\t\t},\n\n\t\tasync lock(key, record) {\n\t\t\tawait ensureTable();\n\t\t\tconst result = await db\n\t\t\t\t.prepare(\n\t\t\t\t\t`INSERT OR IGNORE INTO ${tableName} (key, fingerprint, status, response, created_at)
|
|
1
|
+
{"version":3,"sources":["../../src/stores/cloudflare-d1.ts"],"sourcesContent":["import type { IdempotencyRecord, StoredResponse } from \"../types.js\";\nimport type { IdempotencyStore } from \"./types.js\";\n\nconst DEFAULT_TABLE = \"idempotency_keys\";\nconst DEFAULT_TTL = 86400; // 24 hours in seconds\nconst TABLE_NAME_RE = /^[a-zA-Z_][a-zA-Z0-9_]*$/;\n\n/** Minimal D1Database subset used by d1Store (avoids @cloudflare/workers-types dependency). */\nexport interface D1DatabaseLike {\n\tprepare(sql: string): D1PreparedStatementLike;\n}\n\nexport interface D1PreparedStatementLike {\n\tbind(...params: unknown[]): D1PreparedStatementLike;\n\trun(): Promise<{ success: boolean; meta: { changes: number } }>;\n\tfirst(): Promise<Record<string, unknown> | null>;\n}\n\nexport interface D1StoreOptions {\n\t/** Cloudflare D1 database binding. */\n\tdatabase: D1DatabaseLike;\n\t/** Table name (default: \"idempotency_keys\"). Must match /^[a-zA-Z_][a-zA-Z0-9_]*$/. */\n\ttableName?: string;\n\t/** TTL in seconds (default: 86400 = 24h). Expired rows are ignored by get/lock. */\n\tttl?: number;\n}\n\nexport function d1Store(options: D1StoreOptions): IdempotencyStore {\n\tconst { database: db, tableName = DEFAULT_TABLE, ttl = DEFAULT_TTL } = options;\n\n\tif (!TABLE_NAME_RE.test(tableName)) {\n\t\tthrow new Error(`Invalid table name: \"${tableName}\". Must match ${TABLE_NAME_RE}`);\n\t}\n\n\tlet initialized = false;\n\n\tconst ensureTable = async (): Promise<void> => {\n\t\tif (initialized) return;\n\t\tawait db\n\t\t\t.prepare(\n\t\t\t\t`CREATE TABLE IF NOT EXISTS ${tableName} (\n\t\t\t\tkey TEXT PRIMARY KEY,\n\t\t\t\tfingerprint TEXT NOT NULL,\n\t\t\t\tstatus TEXT NOT NULL,\n\t\t\t\tresponse TEXT,\n\t\t\t\tcreated_at INTEGER NOT NULL\n\t\t\t)`,\n\t\t\t)\n\t\t\t.run();\n\t\tinitialized = true;\n\t};\n\n\tconst ttlThreshold = (): number => {\n\t\treturn Date.now() - ttl * 1000;\n\t};\n\n\tconst toRecord = (row: Record<string, unknown>): IdempotencyRecord => ({\n\t\tkey: row.key as string,\n\t\tfingerprint: row.fingerprint as string,\n\t\tstatus: row.status as \"processing\" | \"completed\",\n\t\tresponse: row.response ? (JSON.parse(row.response as string) as StoredResponse) : undefined,\n\t\tcreatedAt: row.created_at as number,\n\t});\n\n\treturn {\n\t\tasync get(key) {\n\t\t\tawait ensureTable();\n\t\t\tconst row = await db\n\t\t\t\t.prepare(`SELECT * FROM ${tableName} WHERE key = ? AND created_at >= ?`)\n\t\t\t\t.bind(key, ttlThreshold())\n\t\t\t\t.first();\n\t\t\tif (!row) return undefined;\n\t\t\treturn toRecord(row);\n\t\t},\n\n\t\tasync lock(key, record) {\n\t\t\tawait ensureTable();\n\t\t\tconst result = await db\n\t\t\t\t.prepare(\n\t\t\t\t\t`INSERT OR IGNORE INTO ${tableName} (key, fingerprint, status, response, created_at)\n\t\t\t\t\tSELECT ?, ?, ?, ?, ?\n\t\t\t\t\tWHERE NOT EXISTS (SELECT 1 FROM ${tableName} WHERE key = ? AND created_at >= ?)`,\n\t\t\t\t)\n\t\t\t\t.bind(key, record.fingerprint, record.status, null, record.createdAt, key, ttlThreshold())\n\t\t\t\t.run();\n\t\t\treturn result.meta.changes > 0;\n\t\t},\n\n\t\tasync complete(key, response) {\n\t\t\tawait ensureTable();\n\t\t\tawait db\n\t\t\t\t.prepare(`UPDATE ${tableName} SET status = ?, response = ? WHERE key = ?`)\n\t\t\t\t.bind(\"completed\", JSON.stringify(response), key)\n\t\t\t\t.run();\n\t\t},\n\n\t\tasync delete(key) {\n\t\t\tawait ensureTable();\n\t\t\tawait db.prepare(`DELETE FROM ${tableName} WHERE key = ?`).bind(key).run();\n\t\t},\n\t};\n}\n"],"mappings":";AAGA,IAAM,gBAAgB;AACtB,IAAM,cAAc;AACpB,IAAM,gBAAgB;AAsBf,SAAS,QAAQ,SAA2C;AAClE,QAAM,EAAE,UAAU,IAAI,YAAY,eAAe,MAAM,YAAY,IAAI;AAEvE,MAAI,CAAC,cAAc,KAAK,SAAS,GAAG;AACnC,UAAM,IAAI,MAAM,wBAAwB,SAAS,iBAAiB,aAAa,EAAE;AAAA,EAClF;AAEA,MAAI,cAAc;AAElB,QAAM,cAAc,YAA2B;AAC9C,QAAI,YAAa;AACjB,UAAM,GACJ;AAAA,MACA,8BAA8B,SAAS;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,IAOxC,EACC,IAAI;AACN,kBAAc;AAAA,EACf;AAEA,QAAM,eAAe,MAAc;AAClC,WAAO,KAAK,IAAI,IAAI,MAAM;AAAA,EAC3B;AAEA,QAAM,WAAW,CAAC,SAAqD;AAAA,IACtE,KAAK,IAAI;AAAA,IACT,aAAa,IAAI;AAAA,IACjB,QAAQ,IAAI;AAAA,IACZ,UAAU,IAAI,WAAY,KAAK,MAAM,IAAI,QAAkB,IAAuB;AAAA,IAClF,WAAW,IAAI;AAAA,EAChB;AAEA,SAAO;AAAA,IACN,MAAM,IAAI,KAAK;AACd,YAAM,YAAY;AAClB,YAAM,MAAM,MAAM,GAChB,QAAQ,iBAAiB,SAAS,oCAAoC,EACtE,KAAK,KAAK,aAAa,CAAC,EACxB,MAAM;AACR,UAAI,CAAC,IAAK,QAAO;AACjB,aAAO,SAAS,GAAG;AAAA,IACpB;AAAA,IAEA,MAAM,KAAK,KAAK,QAAQ;AACvB,YAAM,YAAY;AAClB,YAAM,SAAS,MAAM,GACnB;AAAA,QACA,yBAAyB,SAAS;AAAA;AAAA,uCAEA,SAAS;AAAA,MAC5C,EACC,KAAK,KAAK,OAAO,aAAa,OAAO,QAAQ,MAAM,OAAO,WAAW,KAAK,aAAa,CAAC,EACxF,IAAI;AACN,aAAO,OAAO,KAAK,UAAU;AAAA,IAC9B;AAAA,IAEA,MAAM,SAAS,KAAK,UAAU;AAC7B,YAAM,YAAY;AAClB,YAAM,GACJ,QAAQ,UAAU,SAAS,6CAA6C,EACxE,KAAK,aAAa,KAAK,UAAU,QAAQ,GAAG,GAAG,EAC/C,IAAI;AAAA,IACP;AAAA,IAEA,MAAM,OAAO,KAAK;AACjB,YAAM,YAAY;AAClB,YAAM,GAAG,QAAQ,eAAe,SAAS,gBAAgB,EAAE,KAAK,GAAG,EAAE,IAAI;AAAA,IAC1E;AAAA,EACD;AACD;","names":[]}
|
package/package.json
CHANGED