-
Notifications
You must be signed in to change notification settings - Fork 1
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
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
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
|
||||||||||||||||
| 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.`, | |
| const failedDriverList = 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
|
||||||||||||
| ## 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
|
||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
| /** | ||||||||||||||||||||||||||||
| * 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
|
||||||||||||||||||||||||||||
| * becomes unavailable (e.g., serverless cold-start, transient error). | |
| */ | |
| setConnectFactory(factory: () => Promise<Client>): void { | |
| this.connectFactory = factory; | |
| * becomes unavailable (e.g., serverless cold-start, transient error). | |
| * | |
| * Replacing the factory invalidates any cached lazy-connect attempt so | |
| * subsequent callers cannot continue awaiting a promise created by the | |
| * previous factory. | |
| */ | |
| setConnectFactory(factory: () => Promise<Client>): void { | |
| this.connectFactory = factory; | |
| this.connectPromise = null; |
Copilot
AI
Apr 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).
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.