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.
- package/README.md +168 -275
- package/package.json +1 -6
- package/scripts/create-interface-smoke.js +32 -0
- package/skills/README.md +22 -0
- package/skills/resplite-command-vertical-slice/SKILL.md +134 -0
- package/skills/resplite-ft-search-workbench/SKILL.md +138 -0
- package/skills/resplite-migration-cutover-assistant/SKILL.md +138 -0
- package/spec/00-INDEX.md +37 -0
- package/spec/01-overview-and-goals.md +125 -0
- package/spec/02-protocol-and-commands.md +174 -0
- package/spec/03-data-model-ttl-transactions.md +157 -0
- package/spec/04-cache-architecture.md +171 -0
- package/spec/05-scan-admin-implementation.md +379 -0
- package/spec/06-migration-strategy-core.md +79 -0
- package/spec/07-type-lists.md +202 -0
- package/spec/08-type-sorted-sets.md +220 -0
- package/spec/{SPEC_D.md → 09-search-ft-commands.md} +3 -1
- package/spec/{SPEC_E.md → 10-blocking-commands.md} +3 -1
- package/spec/{SPEC_F.md → 11-migration-dirty-registry.md} +61 -147
- package/src/commands/object.js +17 -0
- package/src/commands/registry.js +4 -0
- package/src/commands/zrevrange.js +27 -0
- package/src/engine/engine.js +19 -0
- package/src/migration/apply-dirty.js +8 -1
- package/src/migration/index.js +5 -4
- package/src/migration/migrate-search.js +25 -6
- package/src/storage/sqlite/zsets.js +34 -0
- package/test/integration/object-idletime.test.js +51 -0
- package/test/integration/zsets.test.js +18 -0
- package/test/unit/migrate-search.test.js +50 -2
- package/spec/SPEC_A.md +0 -1171
- package/spec/SPEC_B.md +0 -426
- package/src/cli/import-from-redis.js +0 -194
- package/src/cli/resplite-dirty-tracker.js +0 -92
- package/src/cli/resplite-import.js +0 -296
- 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
|
+
```
|