Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 6 additions & 7 deletions drizzle-orm/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -49,8 +49,8 @@
"peerDependencies": {
"@aws-sdk/client-rds-data": ">=3",
"@cloudflare/workers-types": ">=4",
"@effect/sql": "^0.48.5",
"@effect/sql-pg": "^0.49.7",
"@effect/sql-pg": "4.0.0-beta.31",
"effect": "4.0.0-beta.31",
"@electric-sql/pglite": ">=0.2.0",
"@libsql/client": ">=0.10.0",
"@libsql/client-wasm": ">=0.10.0",
Expand Down Expand Up @@ -180,7 +180,7 @@
"@upstash/redis": {
"optional": true
},
"@effect/sql": {
"effect": {
"optional": true
},
"@effect/sql-pg": {
Expand All @@ -200,9 +200,8 @@
"@arktype/attest": "^0.46.0",
"@aws-sdk/client-rds-data": "^3.914.0",
"@cloudflare/workers-types": "^4.20251004.0",
"@effect/sql": "^0.48.5",
"@effect/sql-pg": "^0.49.7",
"@effect/sql-sqlite-node": "^0.49.1",
"@effect/sql-pg": "4.0.0-beta.31",
"@effect/sql-sqlite-node": "4.0.0-beta.31",
"@electric-sql/pglite": "^0.2.12",
"@libsql/client": "^0.10.0",
"@libsql/client-wasm": "^0.10.0",
Expand Down Expand Up @@ -231,7 +230,7 @@
"better-sqlite3": "^11.9.1",
"bun-types": "^1.2.23",
"cpy": "^10.1.0",
"effect": "^3.19.8",
"effect": "4.0.0-beta.31",
"expo-sqlite": "^14.0.0",
"gel": "^2.0.0",
"glob": "^11.0.1",
Expand Down
45 changes: 35 additions & 10 deletions drizzle-orm/src/cache/core/cache-effect.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,29 @@
import * as Effect from 'effect/Effect';
import * as Layer from 'effect/Layer';
import * as Schema from 'effect/Schema';
import * as ServiceMap from 'effect/ServiceMap';
import { entityKind } from '~/entity.ts';
import { type Cache as DrizzleCache, type MutationOption, NoopCache } from './cache.ts';
import type { CacheConfig } from './types.ts';

export interface EffectCacheShape {
readonly strategy: () => 'explicit' | 'all';
readonly get: (
key: string,
tables: string[],
isTag: boolean,
isAutoInvalidate?: boolean,
) => Effect.Effect<any[] | undefined, EffectCacheError, never>;
readonly put: (
hashedQuery: string,
response: any,
tables: string[],
isTag: boolean,
config?: CacheConfig,
) => Effect.Effect<void, EffectCacheError, never>;
readonly onMutate: (params: MutationOption) => Effect.Effect<void, EffectCacheError, never>;
readonly cache: DrizzleCache;
}

/**
* Effect service for caching query results in Drizzle ORM.
Expand All @@ -24,26 +45,30 @@ import { type Cache as DrizzleCache, type MutationOption, NoopCache } from './ca
* );
* ```
*/
export class EffectCache extends Effect.Service<EffectCache>()('drizzle-orm/EffectCache', {
sync: () => make(new NoopCache()),
accessors: true,
export class EffectCache extends ServiceMap.Service<EffectCache>()('drizzle-orm/EffectCache', {
make: Effect.sync((): EffectCacheShape => make(new NoopCache())),
}) {
static readonly [entityKind]: string = this.Service._tag;
static readonly [entityKind]: string = 'drizzle-orm/EffectCache';

/**
* The default layer providing a no-op cache.
*/
static readonly Default = Layer.effect(EffectCache, EffectCache.make);

/**
* Creates an EffectCache instance from a standard Drizzle cache.
* Creates an EffectCacheShape from a standard Drizzle cache.
*
* @param cache - A Drizzle cache instance implementing the `Cache` interface.
* @returns A new EffectCache that delegates to the provided Drizzle cache.
* @returns A new EffectCacheShape that delegates to the provided Drizzle cache.
*
* @example
* ```ts
* const drizzleCache = new MyCustomCache();
* const effectCache = EffectCache.fromDrizzle(drizzleCache);
* ```
*/
static fromDrizzle(cache: DrizzleCache) {
return new EffectCache(make(cache));
static fromDrizzle(cache: DrizzleCache): EffectCacheShape {
return make(cache);
}

/**
Expand All @@ -66,7 +91,7 @@ export class EffectCache extends Effect.Service<EffectCache>()('drizzle-orm/Effe
}
}

function make(cache: DrizzleCache) {
function make(cache: DrizzleCache): EffectCacheShape {
const strategy = () => cache.strategy();

const get = (...args: Parameters<DrizzleCache['get']>) =>
Expand Down Expand Up @@ -102,7 +127,7 @@ function make(cache: DrizzleCache) {
* This error is thrown when any cache operation (get, put, onMutate) fails.
* The original error is available in the `cause` property.
*/
export class EffectCacheError extends Schema.TaggedError<EffectCacheError>()('EffectCacheError', {
export class EffectCacheError extends Schema.TaggedErrorClass<EffectCacheError>()('EffectCacheError', {
cause: Schema.Unknown,
}) {
static readonly [entityKind]: string = 'EffectCacheError';
Expand Down
30 changes: 16 additions & 14 deletions drizzle-orm/src/effect-core/errors.ts
Original file line number Diff line number Diff line change
@@ -1,40 +1,42 @@
import * as Schema from 'effect/Schema';
import { entityKind } from '~/entity.ts';

export class EffectDrizzleError extends Schema.TaggedError<EffectDrizzleError>()('EffectDrizzleError', {
export class EffectDrizzleError extends Schema.TaggedErrorClass<EffectDrizzleError>()('EffectDrizzleError', {
message: Schema.String,
cause: Schema.Unknown,
}) {
static readonly [entityKind]: string = this._tag;
static readonly [entityKind]: string = 'EffectDrizzleError';
}

export class EffectDrizzleQueryError extends Schema.TaggedError<EffectDrizzleQueryError>()('EffectDrizzleQueryError', {
query: Schema.String,
params: Schema.Array(Schema.Any).pipe(Schema.mutable),
cause: Schema.Unknown,
}) {
static readonly [entityKind]: string = this._tag;
export class EffectDrizzleQueryError
extends Schema.TaggedErrorClass<EffectDrizzleQueryError>()('EffectDrizzleQueryError', {
query: Schema.String,
params: Schema.Array(Schema.Any).pipe(Schema.mutable),
cause: Schema.Unknown,
})
{
static readonly [entityKind]: string = 'EffectDrizzleQueryError';

override get message() {
return `Failed query: ${this.query}\nparams: ${this.params}`;
}

constructor(params: Omit<Schema.Struct.Constructor<typeof EffectDrizzleQueryError.fields>, '_tag'>) {
constructor(params: Omit<Schema.Struct.MakeIn<typeof EffectDrizzleQueryError.fields>, '_tag'>) {
super(params);
Error.captureStackTrace(this, EffectDrizzleQueryError);
}
}

export class EffectTransactionRollbackError
extends Schema.TaggedError<EffectTransactionRollbackError>()('EffectTransactionRollbackError', {})
extends Schema.TaggedErrorClass<EffectTransactionRollbackError>()('EffectTransactionRollbackError', {})
{
static readonly [entityKind]: string = this._tag;
static readonly [entityKind]: string = 'EffectTransactionRollbackError';

override readonly message = 'Rollback';
}

export class MigratorInitError extends Schema.TaggedError<MigratorInitError>()('MigratorInitError', {
exitCode: Schema.Literal('databaseMigrations', 'localMigrations'),
export class MigratorInitError extends Schema.TaggedErrorClass<MigratorInitError>()('MigratorInitError', {
exitCode: Schema.Literals(['databaseMigrations', 'localMigrations']),
}) {
static readonly [entityKind]: string = this._tag;
static readonly [entityKind]: string = 'MigratorInitError';
}
37 changes: 22 additions & 15 deletions drizzle-orm/src/effect-core/logger.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,13 @@
import * as Effect from 'effect/Effect';
import * as Layer from 'effect/Layer';
import * as ServiceMap from 'effect/ServiceMap';
import { entityKind } from '~/entity.ts';
import type { Logger } from '~/logger.ts';

export interface EffectLoggerShape {
readonly logQuery: (query: string, params: unknown[]) => Effect.Effect<void, never, never>;
}

/**
* Effect service for logging SQL queries in Drizzle ORM.
*
Expand Down Expand Up @@ -30,34 +35,36 @@ import type { Logger } from '~/logger.ts';
* );
* ```
*/
export class EffectLogger extends Effect.Service<EffectLogger>()('drizzle-orm/EffectLogger', {
sync: () => {
const logQuery = (_query: string, _params: unknown[]) => Effect.void;

return { logQuery };
},
accessors: true,
export class EffectLogger extends ServiceMap.Service<EffectLogger>()('drizzle-orm/EffectLogger', {
make: Effect.sync((): EffectLoggerShape => ({
logQuery: (_query: string, _params: unknown[]) => Effect.void,
})),
}) {
static readonly [entityKind]: string = this.Service._tag;
static readonly [entityKind]: string = 'drizzle-orm/EffectLogger';

/**
* The default layer providing a no-op logger.
*/
static readonly Default = Layer.effect(EffectLogger, EffectLogger.make);

/**
* Creates an EffectLogger instance from a standard Drizzle logger.
* Creates an EffectLoggerShape from a standard Drizzle logger.
*
* @param logger - A Drizzle logger instance implementing the `Logger` interface.
* @returns A new EffectLogger that delegates to the provided Drizzle logger.
* @returns A new EffectLoggerShape that delegates to the provided Drizzle logger.
*
* @example
* ```ts
* const drizzleLogger = new DefaultLogger();
* const effectLogger = EffectLogger.fromDrizzle(drizzleLogger);
* ```
*/
static fromDrizzle(logger: Logger) {
return new EffectLogger({
static fromDrizzle(logger: Logger): EffectLoggerShape {
return {
logQuery: (query: string, params: unknown[]) => {
return Effect.sync(() => logger.logQuery(query, params));
},
});
};
}

/**
Expand Down Expand Up @@ -96,7 +103,7 @@ export class EffectLogger extends Effect.Service<EffectLogger>()('drizzle-orm/Ef
*/
static layer = Layer.succeed(
EffectLogger,
new EffectLogger({
{
logQuery: Effect.fn('EffectLogger.logQuery')(function*(query: string, params: unknown[]) {
const stringifiedParams = params.map((p) => {
try {
Expand All @@ -112,6 +119,6 @@ export class EffectLogger extends Effect.Service<EffectLogger>()('drizzle-orm/Ef
}),
);
}),
}),
} satisfies EffectLoggerShape,
);
}
49 changes: 46 additions & 3 deletions drizzle-orm/src/effect-core/query-effect.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import type * as Effect from 'effect/Effect';
import * as Effectable from 'effect/Effectable';
import { pipeArguments } from 'effect/Pipeable';

export interface QueryEffectHKTBase {
readonly $brand: 'QueryEffectHKT';
Expand All @@ -14,9 +14,52 @@ export type QueryEffectKind<
TContext = never,
> = Effect.Effect<TSuccess, TKind['error'] | TError, TKind['context'] | TContext>;

export function applyEffectWrapper(baseClass: any) {
Object.assign(baseClass.prototype, Effectable.CommitPrototype);
const EffectTypeId = '~effect/Effect';
const EffectIdentifier = `${EffectTypeId}/identifier`;
const EffectEvaluate = `${EffectTypeId}/evaluate`;

const effectVariance = {
_A: (a: unknown) => a,
_E: (e: unknown) => e,
_R: (r: unknown) => r,
};

const QueryEffectProto = {
[EffectTypeId]: effectVariance,
pipe() {
return pipeArguments(this, arguments);
},
[Symbol.iterator]() {
let done = false;
const self = this;

return {
next(value: unknown) {
if (done) {
return { done: true, value };
}

done = true;
return { done: false, value: self };
},
[Symbol.iterator]() {
return this;
},
};
},
[EffectIdentifier]: 'DrizzleQuery',
[EffectEvaluate](this: { execute(): Effect.Effect<any, any, any> }) {
return this.execute();
},
};

export function applyEffectWrapper(baseClass: any) {
// Make query builders real Effect values so direct combinators such as
// `query.pipe(...)` and `Effect.map(query, ...)` behave the same way as `yield* query`.
Object.assign(baseClass.prototype, QueryEffectProto);
baseClass.prototype.asEffect = function(this: { execute(): Effect.Effect<any, any, any> }) {
return this.execute();
};
baseClass.prototype.commit = function(this: { execute(): Effect.Effect<any, any, any> }) {
return this.execute();
};
Expand Down
Loading