refactor(core): finish TypeScript workspace migration

This commit is contained in:
tommy0103
2026-07-12 00:37:32 +08:00
parent 83d5703f7b
commit 535d8edb4d
31 changed files with 326 additions and 199 deletions
@@ -6,8 +6,9 @@
> adapters* (driven by the multi-provider roadmap), and there is *one* shared
> persist layer, not one per binding.
**Context.** Obelisk had two divergent full indexers — `scripts/indexer.mjs`
(`node:sqlite`, skill/runtime) and `app/indexer.js` (`better-sqlite3`, Electron
**Context.** Obelisk had two divergent full indexers — the former
`scripts/indexer.mjs` (`node:sqlite`, skill/runtime) and `app/indexer.js`
(`better-sqlite3`, Electron
app) — that duplicated the same Claude and Codex JSONL parsing and had silently
diverged in write semantics (`INSERT OR REPLACE` vs `ON CONFLICT DO UPDATE`,
message-count accumulation). Two forces shape the fix: (1) the roadmap will add
@@ -7,7 +7,8 @@ the skill artifact must install with **zero build step** on the user's machine
its checkable contracts, but raises how the compiled output is shipped and which
module format it targets.
**Decision.** Author all of Core in TypeScript and compile it ahead-of-time to
**Decision.** Author all of Core in the `@obelisk/core` npm workspace
(`packages/core`) in TypeScript and compile it ahead-of-time to
**ESM JavaScript plus `.d.ts`**. The skill/CLI runtime ships the *precompiled*
ESM JS, so installing the skill never runs a build. Rather than have Core
dual-publish CJS+ESM, the Electron main process migrates to ESM at Phase 5 so it
@@ -20,3 +21,5 @@ in exchange for no dual-build maintenance and a single module format across skil
CLI, and app. The shipped skill artifact contains compiled JS, not TS. The
renderer (Vue) is out of scope and stays JavaScript. Phase 3's TS baseline only
adds root tooling (package.json, tsconfig, ESLint); it does not touch the app.
The app imports Core source so electron-vite can bundle it, while package and
skill builds compile the same workspace source to JavaScript.
+6 -5
View File
@@ -20,8 +20,9 @@ decisions within this:
security) does not support ESM preload. Source stays ESM; only the preload
output format is CJS. `main` loads `../preload/index.js`.
- **The app consumes the Core from source**: electron-vite/rollup bundles
`scripts/providers/*` + `scripts/persist` (and their `scripts/parsing.mjs`
dependency) into the app's main/worker build, injecting `better-sqlite3`. This
`packages/core/src/providers/*` + `packages/core/src/persist.ts` (and their
`packages/core/src/parsing.ts` dependency) into the app's main/worker build,
injecting `better-sqlite3`. This
works because the provider→parsing import graph is node:sqlite-free (ADR-0001),
so nothing drags `node:sqlite` into the app. The `dist/` from `build:core`
(ADR-0003) remains for the skill artifact; the app does not need it.
@@ -30,17 +31,17 @@ decisions within this:
- **The app main + preload source is TypeScript with types at its seams**, but
under a *deliberately more lenient* project than the runtime core. `app/tsconfig.json`
keeps `strict` on yet sets `noImplicitAny: false`, because the app mostly
orchestrates the already-strictly-typed core (`scripts/`), and annotating every
orchestrates the already-strictly-typed core (`packages/core/src/`), and annotating every
internal SQLite-handle helper would be high-cost, low-value churn. Types are
added where they matter: the core-consumption seam (`BuildIndexOptions`/
`BuildIndexResult`, `FileInfo`), the service/worker factories, and the IPC
bridge. Module-to-module specifiers use the real `.ts` extension (mirroring
`scripts/`, since Node's type-stripping does not rewrite `.js``.ts`), which
Core source, since Node's type-stripping does not rewrite `.js``.ts`), which
needs `allowImportingTsExtensions` (safe under the project's `noEmit`); the
worker's *runtime* path stays `indexer-worker.js` because that is the built
output. `@types/better-sqlite3` is a devDependency for the injected binding.
**Two-tier typechecking.** `npm run typecheck` runs the root project (`scripts/` +
**Two-tier typechecking.** `npm run typecheck` runs the root project (`packages/core/src/` +
`tests/`, fully strict including `noImplicitAny`) and then the app project. The
root project **excludes the app-importing tests** (`tests/app-*.test.mjs`,
`tests/recap-capture-query.test.mjs`): those tests import app source, which would
@@ -17,14 +17,15 @@ failed statement can replay part of a transaction.
**Decision.** Use one transaction primitive plus two explicit coordination
layers.
- `scripts/tx.ts` owns the binding-agnostic `runWriteTransaction(db, work)`.
- `packages/core/src/tx.ts` owns the binding-agnostic
`runWriteTransaction(db, work)`.
Adapters expose transaction state from better-sqlite3's `inTransaction` and
node:sqlite's `isTransaction`. The primitive performs `BEGIN IMMEDIATE`, runs
`work` exactly once, commits, and attempts rollback only when the binding says
a transaction is active or its state is unknown. Cleanup never masks the
primary exception. Diagnostics record phase, SQLite code, rollback outcome,
transaction state, label, and attempts.
- Retry is an upper-layer policy in `scripts/write-coordinator.ts`, never hidden
- Retry is an upper-layer policy in `packages/core/src/write-coordinator.ts`, never hidden
inside the transaction primitive. Only an idempotent whole transaction that
failed during work/commit with `SQLITE_BUSY*` and is confirmed inactive may be
retried. The default is three attempts within a one-second budget with short