Skip to content

Commit 1e06bfd

Browse files
authored
Merge pull request #16 from gms1/feature/esm-cjs-dual-support
ESM and CJS dual support
2 parents ce49b67 + ccc8fb1 commit 1e06bfd

20 files changed

+908
-235
lines changed

.github/workflows/ci.yml

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -295,6 +295,14 @@ jobs:
295295
path: '*.tgz'
296296
retention-days: 7
297297

298+
test-package:
299+
needs: [package]
300+
if: github.event_name == 'workflow_dispatch' || (github.event_name == 'pull_request') || (github.event_name == 'push' && !startsWith(github.ref, 'refs/tags/'))
301+
uses: ./.github/workflows/test-npm-package.yml
302+
with:
303+
target_run_id: ${{ github.run_id }}
304+
node_version: '22'
305+
298306
publish-npm:
299307
needs: [build, build-musl]
300308
if: startsWith(github.ref, 'refs/tags/')

.github/workflows/test-npm-package.yml

Lines changed: 72 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,16 @@
11
name: Test npm package
22
on:
3+
workflow_call:
4+
inputs:
5+
target_run_id:
6+
description: 'The Run ID of the upstream CI workflow'
7+
required: true
8+
type: string
9+
node_version:
10+
description: 'Node.js version to test with'
11+
required: false
12+
default: '20'
13+
type: string
314
workflow_dispatch:
415
inputs:
516
target_run_id:
@@ -35,15 +46,15 @@ jobs:
3546
- name: Download artifact from specific run
3647
uses: actions/download-artifact@v5
3748
with:
38-
run-id: ${{ github.event.inputs.target_run_id }}
49+
run-id: ${{ inputs.target_run_id }}
3950
github-token: ${{ secrets.GITHUB_TOKEN }}
4051
name: npm-package-tarball
4152
path: ./downloads
4253

4354
- name: Set up Node.js
4455
uses: actions/setup-node@v6
4556
with:
46-
node-version: ${{ github.event.inputs.node_version || '20' }}
57+
node-version: ${{ inputs.node_version || '20' }}
4758

4859
- name: Install tarball and verify
4960
shell: bash
@@ -93,4 +104,62 @@ jobs:
93104
await db.close();
94105
}
95106
main().catch(err => { console.error(err); process.exit(1); });
96-
"
107+
"
108+
109+
- name: Smoke test - ESM default import
110+
shell: bash
111+
run: |
112+
cd test-project
113+
cat > test-esm.mjs << 'EOF'
114+
import sqlite3 from '@homeofthings/sqlite3';
115+
console.log('ESM: sqlite3 version:', sqlite3.VERSION);
116+
const db = new sqlite3.Database(':memory:');
117+
db.serialize(() => {
118+
db.run('CREATE TABLE test (id INT, name TEXT)');
119+
db.run("INSERT INTO test VALUES (1, 'hello')");
120+
db.get('SELECT * FROM test', (err, row) => {
121+
if (err) { console.error('Query failed:', err); process.exit(1); }
122+
console.log('ESM default import result:', JSON.stringify(row));
123+
db.close();
124+
});
125+
});
126+
EOF
127+
node test-esm.mjs
128+
129+
- name: Smoke test - ESM named imports
130+
shell: bash
131+
run: |
132+
cd test-project
133+
cat > test-named.mjs << 'EOF'
134+
import { Database, OPEN_READWRITE, OPEN_CREATE } from '@homeofthings/sqlite3';
135+
console.log('ESM named: OPEN_READWRITE=', OPEN_READWRITE);
136+
const db = new Database(':memory:');
137+
db.serialize(() => {
138+
db.run('CREATE TABLE test (id INT, name TEXT)');
139+
db.run("INSERT INTO test VALUES (2, 'world')");
140+
db.get('SELECT * FROM test', (err, row) => {
141+
if (err) { console.error('Query failed:', err); process.exit(1); }
142+
console.log('ESM named import result:', JSON.stringify(row));
143+
db.close();
144+
});
145+
});
146+
EOF
147+
node test-named.mjs
148+
149+
- name: Smoke test - ESM promise API
150+
shell: bash
151+
run: |
152+
cd test-project
153+
cat > test-promise.mjs << 'EOF'
154+
import { SqliteDatabase } from '@homeofthings/sqlite3/promise';
155+
async function main() {
156+
const db = await SqliteDatabase.open(':memory:');
157+
await db.run('CREATE TABLE test (id INT, name TEXT)');
158+
await db.run("INSERT INTO test VALUES (3, 'esm')");
159+
const row = await db.get('SELECT * FROM test');
160+
console.log('ESM promise API result:', JSON.stringify(row));
161+
await db.close();
162+
}
163+
main().catch(err => { console.error(err); process.exit(1); });
164+
EOF
165+
node test-promise.mjs

README.md

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ Asynchronous, non-blocking [SQLite3](https://sqlite.org/) bindings for [Node.js]
2626
- Written in modern C++
2727
- Is built using hardening flags
2828
- Promise-based API
29+
- supports ESM and CJS
2930
- Bundles SQLite v3.53.0, or you can build using a local SQLite
3031

3132
# Installing
@@ -108,6 +109,37 @@ async function main() {
108109
main().catch(console.error);
109110
```
110111

112+
## ESM and CJS Support
113+
114+
This package supports both CommonJS (CJS) and ECMAScript Modules (ESM):
115+
116+
### CJS (CommonJS)
117+
118+
```js
119+
// Default import
120+
const sqlite3 = require('@homeofthings/sqlite3');
121+
122+
// Destructured import
123+
const { Database, SqliteDatabase } = require('@homeofthings/sqlite3');
124+
125+
// Promise subpath import
126+
const { SqliteDatabase } = require('@homeofthings/sqlite3/promise');
127+
```
128+
129+
### ESM (ECMAScript Modules)
130+
131+
```js
132+
// Default import
133+
import sqlite3 from '@homeofthings/sqlite3';
134+
135+
// Named imports
136+
import { Database, OPEN_CREATE, SqliteDatabase } from '@homeofthings/sqlite3';
137+
138+
// Promise subpath import
139+
import { SqliteDatabase } from '@homeofthings/sqlite3/promise';
140+
```
141+
142+
111143
# Usage
112144

113145
**Note:** the module must be [installed](#installing) before use.

docs/API.md

Lines changed: 45 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ This document describes the API for `@homeofthings/sqlite3`, a Node.js binding f
66

77
- [Installation](#installation)
88
- [Quick Start](#quick-start)
9+
- [Module Systems (CJS & ESM)](#module-systems-cjs--esm)
910
- [Callback API](#callback-api)
1011
- [Database Class](#database-class-callback)
1112
- [Statement Class](#statement-class-callback)
@@ -67,6 +68,40 @@ main().catch(console.error);
6768

6869
---
6970

71+
## Module Systems (CJS & ESM)
72+
73+
This package supports both CommonJS (CJS) and ECMAScript Modules (ESM) via conditional exports in `package.json`.
74+
75+
### CJS (CommonJS) — Traditional
76+
77+
```javascript
78+
// Default import
79+
const sqlite3 = require('@homeofthings/sqlite3');
80+
81+
// Destructured import
82+
const { Database, SqliteDatabase, OPEN_CREATE } = require('@homeofthings/sqlite3');
83+
84+
// Promise subpath import
85+
const { SqliteDatabase } = require('@homeofthings/sqlite3/promise');
86+
```
87+
88+
### ESM (ECMAScript Modules) — Modern
89+
90+
```javascript
91+
// Default import
92+
import sqlite3 from '@homeofthings/sqlite3';
93+
94+
// Named imports
95+
import { Database, OPEN_CREATE, SqliteDatabase } from '@homeofthings/sqlite3';
96+
97+
// Promise subpath import
98+
import { SqliteDatabase } from '@homeofthings/sqlite3/promise';
99+
```
100+
101+
> **Note:** The ESM wrappers use native CJS→ESM interop (`import` from `.js` files), so ESM imports work seamlessly alongside CJS requires. Both module systems share the same underlying native addon instance.
102+
103+
---
104+
70105
## Callback API
71106

72107
The traditional callback-based API is compatible with the original `node-sqlite3` API.
@@ -942,11 +977,20 @@ const sqlite3 = require('@homeofthings/sqlite3').verbose();
942977

943978
## TypeScript Support
944979

945-
TypeScript definitions are included in the package:
980+
TypeScript definitions are included in the package. Both CJS and ESM import styles are supported:
946981

947982
```typescript
983+
// CJS style
984+
const sqlite3 = require('@homeofthings/sqlite3');
985+
const { Database, SqliteDatabase } = require('@homeofthings/sqlite3');
986+
987+
// ESM style
988+
import sqlite3 from '@homeofthings/sqlite3';
948989
import { Database, Statement, Backup } from '@homeofthings/sqlite3';
949990
import { SqliteDatabase, SqliteStatement, SqliteBackup } from '@homeofthings/sqlite3';
991+
992+
// Promise subpath
993+
import { SqliteDatabase } from '@homeofthings/sqlite3/promise';
950994
```
951995

952996
---

eslint.config.mjs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ export default [
66
ignores: ['node_modules/', 'build/', 'prebuilds/', 'deps/'],
77
},
88
{
9-
files: ['**/*.js'],
9+
files: ['**/*.js', '**/*.mjs'],
1010
languageOptions: {
1111
ecmaVersion: 2020,
1212
sourceType: 'module',
@@ -24,7 +24,7 @@ export default [
2424
},
2525
},
2626
{
27-
files: ['test/**/*.js'],
27+
files: ['test/**/*.js', 'test/**/*.mjs'],
2828
languageOptions: {
2929
globals: {
3030
...globals.mocha,

lib/promise/database.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55

66
'use strict';
77

8-
const { Database } = require('../sqlite3.js');
8+
const { Database } = require('../sqlite3-callback.js');
99
const { SqliteStatement } = require('./statement');
1010
const { SqliteBackup } = require('./backup');
1111

lib/promise/index.d.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export { SqliteDatabase, SqliteStatement, SqliteBackup, SqlRunResult } from '../sqlite3';

lib/promise/index.mjs

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
/**
2+
* ESM wrapper for the Promise-based API of @homeofthings/sqlite3
3+
*
4+
* Usage:
5+
* import { SqliteDatabase } from '@homeofthings/sqlite3/promise';
6+
* const db = await SqliteDatabase.open(':memory:');
7+
*/
8+
9+
export { SqliteDatabase, SqliteStatement, SqliteBackup } from './index.js';

0 commit comments

Comments
 (0)