-
I am using Drizzle and Cloudflare D1 in my project and I like Drizzle a lot. And while I was trying Drizzle Studio, I found it connected to live database instead of local dev environment. I'd like to know if there is a way to connect to D1 local instance? |
Beta Was this translation helpful? Give feedback.
Replies: 14 comments 55 replies
-
Gm @JH365, please check this excerpt of drizzle-kit code for hints :P // src/orm-extenstions/d1-driver/wrangler-client.ts
var wrangler_client_exports = {};
__export(wrangler_client_exports, {
execute: () => execute
});
async function execute(query, configPath, dbName) {
try {
const response = await $`NO_D1_WARNING=true wrangler d1 execute ${dbName} --config=${configPath} --command=${query} --json`;
return { results: JSON.parse(response.stdout)[0].results };
} catch (e) {
try {
await $`NO_D1_WARNING=true wrangler d1 execute ${dbName} --config=${configPath} --command=${query}`;
} catch (e1) {
throw new Error(e1.stdout);
}
throw e;
}
} |
Beta Was this translation helpful? Give feedback.
-
Is there a clear consensus on how this should be addressed now? :) |
Beta Was this translation helpful? Give feedback.
-
Came up with a similar temp solution {
"scripts": {
"db:studio": "cross-env LOCAL_DB_PATH=$(find .wrangler/state/v3/d1/miniflare-D1DatabaseObject -type f -name '*.sqlite' -print -quit) drizzle-kit studio"
}
} Instead of specifying the sqlite file path, just grabbing the path of the import type { Config } from 'drizzle-kit';
export default process.env.LOCAL_DB_PATH
? ({
schema: './src/db/schema.ts',
driver: 'better-sqlite',
dbCredentials: {
url: process.env.LOCAL_DB_PATH!
}
} satisfies Config)
: ({
schema: './src/db/schema.ts',
out: './drizzle',
driver: 'd1',
dbCredentials: {
wranglerConfigPath: 'wrangler.toml',
dbName: 'NAME'
}
} satisfies Config); Haven't been able to get it working with the remote D1 db though https://github.com/drizzle-team/drizzle-kit-mirror/issues/289 |
Beta Was this translation helpful? Give feedback.
-
Joining the thread to get it working on Windows. Create $env:DB_URL=(Get-ChildItem .wrangler\state\v3\d1\miniflare-D1DatabaseObject\*.sqlite).FullName
drizzle-kit studio
export default {
schema: './src/lib/server/schema.ts',
out: './migrations',
driver: 'better-sqlite',
dbCredentials: {
url: process.env.DB_URL!
}
} satisfies Config;
"scripts": {
"db:studio": "powershell -ExecutionPolicy Bypass -File start-studio.ps1",
}, Repeat the same steps for unix environment or use cross-env |
Beta Was this translation helpful? Give feedback.
-
Thanks for sharing the tips, y'all! Here's the solution I came up with piecing everything together. I'm overall pretty happy with it since I can use Drizzle Studio with my local db, the preview db (for pages previews), and the prod db. https://kevinkipp.com/blog/going-full-stack-on-astro-with-cloudflare-d1-and-drizzle/ |
Beta Was this translation helpful? Give feedback.
-
This is now available in both Drizzle Studio for local development and with our Chrome Extension 🎉 |
Beta Was this translation helpful? Give feedback.
-
For the ORM: The simplest and cleanest way would be an orm connector which uses binding when available (in production) and fallsback to rest API when not available. This way we could still connect to a remote db when developing and still get the best performance while in production. For KIT: |
Beta Was this translation helpful? Give feedback.
-
in case anyone else had troubles wiring things up from scratch (bc they didn't have a local D1 database present in npx wrangler d1 execute DB_NAME --local --command='SELECT 1' this created the necessary files in |
Beta Was this translation helpful? Give feedback.
-
So which would be the official way to setup thedrizzle.config.ts with local and production env? |
Beta Was this translation helpful? Give feedback.
-
Here's what works for me //drizzle.config.ts
import type { Config } from 'drizzle-kit';
import fs from "fs";
import path from "path";
const getLocalD1 = () => {
try {
const basePath = path.resolve('.wrangler');
const dbFile = fs
.readdirSync(basePath, { encoding: 'utf-8', recursive: true })
.find((f) => f.endsWith('.sqlite'));
if (!dbFile) {
throw new Error(`.sqlite file not found in ${basePath}`);
}
const url = path.resolve(basePath, dbFile);
return url;
} catch (err) {
console.log(`Error ${err}`);
}
}
const isProd = () => process.env.NODE_ENV === 'production'
const getCredentials = () => {
const prod = {
driver: 'd1-http',
dbCredentials: {
accountId: process.env.CLOUDFLARE_D1_ACCOUNT_ID,
databaseId: 'xxx',
token: process.env.CLOUDFLARE_D1_API_TOKEN
}
}
const dev = {
dbCredentials: {
url: getLocalD1()
}
}
return isProd() ? prod : dev
}
export default {
schema: './src/schemas/*.ts',
out: './drizzle',
dialect: "sqlite",
...getCredentials()
} satisfies Config; |
Beta Was this translation helpful? Give feedback.
-
I've had to deal with this recently and decided to publish 2 packages that can save myself and others time in the future. One is the d1-http driver similar to code in drizzle-kit, but uses cloudflare SDK https://www.npmjs.com/package/@nerdfolio/drizzle-d1-proxy The other is https://www.npmjs.com/package/@nerdfolio/drizzle-d1-helpers , which combines wrangler config reading with getPlatformProxy and the d1-http driver above to cover all the usage situations I can think of. It also generates the sqlite file hash based on Cloudflare's instruction instead of searching for it on disk (as there are situations where you may have multiple .sqlite files in the same directory). fyi @bdmackie Hope it's helpful to you all! |
Beta Was this translation helpful? Give feedback.
-
Why can't I run my local next start command using the node env as production? Is always just seems to default to the development mode. Only when I run wrangler does it allow me to set the environmental variables to production. I have tried overriding them by setting NODE_ENV in the local .env and all. |
Beta Was this translation helpful? Give feedback.
-
For posterity: if you're arriving here to figure out how to use your local Miniflare D1 in development and the remote D1 database in prod, here's what you need to do: Install drizzle-d1-helpers
Update drizzle.config.ts
|
Beta Was this translation helpful? Give feedback.
-
Inspired from
import crypto from 'node:crypto';
import { existsSync } from 'node:fs';
import path from 'node:path';
import type { Config } from 'drizzle-kit';
// using './node_modules/wrangler' instead of 'wrangler' because it may resolve to 'wrangler.json'
import { unstable_readConfig } from './node_modules/wrangler';
function durableObjectNamespaceIdFromName(uniqueKey: string, name: string) {
/**
* In v3.2, miniflare uses durable object to implement D1 and hashes the local sqlite filename.
*
* See the following for more context:
* https://github.com/cloudflare/workers-sdk/issues/4548 (understand the hash of the local D1 filename)
* https://github.com/cloudflare/miniflare/releases/tag/v3.20230918.0
*
* This function is copied from these links
*/
const key = crypto.createHash('sha256').update(uniqueKey).digest();
const nameHmac = crypto.createHmac('sha256', key).update(name).digest().subarray(0, 16);
const hmac = crypto.createHmac('sha256', key).update(nameHmac).digest().subarray(0, 16);
return Buffer.concat([nameHmac, hmac]).toString('hex');
}
function getD1BindingInfo(binding?: string, { persistTo }: { persistTo?: string } = {}) {
const { d1_databases, configPath } = unstable_readConfig({});
if (d1_databases.length === 0) {
throw new Error('No D1 binding exists in config');
}
if (d1_databases.length > 1 && !binding) {
throw new Error("Argument 'binding' is required when more than 1 D1 bindings exist in config");
}
let bindingConfig: (typeof d1_databases)[0] | undefined;
if (binding) {
bindingConfig = d1_databases.find((d1) => d1.binding === binding);
if (!bindingConfig) {
throw new Error(`Could not find D1 binding '${binding}' in config`);
}
} else {
bindingConfig = d1_databases[0];
}
const localDatabaseId = bindingConfig.preview_database_id ?? bindingConfig.database_id;
if (!localDatabaseId) {
throw new Error(`Neither 'preview_database_id' nor 'database_id' is set for D1 binding '${bindingConfig.binding}'`);
}
const wranglerConfigDir = configPath ? path.dirname(configPath) : undefined;
const wranglerStateDir = persistTo ?? path.relative('.', path.join(wranglerConfigDir ?? '', '.wrangler/state/v3'));
const uniqueKey = 'miniflare-D1DatabaseObject';
const miniflarePath = `${wranglerStateDir}/d1/${uniqueKey}`;
const hash = durableObjectNamespaceIdFromName(uniqueKey, localDatabaseId);
const filename = path.join(miniflarePath, `${hash}.sqlite`);
if (!existsSync(filename)) {
throw new Error(`Could not find sqlite file [${filename}] for databaseId [${localDatabaseId}]`);
}
return {
binding: bindingConfig.binding,
databaseId: localDatabaseId,
localSqliteFile: filename,
};
}
const ENV_CF_D1_BINDING = 'CLOUDFLARE_D1_DATABASE_BINDING' as const;
const ENV_CF_D1_ACCOUNT_ID = 'CLOUDFLARE_D1_ACCOUNT_ID' as const;
const ENV_CF_D1_API_TOKEN = 'CLOUDFLARE_D1_API_TOKEN' as const;
const useRemoteDatabase = process.env.NODE_ENV === 'production';
const databaseBinding = process.env[ENV_CF_D1_BINDING];
const binding = getD1BindingInfo(databaseBinding);
console.log(`D1 Binding: ${binding.binding}`);
console.log(`Database Id: ${binding.databaseId}`);
if (useRemoteDatabase) {
const missingEnvVars = [ENV_CF_D1_ACCOUNT_ID, ENV_CF_D1_API_TOKEN].filter((v) => !process.env[v]);
console.log('Mode: remote (using remote Cloudflare D1 database)');
if (missingEnvVars.length > 0) {
throw new Error(`Missing required environment variable${missingEnvVars.length > 1 ? 's' : ''}: ${missingEnvVars.join(', ')}.`);
}
} else {
console.log(`Mode: local (using local database found at ${binding.localSqliteFile})`);
}
// https://orm.drizzle.team/docs/drizzle-config-file
export default {
out: './src/server/db/migrations',
schema: './src/server/db/schema',
dialect: 'sqlite',
...(useRemoteDatabase
? {
driver: 'd1-http',
dbCredentials: {
databaseId: binding.databaseId,
accountId: process.env[ENV_CF_D1_ACCOUNT_ID],
token: process.env[ENV_CF_D1_API_TOKEN],
},
}
: {
dbCredentials: {
url: `file:${binding.localSqliteFile}`,
},
}),
} satisfies Config;
{
// ...
"scripts": {
// ...
"db:studio": "drizzle-kit studio",
"db:studio:prod": "NODE_ENV=production drizzle-kit studio"
// ...
}
// ...
} |
Beta Was this translation helpful? Give feedback.
This is now available in both Drizzle Studio for local development and with our Chrome Extension 🎉