resplite 1.2.6 → 1.2.10

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 (36) 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 +4 -0
  22. package/src/commands/zrevrange.js +27 -0
  23. package/src/engine/engine.js +19 -0
  24. package/src/migration/apply-dirty.js +8 -1
  25. package/src/migration/index.js +5 -4
  26. package/src/migration/migrate-search.js +25 -6
  27. package/src/storage/sqlite/zsets.js +34 -0
  28. package/test/integration/object-idletime.test.js +51 -0
  29. package/test/integration/zsets.test.js +18 -0
  30. package/test/unit/migrate-search.test.js +50 -2
  31. package/spec/SPEC_A.md +0 -1171
  32. package/spec/SPEC_B.md +0 -426
  33. package/src/cli/import-from-redis.js +0 -194
  34. package/src/cli/resplite-dirty-tracker.js +0 -92
  35. package/src/cli/resplite-import.js +0 -296
  36. package/test/contract/import-from-redis.test.js +0 -83
@@ -0,0 +1,174 @@
1
+ # RESPLite Specification v1 — Protocol and Commands
2
+
3
+ ## 6. Command Scope for v1
4
+
5
+ ### 6.1 Connection and basic commands
6
+
7
+ Supported:
8
+
9
+ - `PING`
10
+ - `ECHO`
11
+ - `QUIT`
12
+
13
+ ### 6.2 String commands
14
+
15
+ Supported:
16
+
17
+ - `GET`
18
+ - `SET`
19
+ - `MGET`
20
+ - `MSET`
21
+ - `DEL`
22
+ - `EXISTS`
23
+ - `INCR`
24
+ - `DECR`
25
+ - `INCRBY`
26
+ - `DECRBY`
27
+
28
+ ### 6.3 TTL commands
29
+
30
+ Supported:
31
+
32
+ - `EXPIRE`
33
+ - `PEXPIRE`
34
+ - `TTL`
35
+ - `PTTL`
36
+ - `PERSIST`
37
+
38
+ ### 6.4 Hash commands
39
+
40
+ Supported:
41
+
42
+ - `HSET`
43
+ - `HGET`
44
+ - `HMGET`
45
+ - `HGETALL`
46
+ - `HDEL`
47
+ - `HEXISTS`
48
+ - `HINCRBY`
49
+
50
+ ### 6.5 Set commands
51
+
52
+ Supported:
53
+
54
+ - `SADD`
55
+ - `SREM`
56
+ - `SMEMBERS`
57
+ - `SISMEMBER`
58
+ - `SCARD`
59
+
60
+ ### 6.6 Introspection and navigation
61
+
62
+ Supported:
63
+
64
+ - `TYPE`
65
+ - `OBJECT IDLETIME` (seconds since last write; uses `updated_at`; missing key returns nil)
66
+ - `SCAN`
67
+
68
+ ### 6.7 Administrative extension commands
69
+
70
+ Supported as project-specific commands:
71
+
72
+ - `SQLITE.INFO`
73
+ - `CACHE.INFO`
74
+
75
+ These are not Redis-standard commands.
76
+ They exist for observability and operational insight.
77
+
78
+ ---
79
+
80
+ ## 7. Commands Explicitly Not Supported in v1
81
+
82
+ The following commands are out of scope in v1 and should return a clear unsupported-command error:
83
+
84
+ - `SUBSCRIBE`
85
+ - `PUBLISH`
86
+ - `PSUBSCRIBE`
87
+ - `MULTI`
88
+ - `EXEC`
89
+ - `WATCH`
90
+ - `EVAL`
91
+ - `EVALSHA`
92
+ - `XADD`
93
+ - `XRANGE`
94
+ - `XREAD`
95
+ - `ZADD`
96
+ - `ZRANGE`
97
+ - `LPUSH`
98
+ - `RPUSH`
99
+ - `BLPOP`
100
+ - `SELECT`
101
+
102
+ Future support may be considered only if the implementation maps cleanly to SQLite.
103
+
104
+ ---
105
+
106
+ ## 8. Semantic Rules
107
+
108
+ ### 8.1 Type ownership
109
+
110
+ A key has exactly one logical type at a time.
111
+ Supported types in v1:
112
+
113
+ - `string`
114
+ - `hash`
115
+ - `set`
116
+
117
+ If a command targets a key of the wrong type, the server must return:
118
+
119
+ - `WRONGTYPE Operation against a key holding the wrong kind of value`
120
+
121
+ ### 8.2 Missing keys
122
+
123
+ Behavior should follow Redis-like semantics where reasonable.
124
+ Examples:
125
+
126
+ - `GET missing` returns null bulk string
127
+ - `TTL missing` returns `-2`
128
+ - `PTTL missing` returns `-2`
129
+ - `TYPE missing` returns `none`
130
+
131
+ ### 8.3 Keys without expiration
132
+
133
+ For existing keys without expiration:
134
+
135
+ - `TTL key` returns `-1`
136
+ - `PTTL key` returns `-1`
137
+
138
+ ### 8.4 DEL and EXISTS
139
+
140
+ - `DEL` returns the count of removed keys
141
+ - `EXISTS` returns the count of keys that exist
142
+
143
+ ### 8.5 Numeric string commands
144
+
145
+ `INCR`, `DECR`, `INCRBY`, and `DECRBY` operate on string values interpreted as integers.
146
+ Rules:
147
+
148
+ - missing key behaves like zero, then the operation is applied
149
+ - non-integer content returns an error
150
+ - result is persisted as a string-compatible integer representation
151
+
152
+ ### 8.6 Empty container behavior
153
+
154
+ For hashes and sets, when the last field or member is removed and the structure becomes empty, the logical key should be deleted as well.
155
+ This keeps the logical keyspace clean and avoids stale empty types.
156
+
157
+ ---
158
+
159
+ ## 9. SET Command v1 Scope
160
+
161
+ Supported forms in v1:
162
+
163
+ - `SET key value`
164
+ - `SET key value EX seconds`
165
+ - `SET key value PX milliseconds`
166
+
167
+ Not supported in v1:
168
+
169
+ - `NX`
170
+ - `XX`
171
+ - `GET`
172
+ - `KEEPTTL`
173
+
174
+ Invalid syntax should produce a Redis-style syntax error.
@@ -0,0 +1,157 @@
1
+ # RESPLite Specification v1 — Data Model, TTL, and Transactions
2
+
3
+ ## 10. Data Model
4
+
5
+ SQLite is the persistent source of truth.
6
+ Data is stored in separate tables by logical type.
7
+ This avoids an overly generic storage model and keeps operations natural and efficient.
8
+
9
+ ### 10.1 Key metadata table
10
+
11
+ ```sql
12
+ CREATE TABLE redis_keys (
13
+ key BLOB PRIMARY KEY,
14
+ type INTEGER NOT NULL,
15
+ expires_at INTEGER,
16
+ version INTEGER NOT NULL DEFAULT 1,
17
+ updated_at INTEGER NOT NULL
18
+ );
19
+
20
+ CREATE INDEX redis_keys_expires_at_idx ON redis_keys(expires_at);
21
+ CREATE INDEX redis_keys_type_idx ON redis_keys(type);
22
+ ```
23
+
24
+ Recommended type enum values:
25
+
26
+ - `1` = string
27
+ - `2` = hash
28
+ - `3` = set
29
+
30
+ Notes:
31
+
32
+ - `key` is stored as `BLOB`
33
+ - `expires_at` is an absolute timestamp in milliseconds
34
+ - `version` supports cache invalidation
35
+ - `updated_at` supports observability and future maintenance tasks; also used by `OBJECT IDLETIME` (time since last write)
36
+
37
+ ### 10.2 String storage
38
+
39
+ ```sql
40
+ CREATE TABLE redis_strings (
41
+ key BLOB PRIMARY KEY,
42
+ value BLOB NOT NULL,
43
+ FOREIGN KEY(key) REFERENCES redis_keys(key) ON DELETE CASCADE
44
+ );
45
+ ```
46
+
47
+ ### 10.3 Hash storage
48
+
49
+ ```sql
50
+ CREATE TABLE redis_hashes (
51
+ key BLOB NOT NULL,
52
+ field BLOB NOT NULL,
53
+ value BLOB NOT NULL,
54
+ PRIMARY KEY (key, field),
55
+ FOREIGN KEY(key) REFERENCES redis_keys(key) ON DELETE CASCADE
56
+ );
57
+ ```
58
+
59
+ ### 10.4 Set storage
60
+
61
+ ```sql
62
+ CREATE TABLE redis_sets (
63
+ key BLOB NOT NULL,
64
+ member BLOB NOT NULL,
65
+ PRIMARY KEY (key, member),
66
+ FOREIGN KEY(key) REFERENCES redis_keys(key) ON DELETE CASCADE
67
+ );
68
+ ```
69
+
70
+ ---
71
+
72
+ ## 11. TTL and Expiration
73
+
74
+ Expiration must be implemented in two complementary ways.
75
+
76
+ ### 11.1 Lazy expiration
77
+
78
+ Before any command operates on a key, the engine should verify whether the key has expired.
79
+ If it has expired:
80
+
81
+ - the key must be removed from SQLite
82
+ - any cache entry must be invalidated
83
+ - the command should proceed as if the key does not exist
84
+
85
+ ### 11.2 Active expiration
86
+
87
+ A background sweeper should periodically delete expired keys in batches.
88
+ Suggested configuration:
89
+
90
+ ```js
91
+ {
92
+ expiration: {
93
+ sweepIntervalMs: 1000,
94
+ maxKeysPerSweep: 500
95
+ }
96
+ }
97
+ ```
98
+
99
+ This does not need to guarantee exact expiration timing to the millisecond.
100
+ It must guarantee that expired keys behave as non-existent from the client's point of view.
101
+
102
+ ---
103
+
104
+ ## 15. SQLite Behavior and Pragmas
105
+
106
+ The storage layer should use pragmatic defaults tuned for this workload.
107
+ Suggested initial pragmas:
108
+
109
+ ```sql
110
+ PRAGMA journal_mode=WAL;
111
+ PRAGMA synchronous=NORMAL;
112
+ PRAGMA foreign_keys=ON;
113
+ PRAGMA temp_store=MEMORY;
114
+ PRAGMA cache_size=-20000;
115
+ PRAGMA mmap_size=268435456;
116
+ ```
117
+
118
+ These settings may later become configurable.
119
+
120
+ ---
121
+
122
+ ## 16. Transaction Rules
123
+
124
+ Every state-changing operation that spans multiple storage steps must run inside a SQLite transaction.
125
+
126
+ Examples:
127
+
128
+ ### 16.1 SET
129
+
130
+ A SET operation may require:
131
+
132
+ - writing or updating metadata in `redis_keys`
133
+ - deleting rows from other type tables if type replacement is allowed for string-over-string writes only
134
+ - writing to `redis_strings`
135
+ - updating version and timestamp
136
+ - updating or invalidating cache
137
+
138
+ All logical storage changes must be atomic.
139
+
140
+ ### 16.2 HSET
141
+
142
+ An HSET operation may require:
143
+
144
+ - creating key metadata if the key does not yet exist
145
+ - validating type if the key already exists
146
+ - inserting or updating one or more fields
147
+ - updating version and timestamp
148
+ - updating or invalidating cache
149
+
150
+ ### 16.3 SADD / SREM
151
+
152
+ Set modifications must update:
153
+
154
+ - membership rows
155
+ - metadata timestamps and versions
156
+ - key existence if the set becomes empty
157
+ - cache state
@@ -0,0 +1,171 @@
1
+ # RESPLite Specification v1 — Cache and Architecture
2
+
3
+ ## 12. Hot Cache
4
+
5
+ The cache is an optimization layer only.
6
+ SQLite remains the source of truth.
7
+
8
+ ### 12.1 Purpose
9
+
10
+ The cache should reduce repeated reads from SQLite for hot keys.
11
+ It should not change logical behavior.
12
+
13
+ ### 12.2 Cache candidates
14
+
15
+ Good initial cache candidates:
16
+
17
+ - strings
18
+ - small hashes
19
+ - small sets
20
+ - key metadata such as type and expiration
21
+
22
+ The cache should not aggressively optimize large result sets in v1.
23
+
24
+ ### 12.3 Cache model
25
+
26
+ Suggested internal cache entry structure:
27
+
28
+ ```js
29
+ {
30
+ kind: "string" | "hash" | "set",
31
+ version: number,
32
+ expiresAt: number | null,
33
+ value: Buffer | Map | Array
34
+ }
35
+ ```
36
+
37
+ ### 12.4 Invalidation strategy
38
+
39
+ Writes should update or invalidate cache entries immediately after a successful SQLite transaction.
40
+ Version values stored in `redis_keys` should be used to prevent stale cache reads.
41
+
42
+ ### 12.5 Policy
43
+
44
+ The cache should start with an LRU policy and support limits such as:
45
+
46
+ ```js
47
+ {
48
+ cache: {
49
+ maxEntries: 50000,
50
+ maxBytes: 64 * 1024 * 1024
51
+ }
52
+ }
53
+ ```
54
+
55
+ ---
56
+
57
+ ## 13. Architecture
58
+
59
+ The implementation should be layered and protocol-independent at the core.
60
+
61
+ ### 13.1 Core layers
62
+
63
+ 1. TCP server layer
64
+ 2. RESP parser and encoder layer
65
+ 3. Command dispatcher layer
66
+ 4. Engine layer with Redis-like semantics
67
+ 5. SQLite storage layer
68
+ 6. Cache layer
69
+ 7. Expiration subsystem
70
+
71
+ ### 13.2 Layer responsibilities
72
+
73
+ #### TCP server layer
74
+
75
+ Responsible for:
76
+
77
+ - accepting TCP connections
78
+ - reading data from sockets
79
+ - writing encoded RESP replies
80
+ - handling connection lifecycle
81
+
82
+ #### RESP layer
83
+
84
+ Responsible for:
85
+
86
+ - parsing incoming RESP2 frames
87
+ - handling fragmented packets and multiple commands per chunk
88
+ - encoding valid RESP2 responses
89
+
90
+ #### Command dispatcher
91
+
92
+ Responsible for:
93
+
94
+ - normalizing command names to uppercase
95
+ - routing commands to handlers
96
+ - returning supported or unsupported command results
97
+
98
+ #### Engine
99
+
100
+ Responsible for:
101
+
102
+ - key existence checks
103
+ - expiration handling
104
+ - type validation
105
+ - semantic correctness
106
+ - numeric operations
107
+ - cleanup of empty structures
108
+ - cache coordination
109
+
110
+ #### SQLite storage layer
111
+
112
+ Responsible for:
113
+
114
+ - schema creation and migration
115
+ - prepared statements
116
+ - transactions
117
+ - efficient per-type operations
118
+ - SQLite pragmas
119
+
120
+ #### Cache layer
121
+
122
+ Responsible for:
123
+
124
+ - storing hot results
125
+ - enforcing limits
126
+ - evicting entries
127
+ - exposing metrics
128
+
129
+ #### Expiration subsystem
130
+
131
+ Responsible for:
132
+
133
+ - lazy expiration checks
134
+ - active sweeps
135
+ - deletion batch limits
136
+
137
+ ---
138
+
139
+ ## 14. Internal API Shape
140
+
141
+ Even though v1 is RESP-only, the internal engine should expose clear semantic operations.
142
+ This keeps the system testable and prepares the codebase for a future embedded API if desired.
143
+
144
+ Suggested engine methods:
145
+
146
+ ```js
147
+ engine.get(key)
148
+ engine.set(key, value, options)
149
+ engine.del(keys)
150
+ engine.exists(keys)
151
+ engine.expire(key, ttlMs)
152
+ engine.pttl(key)
153
+ engine.persist(key)
154
+
155
+ engine.hset(key, pairs)
156
+ engine.hget(key, field)
157
+ engine.hmget(key, fields)
158
+ engine.hgetall(key)
159
+ engine.hdel(key, fields)
160
+ engine.hexists(key, field)
161
+ engine.hincrby(key, field, amount)
162
+
163
+ engine.sadd(key, members)
164
+ engine.srem(key, members)
165
+ engine.smembers(key)
166
+ engine.sismember(key, member)
167
+ engine.scard(key)
168
+
169
+ engine.type(key)
170
+ engine.scan(cursor, options)
171
+ ```