Skip to content

Commit

Permalink
Wip. Built in cli-tool. Optimizations.
Browse files Browse the repository at this point in the history
  • Loading branch information
Hexagon committed May 26, 2024
1 parent 3003d00 commit 3d33293
Show file tree
Hide file tree
Showing 28 changed files with 962 additions and 132 deletions.
12 changes: 12 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,15 @@
## 0.14.0

- Cli-tool `ckv` now included in `@cross/kv` through export `@cross/kv/cli`
- Install with `deno install -A -g -n ckv jsr:@cross/kv/cli` and run `ckv`
- Run without installing with `deno run -A jsr:@cross/kv/cli`

## Other changes

- Renamed `key.toStringRepresentation()` to `.stringify()`
- Added static function `KVKeyImplementation.parse()` which returns a `KVKey`.
- Various internal optiomizations

## 0.13.2

- Be more graceful when closing
Expand Down
7 changes: 5 additions & 2 deletions deno.json
Original file line number Diff line number Diff line change
@@ -1,13 +1,15 @@
{
"name": "@cross/kv",
"version": "0.13.2",
"version": "0.13.3",
"exports": {
".": "./mod.ts"
".": "./mod.ts",
"./cli": "./src/cli/mod.ts"
},
"imports": {
"@cross/fs": "jsr:@cross/fs@^0.1.11",
"@cross/runtime": "jsr:@cross/runtime@^1.0.0",
"@cross/test": "jsr:@cross/test@^0.0.9",
"@cross/utils": "jsr:@cross/utils@^0.13.0",
"@std/assert": "jsr:@std/assert@^0.224.0",
"@std/path": "jsr:@std/path@^0.224.0",
"cbor-x": "npm:cbor-x@^1.5.9"
Expand All @@ -16,6 +18,7 @@
"exclude": [".github", "test/*"]
},
"tasks": {
"cli": "deno run -A src/cli/mod.ts",
"check": "deno fmt --check && deno lint && deno check mod.ts && deno doc --lint mod.ts && deno test --allow-read --allow-write --allow-env --allow-net --allow-sys --allow-run --unstable-kv --coverage=cov_profile && echo \"Generating coverage\" && deno coverage cov_profile --exclude=test/ --lcov --output=cov_profile.lcov",
"check-coverage": "deno task check && genhtml cov_profile.lcov --output-directory cov_profile/html && lcov --list cov_profile.lcov && deno run --allow-net --allow-read https://deno.land/std/http/file_server.ts cov_profile/html",
"bench": "deno bench -A --unstable-kv",
Expand Down
9 changes: 6 additions & 3 deletions mod.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,9 @@ export {
type KVOptions,
type KVSyncResult,
type KVSyncResultStatus,
} from "./src/kv.ts";
export type { KVKey, KVQuery, KVQueryRange } from "./src/key.ts";
export type { KVOperation, KVTransactionResult } from "./src/transaction.ts";
} from "./src/lib/kv.ts";
export type { KVKey, KVQuery, KVQueryRange } from "./src/lib/key.ts";
export type {
KVOperation,
KVTransactionResult,
} from "./src/lib/transaction.ts";
32 changes: 32 additions & 0 deletions src/cli/commands/delete.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import { Colors } from "@cross/utils";
import {
ensureMaxParameters,
ensureOpen,
hasParameter,
type KVDBContainer,
userInput,
} from "../common.ts";
import { type KVKey, KVKeyInstance } from "../../lib/key.ts";

export async function del(
container: KVDBContainer,
params: string[],
): Promise<boolean> {
if (!ensureOpen(container)) return false;
if (!ensureMaxParameters(params, 1)) return false;

let key;
if (hasParameter(params, 0)) {
key = params[0];
} else {
key = userInput("Enter key to delete (dot separated):") || "";
if (!key) {
console.error(Colors.red("Key not specified.\n"));
return false;
}
}

const keySplit = KVKeyInstance.parse(key, false) as KVKey;
await container.db?.delete(keySplit);
return true;
}
54 changes: 54 additions & 0 deletions src/cli/commands/get.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
import { Colors } from "@cross/utils";
import {
ensureMaxParameters,
ensureOpen,
hasParameter,
type KVDBContainer,
toHexString,
} from "../common.ts";
import { type KVKey, KVKeyInstance } from "../../lib/key.ts";
import { KVOperation } from "../../lib/transaction.ts";

export async function get(
container: KVDBContainer,
params: string[],
): Promise<boolean> {
if (!ensureOpen(container)) return false;
if (!ensureMaxParameters(params, 1)) return false;

let key;
if (hasParameter(params, 0)) {
key = params[0];
} else {
console.error(Colors.red("No key specified."));
return false;
}

const keyParsed = KVKeyInstance.parse(key, false) as KVKey;
const value = await container.db?.get(keyParsed);

if (value) {
const operationName = KVOperation[value.operation as KVOperation] ??
"Unknown";
console.log("");
console.log(Colors.bold("Key:\t\t"), JSON.stringify(keyParsed));
console.log(
Colors.bold("Operation:\t"),
`${operationName} (${Colors.yellow(value.operation.toString())})`,
);
console.log(
Colors.bold("Timestamp:\t"),
Colors.magenta(new Date(value.timestamp).toISOString()),
);
console.log(
Colors.bold("Hash:\t\t"),
value.hash ? toHexString(value.hash) : null,
);
console.log("");
console.dir(value.data, { depth: 3, colors: true });
return true;
} else {
console.log(Colors.red("Key not found."));
return false;
}
}
56 changes: 56 additions & 0 deletions src/cli/commands/help.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
import { Colors } from "@cross/utils";
import type { KVDBContainer } from "../common.ts";

const helpMessage = `
${Colors.bold(Colors.blue("KV Database CLI"))}
${Colors.underline("Usage:")}
<command> [parameters]
${Colors.underline("Commands:")}
${
Colors.yellow("open")
} <database_path> Opens or creates the specified database.
${
Colors.yellow("get")
} <key> Retrieves the value associated with the key.
${
Colors.yellow("set:boolean")
} <key> <value> Sets the value for the given key.
${
Colors.yellow("set:string")
} <key> <value> Sets the value for the given key.
${
Colors.yellow("set:date")
} <key> <value> Sets the value for the given key.
${
Colors.yellow("set:number")
} <key> <value> Sets the value for the given key.
${
Colors.yellow("set:json")
} <key> <value> Sets the value for the given key.
${
Colors.yellow("list")
} [query] Lists key-value pairs matching the optional query.
${
Colors.yellow("listKeys")
} [query] Lists keys matching the optional query.
${
Colors.yellow("delete")
} <key> Deletes the key-value pair with the given key.
${Colors.yellow("stats")} Displays database statistics.
${Colors.yellow("help")} Shows this help message.
${Colors.yellow("exit")} Exits the CLI.
`;

// Explicit return type for clarity
// deno-lint-ignore require-await
async function help(
_container: KVDBContainer,
_params: string[],
): Promise<boolean> {
console.log(helpMessage);
return true; // Indicate successful command execution
}

export { help };
37 changes: 37 additions & 0 deletions src/cli/commands/list.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import {
ensureMaxParameters,
ensureOpen,
hasParameter,
type KVDBContainer,
userInput,
} from "../common.ts";
import { KVKeyInstance, type KVQuery } from "../../lib/key.ts";

export async function list(
container: KVDBContainer,
params: string[],
): Promise<boolean> {
if (!ensureOpen(container)) return false;
if (!ensureMaxParameters(params, 1)) return false;

let query: KVQuery;
if (hasParameter(params, 0)) {
query = KVKeyInstance.parse(params[0], true); // Query parsing
} else {
const queryInput = userInput("Enter query (dot separated): ");
if (!queryInput) return false; // Exit if no query provided
query = KVKeyInstance.parse(queryInput, true);
}

console.log("");

// Iterate over matching entries
for await (const entry of container.db!.iterate(query)) {
// Display key information
const key = new KVKeyInstance(entry.key).stringify();
console.log(key, entry.data);
}

console.log(""); // Extra newline for separation
return true;
}
40 changes: 40 additions & 0 deletions src/cli/commands/listkeys.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import { Colors } from "@cross/utils";
import {
ensureMaxParameters,
ensureOpen,
hasParameter,
type KVDBContainer,
} from "../common.ts";
import { type KVKey, KVKeyInstance, type KVQuery } from "../../lib/key.ts";

// deno-lint-ignore require-await
export async function listKeys(
container: KVDBContainer,
params: string[],
): Promise<boolean> {
if (!ensureOpen(container)) return false;
if (!ensureMaxParameters(params, 1)) return false;

let key: KVKey | KVQuery | null = null;
if (hasParameter(params, 0)) {
key = KVKeyInstance.parse(params[0], true); // Allow range
} else {
key = null;
}

const childKeys = container.db?.listKeys(key);

if (childKeys && childKeys.length > 0) {
console.log(Colors.bold("Child Keys:"));
for (const childKey of childKeys) {
console.log(` ${childKey}`);
}
} else {
if (key === null) {
console.log(Colors.yellow("No root keys found."));
} else {
console.log(Colors.yellow("No child keys found for the specified key."));
}
}
return true;
}
42 changes: 42 additions & 0 deletions src/cli/commands/open.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
import { Colors } from "@cross/utils";
import { KV } from "../../../mod.ts";
import {
ensureMaxParameters,
hasParameter,
type KVDBContainer,
} from "../common.ts";

/**
* @private
*/
function ensureClosed(container: KVDBContainer): boolean {
if (container.db?.isOpen()) {
console.log(Colors.red("A database is already open."));
return false;
} else {
return true;
}
}

export async function open(
container: KVDBContainer,
params: string[],
): Promise<boolean> {
if (!ensureClosed(container)) return false;
if (!ensureMaxParameters(params, 1)) return false;
let dbPath;
if (hasParameter(params, 0)) {
dbPath = params[0];
} else {
console.error(Colors.red("No database specified."));
return false;
}
container.db = new KV();
try {
await container.db.open(dbPath, true);
return true;
} catch (e) {
console.error(`Could not open database: ${e.message}`);
return false;
}
}
51 changes: 51 additions & 0 deletions src/cli/commands/scan.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
import {
ensureMaxParameters,
ensureOpen,
hasParameter,
type KVDBContainer,
userInput,
} from "../common.ts";
import { KVKeyInstance, type KVQuery } from "../../lib/key.ts";
import { KVOperation } from "../../lib/transaction.ts";
import { Colors } from "@cross/utils";
import { toHexString } from "../common.ts";

export async function scan(
container: KVDBContainer,
params: string[],
): Promise<boolean> {
if (!ensureOpen(container)) return false;
if (!ensureMaxParameters(params, 1)) return false;

let query: KVQuery;
if (hasParameter(params, 0)) {
query = KVKeyInstance.parse(params[0], true); // Query parsing
} else {
const queryInput = userInput("Enter query (dot separated): ");
if (!queryInput) return false; // Exit if no query provided
query = KVKeyInstance.parse(queryInput, true);
}

console.log("");

// Iterate over matching entries
let results = 0;
for await (const value of container.db!.scan(query)) {
const operationName = KVOperation[value.operation as KVOperation] ??
"Unknown";
console.log(
Colors.magenta(new Date(value.timestamp).toISOString()),
JSON.stringify(value.key),
`${operationName} (${Colors.yellow(value.operation.toString())})`,
JSON.stringify(value.data).slice(0, 30),
);
results++;
}
if (results === 0) {
console.log(Colors.red("Key not found."));
return false;
}

console.log(""); // Extra newline for separation
return true;
}
Loading

0 comments on commit 3d33293

Please sign in to comment.