resplite 1.2.6 → 1.2.8

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.
Files changed (33) hide show
  1. package/README.md +168 -275
  2. package/package.json +1 -6
  3. package/scripts/create-interface-smoke.js +32 -0
  4. package/skills/README.md +22 -0
  5. package/skills/resplite-command-vertical-slice/SKILL.md +134 -0
  6. package/skills/resplite-ft-search-workbench/SKILL.md +138 -0
  7. package/skills/resplite-migration-cutover-assistant/SKILL.md +138 -0
  8. package/spec/00-INDEX.md +37 -0
  9. package/spec/01-overview-and-goals.md +125 -0
  10. package/spec/02-protocol-and-commands.md +174 -0
  11. package/spec/03-data-model-ttl-transactions.md +157 -0
  12. package/spec/04-cache-architecture.md +171 -0
  13. package/spec/05-scan-admin-implementation.md +379 -0
  14. package/spec/06-migration-strategy-core.md +79 -0
  15. package/spec/07-type-lists.md +202 -0
  16. package/spec/08-type-sorted-sets.md +220 -0
  17. package/spec/{SPEC_D.md → 09-search-ft-commands.md} +3 -1
  18. package/spec/{SPEC_E.md → 10-blocking-commands.md} +3 -1
  19. package/spec/{SPEC_F.md → 11-migration-dirty-registry.md} +61 -147
  20. package/src/commands/object.js +17 -0
  21. package/src/commands/registry.js +2 -0
  22. package/src/engine/engine.js +11 -0
  23. package/src/migration/apply-dirty.js +8 -1
  24. package/src/migration/index.js +5 -4
  25. package/src/migration/migrate-search.js +25 -6
  26. package/test/integration/object-idletime.test.js +51 -0
  27. package/test/unit/migrate-search.test.js +50 -2
  28. package/spec/SPEC_A.md +0 -1171
  29. package/spec/SPEC_B.md +0 -426
  30. package/src/cli/import-from-redis.js +0 -194
  31. package/src/cli/resplite-dirty-tracker.js +0 -92
  32. package/src/cli/resplite-import.js +0 -296
  33. package/test/contract/import-from-redis.test.js +0 -83
@@ -0,0 +1,379 @@
1
+ # RESPLite Specification v1 — SCAN, Admin Commands, Project Structure, Testing
2
+
3
+ ## 17. SCAN Behavior
4
+
5
+ SCAN is part of the v1 scope.
6
+
7
+ ### 17.1 Minimum supported form
8
+
9
+ Required in v1:
10
+
11
+ - `SCAN cursor`
12
+
13
+ ### 17.2 Response shape
14
+
15
+ The response should follow Redis-like shape:
16
+
17
+ - next cursor
18
+ - array of keys
19
+
20
+ ### 17.3 Implementation note
21
+
22
+ A simple initial strategy is acceptable, such as lexicographic traversal over `redis_keys.key`.
23
+
24
+ ### 17.4 Future extensions
25
+
26
+ Possible later additions:
27
+
28
+ - `MATCH pattern`
29
+ - `COUNT n`
30
+
31
+ These are not required in the first implementation.
32
+
33
+ ---
34
+
35
+ ## 18. Administrative Commands
36
+
37
+ ### 18.1 SQLITE.INFO
38
+
39
+ This command should expose useful operational information such as:
40
+
41
+ - database path
42
+ - SQLite version
43
+ - counts by type
44
+ - total logical key count
45
+ - WAL mode status if available
46
+
47
+ ### 18.2 CACHE.INFO
48
+
49
+ This command should expose cache information such as:
50
+
51
+ - cache enabled or disabled
52
+ - entry count
53
+ - estimated memory use
54
+ - hit count
55
+ - miss count
56
+ - hit ratio if available
57
+
58
+ These commands are intended for observability and debugging.
59
+
60
+ ---
61
+
62
+ ## 19. Future Search Scope
63
+
64
+ Search is not part of the base v1 implementation, but the architecture must remain compatible with a near-term v1.1 search layer.
65
+
66
+ ### 19.1 Search vision
67
+
68
+ The future search subsystem should provide RediSearch-inspired commands powered by SQLite FTS5 with BM25 ranking.
69
+
70
+ Candidate commands:
71
+
72
+ - `FT.CREATE`
73
+ - `FT.SEARCH`
74
+ - `FT.INFO`
75
+ - `FT.DROPINDEX`
76
+
77
+ ### 19.2 Recommended document model
78
+
79
+ The preferred future approach is to index hashes as documents.
80
+ Example logical model:
81
+
82
+ - `article:1` stored as a hash
83
+ - an index with prefix `article:`
84
+ - fields such as `title`, `body`, and `category`
85
+
86
+ ### 19.3 Backend strategy
87
+
88
+ - SQLite FTS5 virtual tables
89
+ - BM25 ranking
90
+ - auxiliary metadata tables for filtering and sorting
91
+
92
+ The project should not attempt to fully clone RediSearch grammar or behavior at the start.
93
+ A clean, reduced, Redis-like search surface is preferable.
94
+
95
+ ---
96
+
97
+ ## 20. Project Structure
98
+
99
+ Recommended Node.js JavaScript ESM project layout:
100
+
101
+ ```text
102
+ resplite/
103
+ package.json
104
+ README.md
105
+ SPEC.md
106
+ src/
107
+ index.js
108
+ server/
109
+ tcp-server.js
110
+ connection.js
111
+ resp/
112
+ parser.js
113
+ encoder.js
114
+ types.js
115
+ commands/
116
+ registry.js
117
+ ping.js
118
+ echo.js
119
+ quit.js
120
+ get.js
121
+ set.js
122
+ del.js
123
+ exists.js
124
+ expire.js
125
+ pexpire.js
126
+ ttl.js
127
+ pttl.js
128
+ persist.js
129
+ incr.js
130
+ decr.js
131
+ incrby.js
132
+ decrby.js
133
+ type.js
134
+ scan.js
135
+ hset.js
136
+ hget.js
137
+ hmget.js
138
+ hgetall.js
139
+ hdel.js
140
+ hexists.js
141
+ hincrby.js
142
+ sadd.js
143
+ srem.js
144
+ smembers.js
145
+ sismember.js
146
+ scard.js
147
+ sqlite-info.js
148
+ cache-info.js
149
+ engine/
150
+ engine.js
151
+ errors.js
152
+ expiration.js
153
+ validate.js
154
+ storage/
155
+ sqlite/
156
+ db.js
157
+ schema.js
158
+ pragmas.js
159
+ keys.js
160
+ strings.js
161
+ hashes.js
162
+ sets.js
163
+ tx.js
164
+ cache/
165
+ lru.js
166
+ cache.js
167
+ util/
168
+ buffers.js
169
+ patterns.js
170
+ clock.js
171
+ logger.js
172
+ test/
173
+ helpers/
174
+ server.js
175
+ client.js
176
+ tmp.js
177
+ clock.js
178
+ fixtures.js
179
+ unit/
180
+ ...
181
+ integration/
182
+ ...
183
+ contract/
184
+ ...
185
+ stress/
186
+ ...
187
+ ```
188
+
189
+ ---
190
+
191
+ ## 21. Implementation Stack
192
+
193
+ Recommended initial stack:
194
+
195
+ - Node.js
196
+ - JavaScript ESM
197
+ - `better-sqlite3`
198
+ - built-in `node:test`
199
+ - `assert/strict`
200
+ - `redis` npm package for contract tests
201
+
202
+ The v1 codebase should prioritize correctness, testability, and operational simplicity over TypeScript or extensive framework usage.
203
+
204
+ ---
205
+
206
+ ## 22. Testing Strategy
207
+
208
+ Testing is a first-class requirement.
209
+ A command is not considered implemented until it has meaningful coverage.
210
+
211
+ ### 22.1 Testing categories
212
+
213
+ The project must include the following categories of tests:
214
+
215
+ - unit tests
216
+ - integration tests
217
+ - contract tests
218
+ - persistence tests
219
+ - expiration tests
220
+ - consistency tests
221
+ - concurrency tests
222
+ - protocol framing tests
223
+ - binary-safety tests
224
+
225
+ ### 22.2 Per-command testing rule
226
+
227
+ Each supported command should have tests for:
228
+
229
+ - normal behavior
230
+ - missing-key behavior where applicable
231
+ - wrong-type behavior where applicable
232
+ - TTL interaction where applicable
233
+ - persistence across restart where applicable
234
+ - binary-safe behavior where applicable
235
+
236
+ ### 22.3 RESP protocol tests
237
+
238
+ The RESP layer must be tested for:
239
+
240
+ - valid parsing of RESP2 types
241
+ - partial frames split across TCP chunks
242
+ - multiple commands arriving in a single TCP chunk
243
+ - correct encoding of all response types
244
+
245
+ ### 22.4 Contract tests with real clients
246
+
247
+ v1 contract tests should use:
248
+
249
+ - `redis-cli`
250
+ - the official `redis` npm client
251
+
252
+ ### 22.5 Persistence tests
253
+
254
+ The project must verify:
255
+
256
+ - data survives server restart
257
+ - TTL metadata survives restart
258
+ - expired keys behave as missing after restart
259
+ - mixed key types remain valid after restart
260
+
261
+ ### 22.6 Consistency tests
262
+
263
+ The project must verify internal invariants such as:
264
+
265
+ - rows in type tables must correspond to a valid row in `redis_keys`
266
+ - logical type must match actual storage table placement
267
+ - expired keys must not appear through command results
268
+ - empty hashes and sets are removed logically
269
+
270
+ ### 22.7 Concurrency tests
271
+
272
+ The project must verify behavior under multiple clients, including:
273
+
274
+ - many concurrent INCR operations on the same key
275
+ - reads and writes overlapping across connections
276
+ - expiration sweeps occurring during active traffic
277
+ - restart safety under recent write activity
278
+
279
+ ### 22.8 Performance sanity tests
280
+
281
+ The project does not need benchmark-grade tests in v1, but it should include sanity checks such as:
282
+
283
+ - repeated SET and GET operations at modest scale
284
+ - many TTL expirations without protocol failure
285
+ - SCAN on a non-trivial keyspace
286
+
287
+ ---
288
+
289
+ ## 23. Package Scripts
290
+
291
+ Suggested package scripts:
292
+
293
+ ```json
294
+ {
295
+ "type": "module",
296
+ "scripts": {
297
+ "test": "node --test",
298
+ "test:unit": "node --test test/unit",
299
+ "test:integration": "node --test test/integration",
300
+ "test:contract": "node --test test/contract",
301
+ "test:stress": "node --test test/stress",
302
+ "test:all": "node --test test"
303
+ }
304
+ }
305
+ ```
306
+
307
+ ---
308
+
309
+ ## 24. Acceptance Criteria for Initial Milestone
310
+
311
+ The first meaningful milestone is achieved when the server can be exercised successfully through `redis-cli` and passes automated tests for the following sequence:
312
+
313
+ ```text
314
+ PING
315
+ SET foo bar
316
+ GET foo
317
+ EXPIRE foo 10
318
+ TTL foo
319
+ DEL foo
320
+ HSET user:1 name Martin age 42
321
+ HGET user:1 name
322
+ HGETALL user:1
323
+ SADD tags a b c
324
+ SMEMBERS tags
325
+ TYPE foo
326
+ SCAN 0
327
+ ```
328
+
329
+ In addition, the system must satisfy these conditions:
330
+
331
+ - persistence survives restart
332
+ - wrong-type errors are returned correctly
333
+ - expired keys behave as missing keys
334
+ - binary-safe values do not break command semantics
335
+ - multiple clients can connect and issue commands without corrupting state
336
+
337
+ ---
338
+
339
+ ## 25. Implementation Order
340
+
341
+ Recommended phased implementation order:
342
+
343
+ ### Phase 1
344
+
345
+ Infrastructure: TCP server, RESP parser/encoder, command registry, SQLite init, error model, protocol tests.
346
+
347
+ ### Phase 2
348
+
349
+ Strings and core: PING, ECHO, GET, SET, DEL, EXISTS, TYPE.
350
+
351
+ ### Phase 3
352
+
353
+ Expiration: EXPIRE, PEXPIRE, TTL, PTTL, PERSIST, lazy expiration, active sweeper.
354
+
355
+ ### Phase 4
356
+
357
+ Numeric string operations: INCR, DECR, INCRBY, DECRBY.
358
+
359
+ ### Phase 5
360
+
361
+ Hashes: HSET, HGET, HMGET, HGETALL, HDEL, HEXISTS, HINCRBY.
362
+
363
+ ### Phase 6
364
+
365
+ Sets: SADD, SREM, SMEMBERS, SISMEMBER, SCARD.
366
+
367
+ ### Phase 7
368
+
369
+ Introspection and observability: SCAN, SQLITE.INFO, CACHE.INFO.
370
+
371
+ ### Phase 8
372
+
373
+ Migration tooling: programmatic Redis migration API using SCAN, import verification and reporting.
374
+
375
+ ### Phase 9
376
+
377
+ Search extension: FT.CREATE, FT.SEARCH, FT.INFO, FT.DROPINDEX (SQLite FTS5 with BM25).
378
+
379
+ Search should only begin after the core command, TTL, persistence, and concurrency suites are stable.
@@ -0,0 +1,79 @@
1
+ # RESPLite Specification v1 — Migration Strategy (Core)
2
+
3
+ ## 26. Migration Strategy from Redis
4
+
5
+ Migration is not part of the RESP protocol surface in v1.
6
+ It should be implemented as a programmatic module outside the RESP command surface.
7
+
8
+ ### 26.1 Initial migration method
9
+
10
+ Recommended initial approach:
11
+
12
+ - connect to a real Redis instance
13
+ - iterate keys using `SCAN`
14
+ - discover key type with `TYPE`
15
+ - fetch values according to type
16
+ - fetch expiration using `PTTL`
17
+ - write translated data into SQLite
18
+
19
+ ### 26.2 Initial migratable subset
20
+
21
+ The initial migration module should support:
22
+
23
+ - strings
24
+ - hashes
25
+ - sets
26
+ - TTL metadata
27
+
28
+ ### 26.3 Not required initially
29
+
30
+ - RDB parsing
31
+ - AOF parsing
32
+ - mirror mode
33
+ - dual-write migration
34
+
35
+ These can be considered later if adoption demands them.
36
+
37
+ ---
38
+
39
+ ## 27. Compatibility Matrix Guidance
40
+
41
+ The README should include a compatibility matrix with at least three groups:
42
+
43
+ ### Supported
44
+
45
+ All commands implemented in v1 (and any extensions such as lists, zsets, FT.*, blocking, migration API).
46
+
47
+ ### Planned
48
+
49
+ Future near-term commands or features as documented in the respective spec files.
50
+
51
+ ### Not Supported
52
+
53
+ Features explicitly excluded from the roadmap for now, such as:
54
+
55
+ - Pub/Sub
56
+ - Streams
57
+ - Lua
58
+ - Cluster
59
+ - Replication
60
+ - Blocking commands (unless added per blocking spec)
61
+
62
+ This matrix is essential for setting correct user expectations.
63
+
64
+ ---
65
+
66
+ ## 28. Final Design Rule
67
+
68
+ A command should only be added if its implementation is:
69
+
70
+ - natural on SQLite
71
+ - semantically clear
72
+ - testable
73
+ - operationally safe
74
+ - maintainable without excessive special handling
75
+
76
+ If a command requires too much implementation gymnastics to preserve Redis-like behavior, it should not be part of the supported surface until a clean design exists.
77
+
78
+ This rule is central to the project.
79
+ It protects correctness, keeps the scope honest, and preserves the identity of RESPLite as a practical Redis-compatible server built on top of SQLite rather than a fragile imitation.
@@ -0,0 +1,202 @@
1
+ # RESPLite — Type: Lists (Appendix B)
2
+
3
+ ## B.1 Goals
4
+
5
+ * Provide Redis-compatible LIST commands over RESP2, backed by SQLite persistence.
6
+ * Avoid O(n) reindexing for push/pop operations.
7
+ * Support binary-safe values (`Buffer`) and keys as `BLOB`.
8
+ * Maintain correct Redis semantics for empty lists and wrong-type errors.
9
+
10
+ ## B.2 Supported Commands (vNext)
11
+
12
+ * `LPUSH key value [value ...]`
13
+ * `RPUSH key value [value ...]`
14
+ * `LPOP key [count]`
15
+ * `RPOP key [count]`
16
+ * `LLEN key`
17
+ * `LRANGE key start stop`
18
+ * `LINDEX key index` (optional, recommended)
19
+ * `LSET key index value` (optional, later)
20
+ * `LTRIM key start stop` (optional, later)
21
+
22
+ **Not supported initially**
23
+
24
+ * Blocking commands: `BLPOP`, `BRPOP`, `BRPOPLPUSH`
25
+ * `RPOPLPUSH`, `LMOVE` (later if needed)
26
+
27
+ ## B.3 Redis Semantics
28
+
29
+ * **Wrong type:** If `key` exists and is not a list, return:
30
+
31
+ * `WRONGTYPE Operation against a key holding the wrong kind of value`
32
+ * **Non-existent key:**
33
+
34
+ * `LLEN` returns `0`
35
+ * `LRANGE` returns empty array
36
+ * `LPOP/RPOP` returns `nil` (or empty array for count > 1)
37
+ * **Empty list after removals:** When a list becomes empty, delete the logical key:
38
+
39
+ * delete metadata from `redis_keys`
40
+ * delete list meta/items rows
41
+
42
+ ## B.4 Data Model (SQLite)
43
+
44
+ Lists are stored using a monotonic sequence strategy per key to avoid shifting indices.
45
+
46
+ ### B.4.1 Metadata table
47
+
48
+ ```sql
49
+ CREATE TABLE redis_list_meta (
50
+ key BLOB PRIMARY KEY,
51
+ head_seq INTEGER NOT NULL,
52
+ tail_seq INTEGER NOT NULL,
53
+ FOREIGN KEY(key) REFERENCES redis_keys(key) ON DELETE CASCADE
54
+ );
55
+ ```
56
+
57
+ **Invariant:**
58
+
59
+ * Empty list should not exist (no meta row). If it exists, it must satisfy `head_seq <= tail_seq`.
60
+
61
+ ### B.4.2 Items table
62
+
63
+ ```sql
64
+ CREATE TABLE redis_list_items (
65
+ key BLOB NOT NULL,
66
+ seq INTEGER NOT NULL,
67
+ value BLOB NOT NULL,
68
+ PRIMARY KEY (key, seq),
69
+ FOREIGN KEY(key) REFERENCES redis_keys(key) ON DELETE CASCADE
70
+ );
71
+
72
+ CREATE INDEX redis_list_items_key_seq_idx ON redis_list_items(key, seq);
73
+ ```
74
+
75
+ ## B.5 Sequence Strategy
76
+
77
+ * Each list key maintains:
78
+
79
+ * `head_seq`: the smallest sequence currently used (front)
80
+ * `tail_seq`: the largest sequence currently used (back)
81
+ * For a new list:
82
+
83
+ * initialize `head_seq = 0`, `tail_seq = -1` (or similar empty sentinel)
84
+ * but we recommend **not creating meta** until first push.
85
+
86
+ ### Push operations
87
+
88
+ * `LPUSH`:
89
+
90
+ * decrement `head_seq` and insert new items at `seq = head_seq - 1` per pushed element
91
+ * `RPUSH`:
92
+
93
+ * increment `tail_seq` and insert new items at `seq = tail_seq + 1`
94
+
95
+ Maintain ordering:
96
+
97
+ * front is smaller `seq`, back is larger `seq`.
98
+
99
+ ## B.6 Command Behavior
100
+
101
+ ### B.6.1 LPUSH
102
+
103
+ **Request:** `LPUSH key v1 v2 ...`
104
+ **Response:** Integer = new list length
105
+
106
+ Implementation notes:
107
+
108
+ * If key does not exist: create `redis_keys` type list + create `redis_list_meta`.
109
+ * Insert values in left-push order consistent with Redis:
110
+
111
+ * `LPUSH mylist a b c` results in list `[c, b, a]` at the head.
112
+ * Use a single transaction:
113
+
114
+ * ensure type
115
+ * upsert meta
116
+ * insert items
117
+ * bump `redis_keys.version`, update `updated_at`
118
+
119
+ ### B.6.2 RPUSH
120
+
121
+ Same structure, response is new length.
122
+
123
+ ### B.6.3 LLEN
124
+
125
+ Return length:
126
+
127
+ * If key not exist: `0`
128
+ * Else length = `tail_seq - head_seq + 1` (derived from meta)
129
+ * Do not count rows for performance.
130
+
131
+ ### B.6.4 LPOP / RPOP
132
+
133
+ * Without `count`: return bulk string or `nil`
134
+ * With `count`:
135
+
136
+ * Redis returns array of popped elements
137
+ * If list has fewer than count: return all available
138
+ * If key does not exist: return `nil` for no-count, or empty array for count
139
+ * After popping last element: delete key and meta.
140
+
141
+ **Implementation:**
142
+
143
+ * In a transaction:
144
+
145
+ * read meta
146
+ * compute seq range to remove
147
+ * select values for response
148
+ * delete those rows
149
+ * update meta head/tail accordingly
150
+ * if empty -> delete key meta entirely
151
+
152
+ ### B.6.5 LRANGE
153
+
154
+ `LRANGE key start stop` returns array of elements.
155
+
156
+ Index rules (Redis):
157
+
158
+ * `start` and `stop` are inclusive.
159
+ * Negative indices count from end (`-1` is last element).
160
+ * Clamp indices to valid bounds.
161
+ * If start > stop after normalization: empty array.
162
+
163
+ **Mapping indices to sequences:**
164
+
165
+ * Let `len = tail_seq - head_seq + 1`
166
+ * Normalize start/stop into `[0..len-1]`
167
+ * Convert to seq:
168
+
169
+ * `seq_start = head_seq + start`
170
+ * `seq_stop = head_seq + stop`
171
+ * SQL:
172
+
173
+ * `SELECT value FROM redis_list_items WHERE key=? AND seq BETWEEN ? AND ? ORDER BY seq ASC`
174
+
175
+ ## B.7 Expiration and Cache
176
+
177
+ * TTL is stored in `redis_keys.expires_at`, same as other types.
178
+ * Lazy expiration must delete list meta/items like other types.
179
+ * Cache strategy (recommended):
180
+
181
+ * Cache small lists (len <= configured threshold) optionally.
182
+ * Otherwise cache only meta (`head_seq`, `tail_seq`) if beneficial.
183
+ * Always invalidate on list writes.
184
+
185
+ ## B.8 Complexity Targets
186
+
187
+ * `LPUSH/RPUSH`: O(k) inserts, no reindexing
188
+ * `LPOP/RPOP`: O(k) deletes + selects for return
189
+ * `LLEN`: O(1)
190
+ * `LRANGE`: O(n) in returned range
191
+
192
+ ## B.9 Tests (Required)
193
+
194
+ For each command above:
195
+
196
+ * Happy path
197
+ * Non-existent key behavior
198
+ * Wrong-type behavior
199
+ * TTL interaction (expires then acts as missing)
200
+ * Persistence across restart
201
+ * Binary safety for values
202
+ * Concurrency sanity (multiple clients pushing/popping does not corrupt meta)