130 lines
3.5 KiB
JavaScript
130 lines
3.5 KiB
JavaScript
import { $fetch } from "ofetch";
|
|
import {
|
|
createError,
|
|
createRequiredError,
|
|
defineDriver,
|
|
joinKeys
|
|
} from "./utils/index.mjs";
|
|
const DRIVER_NAME = "cloudflare-kv-http";
|
|
export default defineDriver((opts) => {
|
|
if (!opts.accountId) {
|
|
throw createRequiredError(DRIVER_NAME, "accountId");
|
|
}
|
|
if (!opts.namespaceId) {
|
|
throw createRequiredError(DRIVER_NAME, "namespaceId");
|
|
}
|
|
let headers;
|
|
if ("apiToken" in opts) {
|
|
headers = { Authorization: `Bearer ${opts.apiToken}` };
|
|
} else if ("userServiceKey" in opts) {
|
|
headers = { "X-Auth-User-Service-Key": opts.userServiceKey };
|
|
} else if (opts.email && opts.apiKey) {
|
|
headers = { "X-Auth-Email": opts.email, "X-Auth-Key": opts.apiKey };
|
|
} else {
|
|
throw createError(
|
|
DRIVER_NAME,
|
|
"One of the `apiToken`, `userServiceKey`, or a combination of `email` and `apiKey` is required."
|
|
);
|
|
}
|
|
const apiURL = opts.apiURL || "https://api.cloudflare.com";
|
|
const baseURL = `${apiURL}/client/v4/accounts/${opts.accountId}/storage/kv/namespaces/${opts.namespaceId}`;
|
|
const kvFetch = $fetch.create({ baseURL, headers });
|
|
const r = (key = "") => opts.base ? joinKeys(opts.base, key) : key;
|
|
const hasItem = async (key) => {
|
|
try {
|
|
const res = await kvFetch(`/metadata/${r(key)}`);
|
|
return res?.success === true;
|
|
} catch (err) {
|
|
if (!err?.response) {
|
|
throw err;
|
|
}
|
|
if (err?.response?.status === 404) {
|
|
return false;
|
|
}
|
|
throw err;
|
|
}
|
|
};
|
|
const getItem = async (key) => {
|
|
try {
|
|
return await kvFetch(`/values/${r(key)}`).then((r2) => r2.text());
|
|
} catch (err) {
|
|
if (!err?.response) {
|
|
throw err;
|
|
}
|
|
if (err?.response?.status === 404) {
|
|
return null;
|
|
}
|
|
throw err;
|
|
}
|
|
};
|
|
const setItem = async (key, value, topts) => {
|
|
return await kvFetch(`/values/${r(key)}`, {
|
|
method: "PUT",
|
|
body: value,
|
|
query: topts?.ttl ? { expiration_ttl: Math.max(topts?.ttl, opts.minTTL || 60) } : void 0
|
|
});
|
|
};
|
|
const removeItem = async (key) => {
|
|
return await kvFetch(`/values/${r(key)}`, { method: "DELETE" });
|
|
};
|
|
const getKeys = async (base) => {
|
|
const keys = [];
|
|
const params = {};
|
|
if (base || opts.base) {
|
|
params.prefix = r(base);
|
|
}
|
|
const firstPage = await kvFetch("/keys", { params });
|
|
for (const item of firstPage.result) {
|
|
keys.push(item.name);
|
|
}
|
|
const cursor = firstPage.result_info.cursor;
|
|
if (cursor) {
|
|
params.cursor = cursor;
|
|
}
|
|
while (params.cursor) {
|
|
const pageResult = await kvFetch("/keys", { params });
|
|
for (const item of pageResult.result) {
|
|
keys.push(item.name);
|
|
}
|
|
const pageCursor = pageResult.result_info.cursor;
|
|
params.cursor = pageCursor ? pageCursor : void 0;
|
|
}
|
|
return keys;
|
|
};
|
|
const clear = async () => {
|
|
const keys = await getKeys();
|
|
const chunks = keys.reduce(
|
|
(acc, key, i) => {
|
|
if (i % 1e4 === 0) {
|
|
acc.push([]);
|
|
}
|
|
acc[acc.length - 1].push(key);
|
|
return acc;
|
|
},
|
|
[[]]
|
|
);
|
|
await Promise.all(
|
|
chunks.map((chunk) => {
|
|
if (chunk.length > 0) {
|
|
return kvFetch("/bulk/delete", {
|
|
method: "POST",
|
|
body: chunk
|
|
});
|
|
}
|
|
})
|
|
);
|
|
};
|
|
return {
|
|
name: DRIVER_NAME,
|
|
options: opts,
|
|
hasItem,
|
|
getItem,
|
|
setItem,
|
|
removeItem,
|
|
getKeys: (base) => getKeys(base).then(
|
|
(keys) => keys.map((key) => opts.base ? key.slice(opts.base.length) : key)
|
|
),
|
|
clear
|
|
};
|
|
});
|