diff --git a/.github/workflows/router.yaml b/.github/workflows/router.yaml
index 0505bc9282..3da3eb1219 100644
--- a/.github/workflows/router.yaml
+++ b/.github/workflows/router.yaml
@@ -8,12 +8,49 @@ on:
workflow_dispatch:
jobs:
+ switch:
+ runs-on: ubuntu-24.04
+ outputs:
+ target: ${{ steps.route.outputs.target }}
+ steps:
+ - name: Route release
+ id: route
+ shell: bash
+ run: |
+ HEAD_REPO="${{ github.event.pull_request.head.repo.full_name }}"
+ if [[ "$GITHUB_EVENT_NAME" == "workflow_dispatch" && "${GITHUB_REF##*/}" == "main" ]]; then
+ echo "target=latest" >> $GITHUB_OUTPUT
+ # only run on all pushes or pull requests from forks
+ elif [[ "$GITHUB_EVENT_NAME" == "push" ]] || [[ "$HEAD_REPO" != "$GITHUB_REPOSITORY" ]]; then
+ echo "target=feature" >> $GITHUB_OUTPUT
+ else
+ echo "target=skip" >> $GITHUB_OUTPUT
+ fi
+
run-feature:
- if: github.event_name != 'workflow_dispatch' && (github.event_name == 'push' || github.event.pull_request.head.repo.full_name != github.repository)
+ needs: switch
+ if: needs.switch.outputs.target == 'feature'
uses: ./.github/workflows/release-feature-branch.yaml
- secrets: inherit
+ secrets:
+ PLANETSCALE_CONNECTION_STRING: ${{ secrets.PLANETSCALE_CONNECTION_STRING }}
+ NEON_CONNECTION_STRING: ${{ secrets.NEON_CONNECTION_STRING }}
+ # NEON_HTTP_CONNECTION_STRING: ${{ secrets.NEON_CONNECTION_STRING }}
+ TIDB_CONNECTION_STRING: ${{ secrets.TIDB_CONNECTION_STRING }}
+ XATA_API_KEY: ${{ secrets.XATA_API_KEY }}
+ XATA_BRANCH: ${{ secrets.XATA_BRANCH }}
+ LIBSQL_REMOTE_URL: ${{ secrets.LIBSQL_REMOTE_URL }}
+ LIBSQL_REMOTE_TOKEN: ${{ secrets.LIBSQL_REMOTE_TOKEN }}
run-latest:
- if: github.event_name == 'workflow_dispatch' && github.ref_name == 'main'
+ needs: switch
+ if: needs.switch.outputs.target == 'latest'
uses: ./.github/workflows/release-latest.yaml
- secrets: inherit
\ No newline at end of file
+ secrets:
+ PLANETSCALE_CONNECTION_STRING: ${{ secrets.PLANETSCALE_CONNECTION_STRING }}
+ NEON_CONNECTION_STRING: ${{ secrets.NEON_CONNECTION_STRING }}
+ # NEON_HTTP_CONNECTION_STRING: ${{ secrets.NEON_CONNECTION_STRING }}
+ TIDB_CONNECTION_STRING: ${{ secrets.TIDB_CONNECTION_STRING }}
+ XATA_API_KEY: ${{ secrets.XATA_API_KEY }}
+ XATA_BRANCH: ${{ secrets.XATA_BRANCH }}
+ LIBSQL_REMOTE_URL: ${{ secrets.LIBSQL_REMOTE_URL }}
+ LIBSQL_REMOTE_TOKEN: ${{ secrets.LIBSQL_REMOTE_TOKEN }}
\ No newline at end of file
diff --git a/changelogs/drizzle-kit/0.31.9.md b/changelogs/drizzle-kit/0.31.9.md
new file mode 100644
index 0000000000..3bdb169da8
--- /dev/null
+++ b/changelogs/drizzle-kit/0.31.9.md
@@ -0,0 +1 @@
+- drizzle-kit api improvements for D1 connections
\ No newline at end of file
diff --git a/drizzle-kit/package.json b/drizzle-kit/package.json
index b5af4ac75a..093ac1d517 100644
--- a/drizzle-kit/package.json
+++ b/drizzle-kit/package.json
@@ -1,6 +1,6 @@
{
"name": "drizzle-kit",
- "version": "0.31.8",
+ "version": "0.31.9",
"homepage": "https://orm.drizzle.team",
"keywords": [
"drizzle",
diff --git a/drizzle-kit/src/api.ts b/drizzle-kit/src/api.ts
index 83e593dbd6..336fce5f60 100644
--- a/drizzle-kit/src/api.ts
+++ b/drizzle-kit/src/api.ts
@@ -1,3 +1,4 @@
+///
import type { PGlite } from '@electric-sql/pglite';
import { randomUUID } from 'crypto';
import { is } from 'drizzle-orm';
@@ -327,7 +328,10 @@ export const pushSQLiteSchema = async (
export const startStudioSQLiteServer = async (
imports: Record,
- credentials: SqliteCredentials,
+ credentials: SqliteCredentials | {
+ driver: 'd1';
+ binding: D1Database;
+ },
options?: {
host?: string;
port?: number;
diff --git a/drizzle-kit/src/cli/connections.ts b/drizzle-kit/src/cli/connections.ts
index 2c653c138d..d79a07e010 100644
--- a/drizzle-kit/src/cli/connections.ts
+++ b/drizzle-kit/src/cli/connections.ts
@@ -1,3 +1,4 @@
+///
import type { PGlite } from '@electric-sql/pglite';
import type { AwsDataApiPgQueryResult, AwsDataApiSessionOptions } from 'drizzle-orm/aws-data-api/pg';
import type { MigrationConfig } from 'drizzle-orm/migrator';
@@ -890,7 +891,7 @@ const prepareSqliteParams = (params: any[], driver?: string) => {
? JSON.stringify(param.value)
: (param.value as string);
- if (driver === 'd1-http') {
+ if (driver === 'd1-http' || driver === 'd1') {
return value;
}
@@ -919,6 +920,81 @@ const preparePGliteParams = (params: any[]) => {
});
};
+export type D1Credentials = {
+ driver: 'd1';
+ binding: D1Database;
+};
+
+export const connectToD1 = async (
+ d1: D1Database,
+): Promise<
+ & SQLiteDB
+ & {
+ packageName: 'd1';
+ migrate: (config: MigrationConfig) => Promise;
+ proxy: Proxy;
+ transactionProxy: TransactionProxy;
+ }
+> => {
+ const db: SQLiteDB = {
+ query: async (sql: string, params?: any[]) => {
+ const stmt = d1.prepare(sql);
+ const boundStmt = params && params.length > 0 ? stmt.bind(...params) : stmt;
+ const result = await boundStmt.all();
+ return (result.results ?? []) as T[];
+ },
+ run: async (query: string) => {
+ const stmt = d1.prepare(query);
+ await stmt.run();
+ },
+ };
+
+ const proxy: Proxy = async (params) => {
+ const preparedParams = prepareSqliteParams(params.params || [], 'd1');
+ const stmt = d1.prepare(params.sql);
+ const boundStmt = preparedParams.length > 0 ? stmt.bind(...preparedParams) : stmt;
+
+ try {
+ if (params.mode === 'array') {
+ return await boundStmt.raw();
+ }
+ const result = await boundStmt.all();
+ return result.results ?? [];
+ } catch (error: any) {
+ // D1 doesn't allow certain introspection queries (sqlite_master with pragma functions)
+ // Return empty array for SQLITE_AUTH errors on these system queries
+ if (error?.message?.includes('SQLITE_AUTH') || error?.message?.includes('not authorized')) {
+ return [];
+ }
+ throw error;
+ }
+ };
+
+ const transactionProxy: TransactionProxy = async (queries) => {
+ const results: any[] = [];
+ try {
+ // D1 doesn't support true transactions via binding, use batch instead
+ const statements = queries.map((q) => d1.prepare(q.sql));
+ const batchResults = await d1.batch(statements);
+ for (const result of batchResults) {
+ results.push(result.results ?? []);
+ }
+ } catch (error) {
+ results.push(error as Error);
+ }
+ return results;
+ };
+
+ const { drizzle } = await import('drizzle-orm/d1');
+ const { migrate } = await import('drizzle-orm/d1/migrator');
+ const drzl = drizzle(d1);
+ const migrateFn = async (config: MigrationConfig) => {
+ return migrate(drzl, config);
+ };
+
+ return { ...db, packageName: 'd1', proxy, transactionProxy, migrate: migrateFn };
+};
+
export const connectToSQLite = async (
credentials: SqliteCredentials,
): Promise<
diff --git a/drizzle-kit/src/serializer/studio.ts b/drizzle-kit/src/serializer/studio.ts
index 246d80f55c..35d3623328 100644
--- a/drizzle-kit/src/serializer/studio.ts
+++ b/drizzle-kit/src/serializer/studio.ts
@@ -1,3 +1,4 @@
+///
import type { PGlite } from '@electric-sql/pglite';
import { serve } from '@hono/node-server';
import { zValidator } from '@hono/zod-validator';
@@ -64,9 +65,10 @@ export type Setup = {
| 'mysql2'
| '@planetscale/database'
| 'd1-http'
+ | 'd1'
| '@libsql/client'
| 'better-sqlite3';
- driver?: 'aws-data-api' | 'd1-http' | 'turso' | 'pglite';
+ driver?: 'aws-data-api' | 'd1-http' | 'd1' | 'turso' | 'pglite';
databaseName?: string; // for planetscale (driver remove database name from connection string)
proxy: Proxy;
transactionProxy: TransactionProxy;
@@ -360,17 +362,45 @@ export const drizzleForMySQL = async (
};
};
+// D1 binding credentials type (mirrors the one in connections.ts)
+type D1BindingCredentials = {
+ driver: 'd1';
+ binding: D1Database;
+};
+
export const drizzleForSQLite = async (
- credentials: SqliteCredentials,
+ credentials: SqliteCredentials | D1BindingCredentials,
sqliteSchema: Record>,
relations: Record,
schemaFiles?: SchemaFile[],
casing?: CasingType,
): Promise => {
- const { connectToSQLite } = await import('../cli/connections');
+ const customDefaults = getCustomDefaults(sqliteSchema, casing);
+ if ('driver' in credentials && credentials.driver === 'd1') {
+ const { connectToD1 } = await import('../cli/connections');
+ const sqliteDB = await connectToD1(credentials.binding);
+
+ const dbUrl = 'd1://binding';
+ const dbHash = createHash('sha256').update(dbUrl).digest('hex');
+
+ return {
+ dbHash,
+ dialect: 'sqlite',
+ driver: 'd1',
+ packageName: 'd1',
+ proxy: sqliteDB.proxy,
+ transactionProxy: sqliteDB.transactionProxy,
+ customDefaults,
+ schema: sqliteSchema,
+ relations,
+ schemaFiles,
+ casing,
+ };
+ }
+
+ const { connectToSQLite } = await import('../cli/connections');
const sqliteDB = await connectToSQLite(credentials);
- const customDefaults = getCustomDefaults(sqliteSchema, casing);
let dbUrl: string;