Movatterモバイル変換


[0]ホーム

URL:


Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

🧰 A modular adapter layer for working with any database (Drizzle, Prisma, MongoDB, Kysely & more)

License

NotificationsYou must be signed in to change notification settings

productdevbook/unadapter

Repository files navigation

A universal adapter interface for connecting various databases and ORMs with a standardized API.

Work In Progress

npm versionnpm downloadsbundleLicenseCI StatusLinesStatementsFunctionsBranches

🚀 Features

  • 🔄 Standardized interface for common database operations (create, read, update, delete)
  • 🛡️ Type-safe operations
  • 🔍 Support for complex queries and transformations
  • 🌐 Database-agnostic application code
  • 🔄 Easy switching between different database providers
  • 🗺️ Custom field mapping
  • 📊 Support for various data types across different database systems
  • 🏗️ Fully customizable schema definition

📚 Table of Contents

🌟 Overview

unadapter provides a consistent interface for database operations, allowing you to switch between different database solutions without changing your application code. This is particularly useful for applications that need database-agnostic operations or might need to switch database providers in the future.

🚧 Development Status

This project is based on the adapter architecture frombetter-auth and is being developed to provide a standalone, ESM-compatible adapter solution that can be used across various open-source projects.

Development Roadmap

  • Initial adapter architecture
  • Basic adapters implementation
  • Comprehensive documentation
  • Performance optimizations
  • Additional adapter types
  • Integration examples
  • Complete abstraction from better-auth and compatibility with all software systems

Test Coverage

LinesStatementsFunctionsBranches

CI StatusJSDocs

📦 Installation

# Using pnpmpnpm add unadapter# Using npmnpm install unadapter# Using yarnyarn add unadapter

You'll also need to install the specific database driver or ORM you plan to use.

🧩 Available Adapters

AdapterDescriptionStatus
Memory AdapterIn-memory adapter ideal for development and testing✅ Ready
Prisma AdapterFor Prisma ORM✅ Ready
MongoDB AdapterFor MongoDB✅ Ready
Drizzle AdapterFor Drizzle ORM✅ Ready
Kysely AdapterFor Kysely SQL query builder✅ Ready

🚀 Getting Started

Basic Usage
importtype{PluginSchema}from'unadapter/types'import{createAdapter,createTable,mergePluginSchemas}from'unadapter'import{memoryAdapter}from'unadapter/memory'// Create an in-memory database for testingconstdb={user:[],session:[]}// Define a consistent options interface that can be reusedinterfaceCustomOptions{appName?:stringplugins?:{schema?:PluginSchema}[]user?:{fields?:{name?:stringemail?:stringemailVerified?:stringimage?:stringcreatedAt?:string}}}consttables=createTable<CustomOptions>((options)=>{const{ user, ...pluginTables}=mergePluginSchemas<CustomOptions>(options)||{}return{user:{modelName:'user',fields:{name:{type:'string',required:true,fieldName:options?.user?.fields?.name||'name',sortable:true,},email:{type:'string',unique:true,required:true,fieldName:options?.user?.fields?.email||'email',sortable:true,},emailVerified:{type:'boolean',defaultValue:()=>false,required:true,fieldName:options?.user?.fields?.emailVerified||'emailVerified',},createdAt:{type:'date',defaultValue:()=>newDate(),required:true,fieldName:options?.user?.fields?.createdAt||'createdAt',},updatedAt:{type:'date',defaultValue:()=>newDate(),required:true,fieldName:options?.user?.fields?.updatedAt||'updatedAt',},        ...user?.fields,        ...options?.user?.fields,}}}})constadapter=createAdapter(tables,{database:memoryAdapter(db,{}),plugins:[]// Optional plugins})// Now you can use the adapter to perform database operationsconstuser=awaitadapter.create({model:'user',data:{name:'John Doe',email:'john@example.com',emailVerified:true,createdAt:newDate(),updatedAt:newDate()}})// Find the userconstfoundUsers=awaitadapter.findMany({model:'user',where:[{field:'email',value:'john@example.com',operator:'eq',}]})
Using Custom Schema and Plugins
importtype{PluginSchema}from'unadapter/types'import{createAdapter,createTable,mergePluginSchemas}from'unadapter'import{memoryAdapter}from'unadapter/memory'// Create an in-memory database for testingconstdb={users:[],products:[]}// Using the same pattern for CustomOptionsinterfaceCustomOptions{appName?:stringplugins?:{schema?:PluginSchema}[]user?:{fields?:{fullName?:stringemail?:stringisActive?:string}}product?:{fields?:{title?:stringprice?:stringownerId?:string}}}consttables=createTable<CustomOptions>((options)=>{const{ user, product, ...pluginTables}=mergePluginSchemas<CustomOptions>(options)||{}return{user:{modelName:'users',// The actual table/collection name in your databasefields:{fullName:{type:'string',required:true,fieldName:options?.user?.fields?.fullName||'full_name',sortable:true,},email:{type:'string',unique:true,required:true,fieldName:options?.user?.fields?.email||'email_address',},isActive:{type:'boolean',fieldName:options?.user?.fields?.isActive||'is_active',defaultValue:()=>true,},createdAt:{type:'date',fieldName:'created_at',defaultValue:()=>newDate(),},        ...user?.fields,        ...options?.user?.fields,}},product:{modelName:'products',fields:{title:{type:'string',required:true,fieldName:options?.product?.fields?.title||'title',},price:{type:'number',required:true,fieldName:options?.product?.fields?.price||'price',},ownerId:{type:'string',references:{model:'user',field:'id',onDelete:'cascade',},required:true,fieldName:options?.product?.fields?.ownerId||'owner_id',},        ...product?.fields,        ...options?.product?.fields,}}}})// User profile plugin schemaconstuserProfilePlugin={schema:{user:{modelName:'user',fields:{bio:{type:'string',required:false,fieldName:'bio',},location:{type:'string',required:false,fieldName:'location',}}}}}constadapter=createAdapter(tables,{database:memoryAdapter(db,{}),plugins:[userProfilePlugin],})// Now you can use the adapter with your custom schemaconstuser=awaitadapter.create({model:'user',data:{fullName:'John Doe',email:'john@example.com',bio:'Software developer',location:'New York'}})// Create a product linked to the userconstproduct=awaitadapter.create({model:'product',data:{title:'Awesome Product',price:99.99,ownerId:user.id}})

Database-Specific Adapters

MongoDB Adapter Example
importtype{PluginSchema}from'unadapter/types'import{createAdapter,createTable,mergePluginSchemas}from'unadapter'import{MongoClient}from'mongodb'import{mongodbAdapter}from'unadapter/mongodb'// Create a database clientconstclient=newMongoClient('mongodb://localhost:27017')awaitclient.connect()constdb=client.db('myDatabase')// Using the same pattern for CustomOptionsinterfaceCustomOptions{appName?:stringplugins?:{schema?:PluginSchema}[]user?:{fields?:{name?:stringemail?:stringsettings?:string}}}consttables=createTable<CustomOptions>((options)=>{const{ user, ...pluginTables}=mergePluginSchemas<CustomOptions>(options)||{}return{user:{modelName:'users',fields:{name:{type:'string',required:true,fieldName:options?.user?.fields?.name||'name',},email:{type:'string',required:true,unique:true,fieldName:options?.user?.fields?.email||'email',},settings:{type:'json',required:false,fieldName:options?.user?.fields?.settings||'settings',},createdAt:{type:'date',defaultValue:()=>newDate(),fieldName:'createdAt',},        ...user?.fields,        ...options?.user?.fields,}}}})// Initialize the adapterconstadapter=createAdapter(tables,{database:mongodbAdapter(db,{useNumberId:false}),plugins:[]})// Use the adapterconstuser=awaitadapter.create({model:'user',data:{name:'Jane Doe',email:'jane@example.com',settings:{theme:'dark',notifications:true}}})
Prisma Adapter Example
importtype{PluginSchema}from'unadapter/types'import{createAdapter,createTable,mergePluginSchemas}from'unadapter'import{PrismaClient}from'@prisma/client'import{prismaAdapter}from'unadapter/prisma'// Initialize Prisma clientconstprisma=newPrismaClient()// Using the same pattern for CustomOptionsinterfaceCustomOptions{appName?:stringplugins?:{schema?:PluginSchema}[]user?:{fields?:{name?:stringemail?:stringprofile?:string}}post?:{fields?:{title?:stringcontent?:stringauthorId?:string}}}consttables=createTable<CustomOptions>((options)=>{const{ user, post, ...pluginTables}=mergePluginSchemas<CustomOptions>(options)||{}return{user:{modelName:'User',// Match your Prisma model name (case-sensitive)fields:{name:{type:'string',required:true,fieldName:options?.user?.fields?.name||'name',},email:{type:'string',required:true,unique:true,fieldName:options?.user?.fields?.email||'email',},profile:{type:'json',required:false,fieldName:options?.user?.fields?.profile||'profile',},createdAt:{type:'date',defaultValue:()=>newDate(),fieldName:'createdAt',},        ...user?.fields,        ...options?.user?.fields,}},post:{modelName:'Post',fields:{title:{type:'string',required:true,fieldName:options?.post?.fields?.title||'title',},content:{type:'string',required:false,fieldName:options?.post?.fields?.content||'content',},published:{type:'boolean',defaultValue:()=>false,fieldName:'published',},authorId:{type:'string',references:{model:'user',field:'id',onDelete:'cascade',},required:true,fieldName:options?.post?.fields?.authorId||'authorId',},        ...post?.fields,        ...options?.post?.fields,}}}})// Initialize the adapterconstadapter=createAdapter(tables,{database:prismaAdapter(prisma,{provider:'postgresql',debugLogs:true,usePlural:false}),plugins:[]})// Use the adapterconstuser=awaitadapter.create({model:'user',data:{name:'John Smith',email:'john.smith@example.com',profile:{bio:'Software developer',location:'New York'}}})
Drizzle Adapter Example
importtype{PluginSchema}from'unadapter/types'import{createAdapter,createTable,mergePluginSchemas}from'unadapter'import{sql}from'drizzle-orm'import{drizzle}from'drizzle-orm/node-postgres'import{pgTable,text,timestamp,uuid,varchar}from'drizzle-orm/pg-core'import{drizzleAdapter}from'unadapter/drizzle'import'dotenv/config'// Define your Drizzle schemaexportconstrole=pgTable('role',{id:uuid('id').primaryKey().default(sql`gen_random_uuid()`),name:varchar('name',{length:255}).notNull(),key:varchar('key',{length:255}).notNull().unique(),type:varchar('type',{length:255}).notNull().default('user'),description:varchar('description',{length:500}).notNull(),userId:uuid('user_id').notNull(),permissions:text('permissions').notNull().default('0'),updatedAt:timestamp('updated_at').notNull().default(sql`now()`),createdAt:timestamp('created_at').notNull().default(sql`now()`),},)// Using the same pattern for CustomOptionsinterfaceCustomOptions{appName?:stringplugins?:{schema?:PluginSchema}[]role?:{fields?:{name?:stringdescription?:stringkey?:stringpermissions?:stringuserId?:string}}}consttables=createTable<CustomOptions>((options)=>{const{ user, role, ...pluginTables}=mergePluginSchemas<CustomOptions>(options)||{}return{role:{modelName:'role',fields:{name:{type:'string',required:true,fieldName:options?.role?.fields?.name||'name',},description:{type:'string',required:true,fieldName:options?.role?.fields?.description||'description',},key:{type:'string',required:true,fieldName:options?.role?.fields?.key||'key',},permissions:{type:'string',required:true,fieldName:options?.role?.fields?.permissions||'permissions',},userId:{type:'string',required:true,references:{model:'user',field:'id',onDelete:'cascade',},fieldName:options?.role?.fields?.userId||'user_id',},createdAt:{type:'date',required:true,defaultValue:newDate(),},updatedAt:{type:'date',required:true,defaultValue:newDate(),},        ...role?.fields,        ...options?.role?.fields,},},}})// Initialize the adapter with the Drizzle schemaconstadapter=createAdapter(tables,{database:drizzleAdapter(drizzle(process.env.DATABASE_URL!),{provider:'pg',debugLogs:true,schema:{        role,},},),plugins:[],// Optional plugins})// Use the adapterconstrole=awaitadapter.create({model:'role',data:{name:'Test Role',description:'This is a test role',key:'test_role',permissions:'read,write',userId:'8eea9d01-6c73-4933-bb0f-811cb7d4a862',createdAt:newDate(),updatedAt:newDate(),},})
Kysely Adapter Example
importtype{PluginSchema}from'unadapter/types'import{createAdapter,createTable,mergePluginSchemas}from'unadapter'import{Kysely,PostgresDialect}from'kysely'importpgfrom'pg'import{kyselyAdapter}from'unadapter/kysely'// Create PostgreSQL connection poolconstpool=newpg.Pool({host:'localhost',database:'mydatabase',user:'myuser',password:'mypassword'})// Initialize Kysely with PostgreSQL dialectconstdb=newKysely({dialect:newPostgresDialect({ pool})})// Using the same pattern for CustomOptionsinterfaceCustomOptions{appName?:stringplugins?:{schema?:PluginSchema}[]user?:{fields?:{name?:stringemail?:stringactive?:stringmeta?:string}}article?:{fields?:{title?:stringcontent?:stringauthorId?:string}}}consttables=createTable<CustomOptions>((options)=>{const{ user, article, ...pluginTables}=mergePluginSchemas<CustomOptions>(options)||{}return{user:{modelName:'users',fields:{name:{type:'string',required:true,fieldName:options?.user?.fields?.name||'name',},email:{type:'string',required:true,unique:true,fieldName:options?.user?.fields?.email||'email',},active:{type:'boolean',defaultValue:()=>true,fieldName:options?.user?.fields?.active||'is_active',},meta:{type:'json',required:false,fieldName:options?.user?.fields?.meta||'meta_data',},createdAt:{type:'date',defaultValue:()=>newDate(),fieldName:'created_at',},        ...user?.fields,        ...options?.user?.fields,}},article:{modelName:'articles',fields:{title:{type:'string',required:true,fieldName:options?.article?.fields?.title||'title',},content:{type:'string',required:true,fieldName:options?.article?.fields?.content||'content',},authorId:{type:'string',references:{model:'user',field:'id',onDelete:'cascade',},required:true,fieldName:options?.article?.fields?.authorId||'author_id',},tags:{type:'array',required:false,fieldName:'tags',},publishedAt:{type:'date',required:false,fieldName:'published_at',},        ...article?.fields,        ...options?.article?.fields,}}}})// Initialize the adapterconstadapter=createAdapter(tables,{database:kyselyAdapter(db,{defaultSchema:'public'}),plugins:[]})// Use the adapterconstuser=awaitadapter.create({model:'user',data:{name:'Robert Chen',email:'robert@example.com',meta:{interests:['programming','reading'],location:'San Francisco'}}})

🔍 API Reference

Adapter Interface

All adapters implement the following interface:

interfaceAdapter{// Create a new recordcreate<T>({model:string,data:Omit<T,'id'>,select?:string[]}):Promise<T>;// Find multiple recordsfindMany<T>({model:string,where?:Where[],limit?:number,sortBy?:{field:string,direction:'asc'|'desc'},offset?:number}):Promise<T[]>;// Update a recordupdate<T>({model:string,where:Where[],update:Record<string,any>}):Promise<T|null>;// Update multiple recordsupdateMany({model:string,where:Where[],update:Record<string,any>}):Promise<number>;// Delete a recorddelete({model:string,where:Where[]}):Promise<void>;// Delete multiple recordsdeleteMany({model:string,where:Where[]}):Promise<number>;// Count recordscount({model:string,where?:Where[]}):Promise<number>;}
Where Clause Interface

TheWhere interface is used for filtering records:

interfaceWhere{field:stringvalue?:anyoperator?:'eq'|'ne'|'gt'|'gte'|'lt'|'lte'|'in'|'contains'|'starts_with'|'ends_with'connector?:'AND'|'OR'}
Field Types and Attributes

When defining your schema, you can use the following field types and attributes:

interfaceFieldAttribute{// The type of the fieldtype:'string'|'number'|'boolean'|'date'|'json'|'array'// Whether this field is requiredrequired?:boolean// Whether this field should be uniqueunique?:boolean// The actual column/field name in the databasefieldName?:string// Whether this field can be sortedsortable?:boolean// Default value functiondefaultValue?:()=>any// Reference to another model (for foreign keys)references?:{model:stringfield:stringonDelete?:'cascade'|'set null'|'restrict'}// Custom transformationstransform?:{input?:(value:any)=>anyoutput?:(value:any)=>any}}

🤝 Contributing

Contributions are welcome! Feel free toopen issues orsubmit pull requests to help improve unadapter.

Development Setup
  1. Clone the repository:

    git clone https://github.com/productdevbook/unadapter.gitcd unadapter
  2. Install dependencies:

    pnpm install
  3. Run tests:

    pnpmtest
  4. Build the project:

    pnpm build

🙏 Credits

This project draws inspiration and core concepts from:

  • better-auth - The original adapter architecture that inspired this project

📝 License

See theLICENSE file for details.

unadapter is a work in progress. Stay tuned for updates!

About

🧰 A modular adapter layer for working with any database (Drizzle, Prisma, MongoDB, Kysely & more)

Topics

Resources

License

Stars

Watchers

Forks

Sponsor this project

 

Packages

No packages published

[8]ページ先頭

©2009-2025 Movatter.jp