kvdb-client 0.1.0
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/LICENSE +21 -0
- package/README.ja.md +147 -0
- package/README.md +147 -0
- package/package.json +22 -0
- package/src/index.d.ts +70 -0
- package/src/index.js +271 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 n0bisuke
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.ja.md
ADDED
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
# kvdb-client
|
|
2
|
+
|
|
3
|
+
KVdb.io 用のシンプルな Node.js クライアントです。サードパーティーによる非公式 SDK です。
|
|
4
|
+
|
|
5
|
+
## インストール
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npm install kvdb-client
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## クイックスタート
|
|
12
|
+
|
|
13
|
+
```js
|
|
14
|
+
import { KVdbClient } from "kvdb-client";
|
|
15
|
+
|
|
16
|
+
const kv = new KVdbClient({ bucket: "YOUR_BUCKET" });
|
|
17
|
+
|
|
18
|
+
await kv.set("myName", "n0bisuke");
|
|
19
|
+
const name = await kv.get("myName");
|
|
20
|
+
console.log(name);
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
## 認証
|
|
24
|
+
|
|
25
|
+
```js
|
|
26
|
+
// Basic 認証(デフォルト)
|
|
27
|
+
const basic = new KVdbClient({ bucket: "YOUR_BUCKET", token: "supersecret" });
|
|
28
|
+
|
|
29
|
+
// Bearer トークン
|
|
30
|
+
const bearer = new KVdbClient({
|
|
31
|
+
bucket: "YOUR_BUCKET",
|
|
32
|
+
token: "ACCESS_TOKEN",
|
|
33
|
+
authType: "bearer"
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
// クエリ文字列でトークン指定
|
|
37
|
+
const query = new KVdbClient({
|
|
38
|
+
bucket: "YOUR_BUCKET",
|
|
39
|
+
token: "ACCESS_TOKEN",
|
|
40
|
+
authType: "query"
|
|
41
|
+
});
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
## サンプル
|
|
45
|
+
|
|
46
|
+
実行可能なスクリプトは GitHub の `examples/` フォルダを参照してください。
|
|
47
|
+
|
|
48
|
+
## 使い方
|
|
49
|
+
|
|
50
|
+
### バケット作成
|
|
51
|
+
|
|
52
|
+
```js
|
|
53
|
+
const bucket = await KVdbClient.createBucket("you@example.com");
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
### 値の保存・取得
|
|
57
|
+
|
|
58
|
+
```js
|
|
59
|
+
await kv.set("myName", "n0bisuke");
|
|
60
|
+
const name = await kv.get("myName");
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
### JSON の保存・取得
|
|
64
|
+
|
|
65
|
+
```js
|
|
66
|
+
await kv.set("myData", { name: "test", value: 123 }, { json: true });
|
|
67
|
+
const data = await kv.get("myData", { parseJson: true });
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
### キー一覧
|
|
71
|
+
|
|
72
|
+
```js
|
|
73
|
+
const keys = await kv.list();
|
|
74
|
+
const items = await kv.list({ values: true, format: "json" });
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
### 更新(PATCH)
|
|
78
|
+
|
|
79
|
+
```js
|
|
80
|
+
await kv.update("myName", "Sugawara");
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
### カウンター操作
|
|
84
|
+
|
|
85
|
+
```js
|
|
86
|
+
await kv.increment("visits");
|
|
87
|
+
await kv.decrement("visits", 2);
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
### トランザクション
|
|
91
|
+
|
|
92
|
+
```js
|
|
93
|
+
await kv.transaction([
|
|
94
|
+
{ set: "users:email:new@example.com", value: "user 1" },
|
|
95
|
+
{ delete: "users:email:old@example.com" }
|
|
96
|
+
]);
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
### アクセストークン作成
|
|
100
|
+
|
|
101
|
+
```js
|
|
102
|
+
const token = await kv.createAccessToken({
|
|
103
|
+
prefix: "user:123:",
|
|
104
|
+
permissions: "read,write",
|
|
105
|
+
ttl: 3600
|
|
106
|
+
});
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
### キー削除
|
|
110
|
+
|
|
111
|
+
```js
|
|
112
|
+
await kv.delete("myName");
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
### バケット削除
|
|
116
|
+
|
|
117
|
+
```js
|
|
118
|
+
await kv.deleteBucket();
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
## API
|
|
122
|
+
|
|
123
|
+
- `new KVdbClient({ bucket, token?, authType?, baseUrl? })`
|
|
124
|
+
- `KVdbClient.createBucket(email, { baseUrl?, secretKey?, writeKey?, readKey?, signingKey?, defaultTtl? })`
|
|
125
|
+
- `get(key, { parseJson? })`
|
|
126
|
+
- `set(key, value, { json?, contentType?, ttl? })`
|
|
127
|
+
- `update(key, value, { json?, contentType?, ttl? })`
|
|
128
|
+
- `increment(key, amount?, { ttl? })`
|
|
129
|
+
- `decrement(key, amount?, { ttl? })`
|
|
130
|
+
- `delete(key)`
|
|
131
|
+
- `list({ values?, format?, limit?, skip?, prefix?, reverse? })`
|
|
132
|
+
- `transaction(operations)`
|
|
133
|
+
- `createAccessToken({ prefix?, permissions?, ttl? })`
|
|
134
|
+
- `updateBucketPolicy({ secretKey?, writeKey?, readKey?, signingKey?, defaultTtl? })`
|
|
135
|
+
- `deleteBucket()`
|
|
136
|
+
|
|
137
|
+
## 注意
|
|
138
|
+
|
|
139
|
+
- KVdb は公開される可能性があるため、重要な情報は保存しないでください。
|
|
140
|
+
- `token` は `curl -u 'token:'` と同じ形式で送信されます。
|
|
141
|
+
- KVdb に保存した情報がどのように扱われるかについて、SDK 開発者(n0bisuke)は一切把握していません。利用は自己責任とし、トラブル等について開発者は責任を負いません。
|
|
142
|
+
|
|
143
|
+
## Language
|
|
144
|
+
|
|
145
|
+
- English: `README.md`
|
|
146
|
+
- Japanese (this file)
|
|
147
|
+
- English (GitHub): `https://github.com/n0bisuke/kvdb-client/blob/main/README.md`
|
package/README.md
ADDED
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
# kvdb-client
|
|
2
|
+
|
|
3
|
+
A simple Node.js client for KVdb.io. This is a third-party, unofficial SDK.
|
|
4
|
+
|
|
5
|
+
## Install
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npm install kvdb-client
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## Quick Start
|
|
12
|
+
|
|
13
|
+
```js
|
|
14
|
+
import { KVdbClient } from "kvdb-client";
|
|
15
|
+
|
|
16
|
+
const kv = new KVdbClient({ bucket: "YOUR_BUCKET" });
|
|
17
|
+
|
|
18
|
+
await kv.set("myName", "n0bisuke");
|
|
19
|
+
const name = await kv.get("myName");
|
|
20
|
+
console.log(name);
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
## Authentication
|
|
24
|
+
|
|
25
|
+
```js
|
|
26
|
+
// Basic auth (default)
|
|
27
|
+
const basic = new KVdbClient({ bucket: "YOUR_BUCKET", token: "supersecret" });
|
|
28
|
+
|
|
29
|
+
// Bearer token
|
|
30
|
+
const bearer = new KVdbClient({
|
|
31
|
+
bucket: "YOUR_BUCKET",
|
|
32
|
+
token: "ACCESS_TOKEN",
|
|
33
|
+
authType: "bearer"
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
// Query string token
|
|
37
|
+
const query = new KVdbClient({
|
|
38
|
+
bucket: "YOUR_BUCKET",
|
|
39
|
+
token: "ACCESS_TOKEN",
|
|
40
|
+
authType: "query"
|
|
41
|
+
});
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
## Examples
|
|
45
|
+
|
|
46
|
+
See the `examples/` folder on GitHub for runnable scripts.
|
|
47
|
+
|
|
48
|
+
## Usage
|
|
49
|
+
|
|
50
|
+
### Create a bucket
|
|
51
|
+
|
|
52
|
+
```js
|
|
53
|
+
const bucket = await KVdbClient.createBucket("you@example.com");
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
### Set / Get values
|
|
57
|
+
|
|
58
|
+
```js
|
|
59
|
+
await kv.set("myName", "n0bisuke");
|
|
60
|
+
const name = await kv.get("myName");
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
### JSON values
|
|
64
|
+
|
|
65
|
+
```js
|
|
66
|
+
await kv.set("myData", { name: "test", value: 123 }, { json: true });
|
|
67
|
+
const data = await kv.get("myData", { parseJson: true });
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
### List keys
|
|
71
|
+
|
|
72
|
+
```js
|
|
73
|
+
const keys = await kv.list();
|
|
74
|
+
const items = await kv.list({ values: true, format: "json" });
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
### Update (PATCH)
|
|
78
|
+
|
|
79
|
+
```js
|
|
80
|
+
await kv.update("myName", "Sugawara");
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
### Counters
|
|
84
|
+
|
|
85
|
+
```js
|
|
86
|
+
await kv.increment("visits");
|
|
87
|
+
await kv.decrement("visits", 2);
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
### Transactions
|
|
91
|
+
|
|
92
|
+
```js
|
|
93
|
+
await kv.transaction([
|
|
94
|
+
{ set: "users:email:new@example.com", value: "user 1" },
|
|
95
|
+
{ delete: "users:email:old@example.com" }
|
|
96
|
+
]);
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
### Create access token
|
|
100
|
+
|
|
101
|
+
```js
|
|
102
|
+
const token = await kv.createAccessToken({
|
|
103
|
+
prefix: "user:123:",
|
|
104
|
+
permissions: "read,write",
|
|
105
|
+
ttl: 3600
|
|
106
|
+
});
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
### Delete a key
|
|
110
|
+
|
|
111
|
+
```js
|
|
112
|
+
await kv.delete("myName");
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
### Delete a bucket
|
|
116
|
+
|
|
117
|
+
```js
|
|
118
|
+
await kv.deleteBucket();
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
## API
|
|
122
|
+
|
|
123
|
+
- `new KVdbClient({ bucket, token?, authType?, baseUrl? })`
|
|
124
|
+
- `KVdbClient.createBucket(email, { baseUrl?, secretKey?, writeKey?, readKey?, signingKey?, defaultTtl? })`
|
|
125
|
+
- `get(key, { parseJson? })`
|
|
126
|
+
- `set(key, value, { json?, contentType?, ttl? })`
|
|
127
|
+
- `update(key, value, { json?, contentType?, ttl? })`
|
|
128
|
+
- `increment(key, amount?, { ttl? })`
|
|
129
|
+
- `decrement(key, amount?, { ttl? })`
|
|
130
|
+
- `delete(key)`
|
|
131
|
+
- `list({ values?, format?, limit?, skip?, prefix?, reverse? })`
|
|
132
|
+
- `transaction(operations)`
|
|
133
|
+
- `createAccessToken({ prefix?, permissions?, ttl? })`
|
|
134
|
+
- `updateBucketPolicy({ secretKey?, writeKey?, readKey?, signingKey?, defaultTtl? })`
|
|
135
|
+
- `deleteBucket()`
|
|
136
|
+
|
|
137
|
+
## Notes
|
|
138
|
+
|
|
139
|
+
- KVdb buckets can be public; avoid storing sensitive data.
|
|
140
|
+
- `token` for Basic auth is sent as `curl -u 'token:'`.
|
|
141
|
+
- The SDK author (n0bisuke) has no knowledge of how data stored in KVdb is handled; use at your own risk. The author is not responsible for any issues or damages.
|
|
142
|
+
|
|
143
|
+
## Language
|
|
144
|
+
|
|
145
|
+
- English (this file)
|
|
146
|
+
- Japanese: `README.ja.md`
|
|
147
|
+
- Japanese (GitHub): `https://github.com/n0bisuke/kvdb-client/blob/main/README.ja.md`
|
package/package.json
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "kvdb-client",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Simple Node.js client for kvdb.io",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "./src/index.js",
|
|
7
|
+
"types": "./src/index.d.ts",
|
|
8
|
+
"exports": {
|
|
9
|
+
".": "./src/index.js"
|
|
10
|
+
},
|
|
11
|
+
"engines": {
|
|
12
|
+
"node": ">=18"
|
|
13
|
+
},
|
|
14
|
+
"keywords": [
|
|
15
|
+
"kvdb",
|
|
16
|
+
"kvdb.io",
|
|
17
|
+
"key-value",
|
|
18
|
+
"sdk",
|
|
19
|
+
"node"
|
|
20
|
+
],
|
|
21
|
+
"license": "MIT"
|
|
22
|
+
}
|
package/src/index.d.ts
ADDED
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
export type KVdbClientOptions = {
|
|
2
|
+
bucket: string;
|
|
3
|
+
token?: string;
|
|
4
|
+
authType?: "basic" | "bearer" | "query";
|
|
5
|
+
baseUrl?: string;
|
|
6
|
+
};
|
|
7
|
+
|
|
8
|
+
export type CreateBucketOptions = {
|
|
9
|
+
baseUrl?: string;
|
|
10
|
+
secretKey?: string;
|
|
11
|
+
writeKey?: string;
|
|
12
|
+
readKey?: string;
|
|
13
|
+
signingKey?: string;
|
|
14
|
+
defaultTtl?: number;
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
export type GetOptions = {
|
|
18
|
+
parseJson?: boolean;
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
export type SetOptions = {
|
|
22
|
+
json?: boolean;
|
|
23
|
+
contentType?: string;
|
|
24
|
+
ttl?: number;
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
export type ListOptions = {
|
|
28
|
+
values?: boolean;
|
|
29
|
+
format?: string;
|
|
30
|
+
limit?: number;
|
|
31
|
+
skip?: number;
|
|
32
|
+
prefix?: string;
|
|
33
|
+
reverse?: boolean;
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
export type TransactionOperation =
|
|
37
|
+
| { set: string; value: unknown; ttl?: number }
|
|
38
|
+
| { delete: string };
|
|
39
|
+
|
|
40
|
+
export type AccessTokenOptions = {
|
|
41
|
+
prefix?: string;
|
|
42
|
+
permissions?: string;
|
|
43
|
+
ttl?: number;
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
export type UpdateBucketPolicyOptions = {
|
|
47
|
+
secretKey?: string;
|
|
48
|
+
writeKey?: string;
|
|
49
|
+
readKey?: string;
|
|
50
|
+
signingKey?: string;
|
|
51
|
+
defaultTtl?: number;
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
export declare class KVdbClient {
|
|
55
|
+
constructor(options: KVdbClientOptions);
|
|
56
|
+
static createBucket(email: string, options?: CreateBucketOptions): Promise<string>;
|
|
57
|
+
get(key: string, options?: GetOptions): Promise<unknown>;
|
|
58
|
+
set(key: string, value: unknown, options?: SetOptions): Promise<unknown>;
|
|
59
|
+
update(key: string, value: unknown, options?: SetOptions): Promise<unknown>;
|
|
60
|
+
increment(key: string, amount?: number, options?: { ttl?: number }): Promise<unknown>;
|
|
61
|
+
decrement(key: string, amount?: number, options?: { ttl?: number }): Promise<unknown>;
|
|
62
|
+
delete(key: string): Promise<unknown>;
|
|
63
|
+
list(options?: ListOptions): Promise<unknown>;
|
|
64
|
+
transaction(operations: TransactionOperation[]): Promise<unknown>;
|
|
65
|
+
createAccessToken(options?: AccessTokenOptions): Promise<unknown>;
|
|
66
|
+
updateBucketPolicy(options?: UpdateBucketPolicyOptions): Promise<unknown>;
|
|
67
|
+
deleteBucket(): Promise<unknown>;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
export default KVdbClient;
|
package/src/index.js
ADDED
|
@@ -0,0 +1,271 @@
|
|
|
1
|
+
const DEFAULT_BASE_URL = "https://kvdb.io";
|
|
2
|
+
|
|
3
|
+
function normalizeBaseUrl(baseUrl) {
|
|
4
|
+
return baseUrl.replace(/\/$/, "");
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
function normalizeKey(key) {
|
|
8
|
+
if (typeof key !== "string" || key.trim() === "") {
|
|
9
|
+
throw new TypeError("key must be a non-empty string");
|
|
10
|
+
}
|
|
11
|
+
return key.replace(/^\/+/, "");
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
function buildAuthHeaders(authType, token) {
|
|
15
|
+
if (!token) return {};
|
|
16
|
+
if (authType === "bearer") {
|
|
17
|
+
return { Authorization: `Bearer ${token}` };
|
|
18
|
+
}
|
|
19
|
+
if (authType === "basic") {
|
|
20
|
+
const encoded = Buffer.from(`${token}:`, "utf8").toString("base64");
|
|
21
|
+
return { Authorization: `Basic ${encoded}` };
|
|
22
|
+
}
|
|
23
|
+
return {};
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
function appendAccessToken(url, authType, token) {
|
|
27
|
+
if (!token || authType !== "query") return url;
|
|
28
|
+
const hasQuery = url.includes("?");
|
|
29
|
+
const separator = hasQuery ? "&" : "?";
|
|
30
|
+
return `${url}${separator}access_token=${encodeURIComponent(token)}`;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
async function readResponseBody(response) {
|
|
34
|
+
const contentType = response.headers.get("content-type") || "";
|
|
35
|
+
const text = await response.text();
|
|
36
|
+
if (contentType.includes("application/json")) {
|
|
37
|
+
try {
|
|
38
|
+
return JSON.parse(text);
|
|
39
|
+
} catch {
|
|
40
|
+
return text;
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
return text;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
async function throwIfNotOk(response) {
|
|
47
|
+
if (response.ok) return response;
|
|
48
|
+
const body = await response.text();
|
|
49
|
+
const error = new Error(`KVdb request failed: ${response.status} ${response.statusText}`);
|
|
50
|
+
error.status = response.status;
|
|
51
|
+
error.body = body;
|
|
52
|
+
throw error;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
export class KVdbClient {
|
|
56
|
+
constructor({ bucket, token, authType = "basic", baseUrl = DEFAULT_BASE_URL } = {}) {
|
|
57
|
+
if (!bucket) throw new TypeError("bucket is required");
|
|
58
|
+
this.bucket = bucket;
|
|
59
|
+
this.token = token;
|
|
60
|
+
this.authType = authType;
|
|
61
|
+
this.baseUrl = normalizeBaseUrl(baseUrl);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
static async createBucket(email, {
|
|
65
|
+
baseUrl = DEFAULT_BASE_URL,
|
|
66
|
+
secretKey,
|
|
67
|
+
writeKey,
|
|
68
|
+
readKey,
|
|
69
|
+
signingKey,
|
|
70
|
+
defaultTtl
|
|
71
|
+
} = {}) {
|
|
72
|
+
if (!email) throw new TypeError("email is required");
|
|
73
|
+
const body = new URLSearchParams({ email });
|
|
74
|
+
if (secretKey) body.set("secret_key", secretKey);
|
|
75
|
+
if (writeKey) body.set("write_key", writeKey);
|
|
76
|
+
if (readKey) body.set("read_key", readKey);
|
|
77
|
+
if (signingKey) body.set("signing_key", signingKey);
|
|
78
|
+
if (typeof defaultTtl === "number") body.set("default_ttl", String(defaultTtl));
|
|
79
|
+
const response = await fetch(normalizeBaseUrl(baseUrl), {
|
|
80
|
+
method: "POST",
|
|
81
|
+
headers: { "content-type": "application/x-www-form-urlencoded" },
|
|
82
|
+
body
|
|
83
|
+
});
|
|
84
|
+
await throwIfNotOk(response);
|
|
85
|
+
return response.text();
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
_urlForKey(key, query = "") {
|
|
89
|
+
const safeKey = normalizeKey(key);
|
|
90
|
+
const suffix = query ? `?${query}` : "";
|
|
91
|
+
return `${this.baseUrl}/${this.bucket}/${safeKey}${suffix}`;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
_urlForBucket(query = "") {
|
|
95
|
+
const suffix = query ? `?${query}` : "";
|
|
96
|
+
return `${this.baseUrl}/${this.bucket}${suffix}`;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
_urlForBucketList(query = "") {
|
|
100
|
+
const suffix = query ? `/?${query}` : "/";
|
|
101
|
+
return `${this.baseUrl}/${this.bucket}${suffix}`;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
_buildUrl(url) {
|
|
105
|
+
return appendAccessToken(url, this.authType, this.token);
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
_authHeaders() {
|
|
109
|
+
return buildAuthHeaders(this.authType, this.token);
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
async _request({ method, url, headers, body }) {
|
|
113
|
+
const response = await fetch(this._buildUrl(url), {
|
|
114
|
+
method,
|
|
115
|
+
headers: {
|
|
116
|
+
...headers,
|
|
117
|
+
...this._authHeaders()
|
|
118
|
+
},
|
|
119
|
+
body
|
|
120
|
+
});
|
|
121
|
+
await throwIfNotOk(response);
|
|
122
|
+
return readResponseBody(response);
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
async get(key, { parseJson } = {}) {
|
|
126
|
+
const body = await this._request({
|
|
127
|
+
method: "GET",
|
|
128
|
+
url: this._urlForKey(key)
|
|
129
|
+
});
|
|
130
|
+
if (parseJson && typeof body === "string") {
|
|
131
|
+
try {
|
|
132
|
+
return JSON.parse(body);
|
|
133
|
+
} catch {
|
|
134
|
+
return body;
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
return body;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
async set(key, value, { json, contentType, ttl } = {}) {
|
|
141
|
+
const { body, headers } = this._serializeValue(value, { json, contentType });
|
|
142
|
+
const query = typeof ttl === "number" ? `ttl=${ttl}` : "";
|
|
143
|
+
return this._request({
|
|
144
|
+
method: "POST",
|
|
145
|
+
url: this._urlForKey(key, query),
|
|
146
|
+
headers,
|
|
147
|
+
body
|
|
148
|
+
});
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
async update(key, value, { json, contentType, ttl } = {}) {
|
|
152
|
+
const { body, headers } = this._serializeValue(value, { json, contentType });
|
|
153
|
+
const query = typeof ttl === "number" ? `ttl=${ttl}` : "";
|
|
154
|
+
return this._request({
|
|
155
|
+
method: "PATCH",
|
|
156
|
+
url: this._urlForKey(key, query),
|
|
157
|
+
headers,
|
|
158
|
+
body
|
|
159
|
+
});
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
async increment(key, amount = 1, { ttl } = {}) {
|
|
163
|
+
if (typeof amount !== "number") throw new TypeError("amount must be a number");
|
|
164
|
+
const sign = amount >= 0 ? "+" : "";
|
|
165
|
+
const query = typeof ttl === "number" ? `ttl=${ttl}` : "";
|
|
166
|
+
return this._request({
|
|
167
|
+
method: "PATCH",
|
|
168
|
+
url: this._urlForKey(key, query),
|
|
169
|
+
headers: { "content-type": "text/plain" },
|
|
170
|
+
body: `${sign}${amount}`
|
|
171
|
+
});
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
async decrement(key, amount = 1, { ttl } = {}) {
|
|
175
|
+
return this.increment(key, -Math.abs(amount), { ttl });
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
async delete(key) {
|
|
179
|
+
return this._request({
|
|
180
|
+
method: "DELETE",
|
|
181
|
+
url: this._urlForKey(key)
|
|
182
|
+
});
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
async list({
|
|
186
|
+
values = false,
|
|
187
|
+
format = "json",
|
|
188
|
+
limit,
|
|
189
|
+
skip,
|
|
190
|
+
prefix,
|
|
191
|
+
reverse
|
|
192
|
+
} = {}) {
|
|
193
|
+
const query = new URLSearchParams();
|
|
194
|
+
if (values) query.set("values", "true");
|
|
195
|
+
if (format) query.set("format", format);
|
|
196
|
+
if (typeof limit === "number") query.set("limit", String(limit));
|
|
197
|
+
if (typeof skip === "number") query.set("skip", String(skip));
|
|
198
|
+
if (prefix) query.set("prefix", prefix);
|
|
199
|
+
if (reverse) query.set("reverse", "true");
|
|
200
|
+
const body = await this._request({
|
|
201
|
+
method: "GET",
|
|
202
|
+
url: this._urlForBucketList(query.toString())
|
|
203
|
+
});
|
|
204
|
+
if (format === "json" && typeof body === "string") {
|
|
205
|
+
try {
|
|
206
|
+
return JSON.parse(body);
|
|
207
|
+
} catch {
|
|
208
|
+
return body;
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
return body;
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
async transaction(operations) {
|
|
215
|
+
if (!Array.isArray(operations)) throw new TypeError("operations must be an array");
|
|
216
|
+
const body = JSON.stringify({ txn: operations });
|
|
217
|
+
return this._request({
|
|
218
|
+
method: "POST",
|
|
219
|
+
url: this._urlForBucket(),
|
|
220
|
+
headers: { "content-type": "application/json" },
|
|
221
|
+
body
|
|
222
|
+
});
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
async createAccessToken({ prefix, permissions, ttl } = {}) {
|
|
226
|
+
const body = new URLSearchParams();
|
|
227
|
+
if (prefix) body.set("prefix", prefix);
|
|
228
|
+
if (permissions) body.set("permissions", permissions);
|
|
229
|
+
if (typeof ttl === "number") body.set("ttl", String(ttl));
|
|
230
|
+
return this._request({
|
|
231
|
+
method: "POST",
|
|
232
|
+
url: `${this.baseUrl}/${this.bucket}/tokens/`,
|
|
233
|
+
headers: { "content-type": "application/x-www-form-urlencoded" },
|
|
234
|
+
body
|
|
235
|
+
});
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
async updateBucketPolicy({ secretKey, writeKey, readKey, signingKey, defaultTtl } = {}) {
|
|
239
|
+
const body = new URLSearchParams();
|
|
240
|
+
if (secretKey) body.set("secret_key", secretKey);
|
|
241
|
+
if (writeKey) body.set("write_key", writeKey);
|
|
242
|
+
if (readKey) body.set("read_key", readKey);
|
|
243
|
+
if (signingKey) body.set("signing_key", signingKey);
|
|
244
|
+
if (typeof defaultTtl === "number") body.set("default_ttl", String(defaultTtl));
|
|
245
|
+
return this._request({
|
|
246
|
+
method: "PATCH",
|
|
247
|
+
url: this._urlForBucket(),
|
|
248
|
+
headers: { "content-type": "application/x-www-form-urlencoded" },
|
|
249
|
+
body
|
|
250
|
+
});
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
async deleteBucket() {
|
|
254
|
+
return this._request({
|
|
255
|
+
method: "DELETE",
|
|
256
|
+
url: `${this.baseUrl}/${this.bucket}/`
|
|
257
|
+
});
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
_serializeValue(value, { json, contentType } = {}) {
|
|
261
|
+
const headers = {};
|
|
262
|
+
if (contentType) headers["content-type"] = contentType;
|
|
263
|
+
if (json || value instanceof Object && !(value instanceof Buffer) && !(value instanceof ArrayBuffer) && !(value instanceof Uint8Array)) {
|
|
264
|
+
headers["content-type"] = headers["content-type"] || "application/json";
|
|
265
|
+
return { body: JSON.stringify(value), headers };
|
|
266
|
+
}
|
|
267
|
+
return { body: typeof value === "string" ? value : String(value), headers };
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
export default KVdbClient;
|