1- import { mkdir , writeFile } from 'node:fs/promises'
21import type { Nuxt } from '@nuxt/schema'
3- import { join } from 'pathe'
4- import { logger , addImportsDir , addServerImportsDir , addServerScanDir , createResolver , addTypeTemplate } from '@nuxt/kit'
5- import { defu } from 'defu'
6- import { addDevToolsCustomTabs } from './utils/devtools'
7- import { copyDatabaseMigrationsToHubDir , copyDatabaseQueriesToHubDir } from './runtime/database/server/utils/migrations/helpers'
8- import type { ConnectorName } from 'db0'
9- import type { NitroOptions } from 'nitropack'
10- import { ensureDependencyInstalled } from 'nypm'
2+ import { logger } from '@nuxt/kit'
113
124const log = logger . withTag ( 'nuxt:hub' )
13- const { resolve } = createResolver ( import . meta. url )
145
15- function logWhenReady ( nuxt : Nuxt , message : string , type : 'info' | 'warn' | 'error' = 'info' ) {
6+ export { setupAI } from './features/ai'
7+ export { setupBase } from './features/base'
8+ export { setupBlob } from './features/blob'
9+ export { setupCache } from './features/cache'
10+ export { setupDatabase } from './features/database'
11+ export { setupKV } from './features/kv'
12+ export { setupOpenAPI } from './features/openapi'
13+
14+ export function logWhenReady ( nuxt : Nuxt , message : string , type : 'info' | 'warn' | 'error' = 'info' ) {
1615 nuxt . hooks . hookOnce ( 'modules:done' , ( ) => {
1716 log [ type ] ( message )
1817 } )
1918}
2019
2120export interface HubConfig {
22- version ?: string
23-
2421 ai ?: 'vercel' | 'cloudflare'
2522 blob ?: boolean
2623 cache ?: boolean
@@ -32,341 +29,3 @@ export interface HubConfig {
3229 databaseQueriesPaths ?: string [ ]
3330 applyDatabaseMigrationsDuringBuild ?: boolean
3431}
35-
36- export async function setupBase ( nuxt : Nuxt , hub : HubConfig ) {
37- // Create the hub.dir directory
38- hub . dir = join ( nuxt . options . rootDir , hub . dir ! )
39- try {
40- await mkdir ( hub . dir , { recursive : true } )
41- } catch ( e : any ) {
42- if ( e . errno === - 17 ) {
43- // File already exists
44- } else {
45- throw e
46- }
47- }
48-
49- // Add custom tabs to Nuxt DevTools
50- if ( nuxt . options . dev ) {
51- addDevToolsCustomTabs ( nuxt , hub )
52- }
53-
54- // Remove trailing slash for prerender routes
55- nuxt . options . nitro . prerender ||= { }
56- nuxt . options . nitro . prerender . autoSubfolderIndex ||= false
57- }
58-
59- export async function setupAI ( nuxt : Nuxt , hub : HubConfig ) {
60- const providerName = hub . ai === 'vercel' ? 'Vercel AI Gateway' : 'Workers AI Provider'
61-
62- if ( hub . ai === 'vercel' ) {
63- await Promise . all ( [
64- ensureDependencyInstalled ( '@ai-sdk/gateway' )
65- ] )
66- } else if ( hub . ai === 'cloudflare' ) {
67- await Promise . all ( [
68- ensureDependencyInstalled ( 'workers-ai-provider' )
69- ] )
70- } else {
71- return logWhenReady ( nuxt , `\`${ hub . ai } \` is not a supported AI provider. Set \`hub.ai\` to \`'vercel'\` or \`'cloudflare'\` in your \`nuxt.config.ts\`. Learn more at https://hub.nuxt.com/docs/features/ai.` , 'error' )
72- }
73-
74- // Used for typing hubAI() with the correct provider
75- addTypeTemplate ( {
76- filename : 'types/nuxthub-ai.d.ts' ,
77- getContents : ( ) => `export type NuxtHubAIProvider = ${ JSON . stringify ( hub . ai ) }
78- `
79- } )
80-
81- if ( hub . ai === 'cloudflare' ) {
82- const isCloudflareRuntime = nuxt . options . nitro . preset ?. includes ( 'cloudflare' )
83- const isAiBindingSet = ! ! ( process . env . AI as { runtime : string } | undefined ) ?. runtime
84-
85- if ( isCloudflareRuntime && ! isAiBindingSet ) {
86- return logWhenReady ( nuxt , 'Ensure a `AI` binding is set in your Cloudflare Workers configuration' , 'error' )
87- }
88-
89- if ( ! process . env . CLOUDFLARE_ACCOUNT_ID || ! process . env . CLOUDFLARE_API_KEY ) {
90- return logWhenReady ( nuxt , `Set \`CLOUDFLARE_ACCOUNT_ID\` and \`CLOUDFLARE_API_KEY\` environment variables to enable \`hubAI()\` with ${ providerName } ` , 'error' )
91- }
92- } else if ( hub . ai === 'vercel' ) {
93- const isMissingEnvVars = ! process . env . AI_GATEWAY_API_KEY && ! process . env . VERCEL_OIDC_TOKEN
94- if ( isMissingEnvVars && nuxt . options . dev ) {
95- return logWhenReady ( nuxt , `Missing \`AI_GATEWAY_API_KEY\` environment variable to enable \`hubAI()\` with ${ providerName } \nCreate an AI Gateway API key at \`${ encodeURI ( 'https://vercel.com/d?to=/[team]/~/ai/api-keys&title=Go+to+AI+Gateway' ) } \` or run \`npx vercel env pull .env\` to pull the environment variables.` , 'error' )
96- } else if ( isMissingEnvVars ) {
97- return logWhenReady ( nuxt , `Set \`AI_GATEWAY_API_KEY\` environment variable to enable \`hubAI()\` with ${ providerName } \nCreate an AI Gateway API key at \`${ encodeURI ( 'https://vercel.com/d?to=/[team]/~/ai/api-keys&title=Go+to+AI+Gateway' ) } \`` , 'error' )
98- }
99- }
100-
101- // Add Server scanning
102- addServerScanDir ( resolve ( './runtime/ai/server' ) )
103- addServerImportsDir ( resolve ( './runtime/ai/server/utils' ) )
104-
105- logWhenReady ( nuxt , `\`hubAI()\` configured with \`${ providerName } \`` )
106- }
107-
108- export function setupBlob ( nuxt : Nuxt , hub : HubConfig ) {
109- // Configure dev storage
110- nuxt . options . nitro . devStorage ||= { }
111- nuxt . options . nitro . devStorage . blob = defu ( nuxt . options . nitro . devStorage . blob , {
112- driver : 'fs-lite' ,
113- base : join ( hub . dir ! , 'blob' )
114- } )
115-
116- // Add Server scanning
117- addServerScanDir ( resolve ( './runtime/blob/server' ) )
118- addServerImportsDir ( resolve ( './runtime/blob/server/utils' ) )
119-
120- // Add Composables
121- addImportsDir ( resolve ( './runtime/blob/app/composables' ) )
122-
123- if ( nuxt . options . nitro . storage ?. blob ?. driver === 'vercel-blob' ) {
124- nuxt . options . runtimeConfig . public . hub . blobProvider = 'vercel-blob'
125- }
126-
127- const driver = nuxt . options . dev ? nuxt . options . nitro . devStorage . blob . driver : nuxt . options . nitro . storage ?. blob ?. driver
128-
129- logWhenReady ( nuxt , `\`hubBlob()\` configured with \`${ driver } \` driver` )
130- }
131-
132- export async function setupCache ( nuxt : Nuxt , hub : HubConfig ) {
133- // Configure dev storage
134- nuxt . options . nitro . devStorage ||= { }
135- nuxt . options . nitro . devStorage . cache = defu ( nuxt . options . nitro . devStorage . cache , {
136- driver : 'fs-lite' ,
137- base : join ( hub . dir ! , 'cache' )
138- } )
139-
140- // Add Server scanning
141- addServerScanDir ( resolve ( './runtime/cache/server' ) )
142- }
143-
144- export async function setupDatabase ( nuxt : Nuxt , hub : HubConfig ) {
145- // Configure dev storage
146- if ( typeof hub . database === 'string' && ! [ 'postgresql' , 'sqlite' , 'mysql' ] . includes ( hub . database ) ) {
147- return logWhenReady ( nuxt , `Unknown database dialect set in hub.database: ${ hub . database } ` , 'error' )
148- }
149-
150- let dialect : string
151- const productionDriver = nuxt . options . nitro . database ?. db ?. connector as ConnectorName
152- const isDialectConfigured = typeof hub . database === 'string' && ( [ 'postgresql' , 'sqlite' , 'mysql' ] . includes ( hub . database ) )
153- if ( isDialectConfigured ) {
154- dialect = hub . database as string
155- } else {
156- // Infer dialect from production database driver
157- // Map connectors to dialects based on the mappings:
158- // "postgresql" -> "postgresql", pglite
159- // "sqlite" -> "better-sqlite3", bun-sqlite, bun, node-sqlite, sqlite3
160- // "mysql" -> mysql2
161- // "libsql" -> libsql-core, libsql-http, libsql-node, libsql-web
162- if ( productionDriver === 'postgresql' || productionDriver === 'pglite' ) {
163- dialect = 'postgresql'
164- } else if ( [ 'better-sqlite3' , 'bun-sqlite' , 'bun' , 'node-sqlite' , 'sqlite3' ] . includes ( productionDriver ) ) {
165- dialect = 'sqlite'
166- } else if ( productionDriver === 'mysql2' ) {
167- dialect = 'mysql'
168- } else if ( [ 'libsql-core' , 'libsql-http' , 'libsql-node' , 'libsql-web' ] . includes ( productionDriver ) ) {
169- // NOTE: libSQL implements additional functionality beyond sqlite, but users can manually configure a libsql database within Nitro if they require them
170- dialect = 'sqlite' // libsql is SQLite-compatible
171- } else {
172- return logWhenReady ( nuxt , 'Please specify a database dialect in `hub.database: \'<dialect>\'` or configure `nitro.database.db` within `nuxt.config.ts`. Learn more at https://hub.nuxt.com/docs/features/database.' , 'error' )
173- }
174- }
175-
176- // Check if the configured dialect matches the production driver
177- if ( isDialectConfigured && productionDriver ) {
178- const dialectMatchesDriver = (
179- ( dialect === 'postgresql' && ( productionDriver === 'postgresql' || productionDriver === 'pglite' ) )
180- || ( dialect === 'sqlite' && [ 'better-sqlite3' , 'bun-sqlite' , 'bun' , 'node-sqlite' , 'sqlite3' , 'libsql-core' , 'libsql-http' , 'libsql-node' , 'libsql-web' ] . includes ( productionDriver ) )
181- || ( dialect === 'mysql' && productionDriver === 'mysql2' )
182- )
183-
184- if ( ! dialectMatchesDriver ) {
185- // Infer the dialect from the production driver for the error message
186- let inferredDialect : string
187- if ( productionDriver === 'postgresql' || productionDriver === 'pglite' ) {
188- inferredDialect = 'postgresql'
189- } else if ( [ 'better-sqlite3' , 'bun-sqlite' , 'bun' , 'node-sqlite' , 'sqlite3' , 'libsql-core' , 'libsql-http' , 'libsql-node' , 'libsql-web' ] . includes ( productionDriver ) ) {
190- inferredDialect = 'sqlite'
191- } else if ( productionDriver === 'mysql2' ) {
192- inferredDialect = 'mysql'
193- } else {
194- inferredDialect = 'unknown'
195- }
196-
197- logWhenReady ( nuxt , `Database dialect mismatch: \`hub.database\` is set to \`${ dialect } \` but \`nitro.database.db\` is \`${ inferredDialect } \` (\`${ productionDriver } \`). Set \`hub.database\` to \`true\` or \`'${ inferredDialect } '\` in your \`nuxt.config.ts\`.` , 'warn' )
198- }
199- }
200-
201- // Configure dev database based on dialect
202- let devDatabaseConfig : NitroOptions [ 'database' ] [ 'default' ]
203-
204- if ( dialect === 'postgresql' ) {
205- if ( process . env . POSTGRES_URL || process . env . POSTGRESQL_URL || process . env . DATABASE_URL ) {
206- // Use postgresql if env variable is set
207- const setEnvVarName = process . env . POSTGRES_URL ? 'POSTGRES_URL' : process . env . POSTGRESQL_URL ? 'POSTGRESQL_URL' : 'DATABASE_URL'
208- logWhenReady ( nuxt , `Using \`PostgreSQL\` during local development using provided \`${ setEnvVarName } \`` )
209- devDatabaseConfig = {
210- connector : 'postgresql' ,
211- options : {
212- url : process . env . POSTGRES_URL || process . env . POSTGRESQL_URL || process . env . DATABASE_URL
213- }
214- }
215- } else {
216- // Use pglite if env variable not provided
217- logWhenReady ( nuxt , 'Using `PGlite` during local development' )
218- devDatabaseConfig = {
219- connector : 'pglite' ,
220- options : {
221- dataDir : join ( hub . dir ! , 'database/pglite' )
222- }
223- }
224- }
225- } else if ( dialect === 'sqlite' ) {
226- logWhenReady ( nuxt , 'Using `SQLite` during local development' )
227- devDatabaseConfig = {
228- connector : 'better-sqlite3' ,
229- options : {
230- path : join ( hub . dir ! , 'database/sqlite/db.sqlite3' )
231- }
232- }
233- } else if ( dialect === 'mysql' ) {
234- if ( ! nuxt . options . nitro . devDatabase ?. db ?. connector ) {
235- logWhenReady ( nuxt , 'Zero-config `MySQL` database setup during local development is not supported yet. Please manually configure your development database in `nitro.devDatabase.db` in `nuxt.config.ts`. Learn more at https://hub.nuxt.com/docs/features/database.' , 'warn' )
236- }
237- }
238-
239- nuxt . options . nitro . devDatabase ||= { }
240- nuxt . options . nitro . devDatabase . db = defu ( nuxt . options . nitro . devDatabase . db , devDatabaseConfig ! ) as NitroOptions [ 'database' ] [ 'default' ]
241-
242- // Verify development database dependencies are installed
243- const developmentDriver = nuxt . options . nitro . devDatabase ?. db ?. connector as ConnectorName
244- if ( developmentDriver === 'postgresql' ) {
245- await ensureDependencyInstalled ( 'pg' )
246- } else if ( developmentDriver === 'pglite' ) {
247- await ensureDependencyInstalled ( '@electric-sql/pglite' )
248- } else if ( developmentDriver === 'mysql2' ) {
249- await ensureDependencyInstalled ( 'mysql2' )
250- } else if ( developmentDriver === 'better-sqlite3' ) {
251- await ensureDependencyInstalled ( 'better-sqlite3' )
252- }
253-
254- // Enable Nitro database
255- nuxt . options . nitro . experimental ||= { }
256- nuxt . options . nitro . experimental . database = true
257-
258- // Add Server scanning
259- addServerScanDir ( resolve ( './runtime/database/server' ) )
260- addServerImportsDir ( resolve ( './runtime/database/server/utils' ) )
261-
262- // Handle migrations
263- nuxt . hook ( 'modules:done' , async ( ) => {
264- // Call hub:database:migrations:dirs hook
265- await nuxt . callHook ( 'hub:database:migrations:dirs' , hub . databaseMigrationsDirs ! )
266- // Copy all migrations files to the hub.dir directory
267- await copyDatabaseMigrationsToHubDir ( hub )
268- // Call hub:database:migrations:queries hook
269- await nuxt . callHook ( 'hub:database:queries:paths' , hub . databaseQueriesPaths ! )
270- await copyDatabaseQueriesToHubDir ( hub )
271- } )
272-
273- // Setup Drizzle ORM
274- let isDrizzleOrmInstalled = false
275- try {
276- require . resolve ( 'drizzle-orm' , { paths : [ nuxt . options . rootDir ] } )
277- isDrizzleOrmInstalled = true
278- } catch {
279- // Ignore
280- }
281-
282- if ( isDrizzleOrmInstalled ) {
283- const connector = nuxt . options . nitro . devDatabase . db . connector as ConnectorName
284- const dbConfig = nuxt . options . nitro . devDatabase . db . options
285-
286- // @ts -expect-error not all connectors are supported
287- const db0ToDrizzle : Record < ConnectorName , string > = {
288- postgresql : 'node-postgres' ,
289- pglite : 'pglite' ,
290- mysql2 : 'mysql2' ,
291- planetscale : 'planetscale-serverless' ,
292- 'better-sqlite3' : 'better-sqlite3' ,
293- 'bun-sqlite' : 'bun-sqlite' ,
294- bun : 'bun-sqlite' ,
295- sqlite3 : 'better-sqlite3' ,
296- libsql : 'libsql/node' ,
297- 'libsql-core' : 'libsql' ,
298- 'libsql-http' : 'libsql/http' ,
299- 'libsql-node' : 'libsql/node' ,
300- 'libsql-web' : 'libsql/web' ,
301- 'cloudflare-d1' : 'd1'
302- // unsupported: sqlite & node-sqlite
303- }
304-
305- // node-postgres requires connectionString instead of url
306- let connectionConfig = dbConfig
307- if ( connector === 'postgresql' && dbConfig ?. url ) {
308- connectionConfig = { connectionString : dbConfig . url , ...dbConfig . options }
309- }
310-
311- let drizzleOrmContent = `import { drizzle } from 'drizzle-orm/${ db0ToDrizzle [ connector ] } '
312- import type { DrizzleConfig } from 'drizzle-orm'
313-
314- export function hubDrizzle<TSchema extends Record<string, unknown> = Record<string, never>>(options?: DrizzleConfig<TSchema>) {
315- return drizzle({
316- ...options,
317- connection: ${ JSON . stringify ( connectionConfig ) }
318- })
319- }`
320-
321- if ( connector === 'pglite' ) {
322- drizzleOrmContent = `import { drizzle } from 'drizzle-orm/pg-proxy'
323- import type { DrizzleConfig } from 'drizzle-orm'
324-
325- export function hubDrizzle<TSchema extends Record<string, unknown> = Record<string, never>>(options?: DrizzleConfig<TSchema>) {
326- return drizzle(async (sql, params, method) => {
327- try {
328- const rows = await $fetch<any[]>('/api/_hub/database/query', { method: 'POST', body: { sql, params, method } })
329- return { rows }
330- } catch (e: any) {
331- console.error(e.response)
332- return { rows: [] }
333- }
334- }, {
335- ...options,
336- })
337- }`
338- }
339-
340- // create hub directory in .nuxt if it doesn't exist
341- const hubBuildDir = join ( nuxt . options . buildDir , 'hub' )
342- await mkdir ( hubBuildDir , { recursive : true } )
343-
344- const drizzleOrmPath = join ( hubBuildDir , 'drizzle-orm.ts' )
345- await writeFile ( drizzleOrmPath , drizzleOrmContent , 'utf-8' )
346-
347- nuxt . options . alias [ '#hub/drizzle-orm' ] = drizzleOrmPath
348- }
349- }
350-
351- export function setupKV ( nuxt : Nuxt , hub : HubConfig ) {
352- // Configure dev storage
353- nuxt . options . nitro . devStorage ||= { }
354- nuxt . options . nitro . devStorage . kv = defu ( nuxt . options . nitro . devStorage . kv , {
355- driver : 'fs-lite' ,
356- base : join ( hub . dir ! , 'kv' )
357- } )
358-
359- // Add Server scanning
360- addServerScanDir ( resolve ( './runtime/kv/server' ) )
361- addServerImportsDir ( resolve ( './runtime/kv/server/utils' ) )
362-
363- const driver = nuxt . options . dev ? nuxt . options . nitro . devStorage . kv . driver : nuxt . options . nitro . storage ?. kv ?. driver
364-
365- logWhenReady ( nuxt , `\`hubKV()\` configured with \`${ driver } \` driver` )
366- }
367-
368- export function setupOpenAPI ( nuxt : Nuxt , _hub : HubConfig ) {
369- // Enable Nitro database
370- nuxt . options . nitro . experimental ||= { }
371- nuxt . options . nitro . experimental . openAPI ??= true
372- }
0 commit comments