ethercalc
Version:
Multi-User Spreadsheet Server — TypeScript rewrite (Cloudflare fullstack)
83 lines (61 loc) • 2.78 kB
Markdown
# @ethercalc/migrate
Phase 11b migrator — streams a legacy EtherCalc Redis out of a live
RESP server and seeds each room into the new Cloudflare stack via the
worker's `PUT /_migrate/seed/:room` endpoint. See `AGENTS.md` §12 for
the full design.
## Exporting the legacy data
Load the legacy `dump.rdb` into a RESP-speaking server on the migration
host. Any of these works — the migrator only talks RESP:
```bash
# Option A — real redis-server
redis-server --dir /path/to/dump/ --dbfilename dump.rdb --port 6379
# Option B — Zedis or any other RESP-compatible RDB loader.
```
The server owns the RDB decode; the migrator owns shape-shifting into
Worker PUTs. Total memory in the migrator stays O(1-per-room)
regardless of dump size — rooms are streamed via `SCAN` and pipelined
`GET`/`LRANGE`/`HGETALL`.
## Running the migration
Start the new worker in one terminal (Miniflare or `wrangler dev`):
```bash
echo 'ETHERCALC_MIGRATE_TOKEN="local-only"' > packages/worker/.dev.vars
./bin/ethercalc
```
Then run the migrator in another:
```bash
./bin/ethercalc migrate \
--source redis://127.0.0.1:6379 \
--target http://127.0.0.1:8000 \
--token local-only
```
`--token` must match `env.ETHERCALC_MIGRATE_TOKEN` on the worker. In
production, set it with `wrangler secret put ETHERCALC_MIGRATE_TOKEN`
before running with `--target https://ethercalc.workers.dev`.
## Dry-run preview
```bash
./bin/ethercalc migrate \
--source redis://127.0.0.1:6379 \
--dry-run
```
Enumerates every room via RESP and prints the intended writes to
stdout without contacting any worker. Useful for sanity-checking the
dump before a real seed.
## What goes where
| Legacy Redis key | New location |
| --------------------------- | --------------------------------------------------- |
| `snapshot-<room>` | DO storage `snapshot` (+ `meta:updated_at`) |
| `log-<room>[i]` | DO storage `log:<padSeq(i+1)>` |
| `audit-<room>[i]` | DO storage `audit:<padSeq(i+1)>` |
| `chat-<room>[i]` | DO storage `chat:<padSeq(i+1)>` |
| `ecell-<room>` hash field | DO storage `ecell:<user>` |
| `timestamps` hash | D1 `rooms(room, updated_at)` |
| (new) existence flag | KV `rooms:exists:<room>` → `"1"` |
Room names preserve the exact bytes they had in Redis (including URL
encoding applied by the legacy `encodeURI` call).
## Testing
```bash
bun run --cwd packages/migrate test
bun run --cwd packages/migrate test:coverage # 100% gate
```
All tests run in pure Node — no Redis, no wrangler, no Cloudflare
credentials required.