Files
Hankan/tsconfig.json
MechaCat02 c2e1fc23fe feat(server): the sync endpoints, on Node 22 with no build step
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>
2026-09-08 19:57:44 +02:00

42 lines
1.3 KiB
JSON

{
"compilerOptions": {
"target": "ES2022",
"lib": ["ES2022", "DOM", "DOM.Iterable"],
"module": "ESNext",
"moduleResolution": "bundler",
"types": ["node", "vite/client"],
"strict": true,
"noUncheckedIndexedAccess": true,
"noImplicitOverride": true,
"noFallthroughCasesInSwitch": true,
"exactOptionalPropertyTypes": false,
"jsx": "react-jsx",
"resolveJsonModule": true,
"isolatedModules": true,
// The server runs its .ts directly under Node's type stripping, so its
// imports carry .ts extensions. noEmit is on, so this only affects
// resolution.
"allowImportingTsExtensions": true,
"verbatimModuleSyntax": true,
"skipLibCheck": true,
"esModuleInterop": true,
"allowJs": true,
"checkJs": false,
"noEmit": true,
// lib/ ships unchanged; its types live alongside in types/lib/.
// Vite resolves the same specifiers to the real .js modules.
"baseUrl": ".",
"paths": {
"@lib/*": ["./types/lib/*"],
"@data/*": ["./data/*"],
"@app/*": ["./app/src/*"],
"@shared/*": ["./types/shared/*"]
}
},
"include": ["app/src", "test", "types", "app/vite.config.ts", "vitest.config.ts"],
"exclude": ["node_modules", "dist", "export", "vendor", "android", "app/android"]
}