c314ba0902
ROADMAP.md establishes status legend, architectural anchors pointing at the wiki, and seven non-negotiable design rules — most importantly the core/domain boundary that protects Phase 1 from Phase 2 churn, the schema-authority split (positions hypertable owned here; everything else owned by Directus), and idempotent-writes via (device_id, ts) ON CONFLICT. Phase 1 (throughput pipeline) is fully detailed across 11 task files: scaffold, core types + sentinel decoder, config + logging, Postgres hypertable, Redis Stream consumer, per-device LRU state, batched writer, main wiring, observability, integration test, Dockerfile + Gitea CI. Observability is in Phase 1 (not deferred) — lesson learned from tcp-ingestion task 1.10. Phases 2-4 are stub READMEs. Phase 2 (domain logic) blocks on Directus schema decisions and lists those open questions explicitly. Phase 3 (production hardening) and Phase 4 (future) sketch the task shape.
4.1 KiB
4.1 KiB
Task 1.1 — Project scaffold
Phase: 1 — Throughput pipeline
Status: ⬜ Not started
Depends on: None
Wiki refs: docs/wiki/entities/processor.md
Goal
Initialize the Node.js / TypeScript project with the directory layout from the Phase 1 README, install the agreed tooling, and produce a minimal main.ts that the rest of Phase 1 builds on. Mirror the tcp-ingestion scaffold conventions exactly so the two services feel uniform.
Deliverables
package.jsondeclaring:"type": "module"(ESM only)."engines": { "node": ">=22" }.- Scripts:
build,dev,start,test,test:watch,test:integration,lint,format,typecheck. - Dependencies:
ioredis,pg,pino,prom-client,zod. - Dev dependencies:
typescript,@types/node,@types/pg,vitest,@vitest/coverage-v8,eslint,@typescript-eslint/parser,@typescript-eslint/eslint-plugin,eslint-plugin-import,eslint-import-resolver-typescript,prettier,pino-pretty,tsx,testcontainers.
tsconfig.json— same astcp-ingestion:strict: true,target: ES2022,module: NodeNext,moduleResolution: NodeNext,outDir: dist,rootDir: src,noUncheckedIndexedAccess: true.eslint.config.js(flat config) with@typescript-eslint/recommended-type-checked,@typescript-eslint/no-floating-promises,@typescript-eslint/no-misused-promises. Addimport/no-restricted-pathsenforcingsrc/core/cannot import fromsrc/domain/. (src/domain/doesn't exist yet — the rule is preemptive so Phase 2 can't violate the boundary by accident.).prettierrc— matchtcp-ingestion(2 spaces, single quotes, semis)..gitignore—node_modules/,dist/,coverage/,.env,.env.local,*.log..dockerignore— same as.gitignoreplus.git/,.planning/,test/,*.mdexceptREADME.md.vitest.config.ts— unit-test config that excludes*.integration.test.ts.vitest.integration.config.ts— integration-test config withhookTimeout: 120_000,testTimeout: 60_000. Include only*.integration.test.ts..env.exampledocumenting every env var (with descriptions and defaults). Required vars only:REDIS_URL,POSTGRES_URL. All others have sensible defaults.- Empty directories with
.gitkeepfiles where Phase 1 will fill them in:src/core/,src/db/migrations/,src/config/,src/observability/test/
src/main.ts— minimal stub: imports nothing yet, printsprocessor startingto stdout, exits with code 0.README.md— short description pointing at.planning/ROADMAP.mdfor the work plan, and at../docs/wiki/entities/processor.mdfor the architectural specification.
Specification
- Package manager: pnpm. Commit
pnpm-lock.yaml. The Dockerfile (task 1.11) will usepnpm fetchfor layer-cache friendliness. - Module style: ESM throughout. Relative imports use
.jssuffix per Node ESM resolution. Nopathsaliases. - No bundler. Build is
tsconly. Runtime is plain Node consumingdist/. - Linting style: Configure ESLint to enforce no-floating-promises and no-misused-promises — both critical in a stream consumer where unhandled rejection silently loses work.
Acceptance criteria
pnpm installsucceeds with no warnings other than peer deps.pnpm typechecksucceeds on the empty project.pnpm lintsucceeds.pnpm buildproducesdist/main.js.pnpm startruns the compiled output and prints the startup message.pnpm testruns (with no tests) and exits successfully.pnpm devrunsmain.tsviatsxand prints the startup message.- Repository builds reproducibly: deleting
node_modulesanddist, thenpnpm install --frozen-lockfile && pnpm buildproduces identical output.
Risks / open questions
- The
import/no-restricted-pathsrule is preemptive and will be silently inert until Phase 2 introducessrc/domain/. Verify it activates correctly with a temporarysrc/domain/foo.tsduring scaffold setup, then remove.
Done
(Fill in once complete: commit SHA, brief notes.)