feat(server): implement KV store API and fingerprinting functionality
- Added a new KV store API with endpoints for querying namespaces and key-value pairs. - Introduced fingerprinting functionality to hash and manage data fingerprints, allowing for efficient change detection. - Enhanced existing scripts to utilize the new fingerprinting capabilities, enabling conditional execution based on data changes. - Updated the web interface to include a dedicated KV page for managing key-value entries and displaying their details. - Improved overall user experience with pagination and search capabilities in the KV management interface.
This commit is contained in:
@@ -257,6 +257,80 @@ export async function kvList(namespace, opts = {}) {
|
||||
return items;
|
||||
}
|
||||
|
||||
function escapeLike(value) {
|
||||
return value.replaceAll("\\", "\\\\").replaceAll("%", "\\%").replaceAll("_", "\\_");
|
||||
}
|
||||
|
||||
async function pruneExpiredKv() {
|
||||
await db("script_state")
|
||||
.whereNotNull("expires_at")
|
||||
.andWhere("expires_at", "<=", nowIso())
|
||||
.del();
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {{
|
||||
* namespace?: string,
|
||||
* q?: string,
|
||||
* limit?: number,
|
||||
* offset?: number,
|
||||
* }} [opts]
|
||||
*/
|
||||
export async function kvQuery(opts = {}) {
|
||||
await pruneExpiredKv();
|
||||
|
||||
const limit = Math.min(Math.max(opts.limit ?? 50, 1), 100);
|
||||
const offset = Math.max(Number(opts.offset) || 0, 0);
|
||||
|
||||
let q = db("script_state");
|
||||
if (opts.namespace) {
|
||||
assertNamespace(opts.namespace);
|
||||
q = q.where({ namespace: opts.namespace });
|
||||
}
|
||||
if (typeof opts.q === "string" && opts.q.length > 0) {
|
||||
const like = `%${escapeLike(opts.q)}%`;
|
||||
q = q.where(function likeSearch() {
|
||||
this.whereRaw("key LIKE ? ESCAPE '\\'", [like]).orWhereRaw(
|
||||
"value LIKE ? ESCAPE '\\'",
|
||||
[like],
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
const countRow = await q.clone().count({ count: "*" }).first();
|
||||
const total = Number(countRow?.count ?? 0);
|
||||
|
||||
const rows = await q
|
||||
.clone()
|
||||
.orderBy("updated_at", "desc")
|
||||
.orderBy("namespace", "asc")
|
||||
.orderBy("key", "asc")
|
||||
.limit(limit)
|
||||
.offset(offset);
|
||||
|
||||
return {
|
||||
items: rows.map((row) => ({
|
||||
namespace: row.namespace,
|
||||
key: row.key,
|
||||
value: deserializeKvValue(row.value),
|
||||
updatedAt: row.updated_at,
|
||||
expiresAt: row.expires_at ?? null,
|
||||
})),
|
||||
total,
|
||||
limit,
|
||||
offset,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns {Promise<string[]>}
|
||||
*/
|
||||
export async function kvNamespaces() {
|
||||
await pruneExpiredKv();
|
||||
const rows = await db("script_state").distinct("namespace").orderBy("namespace", "asc");
|
||||
return rows.map((row) => row.namespace);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} defaultNamespace
|
||||
*/
|
||||
|
||||
Reference in New Issue
Block a user