Skip to content

Publish JSDoc-derived type declarations (icebird-style, no copy) - #185

Merged
platypii merged 2 commits into
masterfrom
publish-types-emit
Jun 28, 2026
Merged

Publish JSDoc-derived type declarations (icebird-style, no copy)#185
platypii merged 2 commits into
masterfrom
publish-types-emit

Conversation

@platypii

Copy link
Copy Markdown
Contributor

Supersedes #184 (auto-closed when its stacked base branch, the phase-2 PR #183, was merged and deleted). Rebased onto master; this diff is publish-types only.

Publish real types to consumers

Consumers got real types only for the two entries with a hand-written sibling .d.ts (././core, ./integration); the other four (./core/observability, ./core/sinks, ./core/query, ./tui) resolved to implicit any, because TypeScript does not read JSDoc out of an installed package's .js and there was no declaration build.

Approach (icebird-style, no copy)

  • tsconfig.build.json emits .d.ts (+ maps) from JSDoc with rootDir: src -> outDir: types, run via build:types and wired to prepare.
  • types/ is gitignored and shipped through the files allowlist.
  • exports gains a types condition per entry: generated types/... for JSDoc-only entries, the hand-written src/...d.ts for the barrel entries.
  • Every type-import specifier is anchored to a repo-root .js path (e.g. ../../src/core/types.js) so the same import resolves identically from both src/<P>/x.js and the generated types/<P>/x.d.ts. Documented as the project convention in AGENTS.md.

Targeted fixes (the only hand-written-.d.ts -> JSDoc-module couplings the split can't bridge)

  • cli/types.d.ts: use the named KernelRuntime instead of ReturnType<typeof createKernelRuntime>.
  • observability/types.d.ts: declare ObservabilityHandle as an explicit interface (precise provider types still reach consumers via the generated index.d.ts).
  • observability/tracer.js: @import { Span } so the emitted declaration resolves Span from its sibling generated runtime.d.ts.
  • One dynamic hypaware-core import in core_commands.js indirected through a variable so the build (rootDir: src) does not pull it under src's emit root (TS6059).
  • cli/integration.d.ts: declare the HypAwareCommandError constructor so its {code,stdout,stderr,json} detail is part of the public type.

Verification

External-consumer probe (strict, nodenext) importing all six entry points resolves real types (negative control + @ts-expect-error guards confirm not any). Validated against the actual npm pack tarball installed into a clean consumer: types resolve and node imports all six entries with live exports. typecheck, lint (423 files), and npm test (1473 pass) all green.

Consumers of the package got real types only for the two entry points that
happened to have a hand-written sibling .d.ts (`.`/`./core` and `./integration`);
the other four (`./core/observability`, `./core/sinks`, `./core/query`, `./tui`)
resolved to implicit `any`, because TypeScript does not read JSDoc out of an
installed package's `.js` and there was no declaration build.
Add a declaration build modeled on icebird: `tsconfig.build.json` emits `.d.ts`
(+ maps) from JSDoc with `rootDir: src` -> `outDir: types`, run via
`build:types` and wired to `prepare` so it runs before pack/publish. `types/` is
gitignored and shipped through the `files` allowlist; `exports` gains a `types`
condition per entry (generated `types/...` for JSDoc-only entries, the
hand-written `src/...d.ts` for the barrel entries).
The icebird mechanism needs every type-import specifier to resolve identically
from both `src/<P>/x.js` and the parallel generated `types/<P>/x.d.ts`. Anchor
all 159 such specifiers to repo-root `.js` paths (e.g. `../../src/core/types.js`)
so tsc keeps them verbatim and they resolve from both trees. Documented as the
project convention in AGENTS.md.
Three targeted fixes remove the only hand-written-.d.ts -> JSDoc-module
couplings that the two-tree split cannot bridge:
- cli/types.d.ts: use the named `KernelRuntime` instead of
`ReturnType<typeof createKernelRuntime>` (drops the activation.js dependency).
- observability/types.d.ts: declare `ObservabilityHandle` as an explicit
interface instead of deriving it from `typeof installObservability`; the
precise provider types still reach consumers via the generated `index.d.ts`.
- observability/tracer.js: `@import { Span }` so the emitted tracer declaration
resolves `Span` from its sibling generated `runtime.d.ts`.
One dynamic `hypaware-core` import in core_commands.js is indirected through a
variable so the declaration build (rootDir: src) does not pull that module under
src's emit root (TS6059).
Verified with an external-consumer probe (strict, nodenext) importing all six
entry points: types resolve for every entry (negative control errors as
expected). typecheck, lint (423 files), and npm test (1473 pass) all green.
The hand-written cli/integration.d.ts exposed the instance fields but no
constructor, so consumers (resolving the public ./integration entry) inherited
Error's (message, options) signature and could not construct the error with its
{code, stdout, stderr, json} detail under types. Declare the real constructor so
the public type matches integration.js. Validated against the packed tarball: a
clean consumer can now construct it with typed detail and mistyped detail is
rejected.
@platypii
platypii merged commit 576a2a4 into masterJun 28, 2026
6 checks passed
@platypii
platypii deleted the publish-types-emit branch June 28, 2026 22:30
platypii added a commit that referenced this pull request Jun 28, 2026
blob-io.d.ts and maintenance.d.ts were emitted in-place by an early
rootDir=src declaration-build experiment (those hypaware-core modules fell
outside the emit root, so tsc wrote their declarations next to the source) and
got swept into #185 by git add -A. They shadow the JSDoc and are build cruft.
The shipped build emits only to types/ and, with the core_commands dynamic
import indirected, no longer pulls these hypaware-core modules into the src
emit, so removing them is safe: typecheck, build:types (does not regenerate
them), and tests (1473 pass) all stay green.
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant

@platypii