Compare commits

...

22 Commits

Author SHA1 Message Date
Paul Popus
ca626288ab empty commit for new hash 2025-07-30 11:34:40 +01:00
Paul Popus
f87db1cf59 add r2 and d1 to publishList in script 2025-07-28 20:26:48 +01:00
Sasha
fb196069d5 delete :memory 2025-07-28 21:56:21 +03:00
Sasha
759bb9899e change version 2025-07-28 20:34:11 +03:00
Sasha
72fbcc3567 Merge branch 'main' of github.com:payloadcms/payload into feat/add-d1-adapter 2025-07-28 20:16:38 +03:00
Sasha
92010510b0 storage r2 2025-07-28 19:49:17 +03:00
Sasha
3f09e27bdd execute method 2025-07-25 15:06:16 +03:00
Sasha
65b110e4e7 fix type 2025-07-23 19:21:04 +03:00
Sasha
3d57b06f83 Merge branch 'main' of github.com:payloadcms/payload into feat/add-d1-adapter 2025-07-23 19:05:44 +03:00
Sasha
8595b575f5 fix type 2025-07-23 19:04:23 +03:00
Sasha
9ce07c75c3 fix type 2025-07-23 18:59:59 +03:00
Sasha
be4f11cd15 Merge branch 'main' of github.com:payloadcms/payload into feat/add-d1-adapter 2025-07-23 18:59:51 +03:00
Sasha
c3af32e133 fix postgres build 2 2025-05-26 19:23:41 +03:00
Sasha
fd850e734b fix postgres build 2025-05-26 18:50:25 +03:00
Sasha
5de2f52aa0 fix vercel postgres build 2025-05-26 18:49:24 +03:00
Sasha
733594b9c2 fix package jason 2025-05-26 17:54:27 +03:00
Sasha
9bd5f6f5f8 try testing 2025-05-26 17:53:47 +03:00
Sasha
da4270f299 sqlOnly migrations 2025-05-26 17:16:15 +03:00
Sasha
52f9dcae82 finish d1 package and fix errors 2025-05-26 17:11:12 +03:00
Sasha
0829cfb712 fix imports 2025-05-25 00:58:32 +03:00
Sasha
f51c972ac1 add exports from drizzle 2025-05-24 16:25:27 +03:00
Sasha
6652608c10 move sqlite logic to the drizzle package 2025-05-24 16:17:28 +03:00
62 changed files with 2213 additions and 141 deletions

View File

@@ -19,6 +19,7 @@
"build:core": "turbo build --filter \"!@payloadcms/plugin-*\" --filter \"!@payloadcms/storage-*\" --filter \"!blank\" --filter \"!website\"",
"build:core:force": "pnpm clean:build && pnpm build:core --no-cache --force",
"build:create-payload-app": "turbo build --filter create-payload-app",
"build:db-d1-sqlite": "turbo build --filter \"@payloadcms/db-d1-sqlite\"",
"build:db-mongodb": "turbo build --filter \"@payloadcms/db-mongodb\"",
"build:db-postgres": "turbo build --filter \"@payloadcms/db-postgres\"",
"build:db-sqlite": "turbo build --filter \"@payloadcms/db-sqlite\"",

1
packages/db-d1-sqlite/.gitignore vendored Normal file
View File

@@ -0,0 +1 @@
/migrations

View File

@@ -0,0 +1,10 @@
.tmp
**/.git
**/.hg
**/.pnp.*
**/.svn
**/.yarn/**
**/build
**/dist/**
**/node_modules
**/temp

View File

@@ -0,0 +1,15 @@
{
"$schema": "https://json.schemastore.org/swcrc",
"sourceMaps": true,
"jsc": {
"target": "esnext",
"parser": {
"syntax": "typescript",
"tsx": true,
"dts": true
}
},
"module": {
"type": "es6"
}
}

View File

@@ -0,0 +1,22 @@
MIT License
Copyright (c) 2018-2025 Payload CMS, Inc. <info@payloadcms.com>
Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
'Software'), to deal in the Software without restriction, including
without limitation the rights to use, copy, modify, merge, publish,
distribute, sublicense, and/or sell copies of the Software, and to
permit persons to whom the Software is furnished to do so, subject to
the following conditions:
The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

View File

@@ -0,0 +1,30 @@
# Payload SQLite Adapter
Official SQLite adapter for [Payload](https://payloadcms.com).
- [Main Repository](https://github.com/payloadcms/payload)
- [Payload Docs](https://payloadcms.com/docs)
## Installation
```bash
npm install @payloadcms/db-sqlite
```
## Usage
```ts
import { buildConfig } from 'payload/config'
import { sqliteAdapter } from '@payloadcms/db-sqlite'
export default buildConfig({
db: sqliteAdapter({
client: {
url: process.env.DATABASE_URI,
},
}),
// ...rest of config
})
```
More detailed usage can be found in the [Payload Docs](https://payloadcms.com/docs/configuration/overview).

View File

@@ -0,0 +1,38 @@
import * as esbuild from 'esbuild'
import fs from 'fs'
import path from 'path'
import { fileURLToPath } from 'url'
const filename = fileURLToPath(import.meta.url)
const dirname = path.dirname(filename)
import { commonjs } from '@hyrious/esbuild-plugin-commonjs'
async function build() {
const resultServer = await esbuild.build({
entryPoints: ['src/index.ts'],
bundle: true,
platform: 'node',
format: 'esm',
outfile: 'dist/index.js',
splitting: false,
external: [
'*.scss',
'*.css',
'drizzle-kit',
'libsql',
'pg',
'@payloadcms/translations',
'@payloadcms/drizzle',
'payload',
'payload/*',
],
minify: true,
metafile: true,
tsconfig: path.resolve(dirname, './tsconfig.json'),
plugins: [commonjs()],
sourcemap: true,
})
console.log('db-sqlite bundled successfully')
fs.writeFileSync('meta_server.json', JSON.stringify(resultServer.metafile))
}
await build()

View File

@@ -0,0 +1,145 @@
{
"name": "@payloadcms/db-d1-sqlite",
"version": "3.49.0",
"description": "The officially supported D1 SQLite database adapter for Payload",
"homepage": "https://payloadcms.com",
"repository": {
"type": "git",
"url": "https://github.com/payloadcms/payload.git",
"directory": "packages/db-d1-sqlite"
},
"license": "MIT",
"author": "Payload <dev@payloadcms.com> (https://payloadcms.com)",
"maintainers": [
{
"name": "Payload",
"email": "info@payloadcms.com",
"url": "https://payloadcms.com"
}
],
"type": "module",
"exports": {
".": {
"import": "./src/index.ts",
"require": "./src/index.ts",
"types": "./src/index.ts"
},
"./types": {
"import": "./src/exports/types-deprecated.ts",
"require": "./src/exports/types-deprecated.ts",
"types": "./src/exports/types-deprecated.ts"
},
"./migration-utils": {
"import": "./src/exports/migration-utils.ts",
"require": "./src/exports/migration-utils.ts",
"types": "./src/exports/migration-utils.ts"
},
"./drizzle": {
"import": "./src/drizzle-proxy/index.ts",
"types": "./src/drizzle-proxy/index.ts",
"default": "./src/drizzle-proxy/index.ts"
},
"./drizzle/sqlite-core": {
"import": "./src/drizzle-proxy/sqlite-core.ts",
"types": "./src/drizzle-proxy/sqlite-core.ts",
"default": "./src/drizzle-proxy/sqlite-core.ts"
},
"./drizzle/d1": {
"import": "./src/drizzle-proxy/d1.ts",
"types": "./src/drizzle-proxy/d1.ts",
"default": "./src/drizzle-proxy/d1.ts"
},
"./drizzle/relations": {
"import": "./src/drizzle-proxy/relations.ts",
"types": "./src/drizzle-proxy/relations.ts",
"default": "./src/drizzle-proxy/relations.ts"
},
"./drizzle/miniflare": {
"import": "./src/drizzle-proxy/miniflare.ts",
"types": "./src/drizzle-proxy/miniflare.ts",
"default": "./src/drizzle-proxy/miniflare.ts"
}
},
"main": "./src/index.ts",
"types": "./src/index.ts",
"files": [
"dist",
"mock.js"
],
"scripts": {
"build": "pnpm build:swc && pnpm build:types",
"build:swc": "swc ./src -d ./dist --config-file .swcrc --strip-leading-paths",
"build:types": "tsc --emitDeclarationOnly --outDir dist",
"clean": "rimraf -g {dist,*.tsbuildinfo}",
"lint": "eslint .",
"lint:fix": "eslint . --fix",
"prepack": "pnpm clean && pnpm turbo build",
"prepublishOnly": "pnpm clean && pnpm turbo build"
},
"dependencies": {
"@miniflare/d1": "2.14.4",
"@payloadcms/drizzle": "workspace:*",
"console-table-printer": "2.12.1",
"drizzle-kit": "0.28.0",
"drizzle-orm": "0.36.1",
"prompts": "2.4.2",
"to-snake-case": "1.0.0",
"uuid": "9.0.0"
},
"devDependencies": {
"@payloadcms/eslint-config": "workspace:*",
"@types/pg": "8.10.2",
"@types/to-snake-case": "1.0.0",
"@types/uuid": "10.0.0",
"payload": "workspace:*"
},
"peerDependencies": {
"payload": "workspace:*"
},
"publishConfig": {
"exports": {
".": {
"import": "./dist/index.js",
"require": "./dist/index.js",
"types": "./dist/index.d.ts"
},
"./types": {
"import": "./dist/exports/types-deprecated.js",
"require": "./dist/exports/types-deprecated.js",
"types": "./dist/exports/types-deprecated.d.ts"
},
"./migration-utils": {
"import": "./dist/exports/migration-utils.js",
"require": "./dist/exports/migration-utils.js",
"types": "./dist/exports/migration-utils.d.ts"
},
"./drizzle": {
"import": "./dist/drizzle-proxy/index.js",
"types": "./dist/drizzle-proxy/index.d.ts",
"default": "./dist/drizzle-proxy/index.js"
},
"./drizzle/sqlite-core": {
"import": "./dist/drizzle-proxy/sqlite-core.js",
"types": "./dist/drizzle-proxy/sqlite-core.d.ts",
"default": "./dist/drizzle-proxy/sqlite-core.js"
},
"./drizzle/d1": {
"import": "./dist/drizzle-proxy/d1.js",
"types": "./dist/drizzle-proxy/d1.d.ts",
"default": "./dist/drizzle-proxy/d1.js"
},
"./drizzle/relations": {
"import": "./dist/drizzle-proxy/relations.js",
"types": "./dist/drizzle-proxy/relations.d.ts",
"default": "./dist/drizzle-proxy/relations.js"
},
"./drizzle/miniflare": {
"import": "./dist/drizzle-proxy/miniflare.js",
"types": "./dist/drizzle-proxy/miniflare.d.ts",
"default": "./dist/drizzle-proxy/miniflare.js"
}
},
"main": "./dist/index.js",
"types": "./dist/index.d.ts"
}
}

View File

@@ -0,0 +1,62 @@
import type { DrizzleAdapter } from '@payloadcms/drizzle/types'
import type { Connect, Migration } from 'payload'
import { D1Database } from '@miniflare/d1'
import { pushDevSchema } from '@payloadcms/drizzle'
import { drizzle } from 'drizzle-orm/d1'
import type { SQLiteD1Adapter } from './types.js'
export const connect: Connect = async function connect(
this: SQLiteD1Adapter,
options = {
hotReload: false,
},
) {
const { hotReload } = options
this.schema = {
...this.tables,
...this.relations,
}
try {
const logger = this.logger || false
this.drizzle = drizzle(new D1Database(this.binding), { logger })
this.client = this.drizzle.$client as any
if (!hotReload) {
if (process.env.PAYLOAD_DROP_DATABASE === 'true') {
this.payload.logger.info(`---- DROPPING TABLES ----`)
await this.dropDatabase({ adapter: this })
this.payload.logger.info('---- DROPPED TABLES ----')
}
}
} catch (err) {
const message = err instanceof Error ? err.message : String(err)
this.payload.logger.error({ err, msg: `Error: cannot connect to SQLite: ${message}` })
if (typeof this.rejectInitializing === 'function') {
this.rejectInitializing()
}
console.error(err)
process.exit(1)
}
// Only push schema if not in production
if (
process.env.NODE_ENV !== 'production' &&
process.env.PAYLOAD_MIGRATING !== 'true' &&
this.push !== false
) {
await pushDevSchema(this as unknown as DrizzleAdapter)
}
if (typeof this.resolveInitializing === 'function') {
this.resolveInitializing()
}
if (process.env.NODE_ENV === 'production' && this.prodMigrations) {
await this.migrate({ migrations: this.prodMigrations as Migration[] })
}
}

View File

@@ -0,0 +1 @@
export * from 'drizzle-orm/d1'

View File

@@ -0,0 +1 @@
export * from 'drizzle-orm'

View File

@@ -0,0 +1 @@
export * from '@miniflare/d1'

View File

@@ -0,0 +1 @@
export * from 'drizzle-orm/relations'

View File

@@ -0,0 +1 @@
export * from 'drizzle-orm/sqlite-core'

View File

@@ -0,0 +1,67 @@
import type { Execute } from '@payloadcms/drizzle'
import type { SQLiteRaw } from 'drizzle-orm/sqlite-core/query-builders/raw'
import { sql } from 'drizzle-orm'
interface D1Meta {
changed_db: boolean
changes: number
duration: number
last_row_id: number
rows_read: number
rows_written: number
/**
* True if-and-only-if the database instance that executed the query was the primary.
*/
served_by_primary?: boolean
/**
* The region of the database instance that executed the query.
*/
served_by_region?: string
size_after: number
timings?: {
/**
* The duration of the SQL query execution by the database instance. It doesn't include any network time.
*/
sql_duration_ms: number
}
}
interface D1Response {
error?: never
meta: D1Meta & Record<string, unknown>
success: true
}
type D1Result<T = unknown> = {
results: T[]
} & D1Response
export const execute: Execute<any> = function execute({ db, drizzle, raw, sql: statement }) {
const executeFrom: any = (db ?? drizzle)!
const mapToLibSql = (query: SQLiteRaw<D1Result<unknown>>): any => {
const execute = query.execute
query.execute = async () => {
const result: D1Result = await execute()
const resultLibSQL = {
columns: undefined,
columnTypes: undefined,
lastInsertRowid: BigInt(result.meta.last_row_id),
rows: result.results as any[],
rowsAffected: result.meta.rows_written,
}
return Object.assign(result, resultLibSQL)
}
return query
}
if (raw) {
const result = mapToLibSql(executeFrom.run(sql.raw(raw)))
return result
} else {
const result = mapToLibSql(executeFrom.run(statement))
return result
}
}

View File

@@ -0,0 +1,79 @@
import type {
Args as _Args,
CountDistinct as _CountDistinct,
DeleteWhere as _DeleteWhere,
DropDatabase as _DropDatabase,
Execute as _Execute,
GeneratedDatabaseSchema as _GeneratedDatabaseSchema,
GenericColumns as _GenericColumns,
GenericRelation as _GenericRelation,
GenericTable as _GenericTable,
IDType as _IDType,
Insert as _Insert,
MigrateDownArgs as _MigrateDownArgs,
MigrateUpArgs as _MigrateUpArgs,
SQLiteD1Adapter as _SQLiteAdapter,
SQLiteSchemaHook as _SQLiteSchemaHook,
} from '../types.js'
/**
* @deprecated - import from `@payloadcms/db-sqlite` instead
*/
export type SQLiteAdapter = _SQLiteAdapter
/**
* @deprecated - import from `@payloadcms/db-sqlite` instead
*/
export type Args = _Args
/**
* @deprecated - import from `@payloadcms/db-sqlite` instead
*/
export type CountDistinct = _CountDistinct
/**
* @deprecated - import from `@payloadcms/db-sqlite` instead
*/
export type DeleteWhere = _DeleteWhere
/**
* @deprecated - import from `@payloadcms/db-sqlite` instead
*/
export type DropDatabase = _DropDatabase
/**
* @deprecated - import from `@payloadcms/db-sqlite` instead
*/
export type Execute<T> = _Execute<T>
/**
* @deprecated - import from `@payloadcms/db-sqlite` instead
*/
export type GeneratedDatabaseSchema = _GeneratedDatabaseSchema
/**
* @deprecated - import from `@payloadcms/db-sqlite` instead
*/
export type GenericColumns = _GenericColumns
/**
* @deprecated - import from `@payloadcms/db-sqlite` instead
*/
export type GenericRelation = _GenericRelation
/**
* @deprecated - import from `@payloadcms/db-sqlite` instead
*/
export type GenericTable = _GenericTable
/**
* @deprecated - import from `@payloadcms/db-sqlite` instead
*/
export type IDType = _IDType
/**
* @deprecated - import from `@payloadcms/db-sqlite` instead
*/
export type Insert = _Insert
/**
* @deprecated - import from `@payloadcms/db-sqlite` instead
*/
export type MigrateDownArgs = _MigrateDownArgs
/**
* @deprecated - import from `@payloadcms/db-sqlite` instead
*/
export type MigrateUpArgs = _MigrateUpArgs
/**
* @deprecated - import from `@payloadcms/db-sqlite` instead
*/
export type SQLiteSchemaHook = _SQLiteSchemaHook

View File

@@ -0,0 +1,224 @@
import type { Operators } from '@payloadcms/drizzle'
import type { DatabaseAdapterObj, Payload } from 'payload'
import {
beginTransaction,
buildCreateMigration,
commitTransaction,
count,
countGlobalVersions,
countVersions,
create,
createGlobal,
createGlobalVersion,
createSchemaGenerator,
createVersion,
deleteMany,
deleteOne,
deleteVersions,
destroy,
find,
findGlobal,
findGlobalVersions,
findMigrationDir,
findOne,
findVersions,
migrate,
migrateDown,
migrateFresh,
migrateRefresh,
migrateReset,
migrateStatus,
operatorMap,
queryDrafts,
rollbackTransaction,
updateGlobal,
updateGlobalVersion,
updateJobs,
updateMany,
updateOne,
updateVersion,
} from '@payloadcms/drizzle'
import {
columnToCodeConverter,
convertPathToJSONTraversal,
countDistinct,
createJSONQuery,
defaultDrizzleSnapshot,
deleteWhere,
dropDatabase,
init,
insert,
requireDrizzleKit,
} from '@payloadcms/drizzle/sqlite'
import { like, notLike } from 'drizzle-orm'
import { createDatabaseAdapter, defaultBeginTransaction } from 'payload'
import { fileURLToPath } from 'url'
import type { Args, SQLiteD1Adapter } from './types.js'
import { connect } from './connect.js'
import { execute } from './execute.js'
const filename = fileURLToPath(import.meta.url)
export function sqliteD1Adapter(args: Args): DatabaseAdapterObj<SQLiteD1Adapter> {
const sqliteIDType = args.idType || 'number'
const payloadIDType = sqliteIDType === 'uuid' ? 'text' : 'number'
const allowIDOnCreate = args.allowIDOnCreate ?? false
function adapter({ payload }: { payload: Payload }) {
const migrationDir = findMigrationDir(args.migrationDir)
let resolveInitializing: () => void = () => {}
let rejectInitializing: () => void = () => {}
const initializing = new Promise<void>((res, rej) => {
resolveInitializing = res
rejectInitializing = rej
})
// sqlite's like operator is case-insensitive, so we overwrite the DrizzleAdapter operators to not use ilike
const operators = {
...operatorMap,
contains: like,
like,
not_like: notLike,
} as unknown as Operators
return createDatabaseAdapter<SQLiteD1Adapter>({
name: 'sqlite',
afterSchemaInit: args.afterSchemaInit ?? [],
allowIDOnCreate,
autoIncrement: args.autoIncrement ?? false,
beforeSchemaInit: args.beforeSchemaInit ?? [],
binding: args.binding,
// @ts-expect-error - vestiges of when tsconfig was not strict. Feel free to improve
client: undefined,
defaultDrizzleSnapshot,
// @ts-expect-error - vestiges of when tsconfig was not strict. Feel free to improve
drizzle: undefined,
features: {
json: true,
},
fieldConstraints: {},
generateSchema: createSchemaGenerator({
columnToCodeConverter,
corePackageSuffix: 'sqlite-core',
defaultOutputFile: args.generateSchemaOutputFile,
tableImport: 'sqliteTable',
}),
idType: sqliteIDType,
initializing,
localesSuffix: args.localesSuffix || '_locales',
logger: args.logger,
operators,
prodMigrations: args.prodMigrations,
// @ts-expect-error - vestiges of when tsconfig was not strict. Feel free to improve
push: args.push,
rawRelations: {},
rawTables: {},
relations: {},
relationshipsSuffix: args.relationshipsSuffix || '_rels',
schema: {},
schemaName: args.schemaName,
sessions: {},
tableNameMap: new Map<string, string>(),
tables: {},
// @ts-expect-error - vestiges of when tsconfig was not strict. Feel free to improve
execute,
// @ts-expect-error - vestiges of when tsconfig was not strict. Feel free to improve
transactionOptions: args.transactionOptions || undefined,
updateJobs,
updateMany,
versionsSuffix: args.versionsSuffix || '_v',
// DatabaseAdapter
beginTransaction: args.transactionOptions ? beginTransaction : defaultBeginTransaction(),
commitTransaction,
connect,
convertPathToJSONTraversal,
count,
countDistinct,
countGlobalVersions,
countVersions,
create,
createGlobal,
createGlobalVersion,
createJSONQuery,
createMigration: buildCreateMigration({
executeMethod: 'run',
filename,
sanitizeStatements({ sqlExecute, statements }) {
return statements
.map((statement) => `${sqlExecute}${statement?.replaceAll('`', '\\`')}\`)`)
.join('\n')
},
sqlOnly: true,
}),
createVersion,
defaultIDType: payloadIDType,
deleteMany,
deleteOne,
deleteVersions,
deleteWhere,
destroy,
dropDatabase,
find,
findGlobal,
findGlobalVersions,
findOne,
findVersions,
indexes: new Set<string>(),
init,
insert,
migrate,
migrateDown,
migrateFresh,
migrateRefresh,
migrateReset,
migrateStatus,
migrationDir,
packageName: '@payloadcms/db-d1-sqlite',
payload,
queryDrafts,
rejectInitializing,
requireDrizzleKit,
resolveInitializing,
rollbackTransaction,
updateGlobal,
updateGlobalVersion,
updateOne,
updateVersion,
upsert: updateOne,
})
}
return {
name: 'd1-sqlite',
allowIDOnCreate,
defaultIDType: payloadIDType,
init: adapter,
}
}
/**
* @todo deprecate /types subpath export in 4.0
*/
export type {
Args as SQLiteAdapterArgs,
CountDistinct,
DeleteWhere,
DropDatabase,
Execute,
GeneratedDatabaseSchema,
GenericColumns,
GenericRelation,
GenericTable,
IDType,
Insert,
MigrateDownArgs,
MigrateUpArgs,
SQLiteD1Adapter as SQLiteAdapter,
SQLiteSchemaHook,
} from './types.js'
export { sql } from 'drizzle-orm'

View File

@@ -0,0 +1,202 @@
import type { Client, ResultSet } from '@libsql/client'
import type { D1Database, D1Options, DatabaseBinding } from '@miniflare/d1'
const o: D1Options = {}
import type { extendDrizzleTable } from '@payloadcms/drizzle'
import type { BaseSQLiteAdapter, BaseSQLiteArgs } from '@payloadcms/drizzle/sqlite'
import type { BuildQueryJoinAliases, DrizzleAdapter } from '@payloadcms/drizzle/types'
import type { DrizzleConfig, Relation, Relations, SQL } from 'drizzle-orm'
import type { DrizzleD1Database } from 'drizzle-orm/d1'
import type { LibSQLDatabase } from 'drizzle-orm/libsql'
import type {
AnySQLiteColumn,
SQLiteInsertOnConflictDoUpdateConfig,
SQLiteTableWithColumns,
SQLiteTransactionConfig,
} from 'drizzle-orm/sqlite-core'
import type { SQLiteRaw } from 'drizzle-orm/sqlite-core/query-builders/raw'
import type { Payload, PayloadRequest } from 'payload'
type SQLiteSchema = {
relations: Record<string, GenericRelation>
tables: Record<string, SQLiteTableWithColumns<any>>
}
type SQLiteSchemaHookArgs = {
extendTable: typeof extendDrizzleTable
schema: SQLiteSchema
}
export type SQLiteSchemaHook = (args: SQLiteSchemaHookArgs) => Promise<SQLiteSchema> | SQLiteSchema
export type Args = {
binding: DatabaseBinding
} & BaseSQLiteArgs
export type GenericColumns = {
[x: string]: AnySQLiteColumn
}
export type GenericTable = SQLiteTableWithColumns<{
columns: GenericColumns
dialect: string
name: string
schema: string
}>
export type GenericRelation = Relations<string, Record<string, Relation<string>>>
export type CountDistinct = (args: {
db: LibSQLDatabase
joins: BuildQueryJoinAliases
tableName: string
where: SQL
}) => Promise<number>
export type DeleteWhere = (args: {
db: LibSQLDatabase
tableName: string
where: SQL
}) => Promise<void>
export type DropDatabase = (args: { adapter: SQLiteD1Adapter }) => Promise<void>
export type Execute<T> = (args: {
db?: LibSQLDatabase
drizzle?: LibSQLDatabase
raw?: string
sql?: SQL<unknown>
}) => SQLiteRaw<Promise<T>> | SQLiteRaw<ResultSet>
export type Insert = (args: {
db: LibSQLDatabase
onConflictDoUpdate?: SQLiteInsertOnConflictDoUpdateConfig<any>
tableName: string
values: Record<string, unknown> | Record<string, unknown>[]
}) => Promise<Record<string, unknown>[]>
// Explicitly omit drizzle property for complete override in SQLiteAdapter, required in ts 5.5
type SQLiteDrizzleAdapter = Omit<
DrizzleAdapter,
| 'countDistinct'
| 'deleteWhere'
| 'drizzle'
| 'dropDatabase'
| 'execute'
| 'idType'
| 'insert'
| 'operators'
| 'relations'
>
export interface GeneratedDatabaseSchema {
schemaUntyped: Record<string, unknown>
}
type ResolveSchemaType<T> = 'schema' extends keyof T
? T['schema']
: GeneratedDatabaseSchema['schemaUntyped']
type Drizzle = { $client: D1Database } & DrizzleD1Database<Record<string, any>>
export type SQLiteD1Adapter = {
binding: Args['binding']
client: D1Database
drizzle: Drizzle
} & BaseSQLiteAdapter &
SQLiteDrizzleAdapter
export type IDType = 'integer' | 'numeric' | 'text'
export type MigrateUpArgs = {
/**
* The SQLite Drizzle instance that you can use to execute SQL directly within the current transaction.
* @example
* ```ts
* import { type MigrateUpArgs, sql } from '@payloadcms/db-sqlite'
*
* export async function up({ db, payload, req }: MigrateUpArgs): Promise<void> {
* const { rows: posts } = await db.run(sql`SELECT * FROM posts`)
* }
* ```
*/
db: Drizzle
/**
* The Payload instance that you can use to execute Local API methods
* To use the current transaction you must pass `req` to arguments
* @example
* ```ts
* import { type MigrateUpArgs } from '@payloadcms/db-sqlite'
*
* export async function up({ db, payload, req }: MigrateUpArgs): Promise<void> {
* const posts = await payload.find({ collection: 'posts', req })
* }
* ```
*/
payload: Payload
/**
* The `PayloadRequest` object that contains the current transaction
*/
req: PayloadRequest
}
export type MigrateDownArgs = {
/**
* The SQLite Drizzle instance that you can use to execute SQL directly within the current transaction.
* @example
* ```ts
* import { type MigrateDownArgs, sql } from '@payloadcms/db-sqlite'
*
* export async function down({ db, payload, req }: MigrateDownArgs): Promise<void> {
* const { rows: posts } = await db.run(sql`SELECT * FROM posts`)
* }
* ```
*/
db: Drizzle
/**
* The Payload instance that you can use to execute Local API methods
* To use the current transaction you must pass `req` to arguments
* @example
* ```ts
* import { type MigrateDownArgs } from '@payloadcms/db-sqlite'
*
* export async function down({ db, payload, req }: MigrateDownArgs): Promise<void> {
* const posts = await payload.find({ collection: 'posts', req })
* }
* ```
*/
payload: Payload
/**
* The `PayloadRequest` object that contains the current transaction
*/
req: PayloadRequest
}
declare module 'payload' {
export interface DatabaseAdapter
extends Omit<Args, 'idType' | 'logger' | 'migrationDir' | 'pool'>,
DrizzleAdapter {
beginTransaction: (options?: SQLiteTransactionConfig) => Promise<null | number | string>
drizzle: Drizzle
/**
* An object keyed on each table, with a key value pair where the constraint name is the key, followed by the dot-notation field name
* Used for returning properly formed errors from unique fields
*/
fieldConstraints: Record<string, Record<string, string>>
idType: Args['idType']
initializing: Promise<void>
localesSuffix?: string
logger: DrizzleConfig['logger']
prodMigrations?: {
down: (args: MigrateDownArgs) => Promise<void>
name: string
up: (args: MigrateUpArgs) => Promise<void>
}[]
push: boolean
rejectInitializing: () => void
relationshipsSuffix?: string
resolveInitializing: () => void
schema: Record<string, GenericRelation | GenericTable>
tableNameMap: Map<string, string>
transactionOptions: SQLiteTransactionConfig
versionsSuffix?: string
}
}

View File

@@ -0,0 +1,14 @@
{
"extends": "../../tsconfig.base.json",
"references": [
{
"path": "../payload"
},
{
"path": "../translations"
},
{
"path": "../drizzle"
}
]
}

View File

@@ -180,8 +180,6 @@ export function postgresAdapter(args: Args): DatabaseAdapterObj<PostgresAdapter>
find,
findGlobal,
findGlobalVersions,
updateJobs,
// @ts-expect-error - vestiges of when tsconfig was not strict. Feel free to improve
findOne,
findVersions,
indexes: new Set<string>(),
@@ -199,6 +197,7 @@ export function postgresAdapter(args: Args): DatabaseAdapterObj<PostgresAdapter>
queryDrafts,
rawRelations: {},
rawTables: {},
updateJobs,
// @ts-expect-error - vestiges of when tsconfig was not strict. Feel free to improve
rejectInitializing,
requireDrizzleKit,

View File

@@ -41,24 +41,26 @@ import {
updateVersion,
upsert,
} from '@payloadcms/drizzle'
import {
columnToCodeConverter,
convertPathToJSONTraversal,
countDistinct,
createJSONQuery,
defaultDrizzleSnapshot,
deleteWhere,
dropDatabase,
execute,
init,
insert,
requireDrizzleKit,
} from '@payloadcms/drizzle/sqlite'
import { like, notLike } from 'drizzle-orm'
import { createDatabaseAdapter, defaultBeginTransaction } from 'payload'
import { fileURLToPath } from 'url'
import type { Args, SQLiteAdapter } from './types.js'
import { columnToCodeConverter } from './columnToCodeConverter.js'
import { connect } from './connect.js'
import { countDistinct } from './countDistinct.js'
import { convertPathToJSONTraversal } from './createJSONQuery/convertPathToJSONTraversal.js'
import { createJSONQuery } from './createJSONQuery/index.js'
import { defaultDrizzleSnapshot } from './defaultSnapshot.js'
import { deleteWhere } from './deleteWhere.js'
import { dropDatabase } from './dropDatabase.js'
import { execute } from './execute.js'
import { init } from './init.js'
import { insert } from './insert.js'
import { requireDrizzleKit } from './requireDrizzleKit.js'
const filename = fileURLToPath(import.meta.url)
@@ -69,8 +71,8 @@ export function sqliteAdapter(args: Args): DatabaseAdapterObj<SQLiteAdapter> {
function adapter({ payload }: { payload: Payload }) {
const migrationDir = findMigrationDir(args.migrationDir)
let resolveInitializing
let rejectInitializing
let resolveInitializing: () => void = () => {}
let rejectInitializing: () => void = () => {}
const initializing = new Promise<void>((res, rej) => {
resolveInitializing = res
@@ -131,7 +133,6 @@ export function sqliteAdapter(args: Args): DatabaseAdapterObj<SQLiteAdapter> {
updateJobs,
updateMany,
versionsSuffix: args.versionsSuffix || '_v',
// DatabaseAdapter
beginTransaction: args.transactionOptions ? beginTransaction : defaultBeginTransaction(),
commitTransaction,
@@ -166,7 +167,6 @@ export function sqliteAdapter(args: Args): DatabaseAdapterObj<SQLiteAdapter> {
find,
findGlobal,
findGlobalVersions,
// @ts-expect-error - vestiges of when tsconfig was not strict. Feel free to improve
findOne,
findVersions,
indexes: new Set<string>(),
@@ -182,10 +182,8 @@ export function sqliteAdapter(args: Args): DatabaseAdapterObj<SQLiteAdapter> {
packageName: '@payloadcms/db-sqlite',
payload,
queryDrafts,
// @ts-expect-error - vestiges of when tsconfig was not strict. Feel free to improve
rejectInitializing,
requireDrizzleKit,
// @ts-expect-error - vestiges of when tsconfig was not strict. Feel free to improve
resolveInitializing,
rollbackTransaction,
updateGlobal,

View File

@@ -1,5 +1,6 @@
import type { Client, Config, ResultSet } from '@libsql/client'
import type { extendDrizzleTable, Operators } from '@payloadcms/drizzle'
import type { BaseSQLiteAdapter, BaseSQLiteArgs } from '@payloadcms/drizzle/sqlite'
import type { BuildQueryJoinAliases, DrizzleAdapter } from '@payloadcms/drizzle/types'
import type { DrizzleConfig, Relation, Relations, SQL } from 'drizzle-orm'
import type { LibSQLDatabase } from 'drizzle-orm/libsql'
@@ -56,23 +57,7 @@ export type Args = {
*/
blocksAsJSON?: boolean
client: Config
/** Generated schema from payload generate:db-schema file path */
generateSchemaOutputFile?: string
idType?: 'number' | 'uuid'
localesSuffix?: string
logger?: DrizzleConfig['logger']
migrationDir?: string
prodMigrations?: {
down: (args: MigrateDownArgs) => Promise<void>
name: string
up: (args: MigrateUpArgs) => Promise<void>
}[]
push?: boolean
relationshipsSuffix?: string
schemaName?: string
transactionOptions?: false | SQLiteTransactionConfig
versionsSuffix?: string
}
} & BaseSQLiteArgs
export type GenericColumns = {
[x: string]: AnySQLiteColumn
@@ -142,45 +127,11 @@ type ResolveSchemaType<T> = 'schema' extends keyof T
type Drizzle = { $client: Client } & LibSQLDatabase<ResolveSchemaType<GeneratedDatabaseSchema>>
export type SQLiteAdapter = {
afterSchemaInit: SQLiteSchemaHook[]
autoIncrement: boolean
beforeSchemaInit: SQLiteSchemaHook[]
client: Client
clientConfig: Args['client']
countDistinct: CountDistinct
defaultDrizzleSnapshot: any
deleteWhere: DeleteWhere
drizzle: Drizzle
dropDatabase: DropDatabase
execute: Execute<unknown>
/**
* An object keyed on each table, with a key value pair where the constraint name is the key, followed by the dot-notation field name
* Used for returning properly formed errors from unique fields
*/
fieldConstraints: Record<string, Record<string, string>>
idType: Args['idType']
initializing: Promise<void>
insert: Insert
localesSuffix?: string
logger: DrizzleConfig['logger']
operators: Operators
prodMigrations?: {
down: (args: MigrateDownArgs) => Promise<void>
name: string
up: (args: MigrateUpArgs) => Promise<void>
}[]
push: boolean
rejectInitializing: () => void
relations: Record<string, GenericRelation>
relationshipsSuffix?: string
resolveInitializing: () => void
schema: Record<string, GenericRelation | GenericTable>
schemaName?: Args['schemaName']
tableNameMap: Map<string, string>
tables: Record<string, GenericTable>
transactionOptions: SQLiteTransactionConfig
versionsSuffix?: string
} & SQLiteDrizzleAdapter
} & BaseSQLiteAdapter &
SQLiteDrizzleAdapter
export type IDType = 'integer' | 'numeric' | 'text'

View File

@@ -178,8 +178,6 @@ export function vercelPostgresAdapter(args: Args = {}): DatabaseAdapterObj<Verce
findDistinct,
findGlobal,
findGlobalVersions,
readReplicaOptions: args.readReplicas,
// @ts-expect-error - vestiges of when tsconfig was not strict. Feel free to improve
findOne,
findVersions,
init,
@@ -194,6 +192,7 @@ export function vercelPostgresAdapter(args: Args = {}): DatabaseAdapterObj<Verce
packageName: '@payloadcms/db-vercel-postgres',
payload,
queryDrafts,
readReplicaOptions: args.readReplicas,
// @ts-expect-error - vestiges of when tsconfig was not strict. Feel free to improve
rejectInitializing,
requireDrizzleKit,

View File

@@ -30,6 +30,11 @@
"types": "./src/exports/postgres.ts",
"default": "./src/exports/postgres.ts"
},
"./sqlite": {
"import": "./src/exports/sqlite.ts",
"types": "./src/exports/sqlite.ts",
"default": "./src/exports/sqlite.ts"
},
"./types": {
"import": "./src/exports/types-deprecated.ts",
"types": "./src/exports/types-deprecated.ts",
@@ -82,6 +87,11 @@
"types": "./dist/exports/postgres.d.ts",
"default": "./dist/exports/postgres.js"
},
"./sqlite": {
"import": "./dist/exports/sqlite.js",
"types": "./dist/exports/sqlite.d.ts",
"default": "./dist/exports/sqlite.js"
},
"./types": {
"import": "./dist/exports/types-deprecated.js",
"types": "./dist/exports/types-deprecated.d.ts",

View File

@@ -23,7 +23,7 @@ export async function createGlobalVersion<T extends TypeWithID>(
updatedAt,
versionData,
}: CreateGlobalVersionArgs,
) {
): Promise<TypeWithVersion<T>> {
const db = await getTransaction(this, req)
const global = this.payload.globals.config.find(({ slug }) => slug === globalSlug)

View File

@@ -24,7 +24,7 @@ export async function createVersion<T extends TypeWithID>(
updatedAt,
versionData,
}: CreateVersionArgs<T>,
) {
): Promise<TypeWithVersion<T>> {
const db = await getTransaction(this, req)
const collection = this.payload.collections[collectionSlug].config
const defaultTableName = toSnakeCase(collection.slug)

View File

@@ -0,0 +1,12 @@
export { columnToCodeConverter } from '../sqlite/columnToCodeConverter.js'
export { countDistinct } from '../sqlite/countDistinct.js'
export { convertPathToJSONTraversal } from '../sqlite/createJSONQuery/convertPathToJSONTraversal.js'
export { createJSONQuery } from '../sqlite/createJSONQuery/index.js'
export { defaultDrizzleSnapshot } from '../sqlite/defaultSnapshot.js'
export { deleteWhere } from '../sqlite/deleteWhere.js'
export { dropDatabase } from '../sqlite/dropDatabase.js'
export { execute } from '../sqlite/execute.js'
export { init } from '../sqlite/init.js'
export { insert } from '../sqlite/insert.js'
export { requireDrizzleKit } from '../sqlite/requireDrizzleKit.js'
export * from '../sqlite/types.js'

View File

@@ -9,7 +9,7 @@ import { findMany } from './find/findMany.js'
export async function findOne<T extends TypeWithID>(
this: DrizzleAdapter,
{ collection, draftsEnabled, joins, locale, req, select, where }: FindOneArgs,
): Promise<T> {
): Promise<null | T> {
const collectionConfig: SanitizedCollectionConfig = this.payload.collections[collection].config
const tableName = this.tableNameMap.get(toSnakeCase(collectionConfig.slug))

View File

@@ -1,4 +1,4 @@
import type { ColumnToCodeConverter } from '@payloadcms/drizzle/types'
import type { ColumnToCodeConverter } from '../types.js'
export const columnToCodeConverter: ColumnToCodeConverter = ({
adapter,

View File

@@ -2,10 +2,10 @@ import type { SQLiteSelect } from 'drizzle-orm/sqlite-core'
import { count, sql } from 'drizzle-orm'
import type { CountDistinct, SQLiteAdapter } from './types.js'
import type { BaseSQLiteAdapter, CountDistinct } from './types.js'
export const countDistinct: CountDistinct = async function countDistinct(
this: SQLiteAdapter,
this: BaseSQLiteAdapter,
{ column, db, joins, tableName, where },
) {
// When we don't have any joins - use a simple COUNT(*) query.

View File

@@ -1,4 +1,4 @@
import type { CreateJSONQueryArgs } from '@payloadcms/drizzle/types'
import type { CreateJSONQueryArgs } from '../../types.js'
type FromArrayArgs = {
isRoot?: true
@@ -74,7 +74,7 @@ export const createJSONQuery = ({
treatAsArray,
value,
}: CreateJSONQueryArgs): string => {
if (treatAsArray?.includes(pathSegments[1]!) && table) {
if (treatAsArray?.includes(pathSegments[1]) && table) {
return fromArray({
operator,
pathSegments,

View File

@@ -1,9 +1,9 @@
import type { DeleteWhere, SQLiteAdapter } from './types.js'
import type { BaseSQLiteAdapter, DeleteWhere } from './types.js'
export const deleteWhere: DeleteWhere = async function (
// Here 'this' is not a parameter. See:
// https://www.typescriptlang.org/docs/handbook/2/classes.html#this-parameters
this: SQLiteAdapter,
this: BaseSQLiteAdapter,
{ db, tableName, where },
) {
const table = this.tables[tableName]

View File

@@ -1,15 +1,15 @@
import type { Row } from '@libsql/client'
import type { DropDatabase, SQLiteAdapter } from './types.js'
import type { BaseSQLiteAdapter, DropDatabase } from './types.js'
const getTables = (adapter: SQLiteAdapter) => {
const getTables = (adapter: BaseSQLiteAdapter) => {
return adapter.client.execute(`SELECT name
FROM sqlite_master
WHERE type = 'table'
AND name NOT LIKE 'sqlite_%';`)
}
const dropTables = (adapter: SQLiteAdapter, rows: Row[]) => {
const dropTables = (adapter: BaseSQLiteAdapter, rows: Row[]) => {
const multi = `
PRAGMA foreign_keys = OFF;\n
${rows.map(({ name }) => `DROP TABLE IF EXISTS ${name as string}`).join(';\n ')};\n

View File

@@ -3,13 +3,13 @@ import { sql } from 'drizzle-orm'
import type { Execute } from './types.js'
export const execute: Execute<any> = function execute({ db, drizzle, raw, sql: statement }) {
const executeFrom = (db ?? drizzle)!
const executeFrom = (db ?? drizzle)
if (raw) {
const result = executeFrom.run(sql.raw(raw))
return result
} else {
const result = executeFrom.run(statement!)
const result = executeFrom.run(statement)
return result
}
}

View File

@@ -1,14 +1,15 @@
import type { DrizzleAdapter } from '@payloadcms/drizzle/types'
import type { Init } from 'payload'
import { buildDrizzleRelations, buildRawSchema, executeSchemaHooks } from '@payloadcms/drizzle'
import type { SQLiteAdapter } from './types.js'
import type { DrizzleAdapter } from '../types.js'
import type { BaseSQLiteAdapter } from './types.js'
import { buildDrizzleRelations } from '../schema/buildDrizzleRelations.js'
import { buildRawSchema } from '../schema/buildRawSchema.js'
import { executeSchemaHooks } from '../utilities/executeSchemaHooks.js'
import { buildDrizzleTable } from './schema/buildDrizzleTable.js'
import { setColumnID } from './schema/setColumnID.js'
export const init: Init = async function init(this: SQLiteAdapter) {
export const init: Init = async function init(this: BaseSQLiteAdapter) {
let locales: string[] | undefined
this.rawRelations = {}
@@ -28,7 +29,7 @@ export const init: Init = async function init(this: SQLiteAdapter) {
await executeSchemaHooks({ type: 'beforeSchemaInit', adapter: this })
for (const tableName in this.rawTables) {
buildDrizzleTable({ adapter, locales: locales!, rawTable: this.rawTables[tableName]! })
buildDrizzleTable({ adapter, locales, rawTable: this.rawTables[tableName] })
}
buildDrizzleRelations({

View File

@@ -1,9 +1,9 @@
import type { Insert, SQLiteAdapter } from './types.js'
import type { BaseSQLiteAdapter, Insert } from './types.js'
export const insert: Insert = async function (
// Here 'this' is not a parameter. See:
// https://www.typescriptlang.org/docs/handbook/2/classes.html#this-parameters
this: SQLiteAdapter,
this: BaseSQLiteAdapter,
{ db, onConflictDoUpdate, tableName, values },
): Promise<Record<string, unknown>[]> {
const table = this.tables[tableName]

View File

@@ -1,7 +1,7 @@
import type { RequireDrizzleKit } from '@payloadcms/drizzle/types'
import { createRequire } from 'module'
import type { RequireDrizzleKit } from '../types.js'
const require = createRequire(import.meta.url)
export const requireDrizzleKit: RequireDrizzleKit = () => {

View File

@@ -1,4 +1,3 @@
import type { BuildDrizzleTable, RawColumn } from '@payloadcms/drizzle/types'
import type { ForeignKeyBuilder, IndexBuilder } from 'drizzle-orm/sqlite-core'
import { sql } from 'drizzle-orm'
@@ -13,6 +12,8 @@ import {
} from 'drizzle-orm/sqlite-core'
import { v4 as uuidv4 } from 'uuid'
import type { BuildDrizzleTable, RawColumn } from '../../types.js'
const rawColumnBuilderMap: Partial<Record<RawColumn['type'], any>> = {
integer,
numeric,

View File

@@ -1,6 +1,5 @@
import type { SetColumnID } from '@payloadcms/drizzle/types'
import type { SQLiteAdapter } from '../types.js'
import type { SetColumnID } from '../../types.js'
import type { BaseSQLiteAdapter } from '../types.js'
export const setColumnID: SetColumnID = ({ adapter, columns, fields }) => {
const idField = fields.find((field) => field.name === 'id')
@@ -38,7 +37,7 @@ export const setColumnID: SetColumnID = ({ adapter, columns, fields }) => {
columns.id = {
name: 'id',
type: 'integer',
autoIncrement: (adapter as unknown as SQLiteAdapter).autoIncrement,
autoIncrement: (adapter as unknown as BaseSQLiteAdapter).autoIncrement,
primaryKey: true,
}

View File

@@ -0,0 +1,244 @@
import type { Client, ResultSet } from '@libsql/client'
import type { DrizzleConfig, Relation, Relations, SQL } from 'drizzle-orm'
import type { DrizzleD1Database } from 'drizzle-orm/d1'
import type { LibSQLDatabase } from 'drizzle-orm/libsql'
import type {
AnySQLiteColumn,
SQLiteColumn,
SQLiteInsertOnConflictDoUpdateConfig,
SQLiteTableWithColumns,
SQLiteTransactionConfig,
} from 'drizzle-orm/sqlite-core'
import type { SQLiteRaw } from 'drizzle-orm/sqlite-core/query-builders/raw'
import type { Payload, PayloadRequest } from 'payload'
import type { Operators } from '../queries/operatorMap.js'
import type { BuildQueryJoinAliases, DrizzleAdapter } from '../types.js'
import type { extendDrizzleTable } from '../utilities/extendDrizzleTable.js'
type SQLiteSchema = {
relations: Record<string, GenericRelation>
tables: Record<string, SQLiteTableWithColumns<any>>
}
type SQLiteSchemaHookArgs = {
extendTable: typeof extendDrizzleTable
schema: SQLiteSchema
}
export type SQLiteSchemaHook = (args: SQLiteSchemaHookArgs) => Promise<SQLiteSchema> | SQLiteSchema
export type BaseSQLiteArgs = {
/**
* Transform the schema after it's built.
* You can use it to customize the schema with features that aren't supported by Payload.
* Examples may include: composite indices, generated columns, vectors
*/
afterSchemaInit?: SQLiteSchemaHook[]
/**
* Enable this flag if you want to thread your own ID to create operation data, for example:
* ```ts
* // doc created with id 1
* const doc = await payload.create({ collection: 'posts', data: {id: 1, title: "my title"}})
* ```
*/
allowIDOnCreate?: boolean
/**
* Enable [AUTOINCREMENT](https://www.sqlite.org/autoinc.html) for Primary Keys.
* This ensures that the same ID cannot be reused from previously deleted rows.
*/
autoIncrement?: boolean
/**
* Transform the schema before it's built.
* You can use it to preserve an existing database schema and if there are any collissions Payload will override them.
* To generate Drizzle schema from the database, see [Drizzle Kit introspection](https://orm.drizzle.team/kit-docs/commands#introspect--pull)
*/
beforeSchemaInit?: SQLiteSchemaHook[]
/** Generated schema from payload generate:db-schema file path */
generateSchemaOutputFile?: string
idType?: 'number' | 'uuid'
localesSuffix?: string
logger?: DrizzleConfig['logger']
migrationDir?: string
prodMigrations?: {
down: (args: MigrateDownArgs) => Promise<void>
name: string
up: (args: MigrateUpArgs) => Promise<void>
}[]
push?: boolean
relationshipsSuffix?: string
schemaName?: string
transactionOptions?: false | SQLiteTransactionConfig
versionsSuffix?: string
}
export type GenericColumns = {
[x: string]: AnySQLiteColumn
}
export type GenericTable = SQLiteTableWithColumns<{
columns: GenericColumns
dialect: string
name: string
schema: string
}>
export type GenericRelation = Relations<string, Record<string, Relation<string>>>
export type CountDistinct = (args: {
column?: SQLiteColumn<any>
db: LibSQLDatabase
joins: BuildQueryJoinAliases
tableName: string
where: SQL
}) => Promise<number>
export type DeleteWhere = (args: {
db: LibSQLDatabase
tableName: string
where: SQL
}) => Promise<void>
export type DropDatabase = (args: { adapter: BaseSQLiteAdapter }) => Promise<void>
export type Execute<T> = (args: {
db?: DrizzleD1Database | LibSQLDatabase
drizzle?: DrizzleD1Database | LibSQLDatabase
raw?: string
sql?: SQL<unknown>
}) => SQLiteRaw<Promise<T>> | SQLiteRaw<ResultSet>
export type Insert = (args: {
db: LibSQLDatabase
onConflictDoUpdate?: SQLiteInsertOnConflictDoUpdateConfig<any>
tableName: string
values: Record<string, unknown> | Record<string, unknown>[]
}) => Promise<Record<string, unknown>[]>
// Explicitly omit drizzle property for complete override in SQLiteAdapter, required in ts 5.5
type SQLiteDrizzleAdapter = Omit<
DrizzleAdapter,
| 'countDistinct'
| 'deleteWhere'
| 'drizzle'
| 'dropDatabase'
| 'execute'
| 'idType'
| 'insert'
| 'operators'
| 'relations'
>
export interface GeneratedDatabaseSchema {
schemaUntyped: Record<string, unknown>
}
type ResolveSchemaType<T> = 'schema' extends keyof T
? T['schema']
: GeneratedDatabaseSchema['schemaUntyped']
type Drizzle = { $client: Client } & LibSQLDatabase<ResolveSchemaType<GeneratedDatabaseSchema>>
export type BaseSQLiteAdapter = {
afterSchemaInit: SQLiteSchemaHook[]
autoIncrement: boolean
beforeSchemaInit: SQLiteSchemaHook[]
client: Client
countDistinct: CountDistinct
defaultDrizzleSnapshot: any
deleteWhere: DeleteWhere
dropDatabase: DropDatabase
execute: Execute<unknown>
/**
* An object keyed on each table, with a key value pair where the constraint name is the key, followed by the dot-notation field name
* Used for returning properly formed errors from unique fields
*/
fieldConstraints: Record<string, Record<string, string>>
idType: BaseSQLiteArgs['idType']
initializing: Promise<void>
insert: Insert
localesSuffix?: string
logger: DrizzleConfig['logger']
operators: Operators
prodMigrations?: {
down: (args: MigrateDownArgs) => Promise<void>
name: string
up: (args: MigrateUpArgs) => Promise<void>
}[]
push: boolean
rejectInitializing: () => void
relations: Record<string, GenericRelation>
relationshipsSuffix?: string
resolveInitializing: () => void
schema: Record<string, GenericRelation | GenericTable>
schemaName?: BaseSQLiteArgs['schemaName']
tableNameMap: Map<string, string>
tables: Record<string, GenericTable>
transactionOptions: SQLiteTransactionConfig
versionsSuffix?: string
} & SQLiteDrizzleAdapter
export type IDType = 'integer' | 'numeric' | 'text'
export type MigrateUpArgs = {
/**
* The SQLite Drizzle instance that you can use to execute SQL directly within the current transaction.
* @example
* ```ts
* import { type MigrateUpArgs, sql } from '@payloadcms/db-sqlite'
*
* export async function up({ db, payload, req }: MigrateUpArgs): Promise<void> {
* const { rows: posts } = await db.run(sql`SELECT * FROM posts`)
* }
* ```
*/
db: Drizzle
/**
* The Payload instance that you can use to execute Local API methods
* To use the current transaction you must pass `req` to arguments
* @example
* ```ts
* import { type MigrateUpArgs } from '@payloadcms/db-sqlite'
*
* export async function up({ db, payload, req }: MigrateUpArgs): Promise<void> {
* const posts = await payload.find({ collection: 'posts', req })
* }
* ```
*/
payload: Payload
/**
* The `PayloadRequest` object that contains the current transaction
*/
req: PayloadRequest
}
export type MigrateDownArgs = {
/**
* The SQLite Drizzle instance that you can use to execute SQL directly within the current transaction.
* @example
* ```ts
* import { type MigrateDownArgs, sql } from '@payloadcms/db-sqlite'
*
* export async function down({ db, payload, req }: MigrateDownArgs): Promise<void> {
* const { rows: posts } = await db.run(sql`SELECT * FROM posts`)
* }
* ```
*/
db: Drizzle
/**
* The Payload instance that you can use to execute Local API methods
* To use the current transaction you must pass `req` to arguments
* @example
* ```ts
* import { type MigrateDownArgs } from '@payloadcms/db-sqlite'
*
* export async function down({ db, payload, req }: MigrateDownArgs): Promise<void> {
* const posts = await payload.find({ collection: 'posts', req })
* }
* ```
*/
payload: Payload
/**
* The `PayloadRequest` object that contains the current transaction
*/
req: PayloadRequest
}

View File

@@ -26,7 +26,7 @@ export async function updateGlobalVersion<T extends TypeWithID>(
versionData,
where: whereArg,
}: UpdateGlobalVersionArgs<T>,
) {
): Promise<TypeWithVersion<T>> {
const db = await getTransaction(this, req)
const globalConfig: SanitizedGlobalConfig = this.payload.globals.config.find(
({ slug }) => slug === global,

View File

@@ -26,7 +26,7 @@ export async function updateVersion<T extends TypeWithID>(
versionData,
where: whereArg,
}: UpdateVersionArgs<T>,
) {
): Promise<TypeWithVersion<T>> {
const db = await getTransaction(this, req)
const collectionConfig: SanitizedCollectionConfig = this.payload.collections[collection].config
const whereToUse = whereArg || { id: { equals: id } }

View File

@@ -14,10 +14,12 @@ export const buildCreateMigration = ({
executeMethod,
filename,
sanitizeStatements,
sqlOnly,
}: {
executeMethod: string
filename: string
sanitizeStatements: (args: { sqlExecute: string; statements: string[] }) => string
sqlOnly?: boolean
}): CreateMigration => {
const dirname = path.dirname(filename)
return async function createMigration(
@@ -78,7 +80,9 @@ export const buildCreateMigration = ({
}
const sqlStatementsUp = await generateMigration(drizzleJsonBefore, drizzleJsonAfter)
const sqlStatementsDown = await generateMigration(drizzleJsonAfter, drizzleJsonBefore)
const sqlStatementsDown = sqlOnly
? []
: await generateMigration(drizzleJsonAfter, drizzleJsonBefore)
const sqlExecute = `await db.${executeMethod}(` + 'sql`'
if (sqlStatementsUp?.length) {
@@ -116,19 +120,22 @@ export const buildCreateMigration = ({
fs.writeFileSync(`${filePath}.json`, JSON.stringify(drizzleJsonAfter, null, 2))
}
const data = sqlOnly
? upSQL
: getMigrationTemplate({
downSQL: downSQL || ` // Migration code`,
imports,
packageName: payload.db.packageName,
upSQL: upSQL || ` // Migration code`,
})
const fullPath = sqlOnly ? `${filePath}.sql` : `${filePath}.ts`
// write migration
fs.writeFileSync(
`${filePath}.ts`,
getMigrationTemplate({
downSQL: downSQL || ` // Migration code`,
imports,
packageName: payload.db.packageName,
upSQL: upSQL || ` // Migration code`,
}),
)
fs.writeFileSync(fullPath, data)
writeMigrationIndex({ migrationsDir: payload.db.migrationDir })
payload.logger.info({ msg: `Migration created at ${filePath}.ts` })
payload.logger.info({ msg: `Migration created at ${fullPath}` })
}
}

View File

@@ -151,6 +151,7 @@ export const rootEslintConfig = [
'../db-postgres/relationships-v2-v3.mjs',
'../db-postgres/scripts/renamePredefinedMigrations.ts',
'../db-sqlite/bundle.js',
'../db-d1-sqlite/bundle.js',
'../db-vercel-postgres/relationships-v2-v3.mjs',
'../db-vercel-postgres/scripts/renamePredefinedMigrations.ts',
'../plugin-cloud-storage/azure.d.ts',

View File

@@ -0,0 +1,10 @@
.tmp
**/.git
**/.hg
**/.pnp.*
**/.svn
**/.yarn/**
**/build
**/dist/**
**/node_modules
**/temp

View File

@@ -0,0 +1,15 @@
{
"$schema": "https://json.schemastore.org/swcrc",
"sourceMaps": true,
"jsc": {
"target": "esnext",
"parser": {
"syntax": "typescript",
"tsx": true,
"dts": true
}
},
"module": {
"type": "es6"
}
}

View File

@@ -0,0 +1,22 @@
MIT License
Copyright (c) 2018-2025 Payload CMS, Inc. <info@payloadcms.com>
Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
'Software'), to deal in the Software without restriction, including
without limitation the rights to use, copy, modify, merge, publish,
distribute, sublicense, and/or sell copies of the Software, and to
permit persons to whom the Software is furnished to do so, subject to
the following conditions:
The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

View File

@@ -0,0 +1,77 @@
{
"name": "@payloadcms/storage-r2",
"version": "3.48.0",
"description": "Payload storage adapter for Cloudflare R2",
"homepage": "https://payloadcms.com",
"repository": {
"type": "git",
"url": "https://github.com/payloadcms/payload.git",
"directory": "packages/storage-r2"
},
"license": "MIT",
"author": "Payload <dev@payloadcms.com> (https://payloadcms.com)",
"maintainers": [
{
"name": "Payload",
"email": "info@payloadcms.com",
"url": "https://payloadcms.com"
}
],
"sideEffects": false,
"type": "module",
"exports": {
".": {
"import": "./src/index.ts",
"types": "./src/index.ts",
"default": "./src/index.ts"
},
"./client": {
"import": "./src/exports/client.ts",
"types": "./src/exports/client.ts",
"default": "./src/exports/client.ts"
}
},
"main": "./src/index.ts",
"types": "./src/index.ts",
"files": [
"dist"
],
"scripts": {
"build": "pnpm build:types && pnpm build:swc",
"build:clean": "find . \\( -type d \\( -name build -o -name dist -o -name .cache \\) -o -type f -name tsconfig.tsbuildinfo \\) -exec rm -rf {} + && pnpm build",
"build:swc": "swc ./src -d ./dist --config-file .swcrc --strip-leading-paths",
"build:types": "tsc --emitDeclarationOnly --outDir dist",
"clean": "rimraf -g {dist,*.tsbuildinfo}",
"lint": "eslint .",
"lint:fix": "eslint . --fix",
"prepublishOnly": "pnpm clean && pnpm turbo build"
},
"dependencies": {
"@payloadcms/plugin-cloud-storage": "workspace:*"
},
"devDependencies": {
"payload": "workspace:*"
},
"peerDependencies": {
"payload": "workspace:*"
},
"engines": {
"node": "^18.20.2 || >=20.9.0"
},
"publishConfig": {
"exports": {
".": {
"import": "./dist/index.js",
"types": "./dist/index.d.ts",
"default": "./dist/index.js"
},
"./client": {
"import": "./dist/exports/client.js",
"types": "./dist/exports/client.d.ts",
"default": "./dist/exports/client.js"
}
},
"main": "./dist/index.js",
"types": "./dist/index.d.ts"
}
}

View File

@@ -0,0 +1,15 @@
import type { HandleDelete } from '@payloadcms/plugin-cloud-storage/types'
import path from 'path'
import type { R2Bucket } from './types.js'
interface Args {
bucket: R2Bucket
}
export const getHandleDelete = ({ bucket }: Args): HandleDelete => {
return async ({ doc: { prefix = '' }, filename }) => {
await bucket.delete(path.posix.join(prefix, filename))
}
}

View File

@@ -0,0 +1,21 @@
import type { HandleUpload } from '@payloadcms/plugin-cloud-storage/types'
import type { CollectionConfig } from 'payload'
import path from 'path'
import type { R2Bucket } from './types.js'
interface Args {
bucket: R2Bucket
collection: CollectionConfig
prefix?: string
}
export const getHandleUpload = ({ bucket, prefix = '' }: Args): HandleUpload => {
return async ({ data, file }) => {
// Read more: https://github.com/cloudflare/workers-sdk/issues/6047#issuecomment-2691217843
const buffer = process.env.NODE_ENV === 'development' ? new Blob([file.buffer]) : file.buffer
await bucket.put(path.posix.join(prefix, file.filename), buffer)
return data
}
}

View File

@@ -0,0 +1,89 @@
import type {
Adapter,
PluginOptions as CloudStoragePluginOptions,
CollectionOptions,
GeneratedAdapter,
} from '@payloadcms/plugin-cloud-storage/types'
import type { Config, Plugin, UploadCollectionSlug } from 'payload'
import { cloudStoragePlugin } from '@payloadcms/plugin-cloud-storage'
import type { R2Bucket } from './types.js'
import { getHandleDelete } from './handleDelete.js'
import { getHandleUpload } from './handleUpload.js'
import { getHandler } from './staticHandler.js'
export interface R2StorageOptions {
bucket: R2Bucket
/**
* Collection options to apply the R2 adapter to.
*/
collections: Partial<Record<UploadCollectionSlug, Omit<CollectionOptions, 'adapter'> | true>>
enabled?: boolean
}
type R2StoragePlugin = (r2StorageArgs: R2StorageOptions) => Plugin
export const r2Storage: R2StoragePlugin =
(r2StorageOptions) =>
(incomingConfig: Config): Config => {
const adapter = r2StorageInternal(r2StorageOptions)
const isPluginDisabled = r2StorageOptions.enabled === false
if (isPluginDisabled) {
return incomingConfig
}
// Add adapter to each collection option object
const collectionsWithAdapter: CloudStoragePluginOptions['collections'] = Object.entries(
r2StorageOptions.collections,
).reduce(
(acc, [slug, collOptions]) => ({
...acc,
[slug]: {
...(collOptions === true ? {} : collOptions),
adapter,
},
}),
{} as Record<string, CollectionOptions>,
)
// Set disableLocalStorage: true for collections specified in the plugin options
const config = {
...incomingConfig,
collections: (incomingConfig.collections || []).map((collection) => {
if (!collectionsWithAdapter[collection.slug]) {
return collection
}
return {
...collection,
upload: {
...(typeof collection.upload === 'object' ? collection.upload : {}),
disableLocalStorage: true,
},
}
}),
}
return cloudStoragePlugin({
collections: collectionsWithAdapter,
})(config)
}
function r2StorageInternal({ bucket }: R2StorageOptions): Adapter {
return ({ collection, prefix }): GeneratedAdapter => {
return {
name: 'r2',
handleDelete: getHandleDelete({ bucket }),
handleUpload: getHandleUpload({
bucket,
collection,
prefix,
}),
staticHandler: getHandler({ bucket, collection }),
}
}
}

View File

@@ -0,0 +1,36 @@
import type { StaticHandler } from '@payloadcms/plugin-cloud-storage/types'
import type { CollectionConfig } from 'payload'
import path from 'path'
import type { R2Bucket } from './types.js'
interface Args {
bucket: R2Bucket
collection: CollectionConfig
prefix?: string
}
const isMiniflare = process.env.NODE_ENV === 'development'
export const getHandler = ({ bucket, prefix = '' }: Args): StaticHandler => {
return async (req, { params: { filename } }) => {
// Due to https://github.com/cloudflare/workers-sdk/issues/6047
// We cannot send a Headers instance to Miniflare
const obj = await bucket?.get(path.posix.join(prefix, filename), {
range: isMiniflare ? undefined : req.headers,
})
if (obj?.body == undefined) {
return new Response(null, { status: 404 })
}
const headers = new Headers()
if (!isMiniflare) {
obj.writeHttpMetadata(headers)
}
return obj.etag === (req.headers.get('etag') || req.headers.get('if-none-match'))
? new Response(null, { headers, status: 304 })
: new Response(obj.body, { headers, status: 200 })
}
}

View File

@@ -0,0 +1,26 @@
export interface R2Bucket {
createMultipartUpload(key: string, options?: any): Promise<any>
delete(keys: string | string[]): Promise<void>
get(
key: string,
options: {
onlyIf: any | Headers
} & any,
): Promise<any | null>
get(key: string, options?: any): Promise<any | null>
head(key: string): Promise<any>
list(options?: any): Promise<any>
put(
key: string,
value: ArrayBuffer | ArrayBufferView | Blob | null | ReadableStream | string,
options?: {
onlyIf: any
} & any,
): Promise<any | null>
put(
key: string,
value: ArrayBuffer | ArrayBufferView | Blob | null | ReadableStream | string,
options?: any,
): Promise<any>
resumeMultipartUpload(key: string, uploadId: string): any
}

View File

@@ -0,0 +1,15 @@
{
"extends": "../../tsconfig.json",
"compilerOptions": {
"composite": true, // Make sure typescript knows that this module depends on their references
"noEmit": false /* Do not emit outputs. */,
"emitDeclarationOnly": true,
"outDir": "./dist" /* Specify an output folder for all emitted files. */,
"rootDir": "./src" /* Specify the root folder within your source files. */,
// Do not include DOM and DOM.Iterable as this is a server-only package.
"lib": ["ES2022"],
},
"exclude": ["dist", "node_modules"],
"include": ["src/**/*.ts", "src/**/*.tsx", "src/**/*.d.ts", "src/**/*.json"],
"references": [{ "path": "../payload" }, { "path": "../plugin-cloud-storage" }]
}

638
pnpm-lock.yaml generated

File diff suppressed because it is too large Load Diff

View File

@@ -113,6 +113,11 @@ export const allDatabaseAdapters = {
process.env.POSTGRES_URL || 'postgresql://postgres:postgres@127.0.0.1:54322/postgres',
},
})`,
d1: `
import { sqliteD1Adapter } from '@payloadcms/db-d1-sqlite'
export const databaseAdapter = sqliteD1Adapter({ binding: global.d1 })
`,
}
/**

View File

@@ -1,3 +1,5 @@
import { D1DatabaseAPI } from '@miniflare/d1'
import { createSQLiteDB } from '@miniflare/shared'
import dotenv from 'dotenv'
import { MongoMemoryReplSet } from 'mongodb-memory-server'
dotenv.config()
@@ -22,6 +24,11 @@ export default async () => {
process.env.NODE_OPTIONS = '--no-deprecation'
process.env.DISABLE_PAYLOAD_HMR = 'true'
if (process.env.PAYLOAD_DATABASE === 'd1' && !global.d1) {
process.env.PAYLOAD_DROP_DATABASE = 'false'
console.log('Starting memory D1 db...')
global.d1 = new D1DatabaseAPI(await createSQLiteDB(':memory'))
}
if (
(!process.env.PAYLOAD_DATABASE ||
['firestore', 'mongodb'].includes(process.env.PAYLOAD_DATABASE)) &&

View File

@@ -10,6 +10,7 @@ import nodemailer from 'nodemailer'
import { generateDatabaseAdapter } from './generateDatabaseAdapter.js'
process.env.PAYLOAD_DISABLE_ADMIN = 'true'
process.env.PAYLOAD_DROP_DATABASE = 'true'
process.env.PAYLOAD_PUBLIC_CLOUD_STORAGE_ADAPTER = 's3'

View File

@@ -25,8 +25,11 @@
"@aws-sdk/client-s3": "^3.614.0",
"@azure/storage-blob": "^12.11.0",
"@date-fns/tz": "1.2.0",
"@miniflare/d1": "2.14.4",
"@miniflare/shared": "2.14.4",
"@next/env": "15.4.4",
"@payloadcms/admin-bar": "workspace:*",
"@payloadcms/db-d1-sqlite": "workspace:*",
"@payloadcms/db-mongodb": "workspace:*",
"@payloadcms/db-postgres": "workspace:*",
"@payloadcms/db-sqlite": "workspace:*",
@@ -66,6 +69,7 @@
"@types/react": "19.1.8",
"@types/react-dom": "19.1.6",
"babel-plugin-react-compiler": "19.1.0-rc.2",
"better-sqlite3": "11.10.0",
"comment-json": "^4.2.3",
"create-payload-app": "workspace:*",
"csv-parse": "^5.6.0",

View File

@@ -23,6 +23,7 @@ export const packagePublishList = [
'db-mongodb',
'db-postgres',
'db-sqlite',
'db-d1-sqlite',
'db-vercel-postgres',
// Adapters
@@ -31,6 +32,7 @@ export const packagePublishList = [
// Storage
'storage-s3',
'storage-r2',
'storage-azure',
'storage-gcs',
'storage-vercel-blob',