@go-go-scope/persistence-sqlite-bun 2.0.0

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/README.md ADDED
@@ -0,0 +1,83 @@
1
+ # @go-go-scope/persistence-sqlite-bun
2
+
3
+ Bun-native SQLite persistence adapter for go-go-scope using `bun:sqlite`. This adapter provides optimal performance when running under Bun by leveraging its native SQLite implementation.
4
+
5
+ ## Installation
6
+
7
+ ```bash
8
+ # For Bun projects
9
+ bun add @go-go-scope/persistence-sqlite-bun
10
+ ```
11
+
12
+ ## Usage
13
+
14
+ ```typescript
15
+ import { Database } from 'bun:sqlite'
16
+ import { scope } from 'go-go-scope'
17
+ import { BunSQLiteAdapter } from '@go-go-scope/persistence-sqlite-bun'
18
+
19
+ // Create a database (in-memory or file-based)
20
+ const db = new Database(':memory:')
21
+ // or: const db = new Database('/path/to/app.db')
22
+
23
+ // Create the adapter
24
+ const persistence = new BunSQLiteAdapter(db, {
25
+ keyPrefix: 'myapp:' // optional prefix for keys
26
+ })
27
+
28
+ // Use with scope
29
+ await using s = scope({ persistence })
30
+
31
+ // Acquire distributed lock
32
+ const lock = await s.acquireLock('resource:123', 30000)
33
+ if (!lock) {
34
+ throw new Error('Could not acquire lock')
35
+ }
36
+
37
+ // Lock automatically expires after TTL
38
+ // Optional: release early
39
+ await lock.release()
40
+ ```
41
+
42
+ ## Features
43
+
44
+ - **Distributed Locks**: In-memory with TTL support
45
+ - **Circuit Breaker State**: Persistent state storage
46
+ - **Native Performance**: Uses Bun's `bun:sqlite` for maximum speed
47
+ - **API Compatibility**: Same interface as other persistence adapters
48
+
49
+ ## Why Use This Adapter?
50
+
51
+ | Feature | `sqlite3` | `bun:sqlite` (this adapter) |
52
+ |---------|-----------|----------------------------|
53
+ | Performance | Good | **Excellent** |
54
+ | Bun-native | No | **Yes** |
55
+ | Bundle size | Larger | **Smaller** |
56
+ | Dependencies | Native bindings | **Built-in** |
57
+
58
+ ## API
59
+
60
+ ### Constructor
61
+
62
+ ```typescript
63
+ new BunSQLiteAdapter(db: Database, options?: { keyPrefix?: string })
64
+ ```
65
+
66
+ - `db`: Bun SQLite Database instance
67
+ - `options.keyPrefix`: Optional prefix for all keys
68
+
69
+ ### Methods
70
+
71
+ See the main [go-go-scope documentation](../../docs) for full persistence API details.
72
+
73
+ ## Testing
74
+
75
+ ```bash
76
+ # Run Bun compatibility tests
77
+ cd packages/go-go-scope
78
+ bun test tests/bun-compatibility.test.ts
79
+ ```
80
+
81
+ ## License
82
+
83
+ MIT
@@ -0,0 +1,68 @@
1
+ import { LockProvider, CircuitBreakerStateProvider, PersistenceAdapter, PersistenceAdapterOptions, LockHandle, CircuitBreakerPersistedState } from 'go-go-scope';
2
+
3
+ /**
4
+ * Bun-native SQLite persistence adapter for go-go-scope
5
+ *
6
+ * Provides distributed locks and circuit breaker state using Bun's native
7
+ * `bun:sqlite` module. This is faster than the sqlite3-based adapter under Bun.
8
+ *
9
+ * @example
10
+ * ```typescript
11
+ * import { Database } from 'bun:sqlite'
12
+ * import { BunSQLiteAdapter } from '@go-go-scope/persistence-sqlite-bun'
13
+ *
14
+ * const db = new Database('/tmp/app.db')
15
+ * const persistence = new BunSQLiteAdapter(db, { keyPrefix: 'myapp:' })
16
+ *
17
+ * await using s = scope({ persistence })
18
+ *
19
+ * // Acquire a lock with 30 second TTL
20
+ * const lock = await s.acquireLock('resource:123', 30000)
21
+ * if (!lock) {
22
+ * throw new Error('Could not acquire lock')
23
+ * }
24
+ *
25
+ * // Lock automatically expires after TTL
26
+ * // Optional: release early with await lock.release()
27
+ * ```
28
+ */
29
+
30
+ interface BunDatabase {
31
+ run(sql: string, params?: unknown[]): {
32
+ changes: number;
33
+ lastInsertRowid: number;
34
+ };
35
+ query<T = unknown>(sql: string): {
36
+ get(...params: unknown[]): T | null;
37
+ all(...params: unknown[]): T[];
38
+ run(...params: unknown[]): {
39
+ changes: number;
40
+ lastInsertRowid: number;
41
+ };
42
+ };
43
+ close(): void;
44
+ }
45
+ /**
46
+ * Bun-native SQLite persistence adapter
47
+ */
48
+ declare class BunSQLiteAdapter implements LockProvider, CircuitBreakerStateProvider, PersistenceAdapter {
49
+ private readonly db;
50
+ private readonly keyPrefix;
51
+ private readonly locks;
52
+ private connected;
53
+ constructor(db: BunDatabase, options?: PersistenceAdapterOptions);
54
+ private prefix;
55
+ private generateOwner;
56
+ connect(): Promise<void>;
57
+ disconnect(): Promise<void>;
58
+ isConnected(): boolean;
59
+ acquire(key: string, ttl: number, owner?: string): Promise<LockHandle | null>;
60
+ extend(key: string, ttl: number, owner: string): Promise<boolean>;
61
+ forceRelease(key: string): Promise<void>;
62
+ getState(key: string): Promise<CircuitBreakerPersistedState | null>;
63
+ setState(key: string, state: CircuitBreakerPersistedState): Promise<void>;
64
+ recordFailure(key: string, maxFailures: number): Promise<number>;
65
+ recordSuccess(key: string): Promise<void>;
66
+ }
67
+
68
+ export { BunSQLiteAdapter };
package/dist/index.mjs ADDED
@@ -0,0 +1,148 @@
1
+ class BunSQLiteAdapter {
2
+ db;
3
+ keyPrefix;
4
+ locks = /* @__PURE__ */ new Map();
5
+ connected = false;
6
+ constructor(db, options = {}) {
7
+ this.db = db;
8
+ this.keyPrefix = options.keyPrefix ?? "";
9
+ }
10
+ prefix(key) {
11
+ return this.keyPrefix ? `${this.keyPrefix}${key}` : key;
12
+ }
13
+ generateOwner() {
14
+ return `${Date.now()}-${Math.random().toString(36).slice(2)}`;
15
+ }
16
+ // ============================================================================
17
+ // Persistence Adapter
18
+ // ============================================================================
19
+ async connect() {
20
+ this.db.run(`
21
+ CREATE TABLE IF NOT EXISTS go_goscope_circuit (
22
+ key TEXT PRIMARY KEY,
23
+ state TEXT NOT NULL,
24
+ failure_count INTEGER NOT NULL DEFAULT 0,
25
+ last_failure_time INTEGER,
26
+ last_success_time INTEGER,
27
+ updated_at INTEGER NOT NULL
28
+ )
29
+ `);
30
+ this.connected = true;
31
+ }
32
+ async disconnect() {
33
+ this.db.close();
34
+ this.connected = false;
35
+ }
36
+ isConnected() {
37
+ return this.connected;
38
+ }
39
+ // ============================================================================
40
+ // Lock Provider (In-Memory with Cleanup)
41
+ // ============================================================================
42
+ async acquire(key, ttl, owner) {
43
+ const fullKey = this.prefix(`lock:${key}`);
44
+ const lockOwner = owner ?? this.generateOwner();
45
+ const now = Date.now();
46
+ for (const [k, v] of this.locks.entries()) {
47
+ if (v.expiry < now) {
48
+ this.locks.delete(k);
49
+ }
50
+ }
51
+ if (this.locks.has(fullKey)) {
52
+ return null;
53
+ }
54
+ this.locks.set(fullKey, { owner: lockOwner, expiry: now + ttl });
55
+ const handle = {
56
+ release: async () => {
57
+ const lock = this.locks.get(fullKey);
58
+ if (lock?.owner === lockOwner) {
59
+ this.locks.delete(fullKey);
60
+ }
61
+ },
62
+ extend: async (newTtl) => {
63
+ return this.extend(key, newTtl, lockOwner);
64
+ },
65
+ isValid: async () => {
66
+ const lock = this.locks.get(fullKey);
67
+ return lock?.owner === lockOwner && lock.expiry > Date.now();
68
+ }
69
+ };
70
+ return handle;
71
+ }
72
+ async extend(key, ttl, owner) {
73
+ const fullKey = this.prefix(`lock:${key}`);
74
+ const lock = this.locks.get(fullKey);
75
+ if (!lock || lock.owner !== owner) {
76
+ return false;
77
+ }
78
+ lock.expiry = Date.now() + ttl;
79
+ return true;
80
+ }
81
+ async forceRelease(key) {
82
+ const fullKey = this.prefix(`lock:${key}`);
83
+ this.locks.delete(fullKey);
84
+ }
85
+ // ============================================================================
86
+ // Circuit Breaker State Provider
87
+ // ============================================================================
88
+ async getState(key) {
89
+ const fullKey = this.prefix(`circuit:${key}`);
90
+ const stmt = this.db.query(
91
+ "SELECT state, failure_count, last_failure_time, last_success_time FROM go_goscope_circuit WHERE key = ?"
92
+ );
93
+ const row = stmt.get(fullKey);
94
+ if (!row) return null;
95
+ return {
96
+ state: row.state,
97
+ failureCount: row.failure_count,
98
+ lastFailureTime: row.last_failure_time ?? void 0,
99
+ lastSuccessTime: row.last_success_time ?? void 0
100
+ };
101
+ }
102
+ async setState(key, state) {
103
+ const fullKey = this.prefix(`circuit:${key}`);
104
+ const now = Date.now();
105
+ this.db.run(
106
+ `
107
+ INSERT INTO go_goscope_circuit (key, state, failure_count, last_failure_time, last_success_time, updated_at)
108
+ VALUES (?, ?, ?, ?, ?, ?)
109
+ ON CONFLICT(key) DO UPDATE SET
110
+ state = excluded.state,
111
+ failure_count = excluded.failure_count,
112
+ last_failure_time = excluded.last_failure_time,
113
+ last_success_time = excluded.last_success_time,
114
+ updated_at = excluded.updated_at
115
+ `,
116
+ [
117
+ fullKey,
118
+ state.state,
119
+ state.failureCount,
120
+ state.lastFailureTime ?? null,
121
+ state.lastSuccessTime ?? null,
122
+ now
123
+ ]
124
+ );
125
+ }
126
+ async recordFailure(key, maxFailures) {
127
+ const state = await this.getState(key) ?? {
128
+ state: "closed",
129
+ failureCount: 0
130
+ };
131
+ state.failureCount++;
132
+ state.lastFailureTime = Date.now();
133
+ if (state.failureCount >= maxFailures) {
134
+ state.state = "open";
135
+ }
136
+ await this.setState(key, state);
137
+ return state.failureCount;
138
+ }
139
+ async recordSuccess(key) {
140
+ await this.setState(key, {
141
+ state: "closed",
142
+ failureCount: 0,
143
+ lastSuccessTime: Date.now()
144
+ });
145
+ }
146
+ }
147
+
148
+ export { BunSQLiteAdapter };
package/package.json ADDED
@@ -0,0 +1,64 @@
1
+ {
2
+ "name": "@go-go-scope/persistence-sqlite-bun",
3
+ "version": "2.0.0",
4
+ "description": "Bun-native SQLite persistence adapter for go-go-scope - uses bun:sqlite for optimal performance",
5
+ "type": "module",
6
+ "main": "./dist/index.mjs",
7
+ "types": "./dist/index.d.mts",
8
+ "exports": {
9
+ ".": {
10
+ "types": "./dist/index.d.mts",
11
+ "default": "./dist/index.mjs"
12
+ }
13
+ },
14
+ "scripts": {
15
+ "build": "pkgroll --clean-dist",
16
+ "lint": "biome check --write src/",
17
+ "test": "echo 'Run tests with: bun test src/index.test.ts'",
18
+ "clean": "rm -rf dist"
19
+ },
20
+ "keywords": [
21
+ "bun",
22
+ "sqlite",
23
+ "persistence",
24
+ "distributed-locks",
25
+ "circuit-breaker",
26
+ "go-go-scope",
27
+ "bun:sqlite"
28
+ ],
29
+ "engines": {
30
+ "node": ">=24.0.0"
31
+ },
32
+ "author": "thelinuxlich",
33
+ "license": "MIT",
34
+ "repository": {
35
+ "type": "git",
36
+ "url": "git+https://github.com/thelinuxlich/go-go-scope.git",
37
+ "directory": "packages/persistence-sqlite-bun"
38
+ },
39
+ "files": [
40
+ "dist/",
41
+ "README.md"
42
+ ],
43
+ "publishConfig": {
44
+ "access": "public"
45
+ },
46
+ "dependencies": {
47
+ "go-go-scope": "workspace:*"
48
+ },
49
+ "peerDependencies": {
50
+ "bun": ">=1.0.0"
51
+ },
52
+ "peerDependenciesMeta": {
53
+ "bun": {
54
+ "optional": true
55
+ }
56
+ },
57
+ "devDependencies": {
58
+ "@biomejs/biome": "^2.4.3",
59
+ "@types/node": "^24",
60
+ "pkgroll": "^2.26.3",
61
+ "typescript": "^5.9.3",
62
+ "vitest": "^4.0.18"
63
+ }
64
+ }