dual ESM and CJS, with types first in every export condition - #4
Merged
Merged
Conversation
The binding napi generates is now binding.cjs and binding.d.cts, and the package is what sits over it: zudb.cjs requires the binding and names every export one at a time, zudb.mjs re-exports those same objects through createRequire, and the two declaration files say the same shape to a resolver reading .d.cts for require and .d.mts for import. One load, so a ZuDate from either format is an instance of the other's class. No default export in either, because a default beside the named exports is a second spelling of every name whose meaning depends on the caller's bundler. types is the first key in both conditions, since conditions match in the order they are written and a types after default is a types nothing reaches. isZuError joins the surface, because a catch clause holds an unknown and there was no way to ask whether it was a zu failure without reading the name off it. npm run check:types compiles a program in each format against the published shape, npm run check:package runs attw over a real npm pack, and both are a CI job. The install round trip now requires and imports the packed package rather than only importing it.
33 tasks
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
The package had one file,
index.js, generated by napi, and oneindex.d.tsbeside it. That is a CommonJS package that an ESM caller reaches through Node's interop, and a package whose types are read the same way for both formats whether or not that is true. This splits the two apart.index.jsandindex.d.tsare nowbinding.cjsandbinding.d.cts, which is what napi generates and nothing else. Over them sit four files that are the package:zudb.cjsrequires the binding and names every export one at a time,zudb.mjsreaches those same objects throughcreateRequireand re-exports them,zudb.d.ctssays the shape plus theAsyncDisposableaugmentation that cannot be spelled in a napi attribute, andzudb.d.mtspoints at it rather than writing the API twice.One load underneath both, so a
ZuDatemade throughimportis an instance of theZuDatereached throughrequire, which is what a program with a mixed dependency tree needs and what two separate loads would quietly break. There is no default export in either format: a default beside the named exports is a second spelling of every name, and which one a caller gets depends on their bundler and theiresModuleInterop.typesis the first key in both conditions. Conditions match in the order they are written, so atypesafterdefaultis atypesnothing ever reaches, and the package would compile here and beanyeverywhere else.isZuErroris new on the surface. Acatchclause holds anunknown, and until now there was no way to ask whether the thing caught was a zu failure without reading the name off it and hoping. It answers no for a plainError, for a string, for null and for anAbortError, which is the one a caller most needs kept separate from the database's own conditions, and in TypeScript it narrows to the full error shape. The test helper now uses the exported guard, so the suite exercises the predicate a caller would write.Three things check the package as a resolver sees it rather than as this checkout sees it, which is the difference that only shows up after publishing:
npm run check:typescompiles a.mtsand a.ctsprogram against the published shape, both undernode16resolution withstrict,exactOptionalPropertyTypesandnoUncheckedIndexedAccess. The tests themselves now import'zudb'rather than a relative path, so they resolve through the package's ownexportsmap.npm run check:packagerunsattw --pack ., which reports no problems for node10, node16 CJS, node16 ESM and bundler.test/exports.test.mjswrites out the eleven exported names rather than deriving them from either format, and asserts both formats give the same names and the same objects, that neither has a default, and that every condition names its types first.Both are a CI job.
tools/install.mjsnow runs a statement throughrequire('zudb')as well as throughimport, and compares a class across the two inside the installed project. The release workflow passes the two file names tonapi build, since a build left on napi's defaults would publish a loader that is not the one that was built.52 tests pass,
attwis clean on all four resolution modes,tscis clean, and the pack and install round trip runs a statement from both formats.Part of tamnd/zu#169.