Skip to content

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

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

Open
wants to merge 36 commits into
base: main
Choose a base branch
from

Conversation

Adammatthiesen
Copy link
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!

Copy link

changeset-bot bot commented Aug 8, 2025

🦋 Changeset detected

Latest commit: 5604a6f

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
Member Author

CC @Fryuni does this look correct?

Copy link
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
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

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
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
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
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?

Copy link
Member

@Fryuni Fryuni left a comment

Choose a reason for hiding this comment

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

Looking really good

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
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
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
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 😄

}: {
tables: DBTables;
appToken: string;
isBuild: boolean;
output: AstroConfig['output'];
__execute?: boolean;
Copy link
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
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
Copy link
Member

@sarah11918 sarah11918 left a comment

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)

Thank god for the save by the docs queen

Co-authored-by: Sarah Rainsberger <[email protected]>
@Adammatthiesen
Copy link
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 on lines 120 to 124
export function getLocalVirtualModContents({
tables,
root,
localExecution = false,
}: {
Copy link
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
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
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

}: {
tables: DBTables;
appToken: string;
isBuild: boolean;
output: AstroConfig['output'];
// Request module to be loaded immediately in process
localExecution?: boolean;
Copy link
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
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
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
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")

Copy link
Member

@ematipico ematipico left a comment

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
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
Copy link
Member

sarah11918 commented Aug 13, 2025

@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
Copy link
Member Author

Adammatthiesen commented Aug 14, 2025

@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
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
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
Member

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

@sarah11918
Copy link
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
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!

});
```

For more information, see the [`@astrojs/db` documentation](https://docs.astro.build/en/guides/integrations-guide/db/).
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
For more information, see the [`@astrojs/db` documentation](https://docs.astro.build/en/guides/integrations-guide/db/).
For more information, see the [`@astrojs/db` documentation](https://docs.astro.build/en/guides/integrations-guide/db/#mode).

We can come back and update this with the direct link to the mode configuration option after the docs are written! I suspect it will end up being just #mode.

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