drizzle-team / drizzle-orm

Headless TypeScript ORM with a head. Runs on Node, Bun and Deno. Lives on the Edge and yes, it's a JavaScript ORM too 😅
https://orm.drizzle.team
Apache License 2.0
23.34k stars 570 forks source link

[FEATURE]: Add findFirstOrThrow() method #889

Open miketromba opened 1 year ago

miketromba commented 1 year ago

Describe what you want

I've been integrating drizzle into my codebase (replacing Prisma) and so far I've had to write hundreds of snippets like this:

const feedTopic = await db.query.feedTopic.findFirst({
    where: eq(schema.feedTopic.id, input.id)
})
if (!feedTopic) throw new Error('Feed topic not found')
return feedTopic

To save users from (mild) carpal tunnel I suggest simply adding a findFirstOrThrow method (like Prisma has)

The above becomes this:

return db.query.feedTopic.findFirstOrThrow({
    where: eq(schema.feedTopic.id, input.id)
})

Bikeshedding, but it would be a nice little DX improvement.

wiredmatt commented 1 year ago

Why copy paste that when you can just make a wrapper that throws an error if the result of a function is null or undefined? Boom carpal tunnel syndrome prevented.

const somethingOrThrow = async (fn) => {
  const result = await fn();

  if (!result) throw new NotFoundError()

  return result
}

I believe you can also mess with the prototype and declaration files to add this to the object itself

ryoppippi commented 1 year ago

Yeah This helps

const [user] = await db.select().from(users).limit(1);
if (user == null) throw new Error('not found')
ryoppippi commented 1 year ago

drizzle is kind of a wrapper of SQL, so I think they shouldn't implement that kind of sugar syntax

erencay commented 11 months ago

Yeah This helps

const [user] = await db.select().from(users).limit(1);
if (user == null) throw new Error('not found')

Considering already included by OP, how this adds any value to issue?

drizzle is kind of a wrapper of SQL, so I think they shouldn't implement that kind of sugar syntax

Drizzle claims to be an ORM-ish whatever. It would be an inconsistent statement if Drizzle supports relations and avoids such basic functionality. Even a true query builder like kysely supports such feature.

goors commented 5 months ago

What about something like this guys:

import { ColumnsSelection } from 'drizzle-orm';
import {
  BuildSubquerySelection,
  JoinNullability,
  SelectMode,
  SelectResult,
} from 'drizzle-orm/query-builders/select.types';
import { PgSelectBase } from 'drizzle-orm/pg-core';

export {};

declare module 'drizzle-orm/pg-core' {
  interface PgSelectBase<
    TTableName extends string | undefined,
    TSelection extends ColumnsSelection,
    TSelectMode extends SelectMode,
    TNullabilityMap extends Record<
      string,
      JoinNullability
    > = TTableName extends string ? Record<TTableName, 'not-null'> : {},
    TDynamic extends boolean = false,
    TExcludedMethods extends string = never,
    TResult extends any[] = SelectResult<
      TSelection,
      TSelectMode,
      TNullabilityMap
    >[],
    TSelectedFields extends ColumnsSelection = BuildSubquerySelection<
      TSelection,
      TNullabilityMap
    >,
  > {
    firstOrDefault(defaultValue?: TResult[0]): Promise<TResult[0] | null>;
  }
}

PgSelectBase.prototype.firstOrDefault = async function <T>(
  this: any,
  defaultValue?: T | null,
): Promise<T | null> {
  const result = await this;
  return result.length > 0 ? result[0] : defaultValue || null;
};

and you can use it like:

await db
      .select()
      .from(table)
      .where().firstOrDefault()

that is sort of same thing in c#

public static TSource FirstOrDefault<TSource>(this IEnumerable<TSource> source)
{
    if (source == null)
    {
        throw Error.ArgumentNull("source");
    }
    IList<TSource> list = source as IList<TSource>;
    if (list != null)
    {
        if (list.Count > 0)
        {
            return list[0];
        }
    }

    return default(TSource);
}

you just do not have enumerator in nodeJs as you have in c#.

wiredmatt commented 5 months ago

that looks nice, would be great to have it integrated into the main library somehow, or in a separate (but community maintained) library. A DSL on top of the already existing DSL, for more ergonomic operations. That'd be great.