The TypeScript client SDK uses Promise.withResolvers() (an ES2024 API) in db_connection_impl.ts, which is not available in React Native's Hermes JavaScript engine. This causes a TypeError: undefined is not a function crash whenever a reducer or procedure is called from a React Native app on iOS or Android.
The SDK connects successfully and subscriptions work, but every reducer call crashes because the promise creation fails before the message is even sent.
Environment
- SpacetimeDB version: 2.5.0
- spacetimedb npm package: 2.5.0
- Client framework: React Native (Expo SDK 56) with Hermes engine
- Platforms affected: iOS Simulator, Android (any React Native app using Hermes)
- Platform working fine: Web (browsers support
Promise.withResolvers)
Steps to Reproduce
- Create a React Native (Expo) project with SpacetimeDB
- Connect to a SpacetimeDB module — connection succeeds
- Call any reducer (e.g.,
conn.reducers.addPlayer({ name: 'Alice' }))
- Observe crash:
TypeError: undefined is not a function
Error & Stack Trace
TypeError: undefined is not a function
at #callReducerWithEncodedName (localhost:8081/index…es-stable:110857:32)
at anonymous (localhost:8081/index…es-stable:110324:52)
at apply (native)
at anonymous (localhost:8081/index…es-stable:103789:16)
at handle (localhost:8081/index…es-stable:101678:17)
at _performTransitionSideEffects (localhost:8081/index…es-stable:63205:20)
...
Root Cause
In crates/bindings-typescript/src/sdk/db_connection_impl.ts, four methods use Promise.withResolvers():
- Line 1107:
#callReducerWithEncodedName — called on every reducer invocation
- Line 1142:
#callReducerGeneric — fallback reducer path
- Line 1223:
#callProcedureWithEncodedName — called on every procedure invocation
- Line 1240:
#callProcedureGeneric — fallback procedure path
Promise.withResolvers() is an ES2024 feature that is supported in modern browsers and Node.js 22+, but not in React Native's Hermes engine (the default JS engine for React Native). Hermes does not yet implement this API — Promise.withResolvers evaluates to undefined, so calling it throws TypeError: undefined is not a function.
This is notable because the SDK already contains a React Native compatibility workaround (line 241-243, the URL.toString() fix), showing awareness of the RN environment. The SDK docs also state support for "web apps, Node.js, Deno, Bun, and other JavaScript runtimes" — React Native is a major JavaScript runtime that should be covered.
Suggested Fix
Option A: Internal polyfill (minimal, zero dependencies)
Add a helper at the top of db_connection_impl.ts:
/**
* Polyfill for Promise.withResolvers (ES2024).
* Required for React Native's Hermes engine which does not support this API.
*/
function promiseWithResolvers<T>(): {
promise: Promise<T>;
resolve: (value: T | PromiseLike<T>) => void;
reject: (reason?: any) => void;
} {
let resolve!: (value: T | PromiseLike<T>) => void;
let reject!: (reason?: any) => void;
const promise = new Promise<T>((res, rej) => {
resolve = res;
reject = rej;
});
return { promise, resolve, reject };
}
Then replace the four Promise.withResolvers<T>() calls with promiseWithResolvers<T>().
This approach:
- Has zero runtime overhead on environments that already support the API (the helper is functionally identical)
- Requires no global mutation (unlike a
Promise.withResolvers polyfill patch)
- Is a 4-line change at each call site
- Keeps the SDK self-contained with no new dependencies
Option B: Global polyfill guard
Add at the top of the SDK entry point:
if (typeof Promise.withResolvers === 'undefined') {
Promise.withResolvers = function <T>() {
let resolve!: (value: T | PromiseLike<T>) => void;
let reject!: (reason?: any) => void;
const promise = new Promise<T>((res, rej) => {
resolve = res;
reject = rej;
});
return { promise, resolve, reject };
};
}
Current Workaround
Users can add a polyfill in their app entry point before any SpacetimeDB imports:
// polyfills.ts — must be imported FIRST
if (typeof Promise.withResolvers === 'undefined') {
Promise.withResolvers = function <T>() {
let resolve!: (value: T | PromiseLike<T>) => void;
let reject!: (reason?: any) => void;
const promise = new Promise<T>((res, rej) => {
resolve = res;
reject = rej;
});
return { promise, resolve, reject };
};
}
This works but requires every React Native developer to discover and apply this fix independently.
The TypeScript client SDK uses
Promise.withResolvers()(an ES2024 API) indb_connection_impl.ts, which is not available in React Native's Hermes JavaScript engine. This causes aTypeError: undefined is not a functioncrash whenever a reducer or procedure is called from a React Native app on iOS or Android.The SDK connects successfully and subscriptions work, but every reducer call crashes because the promise creation fails before the message is even sent.
Environment
Promise.withResolvers)Steps to Reproduce
conn.reducers.addPlayer({ name: 'Alice' }))TypeError: undefined is not a functionError & Stack Trace
Root Cause
In
crates/bindings-typescript/src/sdk/db_connection_impl.ts, four methods usePromise.withResolvers():#callReducerWithEncodedName— called on every reducer invocation#callReducerGeneric— fallback reducer path#callProcedureWithEncodedName— called on every procedure invocation#callProcedureGeneric— fallback procedure pathPromise.withResolvers()is an ES2024 feature that is supported in modern browsers and Node.js 22+, but not in React Native's Hermes engine (the default JS engine for React Native). Hermes does not yet implement this API —Promise.withResolversevaluates toundefined, so calling it throwsTypeError: undefined is not a function.This is notable because the SDK already contains a React Native compatibility workaround (line 241-243, the
URL.toString()fix), showing awareness of the RN environment. The SDK docs also state support for "web apps, Node.js, Deno, Bun, and other JavaScript runtimes" — React Native is a major JavaScript runtime that should be covered.Suggested Fix
Option A: Internal polyfill (minimal, zero dependencies)
Add a helper at the top of
db_connection_impl.ts:Then replace the four
Promise.withResolvers<T>()calls withpromiseWithResolvers<T>().This approach:
Promise.withResolverspolyfill patch)Option B: Global polyfill guard
Add at the top of the SDK entry point:
Current Workaround
Users can add a polyfill in their app entry point before any SpacetimeDB imports:
This works but requires every React Native developer to discover and apply this fix independently.