@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 +83 -0
- package/dist/index.d.mts +68 -0
- package/dist/index.mjs +148 -0
- package/package.json +64 -0
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
|
package/dist/index.d.mts
ADDED
|
@@ -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
|
+
}
|