resplite 1.1.8 → 1.1.12

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,6 +1,6 @@
1
1
  {
2
2
  "name": "resplite",
3
- "version": "1.1.8",
3
+ "version": "1.1.12",
4
4
  "description": "A RESP2 server with practical Redis compatibility, backed by SQLite",
5
5
  "type": "module",
6
6
  "main": "src/index.js",
package/spec/SPEC_D.md CHANGED
@@ -160,7 +160,7 @@ Exact fields may evolve, but these should exist.
160
160
 
161
161
  ## D.5.3 Errors
162
162
 
163
- * Missing index: `-ERR unknown index`
163
+ * Missing index: `-Unknown index name`
164
164
 
165
165
  ---
166
166
 
@@ -212,7 +212,7 @@ RESP reply: `+OK`
212
212
 
213
213
  ## D.6.6 Errors
214
214
 
215
- * Index missing: `-ERR unknown index`
215
+ * Index missing: `-Unknown index name`
216
216
  * Wrong syntax: `-ERR syntax error`
217
217
  * Missing `FIELDS`: `-ERR syntax error`
218
218
  * Unknown field: `-ERR unknown field`
@@ -248,7 +248,7 @@ RESP Integer:
248
248
 
249
249
  ## D.7.4 Errors
250
250
 
251
- * Index missing: `-ERR unknown index`
251
+ * Index missing: `-Unknown index name`
252
252
 
253
253
  ---
254
254
 
@@ -346,7 +346,7 @@ Compute total efficiently:
346
346
 
347
347
  ## D.8.8 Errors
348
348
 
349
- * Index missing: `-ERR unknown index`
349
+ * Index missing: `-Unknown index name`
350
350
  * Bad syntax: `-ERR syntax error`
351
351
  * Invalid LIMIT: `-ERR invalid limit`
352
352
  * Unsafe query: `-ERR invalid query`
@@ -383,7 +383,7 @@ RESP Integer:
383
383
 
384
384
  ### Errors
385
385
 
386
- * Index missing: `-ERR unknown index`
386
+ * Index missing: `-Unknown index name`
387
387
  * Score invalid: `-ERR invalid score`
388
388
 
389
389
  ## D.9.2 FT.SUGGET
@@ -425,7 +425,7 @@ Not supported (v1):
425
425
 
426
426
  ### Errors
427
427
 
428
- * Index missing: `-ERR unknown index`
428
+ * Index missing: `-Unknown index name`
429
429
  * Bad syntax: `-ERR syntax error`
430
430
 
431
431
  ## D.9.3 FT.SUGDEL
@@ -449,7 +449,7 @@ RESP Integer:
449
449
 
450
450
  ### Errors
451
451
 
452
- * Index missing: `-ERR unknown index`
452
+ * Index missing: `-Unknown index name`
453
453
 
454
454
  ---
455
455
 
@@ -20,6 +20,6 @@ export function handleFtAdd(engine, args) {
20
20
  return { simple: 'OK' };
21
21
  } catch (e) {
22
22
  const msg = e?.message ?? String(e);
23
- return { error: msg.startsWith('ERR ') ? msg : 'ERR ' + msg };
23
+ return { error: msg === 'Unknown index name' ? msg : msg.startsWith('ERR ') ? msg : 'ERR ' + msg };
24
24
  }
25
25
  }
@@ -15,6 +15,6 @@ export function handleFtCreate(engine, args) {
15
15
  return { simple: 'OK' };
16
16
  } catch (e) {
17
17
  const msg = e?.message ?? String(e);
18
- return { error: msg.startsWith('ERR ') ? msg : 'ERR ' + msg };
18
+ return { error: msg === 'Unknown index name' ? msg : msg.startsWith('ERR ') ? msg : 'ERR ' + msg };
19
19
  }
20
20
  }
@@ -15,6 +15,6 @@ export function handleFtDel(engine, args) {
15
15
  return n;
16
16
  } catch (e) {
17
17
  const msg = e?.message ?? String(e);
18
- return { error: msg.startsWith('ERR ') ? msg : 'ERR ' + msg };
18
+ return { error: msg === 'Unknown index name' ? msg : msg.startsWith('ERR ') ? msg : 'ERR ' + msg };
19
19
  }
20
20
  }
@@ -32,6 +32,6 @@ export function handleFtInfo(engine, args) {
32
32
  ];
33
33
  } catch (e) {
34
34
  const msg = e?.message ?? String(e);
35
- return { error: msg.startsWith('ERR ') ? msg : 'ERR ' + msg };
35
+ return { error: msg === 'Unknown index name' ? msg : msg.startsWith('ERR ') ? msg : 'ERR ' + msg };
36
36
  }
37
37
  }
@@ -33,6 +33,6 @@ export function handleFtSearch(engine, args) {
33
33
  return out;
34
34
  } catch (e) {
35
35
  const msg = e?.message ?? String(e);
36
- return { error: msg.startsWith('ERR ') ? msg : 'ERR ' + msg };
36
+ return { error: msg === 'Unknown index name' ? msg : msg.startsWith('ERR ') ? msg : 'ERR ' + msg };
37
37
  }
38
38
  }
@@ -22,6 +22,6 @@ export function handleFtSugadd(engine, args) {
22
22
  return n;
23
23
  } catch (e) {
24
24
  const msg = e?.message ?? String(e);
25
- return { error: msg.startsWith('ERR ') ? msg : 'ERR ' + msg };
25
+ return { error: msg === 'Unknown index name' ? msg : msg.startsWith('ERR ') ? msg : 'ERR ' + msg };
26
26
  }
27
27
  }
@@ -15,6 +15,6 @@ export function handleFtSugdel(engine, args) {
15
15
  return n;
16
16
  } catch (e) {
17
17
  const msg = e?.message ?? String(e);
18
- return { error: msg.startsWith('ERR ') ? msg : 'ERR ' + msg };
18
+ return { error: msg === 'Unknown index name' ? msg : msg.startsWith('ERR ') ? msg : 'ERR ' + msg };
19
19
  }
20
20
  }
@@ -19,6 +19,6 @@ export function handleFtSugget(engine, args) {
19
19
  return list;
20
20
  } catch (e) {
21
21
  const msg = e?.message ?? String(e);
22
- return { error: msg.startsWith('ERR ') ? msg : 'ERR ' + msg };
22
+ return { error: msg === 'Unknown index name' ? msg : msg.startsWith('ERR ') ? msg : 'ERR ' + msg };
23
23
  }
24
24
  }
package/src/embed.js CHANGED
@@ -34,11 +34,34 @@ export async function createRESPlite({
34
34
  } = {}) {
35
35
  const db = openDb(dbPath, { pragmaTemplate });
36
36
  const engine = createEngine({ db });
37
- const server = net.createServer((socket) => handleConnection(socket, engine));
37
+ const connections = new Set();
38
+
39
+ const server = net.createServer((socket) => {
40
+ connections.add(socket);
41
+ socket.once('close', () => connections.delete(socket));
42
+ handleConnection(socket, engine);
43
+ });
38
44
  await new Promise((resolve) => server.listen(port, host, resolve));
45
+
46
+ let closePromise = null;
47
+ const close = () => {
48
+ if (closePromise) return closePromise;
49
+ closePromise = new Promise((resolve) => {
50
+ for (const socket of connections) {
51
+ socket.destroy();
52
+ }
53
+ connections.clear();
54
+ server.close(() => {
55
+ db.close();
56
+ resolve();
57
+ });
58
+ });
59
+ return closePromise;
60
+ };
61
+
39
62
  return {
40
63
  port: server.address().port,
41
64
  host,
42
- close: () => new Promise((resolve) => server.close(() => { db.close(); resolve(); })),
65
+ close,
43
66
  };
44
67
  }
@@ -116,7 +116,7 @@ export function getIndexMeta(db, name) {
116
116
  const row = db.prepare(
117
117
  'SELECT name, schema_json, created_at, updated_at FROM search_indices WHERE name = ?'
118
118
  ).get(name);
119
- if (!row) throw new Error('ERR unknown index');
119
+ if (!row) throw new Error('Unknown index name');
120
120
  const schema = JSON.parse(row.schema_json);
121
121
  return {
122
122
  name: row.name,
@@ -117,7 +117,7 @@ describe('Search integration', () => {
117
117
  const reply = await sendCommand(port, argv('FT.INFO', 'nonexistent'));
118
118
  const v = tryParseValue(reply, 0).value;
119
119
  const err = v?.error ?? v;
120
- assert.ok(String(err).includes('unknown index'));
120
+ assert.ok(String(err).includes('Unknown index name'));
121
121
  });
122
122
  });
123
123
 
@@ -135,6 +135,6 @@ describe('Search layer', () => {
135
135
  });
136
136
 
137
137
  it('getIndexMeta throws for unknown index', () => {
138
- assert.throws(() => getIndexMeta(db, 'nonexistent'), /unknown index/);
138
+ assert.throws(() => getIndexMeta(db, 'nonexistent'), /Unknown index name/);
139
139
  });
140
140
  });
package/tasks/todo.md DELETED
@@ -1,63 +0,0 @@
1
- # Migration from Redis (SPEC §26)
2
-
3
- ## Plan
4
-
5
- - [x] Review SPEC §26 (Migration Strategy) and §10 (Data model)
6
- - [x] Implement external CLI tool (not part of RESP server)
7
- - [x] Method: connect to Redis → SCAN → TYPE → fetch by type (GET / HGETALL / SMEMBERS) → PTTL → write to SQLite
8
- - [x] Migratable subset: strings, hashes, sets, TTL metadata
9
- - [x] Skip unsupported types (list, zset, stream, etc.) with report
10
- - [x] Contract test using local Redis (skip if Redis unavailable)
11
-
12
- ## Implementation
13
-
14
- - **CLI**: `src/cli/import-from-redis.js` — `node src/cli/import-from-redis.js --redis-url redis://127.0.0.1:6379 --db ./migrated.db`
15
- - **Storage**: Reuse existing `openDb`, `createKeysStorage`, `createStringsStorage`, `createHashesStorage`, `createSetsStorage`; write via storage layer with Buffer values
16
- - **Binary safety**: Redis client returns strings; coerce to Buffer (utf8) when writing to SQLite
17
-
18
- ## Verification
19
-
20
- - Run contract test: `npm run test:contract` (includes import-from-redis test when Redis is available)
21
- - Manual: populate local Redis, run CLI, start RESPlite with migrated db, verify keys/values/TTL
22
-
23
- ## Not in scope (SPEC §26.3)
24
-
25
- - RDB parsing, AOF parsing, mirror mode, dual-write
26
-
27
- ---
28
-
29
- # Migration with Dirty Key Registry (SPEC_F)
30
-
31
- ## Done
32
-
33
- - [x] Migration schema: `migration_runs`, `migration_dirty_keys`, `migration_errors` in `src/storage/sqlite/migration-schema.js`
34
- - [x] Registry layer: `src/migration/registry.js` (createRun, getRun, updateBulkProgress, upsertDirtyKey, getDirtyBatch, markDirtyState, logError, getDirtyCounts)
35
- - [x] Bulk importer: `src/migration/bulk.js` with run_id, checkpointing, resume, max_rps, batch_keys/batch_bytes, pause/abort via status
36
- - [x] Shared import-one: `src/migration/import-one.js` (fetch key from Redis + write to storages; used by bulk and apply-dirty)
37
- - [x] Delta apply: `src/migration/apply-dirty.js` (apply dirty keys from registry: reimport or delete in destination)
38
- - [x] Preflight: `src/migration/preflight.js` (key count, type distribution, notify-keyspace-events check, recommendations)
39
- - [x] Verify: `src/migration/verify.js` (sample keys, compare Redis vs RespLite)
40
- - [x] CLI `resplite-import`: `src/cli/resplite-import.js` (preflight, bulk, status, apply-dirty, verify)
41
- - [x] CLI `resplite-dirty-tracker`: `src/cli/resplite-dirty-tracker.js` (start = PSUBSCRIBE keyevent, stop = update run status)
42
- - [x] package.json `bin`: resplite-import, resplite-dirty-tracker
43
- - [x] Unit tests: `test/unit/migration-registry.test.js`
44
- - [x] README: minimal-downtime migration flow and commands
45
-
46
- ---
47
-
48
- # KEYS + MONITOR Commands
49
-
50
- ## Plan
51
-
52
- - [x] Add `KEYS pattern` command with glob matching support
53
- - [x] Add `MONITOR` command and streaming monitor bus in TCP server
54
- - [x] Register commands in command registry
55
- - [x] Add integration tests for both commands
56
- - [x] Verify with targeted and full integration suites
57
-
58
- ## Review
59
-
60
- - Added `KEYS` command with wildcard matching (`*`, `?`) and proper arity validation
61
- - Added server-side monitor broadcasting for `MONITOR` clients and restricted monitor-mode commands to `QUIT`
62
- - Added integration coverage in `test/integration/keys.test.js` and `test/integration/monitor.test.js`
63
- - Verified with `npm test -- test/integration/keys.test.js`, `npm test -- test/integration/monitor.test.js`, and `npm run test:integration`