feat(memory-driver-duckdb): support Node.js environment
This commit is contained in:
@@ -11,6 +11,8 @@ words:
|
||||
- audioworklet
|
||||
- awilix
|
||||
- Ayaka
|
||||
- baichuan
|
||||
- baiducloud
|
||||
- bigserial
|
||||
- Bitstream
|
||||
- bumpp
|
||||
@@ -44,16 +46,20 @@ words:
|
||||
- elevenlabs
|
||||
- formkit
|
||||
- gcornut
|
||||
- giteeai
|
||||
- gltf
|
||||
- grammyjs
|
||||
- groq
|
||||
- guiiai
|
||||
- hfspaceup
|
||||
- hfsup
|
||||
- hfup
|
||||
- higress
|
||||
- histoire
|
||||
- hiyori
|
||||
- huggingface
|
||||
- huggingspace
|
||||
- hunyuan
|
||||
- hyoban
|
||||
- iconify
|
||||
- intlify
|
||||
@@ -79,14 +85,17 @@ words:
|
||||
- neuri
|
||||
- Neuro
|
||||
- Neuro-sama
|
||||
- novita
|
||||
- nuxi
|
||||
- nuxt
|
||||
- nuxtjs
|
||||
- ofetch
|
||||
- ollama
|
||||
- Oneliner
|
||||
- onnx
|
||||
- onnxruntime
|
||||
- openai
|
||||
- openrouter
|
||||
- opusscript
|
||||
- pgvector
|
||||
- picklist
|
||||
@@ -97,17 +106,22 @@ words:
|
||||
- pretrained
|
||||
- prismarine
|
||||
- pthread
|
||||
- qwen
|
||||
- rehype
|
||||
- rushstack
|
||||
- Saccade
|
||||
- saccades
|
||||
- SAVEPOINT
|
||||
- sensenova
|
||||
- Shadcn
|
||||
- shiki
|
||||
- shikijs
|
||||
- silero
|
||||
- siliconcloud
|
||||
- sizecheck
|
||||
- smallserial
|
||||
- Sniglet
|
||||
- stepfun
|
||||
- supergroup
|
||||
- superjson
|
||||
- tamagotchi
|
||||
@@ -120,6 +134,7 @@ words:
|
||||
- valibot
|
||||
- vaul
|
||||
- VITE
|
||||
- vllm
|
||||
- vrma
|
||||
- vueuse
|
||||
- wavefile
|
||||
@@ -127,5 +142,6 @@ words:
|
||||
- worklet
|
||||
- Xenova
|
||||
- xsai
|
||||
- zhipu
|
||||
ignoreWords: []
|
||||
import: []
|
||||
|
||||
@@ -26,10 +26,23 @@
|
||||
"import": "./dist/drizzle-orm/index.mjs",
|
||||
"node": "./dist/drizzle-orm/index.cjs"
|
||||
},
|
||||
"./drizzle-orm/vite": {
|
||||
"types": "./dist/drizzle-orm/vite.d.ts",
|
||||
"import": "./dist/drizzle-orm/vite.mjs",
|
||||
"node": "./dist/drizzle-orm/vite.cjs"
|
||||
"./drizzle-orm/bundles/default-browser": {
|
||||
"types": "./dist/drizzle-orm/bundles/default-browser.d.ts",
|
||||
"import": "./dist/drizzle-orm/bundles/default-browser.mjs"
|
||||
},
|
||||
"./drizzle-orm/bundles/default-node": {
|
||||
"types": "./dist/drizzle-orm/bundles/default-node.d.ts",
|
||||
"import": "./dist/drizzle-orm/bundles/default-node.mjs",
|
||||
"node": "./dist/drizzle-orm/bundles/default-node.cjs"
|
||||
},
|
||||
"./drizzle-orm/bundles/import-url-browser": {
|
||||
"types": "./dist/drizzle-orm/bundles/import-url-browser.d.ts",
|
||||
"import": "./dist/drizzle-orm/bundles/import-url-browser.mjs"
|
||||
},
|
||||
"./drizzle-orm/bundles/import-url-node": {
|
||||
"types": "./dist/drizzle-orm/bundles/import-url-node.d.ts",
|
||||
"import": "./dist/drizzle-orm/bundles/import-url-node.mjs",
|
||||
"node": "./dist/drizzle-orm/bundles/import-url-node.cjs"
|
||||
}
|
||||
},
|
||||
"main": "./dist/index.cjs",
|
||||
@@ -50,6 +63,14 @@
|
||||
"typecheck": "tsc --noEmit",
|
||||
"db:generate": "drizzle-kit generate"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"web-worker": "^1.5.0"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"web-worker": {
|
||||
"optional": true
|
||||
}
|
||||
},
|
||||
"dependencies": {
|
||||
"@date-fns/tz": "^1.2.0",
|
||||
"@duckdb/duckdb-wasm": "^1.29.0",
|
||||
@@ -57,14 +78,14 @@
|
||||
"date-fns": "^4.1.0",
|
||||
"defu": "^6.1.4",
|
||||
"drizzle-orm": "^0.39.2",
|
||||
"es-toolkit": "^1.32.0",
|
||||
"superjson": "^2.2.2"
|
||||
"es-toolkit": "^1.32.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@unocss/reset": "^65.4.3",
|
||||
"@vitejs/plugin-vue": "^5.2.1",
|
||||
"@vueuse/core": "^12.5.0",
|
||||
"drizzle-kit": "^0.30.4",
|
||||
"superjson": "^2.2.2",
|
||||
"vite": "^6.1.0",
|
||||
"vue": "^3.5.13",
|
||||
"vue-tsc": "^2.2.0"
|
||||
|
||||
@@ -0,0 +1,19 @@
|
||||
import type { DuckDBBundles } from '@duckdb/duckdb-wasm'
|
||||
|
||||
export async function getBundles(): Promise<DuckDBBundles> {
|
||||
const { createRequire } = await import('node:module')
|
||||
const { dirname, resolve } = await import('node:path')
|
||||
const require = createRequire(import.meta.url)
|
||||
const DUCKDB_DIST = dirname(require.resolve('@duckdb/duckdb-wasm'))
|
||||
|
||||
return {
|
||||
mvp: {
|
||||
mainModule: resolve(DUCKDB_DIST, './duckdb-mvp.wasm'),
|
||||
mainWorker: resolve(DUCKDB_DIST, './duckdb-node-mvp.worker.cjs'),
|
||||
},
|
||||
eh: {
|
||||
mainModule: resolve(DUCKDB_DIST, './duckdb-eh.wasm'),
|
||||
mainWorker: resolve(DUCKDB_DIST, './duckdb-node-eh.worker.cjs'),
|
||||
},
|
||||
}
|
||||
}
|
||||
+1
-1
@@ -9,7 +9,7 @@ import coiMainModule from '@duckdb/duckdb-wasm/dist/duckdb-coi.wasm?url'
|
||||
import coiMainWorker from '@duckdb/duckdb-wasm/dist/duckdb-browser-coi.worker.js?url'
|
||||
import coiPthreadWorker from '@duckdb/duckdb-wasm/dist/duckdb-browser-coi.pthread.worker.js?url'
|
||||
|
||||
export function getViteBundles(): DuckDBBundles {
|
||||
export function getImportUrlBundles(): DuckDBBundles {
|
||||
return {
|
||||
mvp: {
|
||||
mainModule: mvpMainModule,
|
||||
@@ -0,0 +1,28 @@
|
||||
/* eslint-disable perfectionist/sort-imports */
|
||||
import type { DuckDBBundles } from '@duckdb/duckdb-wasm'
|
||||
|
||||
import ehMainWorker from '@duckdb/duckdb-wasm/dist/duckdb-node-eh.worker.cjs?url'
|
||||
import ehMainModule from '@duckdb/duckdb-wasm/dist/duckdb-eh.wasm?url'
|
||||
import mvpMainWorker from '@duckdb/duckdb-wasm/dist/duckdb-node-mvp.worker.cjs?url'
|
||||
import mvpMainModule from '@duckdb/duckdb-wasm/dist/duckdb-mvp.wasm?url'
|
||||
|
||||
function transformUrl(url: string) {
|
||||
if (url.startsWith('/@fs/')) {
|
||||
return url.replace('/@fs/', 'file://')
|
||||
}
|
||||
|
||||
return url
|
||||
}
|
||||
|
||||
export async function getImportUrlBundles(): Promise<DuckDBBundles> {
|
||||
return {
|
||||
mvp: {
|
||||
mainModule: transformUrl(mvpMainModule),
|
||||
mainWorker: transformUrl(mvpMainWorker),
|
||||
},
|
||||
eh: {
|
||||
mainModule: transformUrl(ehMainModule),
|
||||
mainWorker: transformUrl(ehMainWorker),
|
||||
},
|
||||
}
|
||||
}
|
||||
@@ -17,3 +17,25 @@ export function notNullOrUndefined<T>(
|
||||
): value is T {
|
||||
return <T>value !== null && <T>value !== undefined
|
||||
}
|
||||
|
||||
export async function getEnvironment() {
|
||||
if (typeof window !== 'undefined' && window !== null) {
|
||||
return 'browser'
|
||||
}
|
||||
|
||||
try {
|
||||
const process = await import('node:process')
|
||||
if (typeof process !== 'undefined'
|
||||
&& process.versions != null
|
||||
&& process.versions.node != null) {
|
||||
return 'node'
|
||||
}
|
||||
|
||||
return 'unknown'
|
||||
}
|
||||
catch {
|
||||
return 'unknown'
|
||||
}
|
||||
|
||||
return 'unknown'
|
||||
}
|
||||
|
||||
@@ -1,14 +1,14 @@
|
||||
import type { AsyncDuckDBConnection, DuckDBBundles, Logger } from '@duckdb/duckdb-wasm'
|
||||
import type { AsyncDuckDBConnection, DuckDBBundle, DuckDBBundles, Logger } from '@duckdb/duckdb-wasm'
|
||||
|
||||
import { AsyncDuckDB, ConsoleLogger, selectBundle, VoidLogger } from '@duckdb/duckdb-wasm'
|
||||
import { defu } from 'defu'
|
||||
|
||||
import { getBundles } from './duckdb-default-bundles'
|
||||
import { getEnvironment } from './duckdb-common'
|
||||
|
||||
export type ConnectOptions = ConnectRequiredOptions & ConnectOptionalOptions
|
||||
|
||||
export interface ConnectOptionalOptions {
|
||||
bundles?: DuckDBBundles
|
||||
bundles?: DuckDBBundles | Promise<DuckDBBundles>
|
||||
logger?: boolean | Logger
|
||||
}
|
||||
|
||||
@@ -24,10 +24,38 @@ export interface DuckDBWasmClient {
|
||||
}
|
||||
|
||||
export async function connect(options: ConnectOptions): Promise<DuckDBWasmClient> {
|
||||
const opts = defu(options, { bundles: getBundles(), logger: false })
|
||||
const opts = defu(options, { logger: false })
|
||||
|
||||
const bundle = await selectBundle(opts.bundles)
|
||||
const worker = new Worker(bundle.mainWorker!)
|
||||
let worker: Worker
|
||||
let bundle: DuckDBBundle
|
||||
|
||||
const env = await getEnvironment()
|
||||
if (env === 'browser') {
|
||||
if (typeof opts.bundles === 'undefined') {
|
||||
const { getBundles } = await import('../bundles/default-browser')
|
||||
opts.bundles = await getBundles()
|
||||
}
|
||||
|
||||
bundle = await selectBundle(await opts.bundles)
|
||||
worker = new Worker(bundle.mainWorker!)
|
||||
}
|
||||
else if (env === 'node') {
|
||||
if (typeof opts.bundles === 'undefined') {
|
||||
const { getBundles } = await import('../bundles/default-node')
|
||||
opts.bundles = await getBundles()
|
||||
}
|
||||
|
||||
bundle = await selectBundle(await opts.bundles)
|
||||
|
||||
let workerUrl = bundle.mainWorker!
|
||||
if (workerUrl.startsWith('/@fs/')) {
|
||||
workerUrl = workerUrl.replace('/@fs/', 'file://')
|
||||
}
|
||||
|
||||
const ww = await import('web-worker')
|
||||
// eslint-disable-next-line new-cap
|
||||
worker = new ww.default(workerUrl, { type: 'module' })
|
||||
}
|
||||
|
||||
let logger: Logger
|
||||
if (opts.logger === true) {
|
||||
|
||||
@@ -1,6 +1,4 @@
|
||||
export * from './duckdb'
|
||||
export * from './duckdb-common'
|
||||
export * from './duckdb-default-bundles'
|
||||
export * from './duckdb-format'
|
||||
export * from './duckdb-types'
|
||||
export * from './duckdb-vite-bundles'
|
||||
|
||||
@@ -7,7 +7,7 @@ import { ConsoleLogger } from '@duckdb/duckdb-wasm'
|
||||
import { createTableRelationsHelpers, DefaultLogger, entityKind, extractTablesRelationalConfig, isConfig } from 'drizzle-orm'
|
||||
import { PgDatabase, PgDialect } from 'drizzle-orm/pg-core'
|
||||
|
||||
import { connect } from './dialect'
|
||||
import { connect, getEnvironment } from './dialect'
|
||||
import { DuckDBWasmSession } from './session'
|
||||
|
||||
export class DuckDBWasmDatabase<
|
||||
@@ -64,19 +64,35 @@ export function drizzle<
|
||||
TClient extends Promise<DuckDBWasmClient> = Promise<DuckDBWasmClient>,
|
||||
>(
|
||||
...params:
|
||||
[ TClient | string ] |
|
||||
[ TClient | string, DrizzleConfig<TSchema> ] |
|
||||
[(DrizzleConfig<TSchema> & ({ connection: string | ({ url?: string, bundles?: DuckDBBundles, logger?: Logger }) } | { client: TClient })) ]
|
||||
| [{ connection: string | ({ url?: string, bundles?: DuckDBBundles | Promise<DuckDBBundles>, logger?: Logger | false }) }]
|
||||
| [{ connection: string | ({ url?: string, bundles?: DuckDBBundles | Promise<DuckDBBundles>, logger?: Logger | false }) }, DrizzleConfig<TSchema>]
|
||||
| [{ client: TClient }]
|
||||
| [{ client: TClient }, DrizzleConfig<TSchema>]
|
||||
| [ TClient | string ]
|
||||
| [ TClient | string, DrizzleConfig<TSchema> ]
|
||||
): DuckDBWasmDrizzleDatabase<TSchema, TClient> {
|
||||
if (typeof params[0] === 'string') {
|
||||
const parsedDSN = new URL(params[0] as string)
|
||||
if (parsedDSN.searchParams.get('bundles') === 'import-url') {
|
||||
const logger = parsedDSN.searchParams.get('logger') === 'true' ? new ConsoleLogger() : undefined
|
||||
return construct(new Promise<DuckDBWasmClient>((resolve) => {
|
||||
import('./dialect/duckdb-vite-bundles')
|
||||
.then(res => res.getViteBundles())
|
||||
.then(bundles => connect({ bundles, logger }))
|
||||
.then(resolve)
|
||||
getEnvironment().then((env) => {
|
||||
if (env === 'browser') {
|
||||
import('./bundles/import-url-browser')
|
||||
.then(res => res.getImportUrlBundles())
|
||||
.then(bundles => connect({ bundles, logger }))
|
||||
.then(resolve)
|
||||
}
|
||||
else if (env === 'node') {
|
||||
import('./bundles/import-url-node')
|
||||
.then(res => res.getImportUrlBundles())
|
||||
.then(bundles => connect({ bundles, logger }))
|
||||
.then(resolve)
|
||||
}
|
||||
else {
|
||||
throw new Error('Unsupported environment')
|
||||
}
|
||||
})
|
||||
}), params[1]) as any
|
||||
}
|
||||
|
||||
@@ -100,16 +116,32 @@ export function drizzle<
|
||||
if (client)
|
||||
return construct(client, drizzleConfig) as any
|
||||
|
||||
if (typeof connection === 'object' && connection.url !== undefined) {
|
||||
const { url } = connection
|
||||
const parsedDSN = new URL(url)
|
||||
if (parsedDSN.searchParams.get('bundles') === 'import-url') {
|
||||
return construct(new Promise<DuckDBWasmClient>((resolve) => {
|
||||
import('./dialect/duckdb-vite-bundles')
|
||||
.then(res => res.getViteBundles())
|
||||
.then(bundles => connect({ bundles }))
|
||||
.then(resolve)
|
||||
}), drizzleConfig) as any
|
||||
if (typeof connection === 'object') {
|
||||
if (connection.url !== undefined) {
|
||||
const { url } = connection
|
||||
const parsedDSN = new URL(url)
|
||||
const logger = parsedDSN.searchParams.get('logger') === 'true' ? new ConsoleLogger() : undefined
|
||||
if (parsedDSN.searchParams.get('bundles') === 'import-url') {
|
||||
return construct(new Promise<DuckDBWasmClient>((resolve) => {
|
||||
getEnvironment().then((env) => {
|
||||
if (env === 'browser') {
|
||||
import('./bundles/import-url-browser')
|
||||
.then(res => res.getImportUrlBundles())
|
||||
.then(bundles => connect({ bundles, logger }))
|
||||
.then(resolve)
|
||||
}
|
||||
else if (env === 'node') {
|
||||
import('./bundles/import-url-node')
|
||||
.then(res => res.getImportUrlBundles())
|
||||
.then(bundles => connect({ bundles, logger }))
|
||||
.then(resolve)
|
||||
}
|
||||
else {
|
||||
throw new Error('Unsupported environment')
|
||||
}
|
||||
})
|
||||
}), drizzleConfig) as any
|
||||
}
|
||||
}
|
||||
|
||||
return construct(connect({ bundles: connection.bundles }), drizzleConfig) as any
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
import { describe, expect, it } from 'vitest'
|
||||
|
||||
import { drizzle } from '.'
|
||||
import { getBundles } from './bundles/default-node'
|
||||
|
||||
describe('drizzle', { timeout: 10000 }, async () => {
|
||||
it('should connect to a DuckDBWasm database', async () => {
|
||||
const db = drizzle({ connection: { bundles: getBundles() } })
|
||||
const res = await db.execute('SELECT count(*)::INTEGER as v FROM generate_series(0, 100) t(v)')
|
||||
expect(res).toBeDefined()
|
||||
expect(res).toEqual([{ v: 101 }])
|
||||
})
|
||||
})
|
||||
@@ -1,5 +1,5 @@
|
||||
export type { ConnectOptionalOptions, ConnectOptions, ConnectRequiredOptions, DuckDBWasmClient } from './dialect'
|
||||
export { connect, getBundles, mapColumnData } from './dialect'
|
||||
export { connect, mapColumnData } from './dialect'
|
||||
export * from './driver'
|
||||
export * from './migrator'
|
||||
export * from './session'
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
export { getViteBundles } from './dialect'
|
||||
Generated
+553
-48
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user