Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/commonjs-require.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@maple-dev/effect-orm": patch
---

Every entry point can be loaded with `require()`. The export map offered only an `import` condition, so CommonJS code (and CommonJS builds of TypeScript projects) failed with `ERR_PACKAGE_PATH_NOT_EXPORTED`, although the modules themselves load fine through Node's `require(esm)` (Node 20.19+, 22.12+). Each entry now also has a `default` condition pointing at the same `.mjs`, so a CommonJS caller loads the very module an ES module caller does, and shares its Effect. The package-consumer check `require()`s every entry from the packed tarball.
3 changes: 3 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,9 @@ See [Getting started](./docs/getting-started.md) for source builds and examples.
newer Effect 4 releases without opting into Effect 5. Effect 3 and the Effect 4
prereleases are incompatible.

The package is ESM. CommonJS code can `require()` every entry point on Node 20.19+
or 22.12+ (`require(esm)`), the same way it loads Effect 4.

## Quick start

One import per database. Each holds the whole query builder plus that database's column
Expand Down
5 changes: 4 additions & 1 deletion docs/getting-started.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,11 @@ Build and decode your first query without connecting to a database. Then follow

## Installation and compatibility

This is an ESM-only TypeScript package built on **Effect 4**. You do not need an
This is an ESM TypeScript package built on **Effect 4**. You do not need an
Effect application: `Effect.runPromise` lets you use it from ordinary async code.
CommonJS code can `require()` every entry point too, on Node 20.19+ or 22.12+, which
load ES modules synchronously (`require(esm)`). That is the same way such code loads Effect 4,
which is also ESM-only, so the library and its caller share one Effect.
These examples are checked against Effect `4.0.0` and the builder in this repository.
The package declares `effect ^4.0.0` as a peer dependency; Effect 3 is incompatible.

Expand Down
36 changes: 24 additions & 12 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -35,51 +35,63 @@
"exports": {
"./clickhouse": {
"types": "./dist/clickhouse.d.mts",
"import": "./dist/clickhouse.mjs"
"import": "./dist/clickhouse.mjs",
"default": "./dist/clickhouse.mjs"
},
"./expr": {
"types": "./dist/expr.d.mts",
"import": "./dist/expr.mjs"
"import": "./dist/expr.mjs",
"default": "./dist/expr.mjs"
},
"./sql": {
"types": "./dist/sql.d.mts",
"import": "./dist/sql.mjs"
"import": "./dist/sql.mjs",
"default": "./dist/sql.mjs"
},
"./postgres": {
"types": "./dist/postgres.d.mts",
"import": "./dist/postgres.mjs"
"import": "./dist/postgres.mjs",
"default": "./dist/postgres.mjs"
},
"./schema": {
"types": "./dist/schema.d.mts",
"import": "./dist/schema.mjs"
"import": "./dist/schema.mjs",
"default": "./dist/schema.mjs"
},
"./tinybird": {
"types": "./dist/tinybird.d.mts",
"import": "./dist/tinybird.mjs"
"import": "./dist/tinybird.mjs",
"default": "./dist/tinybird.mjs"
},
"./migrate": {
"types": "./dist/migrate.d.mts",
"import": "./dist/migrate.mjs"
"import": "./dist/migrate.mjs",
"default": "./dist/migrate.mjs"
},
"./database": {
"types": "./dist/database.d.mts",
"import": "./dist/database.mjs"
"import": "./dist/database.mjs",
"default": "./dist/database.mjs"
},
"./kit": {
"types": "./dist/kit.d.mts",
"import": "./dist/kit.mjs"
"import": "./dist/kit.mjs",
"default": "./dist/kit.mjs"
},
"./benchmark": {
"types": "./dist/benchmark/index.d.mts",
"import": "./dist/benchmark/index.mjs"
"import": "./dist/benchmark/index.mjs",
"default": "./dist/benchmark/index.mjs"
},
"./benchmark/http": {
"types": "./dist/benchmark/http.d.mts",
"import": "./dist/benchmark/http.mjs"
"import": "./dist/benchmark/http.mjs",
"default": "./dist/benchmark/http.mjs"
},
"./benchmark/cli": {
"types": "./dist/benchmark/cli.d.mts",
"import": "./dist/benchmark/cli.mjs"
"import": "./dist/benchmark/cli.mjs",
"default": "./dist/benchmark/cli.mjs"
}
},
"publishConfig": {
Expand Down
3 changes: 3 additions & 0 deletions scripts/check-package.ts
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,9 @@ const program = Effect.gen(function* () {
yield* runCommand("node", ["consumer-ts5.mts"], temporary)
// The script runs on Bun; the published library must also work in Node.
yield* runCommand("node", ["consumer.mts"], temporary)
// And from CommonJS, through `require(esm)`: every entry, from the real tarball.
yield* fs.copyFile(path.join(root, "tests/package-consumer.cjs"), path.join(temporary, "consumer.cjs"))
yield* runCommand("node", ["consumer.cjs"], temporary)
yield* runCommand(
path.join(temporary, "node_modules/.bin/ch-bench"),
["schema", "--json"],
Expand Down
36 changes: 36 additions & 0 deletions tests/package-consumer.cjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
// Copied into an isolated directory by check-package.ts and run with Node.
// A CommonJS consumer: every public entry must load through `require()`, which
// Node (20.19+, 22.12+) satisfies with `require(esm)` once the export map offers
// a condition other than `import`. Nothing here may resolve dependencies or
// source files from Maple's workspace.
const assert = require("node:assert/strict")
const { readFileSync } = require("node:fs")
const { dirname, join } = require("node:path")

// The package does not export its own package.json, so read the installed copy
// through an entry it does export. Driving the list from `exports` covers every
// entry added later without touching this file.
const root = dirname(dirname(require.resolve("@maple-dev/effect-orm/clickhouse")))
const { exports: exportMap } = JSON.parse(readFileSync(join(root, "package.json"), "utf8"))
const entries = Object.keys(exportMap)
assert.ok(entries.length > 0, "the package exports no entries")
for (const entry of entries) {
const specifier = `@maple-dev/effect-orm${entry.slice(1)}`
const loaded = require(specifier)
assert.ok(Object.keys(loaded).length > 0, `${specifier} loaded with no exports`)
}

// And it works, not just loads: compile and decode with the same Effect a CJS
// host resolves, so the library and its caller share one Effect instance.
const { Effect } = require("effect")
const CH = require("@maple-dev/effect-orm/clickhouse")
const events = CH.table("events", { external: true, columns: { id: CH.uint64, name: CH.string } })
const query = CH.from(events)
.select(($) => ({ id: CH.toString($.id), name: $.name }))
.where(($) => [$.name.eq(CH.param.string("name"))])
const compiled = Effect.runSync(CH.compile(query, { name: "maple" }))
assert.match(compiled.sql, /FROM events/)
assert.deepEqual(Effect.runSync(compiled.decodeRows([{ id: "18446744073709551615", name: "maple" }])), [
{ id: "18446744073709551615", name: "maple" },
])
console.log(`CommonJS consumer ok (${entries.length} entries)`)
Loading