resplite 1.4.2 → 1.4.4
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 +13 -2
- package/package.json +1 -1
- package/src/embed.js +4 -2
- package/src/index.js +2 -1
- package/src/storage/sqlite/db.js +3 -3
- package/src/storage/sqlite/pragmas.js +18 -2
- package/test/integration/embed.test.js +11 -0
- package/test/unit/pragmas.test.js +24 -0
- package/docs/generated/migration-apply-dirty-concurrency-progress.md +0 -7
- package/docs/generated/migration-bulk-concurrency.md +0 -7
- package/docs/generated/migration-bulk-eta-onprogress.md +0 -7
package/README.md
CHANGED
|
@@ -517,16 +517,27 @@ redis-cli -p 6380 PING
|
|
|
517
517
|
| `RESPLITE_DB` | `./data.db` | SQLite database file |
|
|
518
518
|
| `RESPLITE_PRAGMA_TEMPLATE` | `default` | SQLite PRAGMA preset (see below) |
|
|
519
519
|
|
|
520
|
-
### PRAGMA
|
|
520
|
+
### PRAGMA (convention over configuration)
|
|
521
|
+
|
|
522
|
+
A **template** is applied by default (`default`); you usually don't pass anything. Only pass **overrides** when you need to change specific pragmas.
|
|
521
523
|
|
|
522
524
|
| Template | Description | Key settings |
|
|
523
|
-
|
|
525
|
+
|----------|-------------|--------------|
|
|
524
526
|
| `default` | Balanced durability and speed (recommended) | WAL, synchronous=NORMAL, 20 MB cache |
|
|
525
527
|
| `performance` | Maximum throughput, reduced crash safety | WAL, synchronous=OFF, 64 MB cache, 512 MB mmap, exclusive locking |
|
|
526
528
|
| `safety` | Crash-safe writes at the cost of speed | WAL, synchronous=FULL, 20 MB cache |
|
|
527
529
|
| `minimal` | Only WAL + foreign keys | WAL, foreign_keys=ON |
|
|
528
530
|
| `none` | No pragmas applied, pure SQLite defaults | - |
|
|
529
531
|
|
|
532
|
+
Override specific pragmas only when needed. Overrides are applied after the template. Example — 1 GB cache:
|
|
533
|
+
|
|
534
|
+
```javascript
|
|
535
|
+
const srv = await createRESPlite({
|
|
536
|
+
db: './data.db',
|
|
537
|
+
pragma: { cache_size: -1024 * 1024 }, // negative = KiB, so 1 GiB
|
|
538
|
+
});
|
|
539
|
+
```
|
|
540
|
+
|
|
530
541
|
## Benchmark (Redis vs RESPLite)
|
|
531
542
|
|
|
532
543
|
A typical comparison is **Redis (for example, in Docker)** on one side and **RESPLite locally** on the other. In that setup, RESPLite often shows **better latency** because it avoids Docker networking and runs in the same process or host. The benchmark below uses RESPLite with the **default** PRAGMA template only.
|
package/package.json
CHANGED
package/src/embed.js
CHANGED
|
@@ -33,7 +33,8 @@ export { handleConnection, createEngine, openDb };
|
|
|
33
33
|
* @param {string} [options.db=':memory:'] SQLite file path, or ':memory:' for in-memory.
|
|
34
34
|
* @param {string} [options.host='127.0.0.1'] Host to listen on.
|
|
35
35
|
* @param {number} [options.port=0] Port to listen on (0 = OS-assigned).
|
|
36
|
-
* @param {string} [options.pragmaTemplate='default'] PRAGMA preset (default|performance|safety|minimal|none).
|
|
36
|
+
* @param {string} [options.pragmaTemplate='default'] PRAGMA preset (default|performance|safety|minimal|none). Convention: this template is applied by default; no config needed.
|
|
37
|
+
* @param {Record<string, string|number>} [options.pragma] Override specific pragmas only when needed (e.g. { synchronous: 'FULL' }). Applied after the template.
|
|
37
38
|
* @param {RESPliteHooks} [options.hooks] Optional event hooks for observability (onUnknownCommand, onCommandError, onSocketError).
|
|
38
39
|
* @param {boolean} [options.gracefulShutdown=true] If true, register SIGTERM/SIGINT to call close(). Set false if you handle shutdown yourself to avoid double handlers.
|
|
39
40
|
* @returns {Promise<{ port: number, host: string, close: () => Promise<void> }>}
|
|
@@ -43,10 +44,11 @@ export async function createRESPlite({
|
|
|
43
44
|
host = '127.0.0.1',
|
|
44
45
|
port = 0,
|
|
45
46
|
pragmaTemplate = 'default',
|
|
47
|
+
pragma,
|
|
46
48
|
hooks = {},
|
|
47
49
|
gracefulShutdown = true,
|
|
48
50
|
} = {}) {
|
|
49
|
-
const db = openDb(dbPath, { pragmaTemplate });
|
|
51
|
+
const db = openDb(dbPath, { pragmaTemplate, pragma });
|
|
50
52
|
const engine = createEngine({ db });
|
|
51
53
|
const connections = new Set();
|
|
52
54
|
|
package/src/index.js
CHANGED
|
@@ -24,6 +24,7 @@ const DEFAULT_PORT = 6379;
|
|
|
24
24
|
* @param {number} [options.port]
|
|
25
25
|
* @param {string} [options.dbPath]
|
|
26
26
|
* @param {string} [options.pragmaTemplate]
|
|
27
|
+
* @param {Record<string, string|number>} [options.pragma] Override specific pragmas when needed (e.g. { synchronous: 'FULL' }). Convention: template is applied by default.
|
|
27
28
|
* @param {boolean} [options.gracefulShutdown=true] If true, register SIGTERM/SIGINT to close server and DB. Set false if you handle shutdown yourself.
|
|
28
29
|
*/
|
|
29
30
|
export function startServer(options = {}) {
|
|
@@ -32,7 +33,7 @@ export function startServer(options = {}) {
|
|
|
32
33
|
const pragmaTemplate = options.pragmaTemplate ?? process.env.RESPLITE_PRAGMA_TEMPLATE ?? 'default';
|
|
33
34
|
const gracefulShutdown = options.gracefulShutdown !== false;
|
|
34
35
|
|
|
35
|
-
const db = openDb(dbPath, { pragmaTemplate });
|
|
36
|
+
const db = openDb(dbPath, { pragmaTemplate, pragma: options.pragma });
|
|
36
37
|
const cache = createCache({ enabled: true });
|
|
37
38
|
const engine = createEngine({ db, cache });
|
|
38
39
|
const sweeper = createExpirationSweeper({
|
package/src/storage/sqlite/db.js
CHANGED
|
@@ -12,7 +12,7 @@ import { applyMigrationSchema } from './migration-schema.js';
|
|
|
12
12
|
|
|
13
13
|
/**
|
|
14
14
|
* @param {string} dbPath - Database file path (or ':memory:')
|
|
15
|
-
* @param {object} [options] - Options: pragmaTemplate (default|performance|safety|minimal), plus any better-sqlite3 options
|
|
15
|
+
* @param {object} [options] - Options: pragmaTemplate (default|performance|safety|minimal), pragma (custom key-value overrides), plus any better-sqlite3 options
|
|
16
16
|
* @returns {import('better-sqlite3').Database}
|
|
17
17
|
*/
|
|
18
18
|
export function openDb(dbPath, options = {}) {
|
|
@@ -20,9 +20,9 @@ export function openDb(dbPath, options = {}) {
|
|
|
20
20
|
const dir = path.dirname(dbPath);
|
|
21
21
|
if (dir) fs.mkdirSync(dir, { recursive: true });
|
|
22
22
|
}
|
|
23
|
-
const { pragmaTemplate = 'default', ...dbOptions } = options;
|
|
23
|
+
const { pragmaTemplate = 'default', pragma: customPragma, ...dbOptions } = options;
|
|
24
24
|
const db = new Database(dbPath, dbOptions);
|
|
25
|
-
applyPragmas(db, pragmaTemplate);
|
|
25
|
+
applyPragmas(db, pragmaTemplate, customPragma);
|
|
26
26
|
applySchema(db);
|
|
27
27
|
applyMigrationSchema(db);
|
|
28
28
|
return db;
|
|
@@ -69,13 +69,29 @@ export function getPragmasForTemplate(name) {
|
|
|
69
69
|
}
|
|
70
70
|
|
|
71
71
|
/**
|
|
72
|
-
* Apply
|
|
72
|
+
* Apply custom pragma key-value object to an open database.
|
|
73
|
+
* @param {import('better-sqlite3').Database} db
|
|
74
|
+
* @param {Record<string, string|number>} obj - e.g. { journal_mode: 'WAL', cache_size: -64000 }
|
|
75
|
+
*/
|
|
76
|
+
function applyPragmaObject(db, obj) {
|
|
77
|
+
for (const [key, val] of Object.entries(obj)) {
|
|
78
|
+
if (val === undefined) continue;
|
|
79
|
+
db.exec(`PRAGMA ${key}=${val};`);
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* Apply pragmas from a named template and optional overrides to an open database.
|
|
73
85
|
* @param {import('better-sqlite3').Database} db
|
|
74
86
|
* @param {string} [templateName='default'] - One of: default, performance, safety, minimal, none
|
|
87
|
+
* @param {Record<string, string|number>} [customPragma] - Optional overrides, e.g. { synchronous: 'FULL', cache_size: -10000 }
|
|
75
88
|
*/
|
|
76
|
-
export function applyPragmas(db, templateName = 'default') {
|
|
89
|
+
export function applyPragmas(db, templateName = 'default', customPragma = undefined) {
|
|
77
90
|
const pragmas = getPragmasForTemplate(templateName);
|
|
78
91
|
for (const sql of pragmas) {
|
|
79
92
|
db.exec(sql);
|
|
80
93
|
}
|
|
94
|
+
if (customPragma && typeof customPragma === 'object' && Object.keys(customPragma).length > 0) {
|
|
95
|
+
applyPragmaObject(db, customPragma);
|
|
96
|
+
}
|
|
81
97
|
}
|
|
@@ -89,6 +89,17 @@ describe('createRESPlite', () => {
|
|
|
89
89
|
await srv.close();
|
|
90
90
|
});
|
|
91
91
|
|
|
92
|
+
it('accepts pragma overrides (convention: template first, overrides only when needed)', async () => {
|
|
93
|
+
const srv = await createRESPlite({
|
|
94
|
+
pragma: { synchronous: 'FULL', cache_size: -10_000 },
|
|
95
|
+
});
|
|
96
|
+
const client = await redisClient(srv.port);
|
|
97
|
+
await client.set('k', 'v');
|
|
98
|
+
assert.equal(await client.get('k'), 'v');
|
|
99
|
+
await client.quit();
|
|
100
|
+
await srv.close();
|
|
101
|
+
});
|
|
102
|
+
|
|
92
103
|
it('unsupported command still returns ERR command not supported yet to client', async () => {
|
|
93
104
|
const srv = await createRESPlite();
|
|
94
105
|
const client = await redisClient(srv.port);
|
|
@@ -72,4 +72,28 @@ describe('Pragma templates', () => {
|
|
|
72
72
|
db.close();
|
|
73
73
|
}
|
|
74
74
|
});
|
|
75
|
+
|
|
76
|
+
it('openDb with pragma overrides applies them after the template', () => {
|
|
77
|
+
const path = tmpDbPath();
|
|
78
|
+
const db = openDb(path, { pragmaTemplate: 'default', pragma: { synchronous: 'FULL' } });
|
|
79
|
+
try {
|
|
80
|
+
const row = db.prepare('PRAGMA synchronous').get();
|
|
81
|
+
assert.equal(row.synchronous, 2); // FULL = 2
|
|
82
|
+
} finally {
|
|
83
|
+
db.close();
|
|
84
|
+
}
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
it('openDb with pragma cache_size override (e.g. 1 GiB)', () => {
|
|
88
|
+
const path = tmpDbPath();
|
|
89
|
+
const oneGibKib = 1024 * 1024;
|
|
90
|
+
const db = openDb(path, { pragmaTemplate: 'default', pragma: { cache_size: -oneGibKib } });
|
|
91
|
+
try {
|
|
92
|
+
const row = db.prepare('PRAGMA cache_size').get();
|
|
93
|
+
// SQLite returns cache size in KiB when it was set negative
|
|
94
|
+
assert.equal(Math.abs(row.cache_size), oneGibKib);
|
|
95
|
+
} finally {
|
|
96
|
+
db.close();
|
|
97
|
+
}
|
|
98
|
+
});
|
|
75
99
|
});
|
|
@@ -1,7 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
id: 90dhbsvf0f
|
|
3
|
-
type: implementation
|
|
4
|
-
title: Apply dirty migration concurrency and progress
|
|
5
|
-
created: '2026-03-12 14:55:12'
|
|
6
|
-
---
|
|
7
|
-
Improved src/migration/apply-dirty.js to support concurrent dirty-key apply via options.concurrency (chunked Promise.all worker model) while preserving max_rps throttling. Added richer onProgress payload fields: dirty_keys_processed, dirty_pending, dirty_keys_per_second, dirty_eta_seconds, and related counters. Exposed new options through createMigration().applyDirty: concurrency and progressIntervalMs. Updated README migration cutover snippet with high-throughput applyDirty example and progress logging. Added unit test test/unit/migration-apply-dirty.test.js validating concurrency and progress payload. Verified with node --test test/unit/migration-apply-dirty.test.js and npm run test:unit (all passing).
|
|
@@ -1,7 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
id: tucj9i5nh5
|
|
3
|
-
type: implementation
|
|
4
|
-
title: Bulk migration concurrency added
|
|
5
|
-
created: '2026-03-11 11:09:20'
|
|
6
|
-
---
|
|
7
|
-
Added configurable concurrency to runBulkImport and createMigration.bulk with default 1. Implemented chunked parallel import with shared global max_rps limiter. Added unit tests proving default sequential behavior and concurrent behavior with cap.
|
|
@@ -1,7 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
id: 105jsp012x
|
|
3
|
-
type: implementation
|
|
4
|
-
title: Bulk onProgress ETA support
|
|
5
|
-
created: '2026-03-11 11:10:48'
|
|
6
|
-
---
|
|
7
|
-
Added ETA/progress metrics to bulk migration onProgress payload. New optional options: estimated_total_keys in runBulkImport, estimatedTotalKeys in createMigration/bulk(). onProgress payload now includes elapsed_seconds, keys_per_second, estimated_total_keys, remaining_keys_estimate, eta_seconds, progress_pct. README migration example updated to print ETA/rate. Added unit test validating ETA fields and final 100%/eta=0 behavior.
|