@prooflog/node 0.1.0 → 0.1.1

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/package.json CHANGED
@@ -1,7 +1,25 @@
1
1
  {
2
2
  "name": "@prooflog/node",
3
- "version": "0.1.0",
3
+ "version": "0.1.1",
4
4
  "description": "Cryptographically tamper-proof audit logging for Node.js",
5
+ "repository": {
6
+ "type": "git",
7
+ "url": "git+https://github.com/RahulDew/prooflog.git",
8
+ "directory": "packages/sdk"
9
+ },
10
+ "homepage": "https://github.com/RahulDew/prooflog/tree/main/packages/sdk#readme",
11
+ "bugs": {
12
+ "url": "https://github.com/RahulDew/prooflog/issues"
13
+ },
14
+ "keywords": [
15
+ "audit",
16
+ "log",
17
+ "crypto",
18
+ "hash-chain",
19
+ "tamper-proof",
20
+ "security",
21
+ "compliance"
22
+ ],
5
23
  "license": "MIT",
6
24
  "main": "./dist/index.js",
7
25
  "module": "./dist/index.mjs",
@@ -15,7 +33,8 @@
15
33
  },
16
34
  "scripts": {
17
35
  "build": "tsup",
18
- "dev": "tsup --watch"
36
+ "dev": "tsup --watch",
37
+ "test": "vitest run"
19
38
  },
20
39
  "peerDependencies": {
21
40
  "drizzle-orm": ">=0.45.0"
@@ -28,6 +47,7 @@
28
47
  "@prooflog/db": "workspace:*",
29
48
  "drizzle-orm": "^0.45.2",
30
49
  "tsup": "^8.5.0",
31
- "typescript": "^5"
50
+ "typescript": "^5",
51
+ "vitest": "^4.1.9"
32
52
  }
33
53
  }
@@ -0,0 +1,90 @@
1
+ import { describe, it, expect, vi, beforeEach } from "vitest";
2
+ import { ProofLog } from "../client";
3
+ import { GENESIS_HASH } from "@prooflog/crypto";
4
+
5
+ // We use vi.mock to mock the @neondatabase/serverless and drizzle-orm
6
+ // since we don't want to connect to a real DB during unit tests.
7
+
8
+ vi.mock("@neondatabase/serverless", () => ({
9
+ neon: vi.fn(() => "mock-sql"),
10
+ }));
11
+
12
+ const mockInsert = vi.fn();
13
+ const mockValues = vi.fn();
14
+ const mockSelect = vi.fn();
15
+ const mockFrom = vi.fn();
16
+ const mockWhere = vi.fn();
17
+ const mockOrderBy = vi.fn();
18
+ const mockLimit = vi.fn();
19
+
20
+ vi.mock("drizzle-orm/neon-http", () => ({
21
+ drizzle: vi.fn(() => ({
22
+ insert: mockInsert.mockReturnValue({ values: mockValues }),
23
+ select: mockSelect.mockReturnValue({
24
+ from: mockFrom.mockReturnValue({
25
+ where: mockWhere.mockReturnValue({
26
+ orderBy: mockOrderBy.mockReturnValue({
27
+ limit: mockLimit,
28
+ }),
29
+ }),
30
+ }),
31
+ }),
32
+ })),
33
+ }));
34
+
35
+ describe("ProofLog SDK", () => {
36
+ beforeEach(() => {
37
+ vi.clearAllMocks();
38
+ });
39
+
40
+ it("should initialize with correct config", () => {
41
+ expect(() => new ProofLog({ databaseUrl: "" })).toThrow("databaseUrl is required");
42
+ const log = new ProofLog({ databaseUrl: "postgres://fake" });
43
+ expect(log).toBeInstanceOf(ProofLog);
44
+ });
45
+
46
+ it("should ingest a new log with genesis hash if it's the first log", async () => {
47
+ mockLimit.mockResolvedValueOnce([]); // No previous entries
48
+
49
+ const log = new ProofLog({ databaseUrl: "postgres://fake" });
50
+ const result = await log.ingest("org_1", {
51
+ action: "login",
52
+ actor: { id: "user_1" }
53
+ });
54
+
55
+ expect(mockLimit).toHaveBeenCalledWith(1); // Checked for previous entry
56
+ expect(mockInsert).toHaveBeenCalled();
57
+ expect(mockValues).toHaveBeenCalled();
58
+
59
+ const insertedValues = mockValues.mock.calls[0][0];
60
+ expect(insertedValues.sequence).toBe(1);
61
+ expect(insertedValues.previousHash).toBe(GENESIS_HASH);
62
+ expect(result.sequence).toBe(1);
63
+ expect(result.hash).toBeTypeOf("string");
64
+ });
65
+
66
+ it("should retry on unique constraint violation (concurrency)", async () => {
67
+ // 1st attempt: return an entry, but simulate a race condition crash on insert
68
+ mockLimit.mockResolvedValueOnce([{ sequence: 1, hash: "hash1" }]);
69
+ mockValues.mockRejectedValueOnce({ code: "23505" }); // Postgres unique violation
70
+
71
+ // 2nd attempt: return the new tip (sequence 2), and insert succeeds
72
+ mockLimit.mockResolvedValueOnce([{ sequence: 2, hash: "hash2" }]);
73
+ mockValues.mockResolvedValueOnce({ inserted: true });
74
+
75
+ const log = new ProofLog({ databaseUrl: "postgres://fake" });
76
+ const result = await log.ingest("org_1", {
77
+ action: "login",
78
+ actor: { id: "user_1" }
79
+ });
80
+
81
+ expect(mockLimit).toHaveBeenCalledTimes(2);
82
+ expect(mockValues).toHaveBeenCalledTimes(2);
83
+
84
+ // The successful insert should use sequence 3
85
+ const finalInsertedValues = mockValues.mock.calls[1][0];
86
+ expect(finalInsertedValues.sequence).toBe(3);
87
+ expect(finalInsertedValues.previousHash).toBe("hash2");
88
+ expect(result.sequence).toBe(3);
89
+ });
90
+ });
@@ -0,0 +1,8 @@
1
+ import { defineConfig } from 'vitest/config'
2
+
3
+ export default defineConfig({
4
+ test: {
5
+ environment: 'node',
6
+ globals: true,
7
+ },
8
+ })