-
Notifications
You must be signed in to change notification settings - Fork 0
Error Handling
Generated bindings have two error paths:
The JavaScript call is wrong
-> TypeError or RangeError
The call was accepted, but the operation failed
-> SupernoteError with a stable code
That is the useful distinction. A TypeError or RangeError normally means
the call site needs fixing. A SupernoteError describes something your plugin
may need to handle while it is running.
Every generated module exports SupernoteError and its codes:
import document, {SupernoteError} from 'document';Put the call itself inside try:
async function loadPage(page: number): Promise<Uint8Array | null> {
try {
return await document.loadPage(page);
} catch (error) {
if (error instanceof TypeError || error instanceof RangeError) {
// The call does not match index.d.ts. This is normally a bug.
throw error;
}
if (error instanceof SupernoteError) {
switch (error.code) {
case 'RESOURCE_EXHAUSTED':
// Stop submitting more work. Retry later if that is safe.
return null;
case 'CANCELLED':
case 'FEATURE_CLOSED':
// This operation is no longer useful.
return null;
case 'IMPLEMENTATION_ERROR':
case 'INTERNAL':
console.error(error.code, error.message);
throw error;
}
}
// Do not hide an unrelated JavaScript error.
throw error;
}
}This catches both possible timings:
- argument validation can throw before a Promise exists;
- an accepted async operation can reject its Promise later.
This shorter form only handles the second case:
document.loadPage(page).catch(handleError);If loadPage(page) throws while checking the arguments, .catch(...) is
never reached.
The generated boundary checks the call before starting async work:
| Problem | Result |
|---|---|
| Wrong argument count | synchronous TypeError
|
| Wrong JavaScript type | synchronous TypeError
|
| Fractional value for an integer | synchronous RangeError
|
| Number outside the generated integer range | synchronous RangeError
|
For example, int64 requires JavaScript bigint, bytes require
Uint8Array, and initial V2 values do not accept null.
These are programming errors. The generator does not create a worker task or pending Promise for a call that does not match the generated TypeScript API.
Once a call has been accepted, generated and runtime failures use one class:
export type SupernoteErrorCode =
| 'RESOURCE_EXHAUSTED'
| 'CANCELLED'
| 'FEATURE_CLOSED'
| 'IMPLEMENTATION_ERROR'
| 'INTERNAL';
export class SupernoteError extends Error {
readonly code: SupernoteErrorCode;
}Use error.code when your code needs to make a decision. The message is for
people and may change between generator versions.
| Code | What it means | What to do |
|---|---|---|
RESOURCE_EXHAUSTED |
The bounded worker queue or another runtime resource could not accept more work. | Submit less work at once. Retry later only if repeating the operation is safe. |
CANCELLED |
Cooperative cancellation won before normal completion. | Treat the result as unavailable. The underlying native/JVM call may still be finishing safely in the background. |
FEATURE_CLOSED |
The module session closed while the JavaScript runtime was still healthy. | Stop work for that session. A newly opened session is a different session. |
IMPLEMENTATION_ERROR |
Your C++, Kotlin, Java, or user-owned constructor failed unexpectedly. | Log the context and fix or deliberately handle the implementation failure. |
INTERNAL |
Generated/runtime machinery failed or found a broken invariant. | Record the generator version and report a small reproduction. |
Do not parse error.message, and do not depend on a C++ exception type or JVM
exception class appearing in JavaScript.
A synchronous export has no Promise, so both kinds of failure throw directly:
try {
const count = document.pageCount();
useCount(count);
} catch (error) {
if (error instanceof SupernoteError) {
console.error(error.code, error.message);
}
throw error;
}The built-in codes describe binding and runtime failures. They are not a full set of errors for your application.
Do not treat an IMPLEMENTATION_ERROR message as a stable value such as
FILE_NOT_FOUND or INVALID_DOCUMENT. If JavaScript needs to act on one of
those conditions today, expose it deliberately through your own API using the
supported value types.
Most modules can stop at the sections above. The details below matter when an error crosses a JavaScript realm, when C++ calls a generated internal JVM route, or when a runtime is being torn down.
With the normal generated import, error instanceof SupernoteError is
supported. Class identity may be lost after serialization or when an error
crosses into another JavaScript realm. In that case, validate the object and
compare its code against the exported SupernoteErrorCode values.
The initial public contract does not include fields such as backend,
nativeException, details, operationId, or featureId.
A synchronous generated internal C++ call either returns its value or throws
supernote::Error:
try {
const auto page =
supernote::internal::DocumentFeature::internalJvmPage(3);
use_page(page);
} catch (const supernote::Error &error) {
if (error.code() == supernote::ErrorCode::FEATURE_CLOSED) {
return;
}
throw;
}An internal async call reports one supernote::Result<T> while its owning
session remains valid:
supernote::internal::DocumentFeature::internalJvmBlocking(
3,
[](supernote::Result<std::vector<std::byte>> result) {
if (!result) {
log_error(result.error().code(), result.error().what());
return;
}
use_bytes(std::move(result).value());
});The callback runs on a Supernote-managed non-JavaScript context. It has no stable worker, UI, or serial-thread guarantee, so captured state must be thread-safe and the callback must never touch JSI.
The callback runs at most once. If its session closes before delivery, it may not run at all. Do not make it the only cleanup path for a resource that must always be released.
Feature teardown can still report an error when the JavaScript runtime is healthy:
feature closes
-> pending accepted Promises reject with FEATURE_CLOSED
Complete JavaScript-runtime teardown is different:
JS runtime shuts down
-> generated code stops touching JSI
-> late results are discarded
The Promise belongs to the disappearing JavaScript realm, so trying to reject it during runtime shutdown would be unsafe.
Include the error code and message, supernote-module --version, the smallest
marked declaration that reproduces the problem, and the full output from:
supernote-module validate <module-name> --build --verboseFor device-only failures, also include the Supernote model, firmware, whether
it failed on the first load or a same-process reload, and the related
adb logcat lines. See Troubleshooting for the complete
diagnostic checklist.