Skip to content

Commit 4bb9800

Browse files
committed
fix(core): keep the declarations in the ESM .d.ts and re-export them from the .d.cts
Putting the declarations in the .d.cts made core's own imports of @objectstack/spec resolve through spec's require condition, and spec's twin declarations then failed driver-memory's build (TS2345). With the ESM .d.ts as the one declaration, everything core's types import resolves as before. Claude-Session: https://claude.ai/code/session_01VPkxx6ackwXchvtVNidtum Co-authored-by: Claude <noreply@anthropic.com>
1 parent e583179 commit 4bb9800

2 files changed

Lines changed: 20 additions & 12 deletions

File tree

‎.changeset/26205-core-one-declaration-per-condition.md‎

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,4 +9,6 @@ Metadata-protocol: none
99

1010
`@objectstack/core` used to emit two separate declarations of every class, `dist/index.d.ts` and `dist/index.d.cts`, and its export map served the ESM-kind `index.d.ts` to both conditions, so a TypeScript consumer resolving `require` was told the package is ESM (attw `FalseESM` on `.` and `./logger`). Serving the `.d.cts` to `require` was not possible while the two files declared `ObjectKernel` twice: its `private plugins` member makes the two copies incompatible types for any program that reaches core through both resolution modes.
1111

12-
Core's declaration build now runs once, for the CommonJS format. `dist/index.d.cts` and `dist/logger.d.cts` carry the declarations, and `dist/index.d.ts` and `dist/logger.d.ts` are one-line re-exports of them, so both conditions hand out the same classes. `exports` now gives types per condition, `{ import: { types, default }, require: { types, default } }`, on both subpaths. No subpath is added or removed, no JavaScript file changes, and the `dist` file names are unchanged. Nothing for a consumer to do.
12+
Core's declaration build now runs once, for the ESM format. `dist/index.d.ts` and `dist/logger.d.ts` carry the declarations, as before, and `dist/index.d.cts` and `dist/logger.d.cts` are one-line re-exports of them, so both conditions hand out the same classes. `exports` now gives types per condition, `{ import: { types, default }, require: { types, default } }`, on both subpaths. No subpath is added or removed, no JavaScript file changes, and the `dist` file names are unchanged.
13+
14+
A CommonJS consumer compiling with `module: node16` (or with TypeScript older than 6 under `nodenext`) used to get TS1479 at its own `import ... from '@objectstack/core'`. That diagnostic now sits inside core's `.d.cts`, where `skipLibCheck` silences it. Nothing else for a consumer to do.

‎packages/core/tsup.config.ts‎

Lines changed: 17 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -38,16 +38,22 @@ const shared: Options = {
3838
* `src/dev-plugin.ts` (its CommonJS build `await import()`s
3939
* `@objectstack/objectql`, which reaches core the other way).
4040
*
41-
* So the declaration pass runs once, for CJS only, and the ESM `index.d.ts` is
42-
* a one-line re-export of `index.d.cts`: each condition is still handed a
43-
* declaration of its own module kind (attw: no `FalseESM`, no `FalseCJS`),
44-
* but every class in both IS the `.d.cts` one. The re-export runs ESM → CJS on
45-
* purpose: an ES module may import a CommonJS one under every `module`
46-
* setting, while the reverse (`.d.cts` re-exporting `.d.ts`) is TS1479 in a
47-
* consumer's program: measured under `module: node16` on TypeScript 6.0 and
48-
* 5.6, and under `nodenext` too on 5.6.
41+
* So the declaration pass runs once, for ESM only, and the CommonJS
42+
* `index.d.cts` is a one-line re-export of `index.d.ts`: each condition is
43+
* still handed a declaration of its own module kind (attw: no `FalseESM`), but
44+
* every class in both IS the `.d.ts` one, and what that declaration imports
45+
* from `@objectstack/spec` resolves exactly as it did when both conditions
46+
* read the `.d.ts`. ⛔ Not the other way round: with the declarations in the
47+
* `.d.cts` and the `.d.ts` re-exporting it, core's own imports of spec resolve
48+
* through spec's `require` condition, and spec's twin declarations then fail
49+
* `@objectstack/driver-memory`'s build (TS2345, `ComposedReadableIds` against
50+
* itself). The `.d.cts` → `.d.ts` re-export is TS1479 inside this package's
51+
* file for a consumer on `module: node16` (or on TypeScript 5.6 under
52+
* `nodenext`), which `skipLibCheck` silences; the single `types` entry this
53+
* replaces raised that same TS1479 at the consumer's own import, where no
54+
* option silences it.
4955
*
50-
* The JS half's `onSuccess` writes the `.d.ts` files and the declaration half
56+
* The JS half's `onSuccess` writes the `.d.cts` files and the declaration half
5157
* never emits one, so the two halves (which tsup runs concurrently) never
5258
* write the same file. `clean` lives on the JS half only: tsup's clean keeps
5359
* declaration files, so it cannot delete what the declaration half writes.
@@ -61,10 +67,10 @@ export default defineConfig([
6167
onSuccess: emitDts
6268
? async () => {
6369
await Promise.all(
64-
ENTRY_NAMES.map((name) => writeFile(`dist/${name}.d.ts`, `export * from './${name}.cjs';\n`)),
70+
ENTRY_NAMES.map((name) => writeFile(`dist/${name}.d.cts`, `export * from './${name}.js';\n`)),
6571
);
6672
}
6773
: undefined,
6874
},
69-
...(emitDts ? [{ ...shared, clean: false, dts: { only: true }, format: ['cjs'] } satisfies Options] : []),
75+
...(emitDts ? [{ ...shared, clean: false, dts: { only: true }, format: ['esm'] } satisfies Options] : []),
7076
]);

0 commit comments

Comments
 (0)