Phase 1: data pipeline (Postgres+Redis with in-memory fallback, mock telemetry publisher, WebSocket fan-out, hub /debug page)
CI / Lint, typecheck, test, build (pull_request) Failing after 10s

- packages/db: postgres.js + ioredis wrapper, StateStore interface with
  InMemory + Postgres implementations, schema migration runner
- apps/api: refactored to use @kerbal-rt/db; live WebSocket hub subscribes
  to store changes and broadcasts to all clients
- apps/tools/mock-telemetry: Node script that generates realistic KSP
  state and POSTs to /api/v1/ingest at 1Hz (uses same keplerian math
  as the live-map renderer)
- apps/hub: new /debug page that connects to /api/v1/live and shows
  the live state
- ksp/README.md: documents Phase 1c (real kRPC bridge) and the two
  implementation options
- Tests: 17 total (5 kepler math, 5 db store, 5 API health/state,
  2 API WebSocket fan-out)

End-to-end verified: mock publisher → API → hub /debug page,
11 snapshots/5s over WebSocket, vessels advancing in mean anomaly.
This commit is contained in:
Mavis
2026-06-02 18:54:46 +00:00
parent 938ba042b3
commit a457b9d96f
28 changed files with 2165 additions and 98 deletions
+131 -8
View File
@@ -28,6 +28,9 @@ importers:
'@hono/zod-validator':
specifier: ^0.4.0
version: 0.4.3(hono@4.12.23)(zod@3.25.76)
'@kerbal-rt/db':
specifier: workspace:*
version: link:../../packages/db
'@kerbal-rt/orbital-math':
specifier: workspace:*
version: link:../../packages/orbital-math
@@ -137,6 +140,44 @@ importers:
specifier: ^5.4.6
version: 5.4.21(@types/node@22.19.19)
apps/tools/mock-telemetry:
dependencies:
'@kerbal-rt/orbital-math':
specifier: workspace:*
version: link:../../../packages/orbital-math
'@kerbal-rt/shared-types':
specifier: workspace:*
version: link:../../../packages/shared-types
devDependencies:
'@types/node':
specifier: ^22.5.0
version: 22.19.19
tsx:
specifier: ^4.19.1
version: 4.22.4
typescript:
specifier: ^5.6.2
version: 5.9.3
packages/db:
dependencies:
'@kerbal-rt/shared-types':
specifier: workspace:*
version: link:../shared-types
ioredis:
specifier: ^5.4.1
version: 5.11.0
postgres:
specifier: ^3.4.4
version: 3.4.9
devDependencies:
typescript:
specifier: ^5.6.2
version: 5.9.3
vitest:
specifier: ^2.1.1
version: 2.1.9(@types/node@22.19.19)
packages/orbital-math:
dependencies:
'@kerbal-rt/shared-types':
@@ -859,6 +900,12 @@ packages:
}
deprecated: Use @eslint/object-schema instead
'@ioredis/commands@1.10.0':
resolution:
{
integrity: sha512-UmeW7z4LfctwoQ5wkhVzgq8tXkreED2xZGpX+Bg+zA+WJFZCT6c062AfCK/Dfk81xZnnwdhJCUMkitihRaoC2Q==,
}
'@isaacs/cliui@8.0.2':
resolution:
{
@@ -1981,6 +2028,13 @@ packages:
integrity: sha512-IV3Ou0jSMzZrd3pZ48nLkT9DA7Ag1pnPzaiQhpW7c3RbcqqzvzzVu+L8gfqMp/8IM2MQtSiqaCxrrcfu8I8rMA==,
}
cluster-key-slot@1.1.1:
resolution:
{
integrity: sha512-rwHwUfXL40Chm1r08yrhU3qpUvdVlgkKNeyeGPOxnW8/SyVDvgRaed/Uz54AqWNaTCAThlj6QAs3TZcKI0xDEw==,
}
engines: { node: '>=0.10.0' }
color-convert@2.0.1:
resolution:
{
@@ -2096,6 +2150,13 @@ packages:
}
engines: { node: '>= 0.4' }
denque@2.1.0:
resolution:
{
integrity: sha512-HVQE3AAb/pxF8fQAoiqpvg9i3evqug3hoiwakOyZAwJm+6vZehbkYXZ0l4JxS+I3QxM97v5aaRNhj8v5oBhekw==,
}
engines: { node: '>=0.10' }
doctrine@2.1.0:
resolution:
{
@@ -2725,6 +2786,13 @@ packages:
}
engines: { node: '>= 0.4' }
ioredis@5.11.0:
resolution:
{
integrity: sha512-EZBErytyVovD8f6pDfG3Kb37N6Y3lmDA9NNj+4+IP13CzzHGeX+OyeRM2Um13khRzoBSzzL+5lVnCX8V2RLeMg==,
}
engines: { node: '>=12.22.0' }
is-array-buffer@3.0.5:
resolution:
{
@@ -3357,6 +3425,13 @@ packages:
}
engines: { node: ^10 || ^12 || >=14 }
postgres@3.4.9:
resolution:
{
integrity: sha512-GD3qdB0x1z9xgFI6cdRD6xu2Sp2WCOEoe3mtnyB5Ee0XrrL5Pe+e4CCnJrRMnL1zYtRDZmQQVbvOttLnKDLnaw==,
}
engines: { node: '>=12' }
prelude-ls@1.2.1:
resolution:
{
@@ -3419,6 +3494,20 @@ packages:
}
engines: { node: '>=0.10.0' }
redis-errors@1.2.0:
resolution:
{
integrity: sha512-1qny3OExCf0UvUV/5wpYKf2YwPcOqXzkwKKSmKHiE6ZMQs5heeE/c8eXK+PNllPvmjgAbfnsbpkGZWy8cBpn9w==,
}
engines: { node: '>=4' }
redis-parser@3.0.0:
resolution:
{
integrity: sha512-DJnGAeenTdpMEH6uAJRK/uiyEIH9WVsUmoLwzudwGJUwZPp80PDBWPHXSAGNPwNvIXAbe7MSUB1zQFugFml66A==,
}
engines: { node: '>=4' }
reflect.getprototypeof@1.0.10:
resolution:
{
@@ -3620,6 +3709,12 @@ packages:
integrity: sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==,
}
standard-as-callback@2.1.0:
resolution:
{
integrity: sha512-qoRRSyROncaz1z0mvYqIE4lCd9p2R90i6GxW3uZv5ucSu8tU7B5HXUP1gG8pVZsYNVaXjk8ClXHPttLyxAL48A==,
}
std-env@3.10.0:
resolution:
{
@@ -4422,6 +4517,8 @@ snapshots:
'@humanwhocodes/object-schema@2.0.3': {}
'@ioredis/commands@1.10.0': {}
'@isaacs/cliui@8.0.2':
dependencies:
string-width: 5.1.2
@@ -5055,6 +5152,8 @@ snapshots:
client-only@0.0.1: {}
cluster-key-slot@1.1.1: {}
color-convert@2.0.1:
dependencies:
color-name: 1.1.4
@@ -5117,6 +5216,8 @@ snapshots:
has-property-descriptors: 1.0.2
object-keys: 1.1.1
denque@2.1.0: {}
doctrine@2.1.0:
dependencies:
esutils: 2.0.3
@@ -5309,8 +5410,8 @@ snapshots:
'@typescript-eslint/parser': 8.60.1(eslint@8.57.1)(typescript@5.9.3)
eslint: 8.57.1
eslint-import-resolver-node: 0.3.10
eslint-import-resolver-typescript: 3.10.1(eslint-plugin-import@2.32.0)(eslint@8.57.1)
eslint-plugin-import: 2.32.0(@typescript-eslint/parser@8.60.1(eslint@8.57.1)(typescript@5.9.3))(eslint-import-resolver-typescript@3.10.1)(eslint@8.57.1)
eslint-import-resolver-typescript: 3.10.1(eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.60.1(eslint@8.57.1)(typescript@5.9.3))(eslint@8.57.1))(eslint@8.57.1)
eslint-plugin-import: 2.32.0(@typescript-eslint/parser@8.60.1(eslint@8.57.1)(typescript@5.9.3))(eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.60.1(eslint@8.57.1)(typescript@5.9.3))(eslint@8.57.1))(eslint@8.57.1))(eslint@8.57.1)
eslint-plugin-jsx-a11y: 6.10.2(eslint@8.57.1)
eslint-plugin-react: 7.37.5(eslint@8.57.1)
eslint-plugin-react-hooks: 5.0.0-canary-7118f5dd7-20230705(eslint@8.57.1)
@@ -5329,7 +5430,7 @@ snapshots:
transitivePeerDependencies:
- supports-color
eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.32.0)(eslint@8.57.1):
eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.60.1(eslint@8.57.1)(typescript@5.9.3))(eslint@8.57.1))(eslint@8.57.1):
dependencies:
'@nolyfill/is-core-module': 1.0.39
debug: 4.4.3
@@ -5340,22 +5441,22 @@ snapshots:
tinyglobby: 0.2.17
unrs-resolver: 1.12.2
optionalDependencies:
eslint-plugin-import: 2.32.0(@typescript-eslint/parser@8.60.1(eslint@8.57.1)(typescript@5.9.3))(eslint-import-resolver-typescript@3.10.1)(eslint@8.57.1)
eslint-plugin-import: 2.32.0(@typescript-eslint/parser@8.60.1(eslint@8.57.1)(typescript@5.9.3))(eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.60.1(eslint@8.57.1)(typescript@5.9.3))(eslint@8.57.1))(eslint@8.57.1))(eslint@8.57.1)
transitivePeerDependencies:
- supports-color
eslint-module-utils@2.13.0(@typescript-eslint/parser@8.60.1(eslint@8.57.1)(typescript@5.9.3))(eslint-import-resolver-node@0.3.10)(eslint-import-resolver-typescript@3.10.1)(eslint@8.57.1):
eslint-module-utils@2.13.0(@typescript-eslint/parser@8.60.1(eslint@8.57.1)(typescript@5.9.3))(eslint-import-resolver-node@0.3.10)(eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.60.1(eslint@8.57.1)(typescript@5.9.3))(eslint@8.57.1))(eslint@8.57.1))(eslint@8.57.1):
dependencies:
debug: 3.2.7
optionalDependencies:
'@typescript-eslint/parser': 8.60.1(eslint@8.57.1)(typescript@5.9.3)
eslint: 8.57.1
eslint-import-resolver-node: 0.3.10
eslint-import-resolver-typescript: 3.10.1(eslint-plugin-import@2.32.0)(eslint@8.57.1)
eslint-import-resolver-typescript: 3.10.1(eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.60.1(eslint@8.57.1)(typescript@5.9.3))(eslint@8.57.1))(eslint@8.57.1)
transitivePeerDependencies:
- supports-color
eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.60.1(eslint@8.57.1)(typescript@5.9.3))(eslint-import-resolver-typescript@3.10.1)(eslint@8.57.1):
eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.60.1(eslint@8.57.1)(typescript@5.9.3))(eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.60.1(eslint@8.57.1)(typescript@5.9.3))(eslint@8.57.1))(eslint@8.57.1))(eslint@8.57.1):
dependencies:
'@rtsao/scc': 1.1.0
array-includes: 3.1.9
@@ -5366,7 +5467,7 @@ snapshots:
doctrine: 2.1.0
eslint: 8.57.1
eslint-import-resolver-node: 0.3.10
eslint-module-utils: 2.13.0(@typescript-eslint/parser@8.60.1(eslint@8.57.1)(typescript@5.9.3))(eslint-import-resolver-node@0.3.10)(eslint-import-resolver-typescript@3.10.1)(eslint@8.57.1)
eslint-module-utils: 2.13.0(@typescript-eslint/parser@8.60.1(eslint@8.57.1)(typescript@5.9.3))(eslint-import-resolver-node@0.3.10)(eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.60.1(eslint@8.57.1)(typescript@5.9.3))(eslint@8.57.1))(eslint@8.57.1))(eslint@8.57.1)
hasown: 2.0.4
is-core-module: 2.16.2
is-glob: 4.0.3
@@ -5681,6 +5782,18 @@ snapshots:
hasown: 2.0.4
side-channel: 1.1.0
ioredis@5.11.0:
dependencies:
'@ioredis/commands': 1.10.0
cluster-key-slot: 1.1.1
debug: 4.4.3
denque: 2.1.0
redis-errors: 1.2.0
redis-parser: 3.0.0
standard-as-callback: 2.1.0
transitivePeerDependencies:
- supports-color
is-array-buffer@3.0.5:
dependencies:
call-bind: 1.0.9
@@ -6048,6 +6161,8 @@ snapshots:
picocolors: 1.1.1
source-map-js: 1.2.1
postgres@3.4.9: {}
prelude-ls@1.2.1: {}
prettier@3.8.3: {}
@@ -6076,6 +6191,12 @@ snapshots:
dependencies:
loose-envify: 1.4.0
redis-errors@1.2.0: {}
redis-parser@3.0.0:
dependencies:
redis-errors: 1.2.0
reflect.getprototypeof@1.0.10:
dependencies:
call-bind: 1.0.9
@@ -6243,6 +6364,8 @@ snapshots:
stackback@0.0.2: {}
standard-as-callback@2.1.0: {}
std-env@3.10.0: {}
stop-iteration-iterator@1.1.0: