Skip to content

Commit 86ad647

Browse files
committed
feat: add better exception handling
Signed-off-by: Gordon Smith <GordonJSmith@gmail.com>
1 parent 8150490 commit 86ad647

File tree

21 files changed

+190
-65
lines changed

21 files changed

+190
-65
lines changed

packages/base91/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ set(EM_LINK_FLAGS
1414
"-sEXPORT_NAME='${CMAKE_PROJECT_NAME}'"
1515

1616
"-fwasm-exceptions"
17+
"-sEXCEPTION_STACK_TRACES=1"
1718
"--emit-tsd ${CMAKE_CURRENT_BINARY_DIR}/base91lib.d.ts"
1819
)
1920
string(REPLACE ";" " " LINK_FLAGS "${EM_LINK_FLAGS}")

packages/base91/src/base91.ts

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ import { MainModuleEx } from "@hpcc-js/wasm-util";
55

66
// Ref: http://base91.sourceforge.net/#a5
77

8-
let g_base91: Promise<Base91>;
8+
let g_base91: Promise<Base91> | undefined;
99

1010
/**
1111
* Base 91 WASM library, similar to Base 64 but uses more characters resulting in smaller strings.
@@ -49,8 +49,14 @@ export class Base91 extends MainModuleEx<MainModule> {
4949
/**
5050
* Unloades the compiled wasm instance.
5151
*/
52-
static unload() {
53-
reset();
52+
static async unload() {
53+
try {
54+
const base91 = await g_base91;
55+
base91?._base91?.delete();
56+
} finally {
57+
reset();
58+
g_base91 = undefined;
59+
}
5460
}
5561

5662
/**

packages/base91/tests/base91.spec.ts

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,21 +3,37 @@ import { Base91 } from "@hpcc-js/wasm-base91";
33

44
describe("base91", function () {
55

6+
it("unload resets singleton and is idempotent", async function () {
7+
await Base91.unload();
8+
9+
const base91a = await Base91.load();
10+
expect(await Base91.load()).to.equal(base91a);
11+
12+
await Base91.unload();
13+
14+
const base91b = await Base91.load();
15+
expect(base91b).to.not.equal(base91a);
16+
expect(await Base91.load()).to.equal(base91b);
17+
18+
await Base91.unload();
19+
await Base91.unload();
20+
});
21+
622
it("version", async function () {
723
let base91 = await Base91.load();
824
expect(await Base91.load()).to.equal(base91);
925
let v = base91.version();
1026
expect(v).to.be.a.string;
1127
expect(v).to.equal("0.6.0");
1228
console.log("base91 version: " + v);
13-
Base91.unload();
29+
await Base91.unload();
1430

1531
base91 = await Base91.load();
1632
expect(await Base91.load()).to.equal(base91);
1733
v = base91.version();
1834
expect(v).to.be.a.string;
1935
expect(v).to.not.be.empty;
20-
Base91.unload();
36+
await Base91.unload();
2137
});
2238

2339
it("simple", async function () {

packages/duckdb/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ set(EM_LINK_FLAGS
2828
# "-sALLOW_UNIMPLEMENTED_SYSCALLS=1"
2929

3030
"-fwasm-exceptions"
31+
"-sEXCEPTION_STACK_TRACES=1"
3132
"--emit-tsd ${CMAKE_CURRENT_BINARY_DIR}/duckdblib.d.ts"
3233
)
3334
string(REPLACE ";" " " LINK_FLAGS "${EM_LINK_FLAGS}")

packages/duckdb/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@
2727
"bundle-watch": "npm run bundle-dev -- --watch",
2828
"build-dev": "run-p gen-types bundle-dev",
2929
"build": "run-p gen-types bundle",
30+
"build-all": "run-s build-cpp gen-types bundle",
3031
"lint-skypack": "npx -y @skypack/package-check",
3132
"lint-eslint": "eslint \"src/**/*.ts\" \"tests/**/*.ts\"",
3233
"lint": "run-p lint-eslint",

packages/duckdb/src-cpp/main.cpp

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -334,15 +334,15 @@ namespace ConnectionHelper
334334
{
335335
return obj.Query(query).release();
336336
}
337-
338337
std::string queryToJSON(Connection &obj, const string &query)
339338
{
340339
auto result = obj.Query(query);
341-
if (!result->HasError())
340+
if (result->HasError())
342341
{
343-
return MaterializedQueryResultHelper::toJSON(*result);
342+
auto err = result->GetError();
343+
throw std::runtime_error(err.c_str());
344344
}
345-
return std::string();
345+
return MaterializedQueryResultHelper::toJSON(*result);
346346
}
347347
}
348348
namespace DuckDBHelper

packages/duckdb/src/duckdb.ts

Lines changed: 39 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import load, { reset } from "../../../build/packages/duckdb/duckdblib.wasm";
33
import type { MainModule, DuckDB as CPPDuckDB } from "../types/duckdblib.js";
44
import { MainModuleEx } from "@hpcc-js/wasm-util";
55

6-
let g_duckdb: Promise<DuckDB>;
6+
let g_duckdb: Promise<DuckDB> | undefined;
77
const textEncoder = new TextEncoder();
88

99
/**
@@ -91,7 +91,7 @@ const textEncoder = new TextEncoder();
9191
* @see [DuckDB Documentation](https://duckdb.org/docs/)
9292
* @see [DuckDB GitHub](https://github.com/duckdb/duckdb)
9393
*/
94-
export class DuckDB extends MainModuleEx<MainModule> implements Disposable {
94+
export class DuckDB extends MainModuleEx<MainModule> {
9595

9696
db: CPPDuckDB
9797

@@ -103,47 +103,6 @@ export class DuckDB extends MainModuleEx<MainModule> implements Disposable {
103103
FS_createPath("/", "home/web_user", true, true);
104104
}
105105

106-
/**
107-
* Disposes of the DuckDB database instance and releases resources.
108-
*
109-
* This method should be called when you're done using the database to free up
110-
* memory and clean up resources. After calling dispose, the instance should not be used.
111-
*
112-
* @example
113-
* ```ts
114-
* const duckdb = await DuckDB.load();
115-
* const connection = duckdb.connect();
116-
* // ... use the database ...
117-
* connection.delete();
118-
* duckdb.dispose();
119-
* ```
120-
*
121-
* @example Using statement (TypeScript 5.2+)
122-
* ```ts
123-
* {
124-
* using duckdb = await DuckDB.load();
125-
* const connection = duckdb.connect();
126-
* // ... use the database ...
127-
* connection.delete();
128-
* } // duckdb.dispose() is called automatically
129-
* ```
130-
*/
131-
dispose(): void {
132-
try {
133-
this.db?.terminate();
134-
} finally {
135-
this.db?.delete();
136-
}
137-
}
138-
139-
/**
140-
* Symbol.dispose implementation for use with `using` declarations.
141-
* @internal
142-
*/
143-
[Symbol.dispose](): void {
144-
this.dispose();
145-
}
146-
147106
/**
148107
* Loads and initializes the DuckDB WASM module.
149108
*
@@ -167,7 +126,7 @@ export class DuckDB extends MainModuleEx<MainModule> implements Disposable {
167126
return new DuckDB(module)
168127
});
169128
}
170-
return g_duckdb;
129+
return g_duckdb!;
171130
}
172131

173132
/**
@@ -182,8 +141,20 @@ export class DuckDB extends MainModuleEx<MainModule> implements Disposable {
182141
* // The next call to DuckDB.load() will create a fresh instance
183142
* ```
184143
*/
185-
static unload() {
186-
reset();
144+
static async unload() {
145+
try {
146+
const duckdb = await g_duckdb;
147+
if (duckdb?.db) {
148+
try {
149+
duckdb.db.terminate();
150+
} finally {
151+
duckdb.db.delete();
152+
}
153+
}
154+
} finally {
155+
reset();
156+
g_duckdb = undefined;
157+
}
187158
}
188159

189160
/**
@@ -240,6 +211,27 @@ export class DuckDB extends MainModuleEx<MainModule> implements Disposable {
240211
return Number(this.db.numberOfThreads());
241212
}
242213

214+
getExceptionMessage(error: unknown): { type: string, message?: string } {
215+
const mod: any = this._module as any;
216+
try {
217+
const result = mod?.getExceptionMessage?.(error);
218+
if (Array.isArray(result)) {
219+
return {
220+
type: result[0] ?? "",
221+
message: result[1] ?? ""
222+
};
223+
}
224+
} catch (_err) {
225+
void _err;
226+
}
227+
228+
const err: any = error;
229+
return {
230+
type: err?.name ?? "Error",
231+
message: typeof err?.message === "string" ? err.message : String(error)
232+
};
233+
}
234+
243235
/**
244236
* Registers a binary file in the virtual file system.
245237
*
@@ -371,5 +363,5 @@ export class DuckDB extends MainModuleEx<MainModule> implements Disposable {
371363
const encoded = textEncoder.encode(content);
372364
this.registerFile(fileName, encoded);
373365
}
374-
375366
}
367+

packages/duckdb/tests/duckdb.spec.ts

Lines changed: 39 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,17 @@
1-
import { beforeAll, describe, expect, it } from "vitest";
1+
import { afterAll, beforeAll, describe, expect, it } from "vitest";
22
import { DuckDB } from "@hpcc-js/wasm-duckdb";
33

4-
describe("duckdb", () => {
4+
describe.sequential("duckdb", () => {
55
let duckdb: DuckDB;
66

77
beforeAll(async () => {
88
duckdb = await DuckDB.load();
99
});
1010

11+
afterAll(async () => {
12+
await DuckDB.unload();
13+
});
14+
1115
describe("DuckDB static methods", () => {
1216
it("loads once and reports version", async () => {
1317
const secondLoad = await DuckDB.load();
@@ -826,5 +830,38 @@ FROM duckdb_extensions();
826830

827831
con.delete();
828832
});
833+
834+
it("exception handline", () => {
835+
const con = duckdb.connect();
836+
try {
837+
const json = con.queryToJSON("Great Big Error!");
838+
JSON.parse(json);
839+
} catch (e) {
840+
const exception = duckdb.getExceptionMessage(e)
841+
expect(exception).toBeDefined();
842+
expect(exception.type).toBe("std::runtime_error");
843+
expect(e).toBeDefined();
844+
} finally {
845+
con.delete();
846+
}
847+
});
848+
});
849+
850+
describe("unload", () => {
851+
it("resets singleton and is idempotent", async () => {
852+
await DuckDB.unload();
853+
854+
const dba = await DuckDB.load();
855+
expect(await DuckDB.load()).toBe(dba);
856+
857+
await DuckDB.unload();
858+
859+
const dbb = await DuckDB.load();
860+
expect(dbb).not.toBe(dba);
861+
expect(await DuckDB.load()).toBe(dbb);
862+
863+
await DuckDB.unload();
864+
await DuckDB.unload();
865+
});
829866
});
830867
});

packages/expat/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ set(EM_LINK_FLAGS
1414
"-sEXPORT_NAME='${CMAKE_PROJECT_NAME}'"
1515

1616
"-fwasm-exceptions"
17+
"-sEXCEPTION_STACK_TRACES=1"
1718
"--emit-tsd ${CMAKE_CURRENT_BINARY_DIR}/expatlib.d.ts"
1819
)
1920
string(REPLACE ";" " " LINK_FLAGS "${EM_LINK_FLAGS}")

packages/expat/src/expat.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ function parseAttrs(attrs: map_string_string): Attributes {
2222
return retVal;
2323
}
2424

25-
let g_expat: Promise<Expat>;
25+
let g_expat: Promise<Expat> | undefined;
2626

2727
/**
2828
* Expat XML parser WASM library, provides a simplified wrapper around the Expat XML Parser library.
@@ -77,6 +77,7 @@ export class Expat extends MainModuleEx<MainModule> {
7777
*/
7878
static unload() {
7979
reset();
80+
g_expat = undefined;
8081
}
8182

8283
/**

0 commit comments

Comments
 (0)