Hono and pg, run under --experimental-strip-types, so the deployed thing is the source. GET /api/sync?cursor=N pages rows above the cursor; POST /api/sync upserts last-write-wins. Bearer token on everything under /api; /health is open, for the container healthcheck. Rows are stored generically — primary key as text, body as JSONB — because the server never reads inside a row. It stores and orders them and the client interprets them, which keeps the two schemas from having to move in lockstep. change_seq is bumped by a BEFORE UPDATE trigger rather than by the write path. A row edited after a client last pulled would otherwise keep its old sequence, sit below that client's cursor, and never be delivered; putting it in the database means no future write path can forget. The last-write-wins comparison is in the ON CONFLICT clause itself, so a losing row is not written at all and does not bump change_seq — a conflict does not become traffic for every other device. test/sync/roundtrip.test.ts runs two clients against a real Postgres and asserts what actually goes wrong in sync: that a fresh client's seeded rows cannot overwrite the server's history (the artifact's bug, as an executable test), that a delete propagates, and that dict.loadedBands never crosses the wire. It skips without HANKAN_TEST_SERVER, so npm test still runs anywhere. POST /api/test/reset exists only when HANKAN_TEST_MODE=1, so it cannot be reached on the Pi even if the token leaks. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
40 lines
1.1 KiB
JSON
40 lines
1.1 KiB
JSON
{
|
|
"name": "hankan",
|
|
"version": "0.1.0",
|
|
"private": true,
|
|
"type": "module",
|
|
"description": "Hankan — a Korean reading tutor for manhwa. Offline-first, web + Android from one codebase.",
|
|
"workspaces": [
|
|
"app",
|
|
"server"
|
|
],
|
|
"engines": {
|
|
"node": ">=22"
|
|
},
|
|
"scripts": {
|
|
"validate": "node validate.mjs",
|
|
"dict:fetch": "node tools/dict/fetch.mjs",
|
|
"dict:build": "node tools/dict/build.mjs",
|
|
"dict:assert": "node tools/dict/assert-roadmap.mjs",
|
|
"typecheck": "tsc -b --pretty",
|
|
"test": "vitest run",
|
|
"test:watch": "vitest",
|
|
"lint": "eslint .",
|
|
"dev": "npm run dev -w app",
|
|
"build": "npm run build -w app",
|
|
"preview": "npm run preview -w app",
|
|
"cap:sync": "npm run cap:sync -w app",
|
|
"check": "npm run validate && npm run typecheck && npm run test && npm run dict:assert"
|
|
},
|
|
"devDependencies": {
|
|
"@eslint/js": "^9.17.0",
|
|
"@types/node": "^22.10.2",
|
|
"eslint": "^9.17.0",
|
|
"eslint-plugin-react-hooks": "^7.1.1",
|
|
"typescript": "^5.7.2",
|
|
"typescript-eslint": "^8.18.1",
|
|
"vite": "^6.4.3",
|
|
"vitest": "^3.2.4"
|
|
}
|
|
}
|