Skip to content

Commit

Permalink
getMany -> list. Add data integrity check.
Browse files Browse the repository at this point in the history
  • Loading branch information
Hexagon committed May 11, 2024
1 parent 517669a commit fbc58f9
Show file tree
Hide file tree
Showing 6 changed files with 52 additions and 22 deletions.
6 changes: 3 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -93,12 +93,12 @@ await kvStore.set(["users", "by_id", 5], {
// Use the index to select users between 2 and 4
console.log(
"Users 2-4:",
await kvStore.getMany(["users", "by_id", { from: 2, to: 4 }]),
await kvStore.list(["users", "by_id", { from: 2, to: 4 }]),
);
// ... will output the objects of Alice, Ben and Lisa

// Use a plain JavaScript filter (less performant) to find a user named ben
const ben = (await kvStore.getMany(["users"])).filter((user) =>
const ben = (await kvStore.list(["users"])).filter((user) =>
user.name === "Ben"
);
console.log("Ben: ", ben); // Outputs the object of Ben
Expand All @@ -113,7 +113,7 @@ await kvStore.close();
- `open(filepath)`
- `set(key, value)`
- `get(key)`
- `getMany(key)`
- `list(key)`
- `delete(key)`
- `beginTransaction()`
- `endTransaction()`
Expand Down
14 changes: 7 additions & 7 deletions src/kv.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -138,7 +138,7 @@ test("KV: supports numeric key ranges", async () => {
}

// Test if the 'get' function returns the expected values
const rangeResults = await kvStore.getMany(["data", { from: 7, to: 9 }]);
const rangeResults = await kvStore.list(["data", { from: 7, to: 9 }]);
assertEquals(rangeResults.length, 3);
assertEquals(rangeResults[0].data, "Value 7");
assertEquals(rangeResults[1].data, "Value 8");
Expand All @@ -159,7 +159,7 @@ test("KV: supports additional levels after numeric key ranges", async () => {
}

// Test if the 'get' function returns the expected values
const rangeResults = await kvStore.getMany([
const rangeResults = await kvStore.list([
"data",
{ from: 7, to: 9 },
"doc1",
Expand All @@ -184,13 +184,13 @@ test("KV: supports empty numeric key ranges to get all", async () => {
}

// Test if the 'get' function returns the expected values
const rangeResults = await kvStore.getMany(["data", {}, "doc1"]);
const rangeResults = await kvStore.list(["data", {}, "doc1"]);
assertEquals(rangeResults.length, 6);
const rangeResults2 = await kvStore.getMany(["data", {}, "doc2"]);
const rangeResults2 = await kvStore.list(["data", {}, "doc2"]);
assertEquals(rangeResults2.length, 6);
const rangeResults3 = await kvStore.getMany(["data", {}]);
const rangeResults3 = await kvStore.list(["data", {}]);
assertEquals(rangeResults3.length, 12);
const rangeResults4 = await kvStore.getMany(["data"]);
const rangeResults4 = await kvStore.list(["data"]);
assertEquals(rangeResults4.length, 12);

await kvStore.close();
Expand All @@ -207,7 +207,7 @@ test("KV: supports string key ranges", async () => {
await kvStore.set(["files", "image_1"], "Image 1");

// Get all values within the "doc_" range
const rangeResults = await kvStore.getMany(["files", {
const rangeResults = await kvStore.list(["files", {
from: "doc_",
to: "doc_z",
}]);
Expand Down
6 changes: 3 additions & 3 deletions src/kv.ts
Original file line number Diff line number Diff line change
Expand Up @@ -141,7 +141,7 @@ export class KV {
* @returns The retrieved value, or null if not found.
*/
public async get(key: KVKeyRepresentation): Promise<KVDataEntry | null> {
const result = await this.getMany(key, 1);
const result = await this.list(key, 1);
if (result.length) {
return result[0];
} else {
Expand All @@ -156,7 +156,7 @@ export class KV {
* @param limit - Optional maximum number of values to retrieve.
* @returns An array of retrieved values.
*/
async getMany(
async list(
key: KVKeyRepresentation,
limit?: number,
): Promise<KVDataEntry[]> {
Expand All @@ -173,7 +173,7 @@ export class KV {
if (result?.transaction) {
results.push({
ts: result?.transaction.timestamp,
data: result?.transaction.value,
data: result?.transaction.data,
});
count++;
}
Expand Down
9 changes: 8 additions & 1 deletion src/ledger.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import { SUPPORTED_LEDGER_VERSIONS } from "./constants.ts";
import { KVOperation, KVTransaction } from "./transaction.ts";
import type { KVKey } from "./key.ts";
import { rename, unlink } from "@cross/fs";
import { compareHash, sha1 } from "./utils/hash.ts";

export interface KVTransactionMeta {
key: KVKey;
Expand Down Expand Up @@ -235,12 +236,18 @@ export class KVLedger {

// Read transaction data (optional)
if (decodeData) {
const originalHash: Uint8Array = transaction.hash!;
const transactionHeaderData = await readAtPosition(
fd,
dataLength,
offset + 8 + headerLength,
);
transaction.dataFromUint8Array(transactionHeaderData);
await transaction.dataFromUint8Array(transactionHeaderData);

// Validate data
if (!compareHash(originalHash, transaction.hash!)) {
throw new Error("Invalid data");
}
}
return {
offset: offset,
Expand Down
27 changes: 19 additions & 8 deletions src/transaction.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { sha1 } from "./utils/hash.ts";
import { extDecoder, extEncoder } from "./cbor.ts";
import { KVKey, type KVKeyRepresentation } from "./key.ts";

Expand All @@ -24,6 +25,11 @@ export interface KVTransactionHeader {
* Operation timestamp
*/
t: number;

/**
* Hash
*/
h: Uint8Array;
}

export type KVTransactionData = Uint8Array;
Expand All @@ -33,12 +39,13 @@ export class KVTransaction {
public key?: KVKey;
public operation?: KVOperation;
public timestamp?: number;
public value?: unknown;
public data?: Uint8Array;
public hash?: Uint8Array;

constructor() {
}

public create(
public async create(
key: KVKey | KVKeyRepresentation,
operation: KVOperation,
timestamp: number,
Expand All @@ -51,7 +58,8 @@ export class KVTransaction {
}
this.operation = operation;
this.timestamp = timestamp;
this.value = value;
this.data = value ? extEncoder.encode(value) : undefined;
this.hash = this.data ? await sha1(this.data) : undefined;
}

public headerFromUint8Array(data: Uint8Array) {
Expand All @@ -61,10 +69,14 @@ export class KVTransaction {
this.key = decoded.k;
this.operation = decoded.o;
this.timestamp = decoded.t;
this.hash = decoded.h;
}

public dataFromUint8Array(data: Uint8Array) {
this.value = extDecoder.decode(data);
public async dataFromUint8Array(data: Uint8Array) {
this.data = extDecoder.decode(data);
if (data) {
this.hash = await sha1(data);
}
}

/**
Expand All @@ -76,6 +88,7 @@ export class KVTransaction {
k: this.key!,
o: this.operation!,
t: this.timestamp!,
h: this.hash!,
};

// Encode header
Expand All @@ -84,9 +97,7 @@ export class KVTransaction {
);

// Encode data
const pendingTransactionData = this.value
? extEncoder.encode(this.value)
: undefined;
const pendingTransactionData = this.data;
const pendingTransactionDataLength = pendingTransactionData
? pendingTransactionData.length
: 0;
Expand Down
12 changes: 12 additions & 0 deletions src/utils/hash.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
export async function sha1(data: Uint8Array): Promise<Uint8Array> {
return new Uint8Array(await crypto.subtle.digest("SHA-1", data));
}

export function compareHash(arr1: Uint8Array, arr2: Uint8Array): boolean {
if (arr1.length !== arr2.length) return false; // Length mismatch

for (let i = 0; i < arr1.length; i++) {
if (arr1[i] !== arr2[i]) return false; // Value mismatch
}
return true;
}

0 comments on commit fbc58f9

Please sign in to comment.