Skip to content

feat(db): add support for non-node libsql client#14204

Merged
Adammatthiesen merged 39 commits into
withastro:mainfrom
Adammatthiesen:main
Sep 11, 2025
Merged

feat(db): add support for non-node libsql client#14204
Adammatthiesen merged 39 commits into
withastro:mainfrom
Adammatthiesen:main

Conversation

@Adammatthiesen

Copy link
Copy Markdown
Member

Changes

This PR solves the outstanding issue that was being caused by the usage of the @libsql/client's default export of their node client, by introducing a new option to switch from the node client to the web client. Allowing users on platforms like Cloudflare to finally be able to use Astro DB since the Studio sunset.

Testing

There is currently no tests testing external server connections. And im not sure how possible it would be to spin up a libsql server locally in CI for tests.

Docs

CC @sarah11918 at your request i'm tagging you!

@changeset-bot

changeset-bot Bot commented Aug 8, 2025

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: ccdb5d3

The changes in this PR will be included in the next version bump.

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@Adammatthiesen

Copy link
Copy Markdown
Member Author

CC @Fryuni does this look correct?

Comment thread packages/db/src/runtime/db-client.ts Outdated

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In theory @libsql/client automatically switches to the appropriate implementation for the runtime it is running on. So if the missing module is coming from LibSQL this is a bug there.

If it is coming from Drizzle Client's module then I don't know if it was supposed to work or not. Worth bringing it up on their discord or repo.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

image

My understanding would be that as well... but once i looked at how libsql client "decides" which one to use.... I think i found why it doesn't work... since when your deploying to CF, its still running node underlying when doing the import.

As for drizzle docs... they have this note

defaults to node import, automatically changes to web if target or platform is set for bundler, e.g. esbuild --platform=browser

Comment thread packages/db/src/runtime/db-client.ts Outdated
import type { LibSQLDatabase } from 'drizzle-orm/libsql';
import { drizzle as drizzleLibsql } from 'drizzle-orm/libsql';
import { createClient as createClientWeb } from '@libsql/client/web';
import { drizzle as drizzleLibsql, type LibSQLDatabase } from 'drizzle-orm/libsql';

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Won't Cloudflare complain just by importing this module? If that is the case, we'll need to do some tricks using virtual modules to only import the modules that are safe for the runtime.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah i was wondering the same thing... I'll convert the db client config parts into dedicated internal virtual modules that will be enabled depending on mode instead, here after i make my coffee.

fix(db): ensure local client module is returned in default case
@Adammatthiesen

Copy link
Copy Markdown
Member Author

OKAY... @Fryuni I think its at the point that everything is working (finally fixed the tests too), and I've virtualized all the libsql/drizzle clients... that was a bit annoying to do 😅 but should be worth it! I think its ready to be moved from draft?

@Fryuni Fryuni left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looking really good

Comment thread packages/db/src/runtime/utils.ts Outdated
Comment on lines +47 to +55
export function parseOpts(config: Record<string, string>): Partial<LibSQLConfig> {
return {
...config,
...(config.syncInterval ? { syncInterval: parseInt(config.syncInterval) } : {}),
...('readYourWrites' in config ? { readYourWrites: config.readYourWrites !== 'false' } : {}),
...('offline' in config ? { offline: config.offline !== 'false' } : {}),
...('tls' in config ? { tls: config.tls !== 'false' } : {}),
...(config.concurrency ? { concurrency: parseInt(config.concurrency) } : {}),
};

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can't we use Zod for this? It has coercion and transformations.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

........ now that you say that.... yes... yes we could... why i didnt do that in the first place? no freaking clue...

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

okay got the zod transformer built and working 😄

appToken: string;
isBuild: boolean;
output: AstroConfig['output'];
__execute?: boolean;

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
__execute?: boolean;
// Request module to be loaded immediately in process
localExecution?: boolean;

Comment on lines +11 to +13
case 'web': {
return `export { createClient } from '${DB_CLIENTS.web}';`;
}

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
case 'web': {
return `export { createClient } from '${DB_CLIENTS.web}';`;
}
case 'web':
return `export { createClient } from '${DB_CLIENTS.web}';`;

@Adammatthiesen Adammatthiesen marked this pull request as ready for review August 10, 2025 14:16

@sarah11918 sarah11918 left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Woo! What an accomplishment! Quick thoughts from me...

(Also noting, I don't see any new error messages, so checking that we don't need any new specific ones, nor an update to any existing because our existing ones don't know about this option)

Comment thread .changeset/full-dingos-repeat.md Outdated
Comment thread .changeset/full-dingos-repeat.md Outdated
Comment thread .changeset/full-dingos-repeat.md Outdated
Thank god for the save by the docs queen

Co-authored-by: Sarah Rainsberger <5098874+sarah11918@users.noreply.github.com>
@Adammatthiesen

Copy link
Copy Markdown
Member Author

Woo! What an accomplishment! Quick thoughts from me...

(Also noting, I don't see any new error messages, so checking that we don't need any new specific ones, nor an update to any existing because our existing ones don't know about this option)

Only user facing change is the new options added to DB, that previously never existed (not even an object inside the integration function) everything else is the same "internals" just now with the ability to pick web or node drivers (since for some reason the auto detection by the libsql/client doesn't work... likely related to how Astro's build pipeline works under the hood)

Comment thread .changeset/full-dingos-repeat.md
Comment on lines +120 to +124
export function getLocalVirtualModContents({
tables,
root,
localExecution = false,
}: {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you add some JSDocs that explain the business logic of the function? The new localExecution adds some logic that isn't clear to me. For example, it doesn't explain why, when localExecution is true, we need the node client. It's an assumption that isn't explained.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The node client has full protocol support, file:, libsql:, http:. and https: where as the web client is missing the file: support (for obvious reasons) So for users who utilize the file: protocol (when we are running the execute command) we need to ensure that we have the node client available.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've extracted the duplicated logic to a helper function, and added jsdocs to the new function and the two instances of localExecution

Comment thread packages/db/src/core/integration/vite-plugin-db.ts
isBuild: boolean;
output: AstroConfig['output'];
// Request module to be loaded immediately in process
localExecution?: boolean;

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If possible, I would avoid the ? here. Why? Because it's an internal code, and by adding ? we might miss code paths that might require true instead. This is usually a pattern I follow inside code that isn't exposed to the user: the compiler fails all the instances of the code, and forces me to update them all. Would you mind doing that here and in the other function?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Updated in latest commit

Comment on lines +193 to +195
const clientImport = localExecution
? `import { createClient } from '${DB_CLIENTS.node}';`
: `import { createClient } from '${VIRTUAL_CLIENT_MODULE_ID}';`;

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

To me, using a node client for a remote feels like an error. However, I am sure you think it's not. Is it because the execution of commands can be done only locally? If so, then I have two suggestions:

  • commands shouldn't call getRemoteVirtualModContents
  • add some JsDoc to getRemoteVirtualModContents that explains the implications of localExecution for this function

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

see #14204 (comment) for the context, we previously only had the node client, and that is the recommended approach by drizzle and libsql teams unless you specifically need one of the other limited exports. (as a file: protocol CAN be considered "remote")

Comment thread packages/db/src/core/db-client/utils.ts Outdated

@ematipico ematipico left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you for addressing my questions!

@Adammatthiesen

Copy link
Copy Markdown
Member Author

So @sarah11918 I'm guessing that the best place for the new config options docs wise would be replacing the note under this section?

@sarah11918

sarah11918 commented Aug 13, 2025

Copy link
Copy Markdown
Member

@Adammatthiesen That note can certainly be removed!

I will note that section is about connecting to remote databases, and so maybe we don't want to clutter inside one set of instructions (connecting) with an entirely new topic (a configuration option).

I don't think the db integration currently takes any config options? We should probably now have a ## Configuration section to properly document mode like an API reference. (See the Node.js adapter as an example).

Then, in the "connect to a remote database" instructions, if something is different for 'web' vs default mode, we can explain any different steps there, and link to the mode reference as needed.

@Adammatthiesen

Adammatthiesen commented Aug 14, 2025

Copy link
Copy Markdown
Member Author

@Adammatthiesen That note can certainly be removed!

I will note that section is about connecting to remote databases, and so maybe we don't want to clutter inside one set of instructions (connecting) with an entirely new topic (a configuration option).

I don't think the db integration currently takes any config options? We should probably now have a ## Configuration section to properly document mode like an API reference. (See the Node.js adapter as an example).

Then, in the "connect to a remote database" instructions, if something is different for 'web' vs default mode, we can explain any different steps there, and link to the mode reference as needed.

The difference is the fact that the web mode, would allow db to work on environments like CF but would restrict usage of local file paths (i.e. file://path/to/db.db is only available in node mode) which is why i do think it would be relevant on the "Connecting to Remote Databases" esp if we are talking from a deployment guide point of view.

I also agree that we will need the full config reference as well, most likely just adding to the dedicated integration page? https://docs.astro.build/en/guides/integrations-guide/db/

@sarah11918

Copy link
Copy Markdown
Member

I'm confused, the first link you posted was the db integration guide? that's the only guide in question here, right?

@Adammatthiesen

Copy link
Copy Markdown
Member Author

I'm confused, the first link you posted was the db integration guide? that's the only guide in question here, right?

there is two pages... the guide page, and the technical page.... so maybe both... thats why im asking you after all... your the docs person who knows best. 😅

@sarah11918

Copy link
Copy Markdown
Member

Haha, sorry, I thought you were referring to the same page twice. I'll figure out this docs thing eventually...

@sarah11918

Copy link
Copy Markdown
Member

OK, so catching up on this yes, we do need this config option mentioned on the integration page itself. Typically, a ## Configuration section for configuring the integration itself would go after ## Installation. So let's create that new section and document mode as now a configuration option.

Then yes, in "connecting to remote databases" replace the note with a sub-heading, something maybe like ## Non-Node.js clients (replace "clients" with whatever the appropriate word is?). Then, add whatever text you think is needed there, and when you mention that setting mode: 'web' is required, link back to the API reference from the guide page.

Does that sound about right to you?

@Adammatthiesen

Copy link
Copy Markdown
Member Author

OK, so catching up on this yes, we do need this config option mentioned on the integration page itself. Typically, a ## Configuration section for configuring the integration itself would go after ## Installation. So let's create that new section and document mode as now a configuration option.

Then yes, in "connecting to remote databases" replace the note with a sub-heading, something maybe like ## Non-Node.js clients (replace "clients" with whatever the appropriate word is?). Then, add whatever text you think is needed there, and when you mention that setting mode: 'web' is required, link back to the API reference from the guide page.

Does that sound about right to you?

Probably use the term "driver" instead of client? but yup sounds perfect. I'll work on the docs here in the next few days!

Comment thread .changeset/full-dingos-repeat.md Outdated
Co-authored-by: Sarah Rainsberger <5098874+sarah11918@users.noreply.github.com>
@Adammatthiesen

Copy link
Copy Markdown
Member Author

@sarah11918 Docs PR for this is now up withastro/docs#12208

@ematipico ematipico added this to the v5.14.0 milestone Aug 25, 2025
Comment thread .changeset/full-dingos-repeat.md

@sarah11918 sarah11918 left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Approving for docs! 🎉

Adammatthiesen and others added 2 commits September 5, 2025 20:13
Co-authored-by: Sarah Rainsberger <5098874+sarah11918@users.noreply.github.com>
@Adammatthiesen Adammatthiesen merged commit d71448e into withastro:main Sep 11, 2025
18 of 19 checks passed
@astrobot-houston astrobot-houston mentioned this pull request Sep 11, 2025
openscript pushed a commit to openscript/astro that referenced this pull request Sep 12, 2025
* feat(db): add support for non-node libsql client

* feat(db): update remote database info retrieval to include mode parameter

* feat(db): export AstroDBConfig type alongside integration from core integration

* test(db): ensure mode parameter is passed to getRemoteDatabaseInfo in tests

* feat(db): refactor database client handling and remove legacy code

* test(db): remove mode parameter from getRemoteDatabaseInfo assertions

* test(db): remove mode parameter from getRemoteDatabaseInfo call in tests

* fix(db): ensure correct handling of 'node' mode in client module functions

* refactor(db): consolidate utility functions by moving hasPrimaryKey to utils

* fix(db): re-export hasPrimaryKey from utils for consistency

* fix(db): update import path for hasPrimaryKey to utils for consistency

* fix(db): update client import to use VIRTUAL_CLIENT_MODULE_ID for consistency

* fix(db): remove commented export of hasPrimaryKey for clarity

* fix(db): update DB_CLIENTS paths to use '@astrojs' for consistency
fix(db): ensure local client module is returned in default case

* fix(db): update DB_CLIENTS paths to use PACKAGE_NAME for consistency

* fix(db): simplify db-client export paths for consistency

* fix(db): unify client creation function names across db-client modules

* fix(db): standardize client creation function names across db-client modules

* fix(db): rename remote database client import for consistency

* fix(db): change const to let for parsedUrl in createClient function

* fix(db): update variable naming for consistency in createClient function

* fix(test): add logging for prodDbPath and ASTRO_DB_REMOTE_URL in libsql-remote tests

* fix(test): add logging for absoluteFileUrl in libsql-remote tests

* fix(db): correct variable reference for rawUrl in createClient function

* fix(test): remove debug logging for prodDbPath in libsql-remote tests

* fix(db): rename __execute to localExecution for clarity in virtual module loading

* refactor(db): replace parseOpts with parseLibSQLConfig for improved configuration handling

* fix(db): prevent redundant assignment for 'url' in LibSQL config parsing

* refactor(db): streamline libSQL configuration handling in createClient functions

* fix(test): remove redundant 'url' field from config assertions in db-client tests

* refactor(db): enhance boolean parsing logic in LibSQL config handling

* Apply suggestions from code review

Thank god for the save by the docs queen

Co-authored-by: Sarah Rainsberger <5098874+sarah11918@users.noreply.github.com>

* Apply suggestions from code review

Co-authored-by: Emanuele Stoppa <my.burning@gmail.com>

* Refactor localExecution handling in vitePluginDb and related functions

* Update .changeset/full-dingos-repeat.md

Co-authored-by: Sarah Rainsberger <5098874+sarah11918@users.noreply.github.com>

* Update .changeset/full-dingos-repeat.md

Co-authored-by: Sarah Rainsberger <5098874+sarah11918@users.noreply.github.com>

---------

Co-authored-by: Sarah Rainsberger <5098874+sarah11918@users.noreply.github.com>
Co-authored-by: Emanuele Stoppa <my.burning@gmail.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Error when building Astro with @astrojs/db and @astrojs/cloudflare in version v5.0.8 astro:content-asset-propagation] module is not defined

4 participants