Uh oh!
There was an error while loading. Please reload this page.
- Notifications
You must be signed in to change notification settings - Fork 8
fix: lazy connect in RemoteTransport for serverless cold-start resilience#1085
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Uh oh!
There was an error while loading. Please reload this page.
Changes from all commits
File filter
Filter by extension
Conversations
Uh oh!
There was an error while loading. Please reload this page.
Jump to
Uh oh!
There was an error while loading. Please reload this page.
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -594,14 +594,24 @@ export class ObjectQL implements IDataEngine { | ||||||||||||||||
| drivers: Array.from(this.drivers.keys()) | ||||||||||||||||
| }); | ||||||||||||||||
| const failedDrivers: string[] = []; | ||||||||||||||||
| for (const [name, driver] of this.drivers) { | ||||||||||||||||
| try { | ||||||||||||||||
| await driver.connect(); | ||||||||||||||||
| this.logger.info('Driver connected successfully', { driverName: name }); | ||||||||||||||||
| } catch (e) { | ||||||||||||||||
| failedDrivers.push(name); | ||||||||||||||||
| this.logger.error('Failed to connect driver', e as Error, { driverName: name }); | ||||||||||||||||
| } | ||||||||||||||||
| } | ||||||||||||||||
| if (failedDrivers.length > 0) { | ||||||||||||||||
| this.logger.warn( | ||||||||||||||||
| `${failedDrivers.length} of ${this.drivers.size} driver(s) failed initial connect. ` + | ||||||||||||||||
| `Operations may recover via lazy reconnection or fail at query time.`, | ||||||||||||||||
Comment on lines
+609
to
+611
CopilotAI | ||||||||||||||||
| this.logger.warn( | |
| `${failedDrivers.length} of ${this.drivers.size} driver(s) failed initial connect. `+ | |
| `Operations may recover via lazy reconnection or fail at query time.`, | |
| constfailedDriverList=failedDrivers.join(', '); | |
| this.logger.warn( | |
| `${failedDrivers.length} of ${this.drivers.size} driver(s) failed initial connect `+ | |
| `(${failedDriverList}). Operations may recover via lazy reconnection or fail at query time.`, |
| Original file line number | Diff line number | Diff line change | ||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -1,5 +1,11 @@ | ||||||||||||
| # @objectstack/driver-turso | ||||||||||||
| ## 4.0.3 | ||||||||||||
| ### Patch Changes | ||||||||||||
| - fix: implement lazy connect in RemoteTransport to self-heal from serverless cold-start failures, transient network errors, or missed `connect()` calls. The transport now accepts a connect factory and auto-initializes the @libsql/client on first operation when the client is not yet available. Concurrent reconnection attempts are de-duplicated. | ||||||||||||
Comment on lines
+3
to
+8
CopilotAI | ||||||||||||
| ## 4.0.3 | |
| ### Patch Changes | |
| - fix: implement lazy connect in RemoteTransport to self-heal from serverless cold-start failures, transient network errors, or missed `connect()` calls. The transport now accepts a connect factory and auto-initializes the @libsql/client on first operation when the client is not yet available. Concurrent reconnection attempts are de-duplicated. |
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -42,13 +42,40 @@ const SAFE_IDENTIFIER = /^[a-zA-Z_][a-zA-Z0-9_]*$/; | ||||||||||||||||||||||||||||
| export class RemoteTransport { | ||||||||||||||||||||||||||||
| private client: Client | null = null; | ||||||||||||||||||||||||||||
| /** | ||||||||||||||||||||||||||||
| * Factory function for lazy (re)connection. | ||||||||||||||||||||||||||||
| * | ||||||||||||||||||||||||||||
| * When set, `ensureConnected()` will invoke this factory to create a | ||||||||||||||||||||||||||||
| * @libsql/client instance on-demand — recovering from cold-start failures, | ||||||||||||||||||||||||||||
| * transient network errors, or serverless recycling without requiring the | ||||||||||||||||||||||||||||
| * caller to explicitly call `connect()` again. | ||||||||||||||||||||||||||||
| */ | ||||||||||||||||||||||||||||
| private connectFactory: (() => Promise<Client>) | null = null; | ||||||||||||||||||||||||||||
| /** | ||||||||||||||||||||||||||||
| * Tracks whether a lazy-connect attempt is already in progress to prevent | ||||||||||||||||||||||||||||
| * concurrent reconnection storms under high concurrency. | ||||||||||||||||||||||||||||
| */ | ||||||||||||||||||||||||||||
| private connectPromise: Promise<Client> | null = null; | ||||||||||||||||||||||||||||
| /** | ||||||||||||||||||||||||||||
| * Set the @libsql/client instance used for all queries. | ||||||||||||||||||||||||||||
| */ | ||||||||||||||||||||||||||||
| setClient(client: Client): void { | ||||||||||||||||||||||||||||
| this.client = client; | ||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||
Comment on lines
61
to
66
CopilotAI | ||||||||||||||||||||||||||||
| /** | ||||||||||||||||||||||||||||
| * Register a factory function for lazy (re)connection. | ||||||||||||||||||||||||||||
| * | ||||||||||||||||||||||||||||
| * TursoDriver calls this during construction so that the transport can | ||||||||||||||||||||||||||||
| * self-heal when the initial `connect()` call fails or when the client | ||||||||||||||||||||||||||||
| * becomes unavailable (e.g., serverless cold-start, transient error). | ||||||||||||||||||||||||||||
| */ | ||||||||||||||||||||||||||||
| setConnectFactory(factory: () => Promise<Client>): void { | ||||||||||||||||||||||||||||
| this.connectFactory = factory; | ||||||||||||||||||||||||||||
Comment on lines
+73
to
+76
CopilotAI | ||||||||||||||||||||||||||||
| *becomesunavailable(e.g.,serverlesscold-start,transienterror). | |
| */ | |
| setConnectFactory(factory: ()=>Promise<Client>): void{ | |
| this.connectFactory=factory; | |
| *becomesunavailable(e.g.,serverlesscold-start,transienterror). | |
| * | |
| *Replacingthefactoryinvalidates any cachedlazy-connectattemptso | |
| *subsequentcallerscannotcontinueawaitingapromisecreatedby the | |
| *previousfactory. | |
| */ | |
| setConnectFactory(factory: ()=>Promise<Client>): void{ | |
| this.connectFactory=factory; | |
| this.connectPromise=null; |
CopilotAIApr 8, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In execute(), await this.ensureConnected() relies on the side-effect of setting this.client, but the method still uses this.client! later. This is vulnerable to races if another task calls close()/setClient() between awaits, and it also keeps the non-null assertion. Prefer const client = await this.ensureConnected() and use that local for the rest of the method (same applies to other CRUD methods).
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
CopilotAIApr 8, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This introduces a
## 4.0.3changelog entry, butpackages/objectql/package.jsonstill reports version4.0.2. Since this repo is configured for Changesets and other packages keep changelog/version in sync, consider either bumping the package version(s) as part of the release (fixed group) or switching to a Changeset file instead of manually editing the changelog.