keyv-cloudflare-kv 0.1.0-alpha
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/README.md +163 -0
- package/dist/adapter.d.ts +12 -0
- package/dist/adapter.js +42 -0
- package/dist/api-client.d.ts +10 -0
- package/dist/api-client.js +27 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.js +3 -0
- package/dist/types.d.ts +25 -0
- package/dist/types.js +1 -0
- package/dist/utils.d.ts +2 -0
- package/dist/utils.js +13 -0
- package/dist/workers-client.d.ts +8 -0
- package/dist/workers-client.js +17 -0
- package/package.json +58 -0
package/README.md
ADDED
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
# keyv-cloudflare-kv
|
|
2
|
+
|
|
3
|
+
Cloudflare KV adapter for Keyv.
|
|
4
|
+
|
|
5
|
+
This adapter allows Keyv to use Cloudflare Workers KV as a storage backend.
|
|
6
|
+
|
|
7
|
+
It supports two environments:
|
|
8
|
+
|
|
9
|
+
- Cloudflare Workers → uses native KV binding
|
|
10
|
+
- Node.js / Bun / servers → uses the Cloudflare API
|
|
11
|
+
|
|
12
|
+
This allows the same backend code to run anywhere.
|
|
13
|
+
|
|
14
|
+
---
|
|
15
|
+
|
|
16
|
+
## Installation
|
|
17
|
+
|
|
18
|
+
This package is published on GitHub Packages.
|
|
19
|
+
|
|
20
|
+
First configure your `.npmrc`.
|
|
21
|
+
|
|
22
|
+
### .npmrc
|
|
23
|
+
|
|
24
|
+
```bash
|
|
25
|
+
@Sivothajan:registry=https://npm.pkg.github.com
|
|
26
|
+
//npm.pkg.github.com/:_authToken=YOUR_GITHUB_TOKEN
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
Replace:
|
|
30
|
+
|
|
31
|
+
- `YOUR_GITHUB_TOKEN`
|
|
32
|
+
|
|
33
|
+
---
|
|
34
|
+
|
|
35
|
+
### Install
|
|
36
|
+
|
|
37
|
+
```bash
|
|
38
|
+
# npm
|
|
39
|
+
npm install @Sivothajan/keyv-cloudflare-kv
|
|
40
|
+
|
|
41
|
+
# bun
|
|
42
|
+
bun add @Sivothajan/keyv-cloudflare-kv
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
---
|
|
46
|
+
|
|
47
|
+
## Usage
|
|
48
|
+
|
|
49
|
+
### Node / Bun (Cloudflare API mode)
|
|
50
|
+
|
|
51
|
+
```javascript
|
|
52
|
+
import Keyv from 'keyv';
|
|
53
|
+
import Cloudflare from 'cloudflare';
|
|
54
|
+
import KeyvCloudflare from '@Sivothajan/keyv-cloudflare-kv';
|
|
55
|
+
|
|
56
|
+
const client = new Cloudflare({
|
|
57
|
+
apiToken: process.env.CF_API_TOKEN,
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
const store = new KeyvCloudflare({
|
|
61
|
+
mode: 'api',
|
|
62
|
+
client,
|
|
63
|
+
accountId: process.env.CF_ACCOUNT_ID,
|
|
64
|
+
namespaceId: process.env.CF_NAMESPACE_ID,
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
const keyv = new Keyv({ store });
|
|
68
|
+
|
|
69
|
+
await keyv.set('hello', 'world');
|
|
70
|
+
|
|
71
|
+
const value = await keyv.get('hello');
|
|
72
|
+
|
|
73
|
+
console.log(value);
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
---
|
|
77
|
+
|
|
78
|
+
### Cloudflare Workers
|
|
79
|
+
|
|
80
|
+
```javascript
|
|
81
|
+
import Keyv from 'keyv';
|
|
82
|
+
import KeyvCloudflare from '@Sivothajan/keyv-cloudflare-kv';
|
|
83
|
+
|
|
84
|
+
export default {
|
|
85
|
+
async fetch(request, env) {
|
|
86
|
+
const keyv = new Keyv({
|
|
87
|
+
store: new KeyvCloudflare({
|
|
88
|
+
mode: 'workers',
|
|
89
|
+
kv: env.MY_KV,
|
|
90
|
+
}),
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
await keyv.set('hello', 'world');
|
|
94
|
+
|
|
95
|
+
const value = await keyv.get('hello');
|
|
96
|
+
|
|
97
|
+
return new Response(value);
|
|
98
|
+
},
|
|
99
|
+
};
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
---
|
|
103
|
+
|
|
104
|
+
## Options
|
|
105
|
+
|
|
106
|
+
### API Mode
|
|
107
|
+
|
|
108
|
+
| Option | Type | Required | Description |
|
|
109
|
+
| ----------- | ---------- | -------- | --------------------- |
|
|
110
|
+
| mode | "api" | yes | Use Cloudflare API |
|
|
111
|
+
| client | Cloudflare | yes | Cloudflare SDK client |
|
|
112
|
+
| accountId | string | yes | Cloudflare account ID |
|
|
113
|
+
| namespaceId | string | yes | KV namespace ID |
|
|
114
|
+
| namespace | string | no | Optional key prefix |
|
|
115
|
+
|
|
116
|
+
---
|
|
117
|
+
|
|
118
|
+
### Workers Mode
|
|
119
|
+
|
|
120
|
+
| Option | Type | Required | Description |
|
|
121
|
+
| --------- | ----------- | -------- | ------------------- |
|
|
122
|
+
| mode | "workers" | yes | Worker runtime |
|
|
123
|
+
| kv | KVNamespace | yes | Worker KV binding |
|
|
124
|
+
| namespace | string | no | Optional key prefix |
|
|
125
|
+
|
|
126
|
+
---
|
|
127
|
+
|
|
128
|
+
## Namespace Prefixing
|
|
129
|
+
|
|
130
|
+
You can optionally prefix keys:
|
|
131
|
+
|
|
132
|
+
```text
|
|
133
|
+
namespace: "permissions"
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
Example stored keys:
|
|
137
|
+
|
|
138
|
+
```text
|
|
139
|
+
permissions:user:1
|
|
140
|
+
permissions:user:2
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
---
|
|
144
|
+
|
|
145
|
+
## Development
|
|
146
|
+
|
|
147
|
+
Install dependencies
|
|
148
|
+
|
|
149
|
+
```bash
|
|
150
|
+
bun install
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
Run lint
|
|
154
|
+
|
|
155
|
+
```.bash
|
|
156
|
+
bun run lint
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
Build
|
|
160
|
+
|
|
161
|
+
```bash
|
|
162
|
+
bun run build
|
|
163
|
+
```
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { KeyvCloudflareOptions } from './types';
|
|
2
|
+
export default class KeyvCloudflare {
|
|
3
|
+
private client;
|
|
4
|
+
private namespace?;
|
|
5
|
+
constructor(options: KeyvCloudflareOptions);
|
|
6
|
+
get(key: string): Promise<any>;
|
|
7
|
+
set(key: string, value: any, ttl?: number): Promise<boolean>;
|
|
8
|
+
delete(key: string): Promise<boolean>;
|
|
9
|
+
getMany(keys: string[]): Promise<any[]>;
|
|
10
|
+
setMany(entries: [string, any][], ttl?: number): Promise<boolean[]>;
|
|
11
|
+
deleteMany(keys: string[]): Promise<boolean[]>;
|
|
12
|
+
}
|
package/dist/adapter.js
ADDED
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { ApiKVClient } from './api-client';
|
|
2
|
+
import { parse, prefix } from './utils';
|
|
3
|
+
import { WorkersKVClient } from './workers-client';
|
|
4
|
+
export default class KeyvCloudflare {
|
|
5
|
+
client;
|
|
6
|
+
namespace;
|
|
7
|
+
constructor(options) {
|
|
8
|
+
this.namespace = options.namespace;
|
|
9
|
+
if (options.mode === 'workers') {
|
|
10
|
+
this.client = new WorkersKVClient(options.kv);
|
|
11
|
+
return;
|
|
12
|
+
}
|
|
13
|
+
if (options.mode === 'api') {
|
|
14
|
+
this.client = new ApiKVClient(options.client, options.accountId, options.namespaceId);
|
|
15
|
+
return;
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
async get(key) {
|
|
19
|
+
const k = prefix(this.namespace, key);
|
|
20
|
+
const value = await this.client.get(k);
|
|
21
|
+
return parse(value);
|
|
22
|
+
}
|
|
23
|
+
async set(key, value, ttl) {
|
|
24
|
+
const k = prefix(this.namespace, key);
|
|
25
|
+
await this.client.set(k, JSON.stringify(value), ttl);
|
|
26
|
+
return true;
|
|
27
|
+
}
|
|
28
|
+
async delete(key) {
|
|
29
|
+
const k = prefix(this.namespace, key);
|
|
30
|
+
await this.client.delete(k);
|
|
31
|
+
return true;
|
|
32
|
+
}
|
|
33
|
+
async getMany(keys) {
|
|
34
|
+
return Promise.all(keys.map((k) => this.get(k)));
|
|
35
|
+
}
|
|
36
|
+
async setMany(entries, ttl) {
|
|
37
|
+
return Promise.all(entries.map(([k, v]) => this.set(k, v, ttl)));
|
|
38
|
+
}
|
|
39
|
+
async deleteMany(keys) {
|
|
40
|
+
return Promise.all(keys.map((k) => this.delete(k)));
|
|
41
|
+
}
|
|
42
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type Cloudflare from 'cloudflare';
|
|
2
|
+
export declare class ApiKVClient {
|
|
3
|
+
private client;
|
|
4
|
+
private accountId;
|
|
5
|
+
private namespaceId;
|
|
6
|
+
constructor(client: Cloudflare, accountId: string, namespaceId: string);
|
|
7
|
+
get(key: string): Promise<Response>;
|
|
8
|
+
set(key: string, value: string, ttl?: number): Promise<void>;
|
|
9
|
+
delete(key: string): Promise<void>;
|
|
10
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
export class ApiKVClient {
|
|
2
|
+
client;
|
|
3
|
+
accountId;
|
|
4
|
+
namespaceId;
|
|
5
|
+
constructor(client, accountId, namespaceId) {
|
|
6
|
+
this.client = client;
|
|
7
|
+
this.accountId = accountId;
|
|
8
|
+
this.namespaceId = namespaceId;
|
|
9
|
+
}
|
|
10
|
+
async get(key) {
|
|
11
|
+
return this.client.kv.namespaces.values.get(this.namespaceId, key, {
|
|
12
|
+
account_id: this.accountId,
|
|
13
|
+
});
|
|
14
|
+
}
|
|
15
|
+
async set(key, value, ttl) {
|
|
16
|
+
await this.client.kv.namespaces.values.update(this.namespaceId, key, {
|
|
17
|
+
account_id: this.accountId,
|
|
18
|
+
value,
|
|
19
|
+
expiration_ttl: ttl ? Math.floor(ttl / 1000) : undefined,
|
|
20
|
+
});
|
|
21
|
+
}
|
|
22
|
+
async delete(key) {
|
|
23
|
+
await this.client.kv.namespaces.values.delete(this.namespaceId, key, {
|
|
24
|
+
account_id: this.accountId,
|
|
25
|
+
});
|
|
26
|
+
}
|
|
27
|
+
}
|
package/dist/index.d.ts
ADDED
package/dist/index.js
ADDED
package/dist/types.d.ts
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import type { KVNamespace } from '@cloudflare/workers-types';
|
|
2
|
+
import type Cloudflare from 'cloudflare';
|
|
3
|
+
export interface CloudflareApiOptions {
|
|
4
|
+
client: Cloudflare;
|
|
5
|
+
accountId: string;
|
|
6
|
+
namespaceId: string;
|
|
7
|
+
}
|
|
8
|
+
export interface WorkersKVOptions {
|
|
9
|
+
kv: KVNamespace;
|
|
10
|
+
}
|
|
11
|
+
interface BaseOptions {
|
|
12
|
+
namespace?: string;
|
|
13
|
+
}
|
|
14
|
+
export type KeyvCloudflareOptions = (BaseOptions & {
|
|
15
|
+
mode: 'api';
|
|
16
|
+
} & CloudflareApiOptions) | (BaseOptions & {
|
|
17
|
+
mode: 'workers';
|
|
18
|
+
} & WorkersKVOptions) | (BaseOptions & {
|
|
19
|
+
mode?: 'auto';
|
|
20
|
+
kv?: KVNamespace;
|
|
21
|
+
client?: Cloudflare;
|
|
22
|
+
accountId?: string;
|
|
23
|
+
namespaceId?: string;
|
|
24
|
+
});
|
|
25
|
+
export {};
|
package/dist/types.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
package/dist/utils.d.ts
ADDED
package/dist/utils.js
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { KVNamespace } from '@cloudflare/workers-types';
|
|
2
|
+
export declare class WorkersKVClient {
|
|
3
|
+
private kv;
|
|
4
|
+
constructor(kv: KVNamespace);
|
|
5
|
+
get(key: string): Promise<string | null>;
|
|
6
|
+
set(key: string, value: string, ttl?: number): Promise<void>;
|
|
7
|
+
delete(key: string): Promise<void>;
|
|
8
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
export class WorkersKVClient {
|
|
2
|
+
kv;
|
|
3
|
+
constructor(kv) {
|
|
4
|
+
this.kv = kv;
|
|
5
|
+
}
|
|
6
|
+
async get(key) {
|
|
7
|
+
return this.kv.get(key);
|
|
8
|
+
}
|
|
9
|
+
async set(key, value, ttl) {
|
|
10
|
+
await this.kv.put(key, value, {
|
|
11
|
+
expirationTtl: ttl ? Math.floor(ttl / 1000) : undefined,
|
|
12
|
+
});
|
|
13
|
+
}
|
|
14
|
+
async delete(key) {
|
|
15
|
+
await this.kv.delete(key);
|
|
16
|
+
}
|
|
17
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "keyv-cloudflare-kv",
|
|
3
|
+
"version": "0.1.0-alpha",
|
|
4
|
+
"description": "Cloudflare KV storage adapter for Keyv",
|
|
5
|
+
"author": {
|
|
6
|
+
"name": "Sivothayan",
|
|
7
|
+
"email": "hi@sivothayan.com",
|
|
8
|
+
"url": "https://sivothayan.com"
|
|
9
|
+
},
|
|
10
|
+
"main": "dist/index.js",
|
|
11
|
+
"types": "dist/index.d.ts",
|
|
12
|
+
"exports": {
|
|
13
|
+
".": {
|
|
14
|
+
"import": "./dist/index.js",
|
|
15
|
+
"types": "./dist/index.d.ts"
|
|
16
|
+
}
|
|
17
|
+
},
|
|
18
|
+
"files": [
|
|
19
|
+
"dist",
|
|
20
|
+
"README.md"
|
|
21
|
+
],
|
|
22
|
+
"type": "module",
|
|
23
|
+
"scripts": {
|
|
24
|
+
"build": "tsc -p tsconfig.build.json",
|
|
25
|
+
"dev": "tsc -w",
|
|
26
|
+
"format": "prettier --write .",
|
|
27
|
+
"lint": "eslint .",
|
|
28
|
+
"lint:fix": "eslint . --fix"
|
|
29
|
+
},
|
|
30
|
+
"dependencies": {
|
|
31
|
+
"cloudflare": "^4.0.0"
|
|
32
|
+
},
|
|
33
|
+
"peerDependencies": {
|
|
34
|
+
"keyv": "^4 || ^5 || ^6"
|
|
35
|
+
},
|
|
36
|
+
"devDependencies": {
|
|
37
|
+
"@cloudflare/workers-types": "^4.20260312.1",
|
|
38
|
+
"@types/node": "^25.5.0",
|
|
39
|
+
"@typescript-eslint/eslint-plugin": "^8.57.0",
|
|
40
|
+
"@typescript-eslint/parser": "^8.57.0",
|
|
41
|
+
"eslint": "^9.39.2",
|
|
42
|
+
"eslint-plugin-import": "^2.32.0",
|
|
43
|
+
"jiti": "^2.6.1",
|
|
44
|
+
"prettier": "^3.8.1",
|
|
45
|
+
"typescript": "^5.9.3"
|
|
46
|
+
},
|
|
47
|
+
"prettier": {
|
|
48
|
+
"printWidth": 80,
|
|
49
|
+
"tabWidth": 2,
|
|
50
|
+
"useTabs": false,
|
|
51
|
+
"semi": true,
|
|
52
|
+
"singleQuote": true,
|
|
53
|
+
"trailingComma": "es5",
|
|
54
|
+
"bracketSpacing": true,
|
|
55
|
+
"arrowParens": "always",
|
|
56
|
+
"endOfLine": "auto"
|
|
57
|
+
}
|
|
58
|
+
}
|