Merge commit '36c66dd290d3ce6eb1ccd310d0c658d4a32bb8eb' as 'repos/effect'

This commit is contained in:
-Puter
2026-07-19 03:25:10 +05:30
parent dd1071cc10
commit 2daf979036
2214 changed files with 673090 additions and 0 deletions

View File

@@ -0,0 +1,268 @@
/**
* Connects Effect SQL to SQLite when running on Bun, using `bun:sqlite`.
*
* This module opens a SQLite database and exposes it as both `SqliteClient` and
* the generic Effect SQL client. It serializes access to the database, enables
* WAL mode unless disabled, and supports database export and extension loading.
* Streaming queries and `updateValues` are not supported by this driver.
*
* @since 4.0.0
*/
import { Database } from "bun:sqlite"
import * as Config from "effect/Config"
import * as Context from "effect/Context"
import * as Effect from "effect/Effect"
import * as Fiber from "effect/Fiber"
import { identity } from "effect/Function"
import * as Layer from "effect/Layer"
import * as Scope from "effect/Scope"
import * as Semaphore from "effect/Semaphore"
import * as Stream from "effect/Stream"
import * as Reactivity from "effect/unstable/reactivity/Reactivity"
import * as Client from "effect/unstable/sql/SqlClient"
import type { Connection } from "effect/unstable/sql/SqlConnection"
import { classifySqliteError, SqlError } from "effect/unstable/sql/SqlError"
import * as Statement from "effect/unstable/sql/Statement"
const ATTR_DB_SYSTEM_NAME = "db.system.name"
const classifyError = (cause: unknown, message: string, operation: string) =>
classifySqliteError(cause, { message, operation })
/**
* Runtime type identifier used to mark Bun `SqliteClient` values.
*
* @category type IDs
* @since 4.0.0
*/
export const TypeId: TypeId = "~@effect/sql-sqlite-bun/SqliteClient"
/**
* Type-level identifier used to mark Bun `SqliteClient` values.
*
* @category type IDs
* @since 4.0.0
*/
export type TypeId = "~@effect/sql-sqlite-bun/SqliteClient"
/**
* Bun SQLite client service, extending `SqlClient` with database export and extension loading helpers. `updateValues` is not supported.
*
* @category models
* @since 4.0.0
*/
export interface SqliteClient extends Client.SqlClient {
readonly [TypeId]: TypeId
readonly config: SqliteClientConfig
readonly export: Effect.Effect<Uint8Array, SqlError>
readonly loadExtension: (path: string) => Effect.Effect<void, SqlError>
/** Not supported in sqlite */
readonly updateValues: never
}
/**
* Service tag for the Bun SQLite client service.
*
* **When to use**
*
* Use to access or provide a Bun SQLite client through the Effect context.
*
* @category services
* @since 4.0.0
*/
export const SqliteClient = Context.Service<SqliteClient>("@effect/sql-sqlite-bun/Client")
/**
* Configuration for a Bun SQLite client, including filename, open mode flags, WAL behavior, span attributes, and query/result name transforms.
*
* @category models
* @since 4.0.0
*/
export interface SqliteClientConfig {
readonly filename: string
readonly readonly?: boolean | undefined
readonly create?: boolean | undefined
readonly readwrite?: boolean | undefined
readonly disableWAL?: boolean | undefined
readonly spanAttributes?: Record<string, unknown> | undefined
readonly transformResultNames?: ((str: string) => string) | undefined
readonly transformQueryNames?: ((str: string) => string) | undefined
}
interface SqliteConnection extends Connection {
readonly export: Effect.Effect<Uint8Array, SqlError>
readonly loadExtension: (path: string) => Effect.Effect<void, SqlError>
}
/**
* Creates a scoped Bun SQLite client for a database file, enabling WAL by default and serializing access. Streaming queries are not implemented.
*
* @category constructors
* @since 4.0.0
*/
export const make = (
options: SqliteClientConfig
): Effect.Effect<SqliteClient, never, Scope.Scope | Reactivity.Reactivity> =>
Effect.gen(function*() {
const compiler = Statement.makeCompilerSqlite(options.transformQueryNames)
const transformRows = options.transformResultNames ?
Statement.defaultTransforms(
options.transformResultNames
).array :
undefined
const makeConnection = Effect.gen(function*() {
const db = new Database(options.filename, {
readonly: options.readonly,
readwrite: options.readwrite ?? true,
create: options.create ?? true
} as any)
yield* Effect.addFinalizer(() => Effect.sync(() => db.close()))
if (options.disableWAL !== true) {
db.run("PRAGMA journal_mode = WAL;")
}
const prepare = (sql: string, useSafeIntegers: boolean) => {
const statement = db.query(sql)
// @ts-ignore bun-types missing safeIntegers method, fixed in https://github.com/oven-sh/bun/pull/26627
statement.safeIntegers(useSafeIntegers)
return statement
}
const run = (
sql: string,
params: ReadonlyArray<unknown> = []
) =>
Effect.withFiber<Array<any>, SqlError>((fiber) => {
const useSafeIntegers = Context.get(fiber.context, Client.SafeIntegers)
try {
return Effect.succeed((prepare(sql, useSafeIntegers).all(...(params as any)) ?? []) as Array<any>)
} catch (cause) {
return Effect.fail(new SqlError({ reason: classifyError(cause, "Failed to execute statement", "execute") }))
}
})
const runValues = (
sql: string,
params: ReadonlyArray<unknown> = []
) =>
Effect.withFiber<Array<any>, SqlError>((fiber) => {
const useSafeIntegers = Context.get(fiber.context, Client.SafeIntegers)
try {
return Effect.succeed((prepare(sql, useSafeIntegers).values(...(params as any)) ?? []) as Array<any>)
} catch (cause) {
return Effect.fail(
new SqlError({ reason: classifyError(cause, "Failed to execute statement", "executeValues") })
)
}
})
return identity<SqliteConnection>({
execute(sql, params, transformRows) {
return transformRows
? Effect.map(run(sql, params), transformRows)
: run(sql, params)
},
executeRaw(sql, params) {
return run(sql, params)
},
executeValues(sql, params) {
return runValues(sql, params)
},
executeValuesUnprepared(sql, params) {
return runValues(sql, params)
},
executeUnprepared(sql, params, transformRows) {
return this.execute(sql, params, transformRows)
},
executeStream(_sql, _params) {
return Stream.die("executeStream not implemented")
},
export: Effect.try({
try: () => db.serialize(),
catch: (cause) => new SqlError({ reason: classifyError(cause, "Failed to export database", "export") })
}),
loadExtension: (path) =>
Effect.try({
try: () => db.loadExtension(path),
catch: (cause) =>
new SqlError({ reason: classifyError(cause, "Failed to load extension", "loadExtension") })
})
})
})
const semaphore = yield* Semaphore.make(1)
const connection = yield* makeConnection
const acquirer = semaphore.withPermits(1)(Effect.succeed(connection))
const transactionAcquirer = Effect.uninterruptibleMask((restore) => {
const fiber = Fiber.getCurrent()!
const scope = Context.getUnsafe(fiber.context, Scope.Scope)
return Effect.as(
Effect.tap(
restore(semaphore.take(1)),
() => Scope.addFinalizer(scope, semaphore.release(1))
),
connection
)
})
return Object.assign(
(yield* Client.make({
acquirer,
compiler,
transactionAcquirer,
spanAttributes: [
...(options.spanAttributes ? Object.entries(options.spanAttributes) : []),
[ATTR_DB_SYSTEM_NAME, "sqlite"]
],
transformRows
})) as SqliteClient,
{
[TypeId]: TypeId as TypeId,
config: options,
export: Effect.flatMap(acquirer, (_) => _.export),
loadExtension: (path: string) => Effect.flatMap(acquirer, (_) => _.loadExtension(path))
}
)
})
/**
* Creates a layer from a `Config`-wrapped Bun SQLite client configuration, providing both `SqliteClient` and `SqlClient`.
*
* @category layers
* @since 4.0.0
*/
export const layerConfig = (
config: Config.Wrap<SqliteClientConfig>
): Layer.Layer<SqliteClient | Client.SqlClient, Config.ConfigError> =>
Layer.effectContext(
Config.unwrap(config).pipe(
Effect.flatMap(make),
Effect.map((client) =>
Context.make(SqliteClient, client).pipe(
Context.add(Client.SqlClient, client)
)
)
)
).pipe(Layer.provide(Reactivity.layer))
/**
* Creates a layer from a concrete Bun SQLite client configuration, providing both `SqliteClient` and `SqlClient`.
*
* @category layers
* @since 4.0.0
*/
export const layer = (
config: SqliteClientConfig
): Layer.Layer<SqliteClient | Client.SqlClient> =>
Layer.effectContext(
Effect.map(make(config), (client) =>
Context.make(SqliteClient, client).pipe(
Context.add(Client.SqlClient, client)
))
).pipe(Layer.provide(Reactivity.layer))

View File

@@ -0,0 +1,90 @@
/**
* Runs database migrations for Bun SQLite projects that use Effect SQL.
*
* This module re-exports the shared migration loaders and errors, then provides
* `run` and `layer` helpers that apply pending migration files with the current
* `SqlClient`. It does not add Bun-specific schema dump support; migration
* execution is handled by the shared SQL migrator.
*
* @since 4.0.0
*/
import type * as Effect from "effect/Effect"
import * as Layer from "effect/Layer"
import * as Migrator from "effect/unstable/sql/Migrator"
import type * as Client from "effect/unstable/sql/SqlClient"
import type { SqlError } from "effect/unstable/sql/SqlError"
/**
* @since 4.0.0
*/
export * from "effect/unstable/sql/Migrator"
/**
* Runs SQL migrations using the configured `SqlClient`, returning the migrations that were applied.
*
* @category constructors
* @since 4.0.0
*/
export const run: <R2 = never>(
options: Migrator.MigratorOptions<R2>
) => Effect.Effect<
ReadonlyArray<readonly [id: number, name: string]>,
Migrator.MigrationError | SqlError,
Client.SqlClient | R2
> = Migrator.make({
// dumpSchema(path, table) {
// const dump = (args: Array<string>) =>
// Effect.gen(function*() {
// const sql = yield* SqliteClient
// const dump = yield* pipe(
// Command.make("sqlite3", (sql as SqliteClient).config.filename, ...args),
// Command.string
// )
// return dump.replace(/^create table sqlite_sequence\(.*$/im, "")
// .replace(/\n{2,}/gm, "\n\n")
// .trim()
// }).pipe(
// Effect.mapError((error) => new Migrator.MigrationError({ kind: "Failed", message: error.message }))
// )
//
// const dumpSchema = dump([".schema"])
//
// const dumpMigrations = dump([
// "--cmd",
// `.mode insert ${table}`,
// `select * from ${table}`
// ])
//
// const dumpAll = Effect.map(
// Effect.all([dumpSchema, dumpMigrations], { concurrency: 2 }),
// ([schema, migrations]) => schema + "\n\n" + migrations
// )
//
// const dumpFile = (file: string) =>
// Effect.gen(function*() {
// const fs = yield* FileSystem
// const path = yield* Path
// const dump = yield* dumpAll
// yield* fs.makeDirectory(path.dirname(file), { recursive: true })
// yield* fs.writeFileString(file, dump)
// }).pipe(
// Effect.mapError((error) => new Migrator.MigrationError({ kind: "Failed", message: error.message }))
// )
//
// return dumpFile(path)
// }
})
/**
* Creates a layer that runs the configured SQL migrations during layer construction.
*
* @category constructors
* @since 4.0.0
*/
export const layer = <R>(
options: Migrator.MigratorOptions<R>
): Layer.Layer<
never,
SqlError | Migrator.MigrationError,
Client.SqlClient | R
> => Layer.effectDiscard(run(options))

View File

@@ -0,0 +1,15 @@
/**
* @since 4.0.0
*/
// @barrel: Auto-generated exports. Do not edit manually.
/**
* @since 4.0.0
*/
export * as SqliteClient from "./SqliteClient.ts"
/**
* @since 4.0.0
*/
export * as SqliteMigrator from "./SqliteMigrator.ts"