From a2649a248b440a17e004517c94bdde869adb12b2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E2=9B=B5?= <809067559@qq.com> Date: Sun, 6 Apr 2025 21:56:17 +0800 Subject: [PATCH 01/54] feat(drizzle): adapt Drizzle Relational API v2 --- examples/cattery-valibot/package.json | 4 +- .../cattery-valibot/src/providers/index.ts | 4 + examples/cattery-valibot/src/schema/index.ts | 12 - .../cattery-valibot/src/schema/relations.ts | 14 + examples/cattery-valibot/src/services/user.ts | 5 +- examples/cattery-zod/package.json | 4 +- examples/cattery-zod/src/providers/index.ts | 2 + examples/cattery-zod/src/schema/index.ts | 12 - examples/cattery-zod/src/schema/relations.ts | 14 + examples/cattery-zod/src/services/user.ts | 5 +- examples/drizzle/package.json | 4 +- examples/drizzle/src/relations.ts | 14 + examples/drizzle/src/schema.ts | 9 - packages/drizzle/package.json | 4 +- packages/drizzle/src/factory/index.ts | 12 +- packages/drizzle/src/factory/input.ts | 16 +- packages/drizzle/src/factory/resolver.ts | 90 +++---- packages/drizzle/src/factory/types.ts | 22 +- packages/drizzle/src/helper.ts | 5 +- .../drizzle/test/resolver-factory.spec.ts | 69 ++--- packages/drizzle/test/resolver-mysql.spec.gql | 210 +++++++-------- packages/drizzle/test/resolver-mysql.spec.ts | 160 ++++++----- .../drizzle/test/resolver-postgres.spec.gql | 224 ++++++++-------- .../drizzle/test/resolver-postgres.spec.ts | 153 ++++++----- .../drizzle/test/resolver-sqlite.spec.gql | 24 +- packages/drizzle/test/resolver-sqlite.spec.ts | 41 +-- .../drizzle/test/schema/mysql-relations.ts | 14 + packages/drizzle/test/schema/mysql.ts | 16 +- .../drizzle/test/schema/postgres-relations.ts | 14 + packages/drizzle/test/schema/postgres.ts | 16 +- .../drizzle/test/schema/sqlite-relations.ts | 32 +++ packages/drizzle/test/schema/sqlite.ts | 36 +-- pnpm-lock.yaml | 249 +++++++++++++++--- 33 files changed, 864 insertions(+), 646 deletions(-) create mode 100644 examples/cattery-valibot/src/schema/relations.ts create mode 100644 examples/cattery-zod/src/schema/relations.ts create mode 100644 examples/drizzle/src/relations.ts create mode 100644 packages/drizzle/test/schema/mysql-relations.ts create mode 100644 packages/drizzle/test/schema/postgres-relations.ts create mode 100644 packages/drizzle/test/schema/sqlite-relations.ts diff --git a/examples/cattery-valibot/package.json b/examples/cattery-valibot/package.json index b916f1d5..3572fdbd 100644 --- a/examples/cattery-valibot/package.json +++ b/examples/cattery-valibot/package.json @@ -11,7 +11,8 @@ "description": "", "devDependencies": { "@types/node": "^22.13.4", - "drizzle-kit": "^0.30.1", + "drizzle-kit": "1.0.0-beta.1-bd417c1", + "drizzle-orm": "1.0.0-beta.1-bd417c1", "tsx": "^4.7.2", "typescript": "^5.7.3" }, @@ -21,7 +22,6 @@ "@gqloom/valibot": "^0.7.1", "@libsql/client": "^0.14.0", "dotenv": "^16.4.7", - "drizzle-orm": "^0.39.3", "graphql": "^16.8.1", "graphql-yoga": "^5.6.0", "valibot": "1.0.0-rc.1" diff --git a/examples/cattery-valibot/src/providers/index.ts b/examples/cattery-valibot/src/providers/index.ts index 3002ff0f..5d4f1880 100644 --- a/examples/cattery-valibot/src/providers/index.ts +++ b/examples/cattery-valibot/src/providers/index.ts @@ -1,6 +1,10 @@ import { drizzle } from "drizzle-orm/libsql" import * as schema from "../schema" +import { relations } from "../schema/relations" export const db = drizzle(process.env.DB_FILE_NAME ?? "file:local.db", { schema, + relations, }) + +db._.relations["config"]["cats"]["owner"] diff --git a/examples/cattery-valibot/src/schema/index.ts b/examples/cattery-valibot/src/schema/index.ts index d4140ec5..65206df6 100644 --- a/examples/cattery-valibot/src/schema/index.ts +++ b/examples/cattery-valibot/src/schema/index.ts @@ -1,5 +1,4 @@ import { drizzleSilk } from "@gqloom/drizzle" -import { relations } from "drizzle-orm" import { integer, sqliteTable, text } from "drizzle-orm/sqlite-core" export const users = drizzleSilk( @@ -10,10 +9,6 @@ export const users = drizzleSilk( }) ) -export const usersRelations = relations(users, ({ many }) => ({ - cats: many(cats), -})) - export const cats = drizzleSilk( sqliteTable("cats", { id: integer().primaryKey({ autoIncrement: true }), @@ -24,10 +19,3 @@ export const cats = drizzleSilk( .references(() => users.id), }) ) - -export const catsRelations = relations(cats, ({ one }) => ({ - owner: one(users, { - fields: [cats.ownerId], - references: [users.id], - }), -})) diff --git a/examples/cattery-valibot/src/schema/relations.ts b/examples/cattery-valibot/src/schema/relations.ts new file mode 100644 index 00000000..53b9fd7f --- /dev/null +++ b/examples/cattery-valibot/src/schema/relations.ts @@ -0,0 +1,14 @@ +import { defineRelations } from "drizzle-orm" +import * as schema from "./index" + +export const relations = defineRelations(schema, (r) => ({ + users: { + cats: r.many.cats(), + }, + cats: { + owner: r.one.users({ + from: r.cats.ownerId, + to: r.users.id, + }), + }, +})) diff --git a/examples/cattery-valibot/src/services/user.ts b/examples/cattery-valibot/src/services/user.ts index d0e48f78..af5ced0f 100644 --- a/examples/cattery-valibot/src/services/user.ts +++ b/examples/cattery-valibot/src/services/user.ts @@ -1,4 +1,3 @@ -import { eq } from "drizzle-orm" import { db } from "../providers" import { users } from "../schema" @@ -9,12 +8,12 @@ export async function createUser(input: typeof users.$inferInsert) { export async function findUsersByName(name: string) { return await db.query.users.findMany({ - where: eq(users.name, name), + where: { name }, }) } export async function findUserByPhone(phone: string) { return await db.query.users.findFirst({ - where: eq(users.phone, phone), + where: { phone }, }) } diff --git a/examples/cattery-zod/package.json b/examples/cattery-zod/package.json index 3c0cc1d1..c6eebcda 100644 --- a/examples/cattery-zod/package.json +++ b/examples/cattery-zod/package.json @@ -11,7 +11,8 @@ "description": "", "devDependencies": { "@types/node": "^22.13.4", - "drizzle-kit": "^0.30.1", + "drizzle-kit": "1.0.0-beta.1-bd417c1", + "drizzle-orm": "1.0.0-beta.1-bd417c1", "tsx": "^4.7.2", "typescript": "^5.7.3" }, @@ -21,7 +22,6 @@ "@gqloom/zod": "latest", "@libsql/client": "^0.14.0", "dotenv": "^16.4.7", - "drizzle-orm": "^0.39.3", "graphql": "^16.8.1", "graphql-yoga": "^5.6.0", "zod": "^3.24.2" diff --git a/examples/cattery-zod/src/providers/index.ts b/examples/cattery-zod/src/providers/index.ts index 3002ff0f..a736f276 100644 --- a/examples/cattery-zod/src/providers/index.ts +++ b/examples/cattery-zod/src/providers/index.ts @@ -1,6 +1,8 @@ import { drizzle } from "drizzle-orm/libsql" import * as schema from "../schema" +import { relations } from "../schema/relations" export const db = drizzle(process.env.DB_FILE_NAME ?? "file:local.db", { schema, + relations, }) diff --git a/examples/cattery-zod/src/schema/index.ts b/examples/cattery-zod/src/schema/index.ts index d4140ec5..65206df6 100644 --- a/examples/cattery-zod/src/schema/index.ts +++ b/examples/cattery-zod/src/schema/index.ts @@ -1,5 +1,4 @@ import { drizzleSilk } from "@gqloom/drizzle" -import { relations } from "drizzle-orm" import { integer, sqliteTable, text } from "drizzle-orm/sqlite-core" export const users = drizzleSilk( @@ -10,10 +9,6 @@ export const users = drizzleSilk( }) ) -export const usersRelations = relations(users, ({ many }) => ({ - cats: many(cats), -})) - export const cats = drizzleSilk( sqliteTable("cats", { id: integer().primaryKey({ autoIncrement: true }), @@ -24,10 +19,3 @@ export const cats = drizzleSilk( .references(() => users.id), }) ) - -export const catsRelations = relations(cats, ({ one }) => ({ - owner: one(users, { - fields: [cats.ownerId], - references: [users.id], - }), -})) diff --git a/examples/cattery-zod/src/schema/relations.ts b/examples/cattery-zod/src/schema/relations.ts new file mode 100644 index 00000000..53b9fd7f --- /dev/null +++ b/examples/cattery-zod/src/schema/relations.ts @@ -0,0 +1,14 @@ +import { defineRelations } from "drizzle-orm" +import * as schema from "./index" + +export const relations = defineRelations(schema, (r) => ({ + users: { + cats: r.many.cats(), + }, + cats: { + owner: r.one.users({ + from: r.cats.ownerId, + to: r.users.id, + }), + }, +})) diff --git a/examples/cattery-zod/src/services/user.ts b/examples/cattery-zod/src/services/user.ts index d0e48f78..af5ced0f 100644 --- a/examples/cattery-zod/src/services/user.ts +++ b/examples/cattery-zod/src/services/user.ts @@ -1,4 +1,3 @@ -import { eq } from "drizzle-orm" import { db } from "../providers" import { users } from "../schema" @@ -9,12 +8,12 @@ export async function createUser(input: typeof users.$inferInsert) { export async function findUsersByName(name: string) { return await db.query.users.findMany({ - where: eq(users.name, name), + where: { name }, }) } export async function findUserByPhone(phone: string) { return await db.query.users.findFirst({ - where: eq(users.phone, phone), + where: { phone }, }) } diff --git a/examples/drizzle/package.json b/examples/drizzle/package.json index e6ebe90e..cf208390 100644 --- a/examples/drizzle/package.json +++ b/examples/drizzle/package.json @@ -15,8 +15,8 @@ "devDependencies": { "@types/node": "^22.13.1", "@types/pg": "^8.11.10", - "drizzle-kit": "^0.30.1", - "drizzle-orm": "^0.39.3", + "drizzle-kit": "1.0.0-beta.1-bd417c1", + "drizzle-orm": "1.0.0-beta.1-bd417c1", "tsx": "^4.7.2", "typescript": "^5.7.3" }, diff --git a/examples/drizzle/src/relations.ts b/examples/drizzle/src/relations.ts new file mode 100644 index 00000000..5d67997a --- /dev/null +++ b/examples/drizzle/src/relations.ts @@ -0,0 +1,14 @@ +import { defineRelations } from "drizzle-orm" +import * as schema from "./schema" + +export const relations = defineRelations(schema, (r) => ({ + users: { + posts: r.many.posts(), + }, + posts: { + author: r.one.users({ + from: r.posts.authorId, + to: r.users.id, + }), + }, +})) diff --git a/examples/drizzle/src/schema.ts b/examples/drizzle/src/schema.ts index c30ca21e..09a22422 100644 --- a/examples/drizzle/src/schema.ts +++ b/examples/drizzle/src/schema.ts @@ -1,5 +1,4 @@ import { drizzleSilk } from "@gqloom/drizzle" -import { relations } from "drizzle-orm" import * as t from "drizzle-orm/pg-core" export const roleEnum = t.pgEnum("role", ["user", "admin"]) @@ -14,10 +13,6 @@ export const users = drizzleSilk( }) ) -export const usersRelations = relations(users, ({ many }) => ({ - posts: many(posts), -})) - export const posts = drizzleSilk( t.pgTable("posts", { id: t.serial().primaryKey(), @@ -31,7 +26,3 @@ export const posts = drizzleSilk( authorId: t.integer().notNull(), }) ) - -export const postsRelations = relations(posts, ({ one }) => ({ - author: one(users, { fields: [posts.authorId], references: [users.id] }), -})) diff --git a/packages/drizzle/package.json b/packages/drizzle/package.json index 2fa37e6d..b1dda956 100644 --- a/packages/drizzle/package.json +++ b/packages/drizzle/package.json @@ -42,8 +42,8 @@ "@libsql/client": "^0.14.0", "@types/pg": "^8.11.10", "dotenv": "^16.4.7", - "drizzle-kit": "^0.30.1", - "drizzle-orm": "^0.39.3", + "drizzle-kit": "1.0.0-beta.1-bd417c1", + "drizzle-orm": "1.0.0-beta.1-bd417c1", "graphql": "^16.8.1", "graphql-yoga": "^5.6.0", "mysql2": "^3.12.0", diff --git a/packages/drizzle/src/factory/index.ts b/packages/drizzle/src/factory/index.ts index 45575fad..8ba21d42 100644 --- a/packages/drizzle/src/factory/index.ts +++ b/packages/drizzle/src/factory/index.ts @@ -8,7 +8,7 @@ import { DrizzleSQLiteResolverFactory } from "./resolver-sqlite" import type { BaseDatabase } from "./types" export function drizzleResolverFactory< - TDatabase extends BaseSQLiteDatabase, + TDatabase extends BaseSQLiteDatabase, TTableName extends keyof NonNullable, >( db: TDatabase, @@ -18,12 +18,12 @@ export function drizzleResolverFactory< NonNullable[TTableName] > export function drizzleResolverFactory< - TDatabase extends BaseSQLiteDatabase, + TDatabase extends BaseSQLiteDatabase, TTable extends SQLiteTable, >(db: TDatabase, table: TTable): DrizzleSQLiteResolverFactory export function drizzleResolverFactory< - TDatabase extends PgDatabase, + TDatabase extends PgDatabase, TTableName extends keyof NonNullable, >( db: TDatabase, @@ -33,7 +33,7 @@ export function drizzleResolverFactory< NonNullable[TTableName] > export function drizzleResolverFactory< - TDatabase extends PgDatabase, + TDatabase extends PgDatabase, TTable extends PgTable, >( db: TDatabase, @@ -41,7 +41,7 @@ export function drizzleResolverFactory< ): DrizzlePostgresResolverFactory export function drizzleResolverFactory< - TDatabase extends MySqlDatabase, + TDatabase extends MySqlDatabase, TTableName extends keyof NonNullable, >( db: TDatabase, @@ -51,7 +51,7 @@ export function drizzleResolverFactory< NonNullable[TTableName] > export function drizzleResolverFactory< - TDatabase extends MySqlDatabase, + TDatabase extends MySqlDatabase, TTable extends MySqlTable, >(db: TDatabase, table: TTable): DrizzleMySQLResolverFactory diff --git a/packages/drizzle/src/factory/input.ts b/packages/drizzle/src/factory/input.ts index 3f370c2d..c74ac14e 100644 --- a/packages/drizzle/src/factory/input.ts +++ b/packages/drizzle/src/factory/input.ts @@ -35,7 +35,7 @@ export class DrizzleInputFactory { offset: { type: GraphQLInt }, limit: { type: GraphQLInt }, orderBy: { - type: new GraphQLList(new GraphQLNonNull(this.orderBy())), + type: this.orderBy(), }, where: { type: this.filters() }, }, @@ -54,7 +54,7 @@ export class DrizzleInputFactory { fields: { offset: { type: GraphQLInt }, orderBy: { - type: new GraphQLList(new GraphQLNonNull(this.orderBy())), + type: this.orderBy(), }, where: { type: this.filters() }, }, @@ -216,8 +216,8 @@ export class DrizzleInputFactory { ilike: { type: GraphQLString }, notIlike: { type: GraphQLString }, }), - inArray: { type: gqlListType }, - notInArray: { type: gqlListType }, + in: { type: gqlListType }, + notIn: { type: gqlListType }, isNull: { type: GraphQLBoolean }, isNotNull: { type: GraphQLBoolean }, } @@ -290,13 +290,13 @@ export class DrizzleInputFactory { export interface SelectArrayArgs { offset?: number limit?: number - orderBy?: Partial, "asc" | "desc">>[] + orderBy?: Partial, "asc" | "desc">> where?: Filters } export interface SelectSingleArgs { offset?: number - orderBy?: Partial, "asc" | "desc">>[] + orderBy?: Partial, "asc" | "desc">> where?: Filters } @@ -338,8 +338,8 @@ export interface ColumnFiltersCore { notLike?: TType extends string ? string : never ilike?: TType extends string ? string : never notIlike?: TType extends string ? string : never - inArray?: TType[] - notInArray?: TType[] + in?: TType[] + notIn?: TType[] isNull?: boolean isNotNull?: boolean } diff --git a/packages/drizzle/src/factory/resolver.ts b/packages/drizzle/src/factory/resolver.ts index f7260048..608cc6f9 100644 --- a/packages/drizzle/src/factory/resolver.ts +++ b/packages/drizzle/src/factory/resolver.ts @@ -21,10 +21,8 @@ import { Many, type Relation, type SQL, - type Table, + Table, and, - asc, - desc, eq, getTableColumns, getTableName, @@ -38,7 +36,6 @@ import { lt, lte, ne, - normalizeRelation, notIlike, notInArray, notLike, @@ -126,8 +123,8 @@ export abstract class DrizzleResolverFactory< () => this.inputFactory.selectArrayArgs(), (args) => ({ value: { - where: this.extractFilters(args.where), - orderBy: this.extractOrderBy(args.orderBy), + where: args.where, + orderBy: args.orderBy, limit: args.limit, offset: args.offset, }, @@ -158,8 +155,8 @@ export abstract class DrizzleResolverFactory< () => this.inputFactory.selectSingleArgs(), (args) => ({ value: { - where: this.extractFilters(args.where), - orderBy: this.extractOrderBy(args.orderBy), + where: args.where, + orderBy: args.orderBy, offset: args.offset, }, }) @@ -174,25 +171,6 @@ export abstract class DrizzleResolverFactory< } as QueryOptions) } - protected extractOrderBy( - orders?: SelectArrayArgs["orderBy"] - ): SQL[] | undefined { - if (orders == null) return - const answer: SQL[] = [] - const columns = getTableColumns(this.table) - for (const order of orders) { - for (const [column, direction] of Object.entries(order)) { - if (!direction) continue - if (column in columns) { - answer.push( - direction === "asc" ? asc(columns[column]) : desc(columns[column]) - ) - } - } - } - return answer - } - protected extractFilters( filters: SelectArrayArgs["where"] ): SQL | undefined { @@ -261,7 +239,7 @@ export abstract class DrizzleResolverFactory< const variants: SQL[] = [] const binaryOperators = { eq, ne, gt, gte, lt, lte } const textOperators = { like, notLike, ilike, notIlike } - const arrayOperators = { inArray, notInArray } + const arrayOperators = { in: inArray, notIn: notInArray } const nullOperators = { isNull, isNotNull } for (const [operatorName, operatorValue] of entries) { @@ -299,7 +277,7 @@ export abstract class DrizzleResolverFactory< middlewares?: Middleware< InferTableRelationalConfig< QueryBuilder> - >["relations"][TRelationName] extends Many + >["relations"][TRelationName] extends Many ? RelationManyField< TTable, InferRelationTable @@ -312,7 +290,7 @@ export abstract class DrizzleResolverFactory< } ): InferTableRelationalConfig< QueryBuilder> - >["relations"][TRelationName] extends Many + >["relations"][TRelationName] extends Many ? RelationManyField< TTable, InferRelationTable @@ -321,42 +299,46 @@ export abstract class DrizzleResolverFactory< TTable, InferRelationTable > { - const relation = this.db._.schema?.[this.tableName]?.relations?.[ + const relation = this.db._.relations["config"]?.[this.tableName]?.[ relationName ] as Relation if (!relation) { throw new Error( - `GQLoom-Drizzle Error: Relation ${this.tableName}.${String(relationName)} not found in drizzle instance. Did you forget to pass relations to drizzle constructor?` + `GQLoom-Drizzle Error: Relation ${this.tableName}.${String( + relationName + )} not found in drizzle instance. Did you forget to pass relations to drizzle constructor?` ) } - const output = DrizzleWeaver.unravel(relation.referencedTable) - const tableName = getTableName(relation.referencedTable) + const targetTable = relation.targetTable + if (!(targetTable instanceof Table)) { + throw new Error( + `GQLoom-Drizzle Error: Relation ${this.tableName}.${String( + relationName + )} is not a table relation!` + ) + } + + const output = DrizzleWeaver.unravel(targetTable) + const tableName = getTableName(targetTable) const queryBuilder = this.db.query[ tableName as keyof typeof this.db.query ] as AnyQueryBuilder - const normalizedRelation = normalizeRelation( - this.db._.schema, - this.db._.tableNamesMap, - relation - ) const isList = relation instanceof Many - const fieldsLength = normalizedRelation.fields.length + const fieldsLength = relation.sourceColumns.length const getKeyByField = (parent: any) => { if (fieldsLength === 1) { - return parent[normalizedRelation.fields[0].name] + return parent[relation.sourceColumns[0].name] } - return normalizedRelation.fields - .map((field) => parent[field.name]) - .join("-") + return relation.sourceColumns.map((field) => parent[field.name]).join("-") } const getKeyByReference = (item: any) => { if (fieldsLength === 1) { - return item[normalizedRelation.references[0].name] + return item[relation.targetColumns[0].name] } - return normalizedRelation.references + return relation.targetColumns .map((reference) => item[reference.name]) .join("-") } @@ -366,14 +348,20 @@ export abstract class DrizzleResolverFactory< const where = (() => { if (fieldsLength === 1) { const values = parents.map( - (parent) => parent[normalizedRelation.fields[0].name] + (parent) => parent[relation.sourceColumns[0].name] ) - return inArray(normalizedRelation.references[0], values) + // return inArray(relation.targetColumns[0], values) + return { + [relation.targetColumns[0].name]: { in: values }, + } } const values = parents.map((parent) => - normalizedRelation.fields.map((field) => parent[field.name]) + relation.sourceColumns.map((field) => parent[field.name]) ) - return inArrayMultiple(normalizedRelation.references, values) + return { + RAW: (table: Table) => + inArrayMultiple(relation.targetColumns, values, table), + } })() const list = await queryBuilder.findMany({ where }) @@ -414,7 +402,7 @@ export abstract class DrizzleResolverFactory< const name = options?.name ?? this.tableName const fields: Record> = mapValue( - this.db._.schema?.[this.tableName]?.relations ?? {}, + this.db._.relations.config[this.tableName] ?? {}, (_, key) => this.relationField(key) ) diff --git a/packages/drizzle/src/factory/types.ts b/packages/drizzle/src/factory/types.ts index d6d7f778..d782b1b5 100644 --- a/packages/drizzle/src/factory/types.ts +++ b/packages/drizzle/src/factory/types.ts @@ -4,7 +4,7 @@ import type { MutationFactoryWithResolve, QueryFactoryWithResolve, } from "@gqloom/core" -import type { InferSelectModel, Many, Table } from "drizzle-orm" +import type { AnyRelations, InferSelectModel, Many, Table } from "drizzle-orm" import type { MySqlDatabase } from "drizzle-orm/mysql-core" import type { RelationalQueryBuilder as MySqlRelationalQueryBuilder } from "drizzle-orm/mysql-core/query-builders/query" import type { PgDatabase } from "drizzle-orm/pg-core" @@ -74,7 +74,7 @@ export type DrizzleResolverRelations< QueryBuilder> >["relations"]]: InferTableRelationalConfig< QueryBuilder> - >["relations"][TRelationName] extends Many + >["relations"][TRelationName] extends Many ? RelationManyField< TTable, InferRelationTable @@ -247,7 +247,7 @@ export type QueryBuilder< export type AnyQueryBuilder = | MySqlRelationalQueryBuilder | PgRelationalQueryBuilder - | SQLiteRelationalQueryBuilder + | SQLiteRelationalQueryBuilder export type InferTableRelationalConfig = TQueryBuilder extends MySqlRelationalQueryBuilder< @@ -262,7 +262,6 @@ export type InferTableRelationalConfig = > ? TTableRelationalConfig : TQueryBuilder extends SQLiteRelationalQueryBuilder< - any, any, any, infer TTableRelationalConfig @@ -271,18 +270,15 @@ export type InferTableRelationalConfig = : never export type BaseDatabase = - | BaseSQLiteDatabase - | PgDatabase - | MySqlDatabase + | BaseSQLiteDatabase + | PgDatabase + | MySqlDatabase export type InferTableName = TTable["_"]["name"] export type InferRelationTable< TDatabase extends BaseDatabase, TTable extends Table, - TRelationName extends keyof InferTableRelationalConfig< - QueryBuilder> - >["relations"], -> = TDatabase["_"]["fullSchema"][InferTableRelationalConfig< - QueryBuilder> ->["relations"][TRelationName]["referencedTableName"]] + TTargetTableName extends + keyof TDatabase["_"]["relations"]["config"][TTable["_"]["name"]], +> = TDatabase["_"]["relations"]["config"][TTable["_"]["name"]]["relations"][TTargetTableName]["targetTable"] diff --git a/packages/drizzle/src/helper.ts b/packages/drizzle/src/helper.ts index 809b3d9f..f5aba068 100644 --- a/packages/drizzle/src/helper.ts +++ b/packages/drizzle/src/helper.ts @@ -7,14 +7,15 @@ import { sql } from "drizzle-orm" */ export function inArrayMultiple( columns: Column[], - values: readonly unknown[][] + values: readonly unknown[][], + table: any ): SQL { // Early return for empty values if (values.length === 0) return sql`FALSE` // Create (col1, col2, ...) part const columnsPart = sql`(${sql.join( - columns.map((c) => sql`${c}`), + columns.map((c) => sql`${table[c.name]}`), sql`, ` )})` diff --git a/packages/drizzle/test/resolver-factory.spec.ts b/packages/drizzle/test/resolver-factory.spec.ts index 49e92e1a..88b4e775 100644 --- a/packages/drizzle/test/resolver-factory.spec.ts +++ b/packages/drizzle/test/resolver-factory.spec.ts @@ -27,13 +27,16 @@ import type { InferSelectSingleOptions, } from "../src/factory/types" import * as mysqlSchemas from "./schema/mysql" +import { relations as mysqlRelations } from "./schema/mysql-relations" import * as pgSchemas from "./schema/postgres" +import { relations as pgRelations } from "./schema/postgres-relations" import * as sqliteSchemas from "./schema/sqlite" +import { relations as sqliteRelations } from "./schema/sqlite-relations" const pathToDB = new URL("./schema/sqlite.db", import.meta.url) describe.concurrent("DrizzleResolverFactory", () => { - let db: LibSQLDatabase + let db: LibSQLDatabase let userFactory: DrizzleSQLiteResolverFactory< typeof db, typeof sqliteSchemas.user @@ -42,10 +45,11 @@ describe.concurrent("DrizzleResolverFactory", () => { beforeAll(async () => { db = sqliteDrizzle({ schema: sqliteSchemas, + relations: sqliteRelations, connection: { url: `file:${pathToDB.pathname}` }, }) - userFactory = drizzleResolverFactory(db, sqliteSchemas.user) + userFactory = drizzleResolverFactory(db, "user") await db.insert(sqliteSchemas.user).values([ { @@ -90,6 +94,7 @@ describe.concurrent("DrizzleResolverFactory", () => { }) describe.concurrent("selectArrayQuery", () => { + // db.query.user.findMany({ orderBy: () => [] }) it("should be created without error", async () => { const query = userFactory.selectArrayQuery() expect(query).toBeDefined() @@ -99,7 +104,7 @@ describe.concurrent("DrizzleResolverFactory", () => { const query = userFactory.selectArrayQuery() let answer - answer = await query["~meta"].resolve({ orderBy: [{ age: "asc" }] }) + answer = await query["~meta"].resolve({ orderBy: { age: "asc" } }) expect(answer).toMatchObject([ { age: 10 }, { age: 11 }, @@ -108,7 +113,7 @@ describe.concurrent("DrizzleResolverFactory", () => { { age: 14 }, ]) - answer = await query["~meta"].resolve({ orderBy: [{ age: "desc" }] }) + answer = await query["~meta"].resolve({ orderBy: { age: "desc" } }) expect(answer).toMatchObject([ { age: 14 }, { age: 13 }, @@ -139,12 +144,12 @@ describe.concurrent("DrizzleResolverFactory", () => { expect(answer).toMatchObject([{ age: 12 }]) answer = await query["~meta"].resolve({ - where: { age: { inArray: [10, 11] } }, + where: { age: { in: [10, 11] } }, }) expect(new Set(answer)).toMatchObject(new Set([{ age: 10 }, { age: 11 }])) answer = await query["~meta"].resolve({ - where: { age: { notInArray: [10, 11] } }, + where: { age: { notIn: [10, 11] } }, }) expect(new Set(answer)).toMatchObject( new Set([{ age: 12 }, { age: 13 }, { age: 14 }]) @@ -165,19 +170,6 @@ describe.concurrent("DrizzleResolverFactory", () => { }) expect(answer).toHaveLength(5) - await expect(() => - query["~meta"].resolve({ - where: { age: { eq: 10 }, OR: [{ age: { eq: 11 } }] }, - }) - ).rejects.toThrow("Cannot specify both fields and 'OR' in table filters!") - await expect(() => - query["~meta"].resolve({ - where: { age: { eq: 10, OR: [{ eq: 11 }] } }, - }) - ).rejects.toThrow( - "WHERE age: Cannot specify both fields and 'OR' in column operators!" - ) - answer = await query["~meta"].resolve({ where: { age: { isNull: true } }, }) @@ -192,7 +184,7 @@ describe.concurrent("DrizzleResolverFactory", () => { age: v.nullish(v.number()), }), v.transform(({ age }) => ({ - where: age != null ? eq(sqliteSchemas.user.age, age) : undefined, + where: age != null ? { age } : undefined, })) ), }) @@ -210,7 +202,7 @@ describe.concurrent("DrizzleResolverFactory", () => { age: v.nullish(v.number()), }), v.transform(({ age }) => ({ - where: age != null ? eq(sqliteSchemas.user.age, age) : undefined, + where: age != null ? { age } : undefined, })) ) ) @@ -274,7 +266,7 @@ describe.concurrent("DrizzleResolverFactory", () => { const query = userFactory.selectSingleQuery() expect( await query["~meta"].resolve({ - orderBy: [{ age: "asc" }], + orderBy: { age: "asc" }, }) ).toMatchObject({ age: 10 }) }) @@ -295,7 +287,7 @@ describe.concurrent("DrizzleResolverFactory", () => { age: v.nullish(v.number()), }), v.transform(({ age }) => ({ - where: age != null ? eq(sqliteSchemas.user.age, age) : undefined, + where: age != null ? { age } : undefined, })) ), }) @@ -359,11 +351,11 @@ describe.concurrent("DrizzleResolverFactory", () => { const studentCourseFactory = drizzleResolverFactory(db, "studentToCourse") const gradeField = studentCourseFactory.relationField("grade") const John = await db.query.user.findFirst({ - where: eq(sqliteSchemas.user.name, "John"), + where: { name: "John" }, }) if (!John) throw new Error("John not found") const Joe = await db.query.user.findFirst({ - where: eq(sqliteSchemas.user.name, "Joe"), + where: { name: "Joe" }, }) if (!Joe) throw new Error("Joe not found") @@ -395,6 +387,7 @@ describe.concurrent("DrizzleResolverFactory", () => { return gradeField["~meta"].resolve(sc, undefined) }) ) + expect(new Set(answer)).toMatchObject( new Set([ { studentId: John.id, courseId: math.id, grade: expect.any(Number) }, @@ -443,17 +436,21 @@ describe.concurrent("DrizzleResolverFactory", () => { describe.concurrent("DrizzleMySQLResolverFactory", () => { const schema = { - drizzle_user: mysqlSchemas.user, + user: mysqlSchemas.user, } - let db: MySql2Database + let db: MySql2Database let userFactory: DrizzleMySQLResolverFactory< typeof db, typeof mysqlSchemas.user > beforeAll(async () => { - db = mysqlDrizzle(config.mysqlUrl, { schema, mode: "default" }) - userFactory = drizzleResolverFactory(db, "drizzle_user") + db = mysqlDrizzle(config.mysqlUrl, { + schema, + relations: mysqlRelations, + mode: "default", + }) + userFactory = drizzleResolverFactory(db, "user") await db.execute(sql`select 1`) }) @@ -569,17 +566,20 @@ describe.concurrent("DrizzleMySQLResolverFactory", () => { describe.concurrent("DrizzlePostgresResolverFactory", () => { const schema = { - drizzle_user: pgSchemas.user, + user: pgSchemas.user, } - let db: NodePgDatabase + let db: NodePgDatabase let userFactory: DrizzlePostgresResolverFactory< typeof db, typeof pgSchemas.user > beforeAll(async () => { - db = pgDrizzle(config.postgresUrl, { schema }) - userFactory = drizzleResolverFactory(db, "drizzle_user") + db = pgDrizzle(config.postgresUrl, { + schema, + relations: pgRelations, + }) + userFactory = drizzleResolverFactory(db, "user") await db.execute(sql`select 1`) }) @@ -694,7 +694,7 @@ describe.concurrent("DrizzlePostgresResolverFactory", () => { }) describe.concurrent("DrizzleSQLiteResolverFactory", () => { - let db: LibSQLDatabase + let db: LibSQLDatabase let userFactory: DrizzleSQLiteResolverFactory< typeof db, typeof sqliteSchemas.user @@ -703,6 +703,7 @@ describe.concurrent("DrizzleSQLiteResolverFactory", () => { beforeAll(async () => { db = sqliteDrizzle({ schema: sqliteSchemas, + relations: sqliteRelations, connection: { url: `file:${pathToDB.pathname}` }, }) diff --git a/packages/drizzle/test/resolver-mysql.spec.gql b/packages/drizzle/test/resolver-mysql.spec.gql index 7c9accc9..8ca0d031 100644 --- a/packages/drizzle/test/resolver-mysql.spec.gql +++ b/packages/drizzle/test/resolver-mysql.spec.gql @@ -1,176 +1,176 @@ -input DrizzlePostFilters { - OR: [DrizzlePostFiltersOr!] +type Mutation { + deleteFromPost(where: PostFilters): MutationSuccessResult + deleteFromUser(where: UserFilters): MutationSuccessResult + insertIntoPost(values: [PostInsertInput!]!): MutationSuccessResult + insertIntoPostSingle(value: PostInsertInput!): MutationSuccessResult + insertIntoUser(values: [UserInsertInput!]!): MutationSuccessResult + insertIntoUserSingle(value: UserInsertInput!): MutationSuccessResult + updatePost(set: PostUpdateInput!, where: PostFilters): MutationSuccessResult + updateUser(set: UserUpdateInput!, where: UserFilters): MutationSuccessResult +} + +type MutationSuccessResult { + isSuccess: Boolean! +} + +input MySqlIntFilters { + OR: [MySqlIntFiltersOr!] + eq: Int + gt: Int + gte: Int + in: [Int!] + isNotNull: Boolean + isNull: Boolean + lt: Int + lte: Int + ne: Int + notIn: [Int!] +} + +input MySqlIntFiltersOr { + eq: Int + gt: Int + gte: Int + in: [Int!] + isNotNull: Boolean + isNull: Boolean + lt: Int + lte: Int + ne: Int + notIn: [Int!] +} + +input MySqlTextFilters { + OR: [MySqlTextFiltersOr!] + eq: String + gt: String + gte: String + ilike: String + in: [String!] + isNotNull: Boolean + isNull: Boolean + like: String + lt: String + lte: String + ne: String + notIlike: String + notIn: [String!] + notLike: String +} + +input MySqlTextFiltersOr { + eq: String + gt: String + gte: String + ilike: String + in: [String!] + isNotNull: Boolean + isNull: Boolean + like: String + lt: String + lte: String + ne: String + notIlike: String + notIn: [String!] + notLike: String +} + +enum OrderDirection { + asc + desc +} + +input PostFilters { + OR: [PostFiltersOr!] authorId: MySqlIntFilters content: MySqlTextFilters id: MySqlIntFilters title: MySqlTextFilters } -input DrizzlePostFiltersOr { +input PostFiltersOr { authorId: MySqlIntFilters content: MySqlTextFilters id: MySqlIntFilters title: MySqlTextFilters } -input DrizzlePostInsertInput { +input PostInsertInput { authorId: Int content: String id: Int title: String! } -type DrizzlePostItem { - author: DrizzleUserItem +type PostItem { + author: UserItem authorId: Int content: String id: Int! title: String! } -input DrizzlePostOrderBy { +input PostOrderBy { authorId: OrderDirection content: OrderDirection id: OrderDirection title: OrderDirection } -input DrizzlePostUpdateInput { +input PostUpdateInput { authorId: Int content: String id: Int title: String } -input DrizzleUserFilters { - OR: [DrizzleUserFiltersOr!] +type Query { + post(limit: Int, offset: Int, orderBy: PostOrderBy, where: PostFilters): [PostItem!]! + postSingle(offset: Int, orderBy: PostOrderBy, where: PostFilters): PostItem + user(limit: Int, offset: Int, orderBy: UserOrderBy, where: UserFilters): [UserItem!]! + userSingle(offset: Int, orderBy: UserOrderBy, where: UserFilters): UserItem +} + +input UserFilters { + OR: [UserFiltersOr!] age: MySqlIntFilters email: MySqlTextFilters id: MySqlIntFilters name: MySqlTextFilters } -input DrizzleUserFiltersOr { +input UserFiltersOr { age: MySqlIntFilters email: MySqlTextFilters id: MySqlIntFilters name: MySqlTextFilters } -input DrizzleUserInsertInput { +input UserInsertInput { age: Int email: String id: Int name: String! } -type DrizzleUserItem { +type UserItem { age: Int email: String id: Int! name: String! - posts: [DrizzlePostItem!]! + posts: [PostItem!]! } -input DrizzleUserOrderBy { +input UserOrderBy { age: OrderDirection email: OrderDirection id: OrderDirection name: OrderDirection } -input DrizzleUserUpdateInput { +input UserUpdateInput { age: Int email: String id: Int name: String -} - -type Mutation { - deleteFromPost(where: DrizzlePostFilters): MutationSuccessResult - deleteFromUser(where: DrizzleUserFilters): MutationSuccessResult - insertIntoPost(values: [DrizzlePostInsertInput!]!): MutationSuccessResult - insertIntoPostSingle(value: DrizzlePostInsertInput!): MutationSuccessResult - insertIntoUser(values: [DrizzleUserInsertInput!]!): MutationSuccessResult - insertIntoUserSingle(value: DrizzleUserInsertInput!): MutationSuccessResult - updatePost(set: DrizzlePostUpdateInput!, where: DrizzlePostFilters): MutationSuccessResult - updateUser(set: DrizzleUserUpdateInput!, where: DrizzleUserFilters): MutationSuccessResult -} - -type MutationSuccessResult { - isSuccess: Boolean! -} - -input MySqlIntFilters { - OR: [MySqlIntFiltersOr!] - eq: Int - gt: Int - gte: Int - inArray: [Int!] - isNotNull: Boolean - isNull: Boolean - lt: Int - lte: Int - ne: Int - notInArray: [Int!] -} - -input MySqlIntFiltersOr { - eq: Int - gt: Int - gte: Int - inArray: [Int!] - isNotNull: Boolean - isNull: Boolean - lt: Int - lte: Int - ne: Int - notInArray: [Int!] -} - -input MySqlTextFilters { - OR: [MySqlTextFiltersOr!] - eq: String - gt: String - gte: String - ilike: String - inArray: [String!] - isNotNull: Boolean - isNull: Boolean - like: String - lt: String - lte: String - ne: String - notIlike: String - notInArray: [String!] - notLike: String -} - -input MySqlTextFiltersOr { - eq: String - gt: String - gte: String - ilike: String - inArray: [String!] - isNotNull: Boolean - isNull: Boolean - like: String - lt: String - lte: String - ne: String - notIlike: String - notInArray: [String!] - notLike: String -} - -enum OrderDirection { - asc - desc -} - -type Query { - post(limit: Int, offset: Int, orderBy: [DrizzlePostOrderBy!], where: DrizzlePostFilters): [DrizzlePostItem!]! - postSingle(offset: Int, orderBy: [DrizzlePostOrderBy!], where: DrizzlePostFilters): DrizzlePostItem - user(limit: Int, offset: Int, orderBy: [DrizzleUserOrderBy!], where: DrizzleUserFilters): [DrizzleUserItem!]! - userSingle(offset: Int, orderBy: [DrizzleUserOrderBy!], where: DrizzleUserFilters): DrizzleUserItem } \ No newline at end of file diff --git a/packages/drizzle/test/resolver-mysql.spec.ts b/packages/drizzle/test/resolver-mysql.spec.ts index d3ade139..38526119 100644 --- a/packages/drizzle/test/resolver-mysql.spec.ts +++ b/packages/drizzle/test/resolver-mysql.spec.ts @@ -1,5 +1,4 @@ import { weave } from "@gqloom/core" -import { eq } from "drizzle-orm" import { drizzle } from "drizzle-orm/mysql2" import type { MySql2Database } from "drizzle-orm/mysql2" import { @@ -11,17 +10,16 @@ import { type YogaServerInstance, createYoga } from "graphql-yoga" import { afterAll, beforeAll, describe, expect, it } from "vitest" import { config } from "../env.config" import { drizzleResolverFactory } from "../src" -import { post, postsRelations, user, usersRelations } from "./schema/mysql" +import { post, user } from "./schema/mysql" +import { relations } from "./schema/mysql-relations" const schema = { - drizzle_user: user, - drizzle_post: post, - usersRelations, - postsRelations, + user, + post, } describe("resolver by mysql", () => { - let db: MySql2Database + let db: MySql2Database let gqlSchema: GraphQLSchema let yoga: YogaServerInstance<{}, {}> @@ -48,9 +46,9 @@ describe("resolver by mysql", () => { beforeAll(async () => { try { - db = drizzle(config.mysqlUrl, { schema, mode: "default" }) - const userFactory = drizzleResolverFactory(db, "drizzle_user") - const postFactory = drizzleResolverFactory(db, "drizzle_post") + db = drizzle(config.mysqlUrl, { schema, relations, mode: "default" }) + const userFactory = drizzleResolverFactory(db, "user") + const postFactory = drizzleResolverFactory(db, "post") gqlSchema = weave( userFactory.resolver({ name: "user" }), postFactory.resolver({ name: "post" }) @@ -60,14 +58,14 @@ describe("resolver by mysql", () => { await db .insert(user) .values([{ name: "Tom" }, { name: "Tony" }, { name: "Taylor" }]) - const Tom = await db.query.drizzle_user.findFirst({ - where: eq(user.name, "Tom"), + const Tom = await db.query.user.findFirst({ + where: { name: "Tom" }, }) - const Tony = await db.query.drizzle_user.findFirst({ - where: eq(user.name, "Tony"), + const Tony = await db.query.user.findFirst({ + where: { name: "Tony" }, }) - const Taylor = await db.query.drizzle_user.findFirst({ - where: eq(user.name, "Taylor"), + const Taylor = await db.query.user.findFirst({ + where: { name: "Taylor" }, }) if (!Tom || !Tony || !Taylor) throw new Error("User not found") @@ -96,16 +94,26 @@ describe("resolver by mysql", () => { describe.concurrent("query", () => { it("should query users correctly", async () => { const q = /* GraphQL */ ` - query user ($orderBy: [DrizzleUserOrderBy!], $where: DrizzleUserFilters!, $limit: Int, $offset: Int) { - user(orderBy: $orderBy, where: $where, limit: $limit, offset: $offset) { - id - name + query user( + $orderBy: UserOrderBy! + $where: UserFilters! + $limit: Int + $offset: Int + ) { + user( + orderBy: $orderBy + where: $where + limit: $limit + offset: $offset + ) { + id + name + } } - } - ` + ` await expect( execute(q, { - orderBy: [{ name: "asc" }], + orderBy: { name: "asc" }, where: { name: { like: "T%" } }, }) ).resolves.toMatchObject({ @@ -114,7 +122,7 @@ describe("resolver by mysql", () => { await expect( execute(q, { - orderBy: [{ name: "asc" }], + orderBy: { name: "asc" }, where: { name: { like: "T%" } }, limit: 2, }) @@ -124,7 +132,7 @@ describe("resolver by mysql", () => { await expect( execute(q, { - orderBy: [{ name: "asc" }], + orderBy: { name: "asc" }, where: { name: { like: "T%" } }, limit: 1, offset: 1, @@ -138,13 +146,17 @@ describe("resolver by mysql", () => { await expect( execute( /* GraphQL */ ` - query user ($orderBy: [DrizzleUserOrderBy!], $where: DrizzleUserFilters!, $offset: Int) { - userSingle(orderBy: $orderBy, where: $where, offset: $offset) { - id - name + query user( + $orderBy: UserOrderBy + $where: UserFilters! + $offset: Int + ) { + userSingle(orderBy: $orderBy, where: $where, offset: $offset) { + id + name + } } - } - `, + `, { where: { name: { eq: "Taylor" } }, } @@ -156,8 +168,18 @@ describe("resolver by mysql", () => { it("should query user with posts correctly", async () => { const q = /* GraphQL */ ` - query user ($orderBy: [DrizzleUserOrderBy!], $where: DrizzleUserFilters!, $limit: Int, $offset: Int) { - user(orderBy: $orderBy,where: $where, limit: $limit, offset: $offset) { + query user( + $orderBy: UserOrderBy! + $where: UserFilters! + $limit: Int + $offset: Int + ) { + user( + orderBy: $orderBy + where: $where + limit: $limit + offset: $offset + ) { id name posts { @@ -170,7 +192,7 @@ describe("resolver by mysql", () => { await expect( execute(q, { - orderBy: [{ name: "asc" }], + orderBy: { name: "asc" }, where: { name: { like: "T%" } }, }) ).resolves.toMatchObject({ @@ -195,12 +217,12 @@ describe("resolver by mysql", () => { describe("mutation", () => { it("should insert a new user correctly", async () => { const q = /* GraphQL */ ` - mutation insertIntoUser($values: [DrizzleUserInsertInput!]!) { - insertIntoUser(values: $values) { - isSuccess + mutation insertIntoUser($values: [UserInsertInput!]!) { + insertIntoUser(values: $values) { + isSuccess + } } - } - ` + ` await expect( execute(q, { @@ -211,15 +233,18 @@ describe("resolver by mysql", () => { }) // Verify the user was inserted - const Tina = await db.query.drizzle_user.findFirst({ - where: eq(user.name, "Tina"), + const Tina = await db.query.user.findFirst({ + where: { name: "Tina" }, }) expect(Tina).toBeDefined() }) it("should update user information correctly", async () => { const q = /* GraphQL */ ` - mutation updateUser($set: DrizzleUserUpdateInput!, $where: DrizzleUserFilters!) { + mutation updateUser( + $set: UserUpdateInput! + $where: UserFilters! + ) { updateUser(set: $set, where: $where) { isSuccess } @@ -230,8 +255,8 @@ describe("resolver by mysql", () => { .insert(user) .values({ name: "Troy" }) .$returningId() - const Troy = await db.query.drizzle_user.findFirst({ - where: eq(user.id, TroyID.id), + const Troy = await db.query.user.findFirst({ + where: { id: TroyID.id }, }) if (!Troy) throw new Error("User not found") @@ -245,23 +270,23 @@ describe("resolver by mysql", () => { }) // Verify the user was updated - const updatedUser = await db.query.drizzle_user.findFirst({ - where: eq(user.name, "Tiffany"), + const updatedUser = await db.query.user.findFirst({ + where: { name: "Tiffany" }, }) expect(updatedUser).toBeDefined() }) it("should delete a user correctly", async () => { const q = /* GraphQL */ ` - mutation deleteFromUser($where: DrizzleUserFilters!) { + mutation deleteFromUser($where: UserFilters!) { deleteFromUser(where: $where) { isSuccess } } ` - const Tony = await db.query.drizzle_user.findFirst({ - where: eq(user.name, "Tony"), + const Tony = await db.query.user.findFirst({ + where: { name: "Tony" }, }) if (!Tony) throw new Error("User not found") @@ -276,23 +301,23 @@ describe("resolver by mysql", () => { }) // Verify the user was deleted - const deletedUser = await db.query.drizzle_user.findFirst({ - where: eq(user.name, "Tony"), + const deletedUser = await db.query.user.findFirst({ + where: { name: "Tony" }, }) expect(deletedUser).toBeUndefined() }) it("should insert a new post correctly", async () => { const q = /* GraphQL */ ` - mutation insertIntoPost($values: [DrizzlePostInsertInput!]!) { + mutation insertIntoPost($values: [PostInsertInput!]!) { insertIntoPost(values: $values) { isSuccess } } ` - const Tom = await db.query.drizzle_user.findFirst({ - where: eq(user.name, "Tom"), + const Tom = await db.query.user.findFirst({ + where: { name: "Tom" }, }) if (!Tom) throw new Error("User not found") @@ -307,15 +332,18 @@ describe("resolver by mysql", () => { }) // Verify the post was inserted - const p = await db.query.drizzle_post.findFirst({ - where: eq(post.title, "Post 5"), + const p = await db.query.post.findFirst({ + where: { title: "Post 5" }, }) expect(p).toBeDefined() }) it("should update post information correctly", async () => { const q = /* GraphQL */ ` - mutation updatePost($set: DrizzlePostUpdateInput!, $where: DrizzlePostFilters!) { + mutation updatePost( + $set: PostUpdateInput! + $where: PostFilters! + ) { updatePost(set: $set, where: $where) { isSuccess } @@ -327,8 +355,8 @@ describe("resolver by mysql", () => { .values({ title: "Post U" }) .$returningId() - const PostU = await db.query.drizzle_post.findFirst({ - where: eq(post.id, PostUID.id), + const PostU = await db.query.post.findFirst({ + where: { id: PostUID.id }, }) if (!PostU) throw new Error("Post not found") @@ -342,15 +370,15 @@ describe("resolver by mysql", () => { }) // Verify the post was updated - const updatedPost = await db.query.drizzle_post.findFirst({ - where: eq(post.title, "Updated Post U"), + const updatedPost = await db.query.post.findFirst({ + where: { title: "Updated Post U" }, }) expect(updatedPost).toBeDefined() }) it("should delete a post correctly", async () => { const q = /* GraphQL */ ` - mutation deleteFromPost($where: DrizzlePostFilters!) { + mutation deleteFromPost($where: PostFilters!) { deleteFromPost(where: $where) { isSuccess } @@ -362,8 +390,8 @@ describe("resolver by mysql", () => { .values({ title: "Post D" }) .$returningId() - const PostD = await db.query.drizzle_post.findFirst({ - where: eq(post.id, PostDID.id), + const PostD = await db.query.post.findFirst({ + where: { id: PostDID.id }, }) if (!PostD) throw new Error("Post not found") @@ -376,8 +404,8 @@ describe("resolver by mysql", () => { }) // Verify the post was deleted - const deletedPost = await db.query.drizzle_post.findFirst({ - where: eq(post.id, PostD.id), + const deletedPost = await db.query.post.findFirst({ + where: { id: PostD.id }, }) expect(deletedPost).toBeUndefined() }) diff --git a/packages/drizzle/test/resolver-postgres.spec.gql b/packages/drizzle/test/resolver-postgres.spec.gql index 77ba955a..057be801 100644 --- a/packages/drizzle/test/resolver-postgres.spec.gql +++ b/packages/drizzle/test/resolver-postgres.spec.gql @@ -1,100 +1,12 @@ -input DrizzlePostFilters { - OR: [DrizzlePostFiltersOr!] - authorId: PgIntegerFilters - content: PgTextFilters - id: PgSerialFilters - title: PgTextFilters -} - -input DrizzlePostFiltersOr { - authorId: PgIntegerFilters - content: PgTextFilters - id: PgSerialFilters - title: PgTextFilters -} - -input DrizzlePostInsertInput { - authorId: Int - content: String - id: Int - title: String! -} - -type DrizzlePostItem { - author: DrizzleUserItem - authorId: Int - content: String - id: Int! - title: String! -} - -input DrizzlePostOrderBy { - authorId: OrderDirection - content: OrderDirection - id: OrderDirection - title: OrderDirection -} - -input DrizzlePostUpdateInput { - authorId: Int - content: String - id: Int - title: String -} - -input DrizzleUserFilters { - OR: [DrizzleUserFiltersOr!] - age: PgIntegerFilters - email: PgTextFilters - id: PgSerialFilters - name: PgTextFilters -} - -input DrizzleUserFiltersOr { - age: PgIntegerFilters - email: PgTextFilters - id: PgSerialFilters - name: PgTextFilters -} - -input DrizzleUserInsertInput { - age: Int - email: String - id: Int - name: String! -} - -type DrizzleUserItem { - age: Int - email: String - id: Int! - name: String! - posts: [DrizzlePostItem!]! -} - -input DrizzleUserOrderBy { - age: OrderDirection - email: OrderDirection - id: OrderDirection - name: OrderDirection -} - -input DrizzleUserUpdateInput { - age: Int - email: String - id: Int - name: String -} - type Mutation { - deleteFromPost(where: DrizzlePostFilters): [DrizzlePostItem!]! - deleteFromUser(where: DrizzleUserFilters): [DrizzleUserItem!]! - insertIntoPost(values: [DrizzlePostInsertInput!]!): [DrizzlePostItem!]! - insertIntoPostSingle(value: DrizzlePostInsertInput!): DrizzlePostItem - insertIntoUser(values: [DrizzleUserInsertInput!]!): [DrizzleUserItem!]! - insertIntoUserSingle(value: DrizzleUserInsertInput!): DrizzleUserItem - updatePost(set: DrizzlePostUpdateInput!, where: DrizzlePostFilters): [DrizzlePostItem!]! - updateUser(set: DrizzleUserUpdateInput!, where: DrizzleUserFilters): [DrizzleUserItem!]! + deleteFromPost(where: PostFilters): [PostItem!]! + deleteFromUser(where: UserFilters): [UserItem!]! + insertIntoPost(values: [PostInsertInput!]!): [PostItem!]! + insertIntoPostSingle(value: PostInsertInput!): PostItem + insertIntoUser(values: [UserInsertInput!]!): [UserItem!]! + insertIntoUserSingle(value: UserInsertInput!): UserItem + updatePost(set: PostUpdateInput!, where: PostFilters): [PostItem!]! + updateUser(set: UserUpdateInput!, where: UserFilters): [UserItem!]! } enum OrderDirection { @@ -107,26 +19,26 @@ input PgIntegerFilters { eq: Int gt: Int gte: Int - inArray: [Int!] + in: [Int!] isNotNull: Boolean isNull: Boolean lt: Int lte: Int ne: Int - notInArray: [Int!] + notIn: [Int!] } input PgIntegerFiltersOr { eq: Int gt: Int gte: Int - inArray: [Int!] + in: [Int!] isNotNull: Boolean isNull: Boolean lt: Int lte: Int ne: Int - notInArray: [Int!] + notIn: [Int!] } input PgSerialFilters { @@ -134,26 +46,26 @@ input PgSerialFilters { eq: Int gt: Int gte: Int - inArray: [Int!] + in: [Int!] isNotNull: Boolean isNull: Boolean lt: Int lte: Int ne: Int - notInArray: [Int!] + notIn: [Int!] } input PgSerialFiltersOr { eq: Int gt: Int gte: Int - inArray: [Int!] + in: [Int!] isNotNull: Boolean isNull: Boolean lt: Int lte: Int ne: Int - notInArray: [Int!] + notIn: [Int!] } input PgTextFilters { @@ -162,7 +74,7 @@ input PgTextFilters { gt: String gte: String ilike: String - inArray: [String!] + in: [String!] isNotNull: Boolean isNull: Boolean like: String @@ -170,7 +82,7 @@ input PgTextFilters { lte: String ne: String notIlike: String - notInArray: [String!] + notIn: [String!] notLike: String } @@ -179,7 +91,7 @@ input PgTextFiltersOr { gt: String gte: String ilike: String - inArray: [String!] + in: [String!] isNotNull: Boolean isNull: Boolean like: String @@ -187,13 +99,101 @@ input PgTextFiltersOr { lte: String ne: String notIlike: String - notInArray: [String!] + notIn: [String!] notLike: String } +input PostFilters { + OR: [PostFiltersOr!] + authorId: PgIntegerFilters + content: PgTextFilters + id: PgSerialFilters + title: PgTextFilters +} + +input PostFiltersOr { + authorId: PgIntegerFilters + content: PgTextFilters + id: PgSerialFilters + title: PgTextFilters +} + +input PostInsertInput { + authorId: Int + content: String + id: Int + title: String! +} + +type PostItem { + author: UserItem + authorId: Int + content: String + id: Int! + title: String! +} + +input PostOrderBy { + authorId: OrderDirection + content: OrderDirection + id: OrderDirection + title: OrderDirection +} + +input PostUpdateInput { + authorId: Int + content: String + id: Int + title: String +} + type Query { - post(limit: Int, offset: Int, orderBy: [DrizzlePostOrderBy!], where: DrizzlePostFilters): [DrizzlePostItem!]! - postSingle(offset: Int, orderBy: [DrizzlePostOrderBy!], where: DrizzlePostFilters): DrizzlePostItem - user(limit: Int, offset: Int, orderBy: [DrizzleUserOrderBy!], where: DrizzleUserFilters): [DrizzleUserItem!]! - userSingle(offset: Int, orderBy: [DrizzleUserOrderBy!], where: DrizzleUserFilters): DrizzleUserItem + post(limit: Int, offset: Int, orderBy: PostOrderBy, where: PostFilters): [PostItem!]! + postSingle(offset: Int, orderBy: PostOrderBy, where: PostFilters): PostItem + user(limit: Int, offset: Int, orderBy: UserOrderBy, where: UserFilters): [UserItem!]! + userSingle(offset: Int, orderBy: UserOrderBy, where: UserFilters): UserItem +} + +input UserFilters { + OR: [UserFiltersOr!] + age: PgIntegerFilters + email: PgTextFilters + id: PgSerialFilters + name: PgTextFilters +} + +input UserFiltersOr { + age: PgIntegerFilters + email: PgTextFilters + id: PgSerialFilters + name: PgTextFilters +} + +input UserInsertInput { + age: Int + email: String + id: Int + name: String! +} + +type UserItem { + age: Int + email: String + id: Int! + name: String! + posts: [PostItem!]! +} + +input UserOrderBy { + age: OrderDirection + email: OrderDirection + id: OrderDirection + name: OrderDirection +} + +input UserUpdateInput { + age: Int + email: String + id: Int + name: String } \ No newline at end of file diff --git a/packages/drizzle/test/resolver-postgres.spec.ts b/packages/drizzle/test/resolver-postgres.spec.ts index 901c8635..77980218 100644 --- a/packages/drizzle/test/resolver-postgres.spec.ts +++ b/packages/drizzle/test/resolver-postgres.spec.ts @@ -1,5 +1,4 @@ import { weave } from "@gqloom/core" -import { eq } from "drizzle-orm" import { type NodePgDatabase, drizzle } from "drizzle-orm/node-postgres" import { type GraphQLSchema, @@ -10,17 +9,13 @@ import { type YogaServerInstance, createYoga } from "graphql-yoga" import { afterAll, beforeAll, describe, expect, it } from "vitest" import { config } from "../env.config" import { drizzleResolverFactory } from "../src" -import { post, postsRelations, user, usersRelations } from "./schema/postgres" +import { post, user } from "./schema/postgres" +import { relations } from "./schema/postgres-relations" -const schema = { - drizzle_user: user, - drizzle_post: post, - usersRelations, - postsRelations, -} +const schema = { user, post } describe("resolver by postgres", () => { - let db: NodePgDatabase + let db: NodePgDatabase let gqlSchema: GraphQLSchema let yoga: YogaServerInstance<{}, {}> @@ -47,9 +42,9 @@ describe("resolver by postgres", () => { beforeAll(async () => { try { - db = drizzle(config.postgresUrl, { schema }) - const userFactory = drizzleResolverFactory(db, "drizzle_user") - const postFactory = drizzleResolverFactory(db, "drizzle_post") + db = drizzle(config.postgresUrl, { schema, relations }) + const userFactory = drizzleResolverFactory(db, "user") + const postFactory = drizzleResolverFactory(db, "post") gqlSchema = weave( userFactory.resolver({ name: "user" }), postFactory.resolver({ name: "post" }) @@ -59,14 +54,14 @@ describe("resolver by postgres", () => { await db .insert(user) .values([{ name: "Tom" }, { name: "Tony" }, { name: "Taylor" }]) - const Tom = await db.query.drizzle_user.findFirst({ - where: eq(user.name, "Tom"), + const Tom = await db.query.user.findFirst({ + where: { name: "Tom" }, }) - const Tony = await db.query.drizzle_user.findFirst({ - where: eq(user.name, "Tony"), + const Tony = await db.query.user.findFirst({ + where: { name: "Tony" }, }) - const Taylor = await db.query.drizzle_user.findFirst({ - where: eq(user.name, "Taylor"), + const Taylor = await db.query.user.findFirst({ + where: { name: "Taylor" }, }) if (!Tom || !Tony || !Taylor) throw new Error("User not found") @@ -95,16 +90,26 @@ describe("resolver by postgres", () => { describe.concurrent("query", () => { it("should query users correctly", async () => { const q = /* GraphQL */ ` - query user ($orderBy: [DrizzleUserOrderBy!], $where: DrizzleUserFilters!, $limit: Int, $offset: Int) { - user(orderBy: $orderBy, where: $where, limit: $limit, offset: $offset) { - id - name + query user( + $orderBy: UserOrderBy + $where: UserFilters! + $limit: Int + $offset: Int + ) { + user( + orderBy: $orderBy + where: $where + limit: $limit + offset: $offset + ) { + id + name + } } - } - ` + ` await expect( execute(q, { - orderBy: [{ name: "asc" }], + orderBy: { name: "asc" }, where: { name: { like: "T%" } }, }) ).resolves.toMatchObject({ @@ -113,7 +118,7 @@ describe("resolver by postgres", () => { await expect( execute(q, { - orderBy: [{ name: "asc" }], + orderBy: { name: "asc" }, where: { name: { like: "T%" } }, limit: 2, }) @@ -123,7 +128,7 @@ describe("resolver by postgres", () => { await expect( execute(q, { - orderBy: [{ name: "asc" }], + orderBy: { name: "asc" }, where: { name: { like: "T%" } }, limit: 1, offset: 1, @@ -137,13 +142,17 @@ describe("resolver by postgres", () => { await expect( execute( /* GraphQL */ ` - query user ($orderBy: [DrizzleUserOrderBy!], $where: DrizzleUserFilters!, $offset: Int) { - userSingle(orderBy: $orderBy, where: $where, offset: $offset) { - id - name + query user( + $orderBy: UserOrderBy + $where: UserFilters! + $offset: Int + ) { + userSingle(orderBy: $orderBy, where: $where, offset: $offset) { + id + name + } } - } - `, + `, { where: { name: { eq: "Taylor" } }, } @@ -155,8 +164,18 @@ describe("resolver by postgres", () => { it("should query user with posts correctly", async () => { const q = /* GraphQL */ ` - query user ($orderBy: [DrizzleUserOrderBy!], $where: DrizzleUserFilters!, $limit: Int, $offset: Int) { - user(orderBy: $orderBy,where: $where, limit: $limit, offset: $offset) { + query user( + $orderBy: UserOrderBy + $where: UserFilters! + $limit: Int + $offset: Int + ) { + user( + orderBy: $orderBy + where: $where + limit: $limit + offset: $offset + ) { id name posts { @@ -169,7 +188,7 @@ describe("resolver by postgres", () => { await expect( execute(q, { - orderBy: [{ name: "asc" }], + orderBy: { name: "asc" }, where: { name: { like: "T%" } }, }) ).resolves.toMatchObject({ @@ -194,7 +213,7 @@ describe("resolver by postgres", () => { describe("mutation", () => { it("should insert a new user correctly", async () => { const q = /* GraphQL */ ` - mutation insertIntoUser($values: [DrizzleUserInsertInput!]!) { + mutation insertIntoUser($values: [UserInsertInput!]!) { insertIntoUser(values: $values) { id name @@ -211,15 +230,18 @@ describe("resolver by postgres", () => { }) // Verify the user was inserted - const Tina = await db.query.drizzle_user.findFirst({ - where: eq(user.name, "Tina"), + const Tina = await db.query.user.findFirst({ + where: { name: "Tina" }, }) expect(Tina).toBeDefined() }) it("should update user information correctly", async () => { const q = /* GraphQL */ ` - mutation updateUser($set: DrizzleUserUpdateInput!, $where: DrizzleUserFilters!) { + mutation updateUser( + $set: UserUpdateInput! + $where: UserFilters! + ) { updateUser(set: $set, where: $where) { id name @@ -231,8 +253,8 @@ describe("resolver by postgres", () => { .insert(user) .values({ name: "Troy" }) .returning() - const Troy = await db.query.drizzle_user.findFirst({ - where: eq(user.id, TroyID.id), + const Troy = await db.query.user.findFirst({ + where: { id: TroyID.id }, }) if (!Troy) throw new Error("User not found") @@ -246,15 +268,15 @@ describe("resolver by postgres", () => { }) // Verify the user was updated - const updatedUser = await db.query.drizzle_user.findFirst({ - where: eq(user.name, "Tiffany"), + const updatedUser = await db.query.user.findFirst({ + where: { name: "Tiffany" }, }) expect(updatedUser).toBeDefined() }) it("should delete a user correctly", async () => { const q = /* GraphQL */ ` - mutation deleteFromUser($where: DrizzleUserFilters!) { + mutation deleteFromUser($where: UserFilters!) { deleteFromUser(where: $where) { id name @@ -262,8 +284,8 @@ describe("resolver by postgres", () => { } ` - const Tony = await db.query.drizzle_user.findFirst({ - where: eq(user.name, "Tony"), + const Tony = await db.query.user.findFirst({ + where: { name: "Tony" }, }) if (!Tony) throw new Error("User not found") @@ -276,15 +298,15 @@ describe("resolver by postgres", () => { }) // Verify the user was deleted - const deletedUser = await db.query.drizzle_user.findFirst({ - where: eq(user.name, "Tony"), + const deletedUser = await db.query.user.findFirst({ + where: { name: "Tony" }, }) expect(deletedUser).toBeUndefined() }) it("should insert a new post correctly", async () => { const q = /* GraphQL */ ` - mutation insertIntoPost($values: [DrizzlePostInsertInput!]!) { + mutation insertIntoPost($values: [PostInsertInput!]!) { insertIntoPost(values: $values) { id title @@ -293,8 +315,8 @@ describe("resolver by postgres", () => { } ` - const Tom = await db.query.drizzle_user.findFirst({ - where: eq(user.name, "Tom"), + const Tom = await db.query.user.findFirst({ + where: { name: "Tom" }, }) if (!Tom) throw new Error("User not found") @@ -307,15 +329,18 @@ describe("resolver by postgres", () => { }) // Verify the post was inserted - const p = await db.query.drizzle_post.findFirst({ - where: eq(post.title, "Post 5"), + const p = await db.query.post.findFirst({ + where: { title: "Post 5" }, }) expect(p).toBeDefined() }) it("should update post information correctly", async () => { const q = /* GraphQL */ ` - mutation updatePost($set: DrizzlePostUpdateInput!, $where: DrizzlePostFilters!) { + mutation updatePost( + $set: PostUpdateInput! + $where: PostFilters! + ) { updatePost(set: $set, where: $where) { id title @@ -328,8 +353,8 @@ describe("resolver by postgres", () => { .values({ title: "Post U" }) .returning() - const PostU = await db.query.drizzle_post.findFirst({ - where: eq(post.id, PostUID.id), + const PostU = await db.query.post.findFirst({ + where: { id: PostUID.id }, }) if (!PostU) throw new Error("Post not found") @@ -343,15 +368,15 @@ describe("resolver by postgres", () => { }) // Verify the post was updated - const updatedPost = await db.query.drizzle_post.findFirst({ - where: eq(post.title, "Updated Post U"), + const updatedPost = await db.query.post.findFirst({ + where: { title: "Updated Post U" }, }) expect(updatedPost).toBeDefined() }) it("should delete a post correctly", async () => { const q = /* GraphQL */ ` - mutation deleteFromPost($where: DrizzlePostFilters!) { + mutation deleteFromPost($where: PostFilters!) { deleteFromPost(where: $where) { id title @@ -364,8 +389,8 @@ describe("resolver by postgres", () => { .values({ title: "Post D" }) .returning() - const PostD = await db.query.drizzle_post.findFirst({ - where: eq(post.id, PostDID.id), + const PostD = await db.query.post.findFirst({ + where: { id: PostDID.id }, }) if (!PostD) throw new Error("Post not found") @@ -378,8 +403,8 @@ describe("resolver by postgres", () => { }) // Verify the post was deleted - const deletedPost = await db.query.drizzle_post.findFirst({ - where: eq(post.id, PostD.id), + const deletedPost = await db.query.post.findFirst({ + where: { id: PostD.id }, }) expect(deletedPost).toBeUndefined() }) diff --git a/packages/drizzle/test/resolver-sqlite.spec.gql b/packages/drizzle/test/resolver-sqlite.spec.gql index 223a0e6b..e2c1aed6 100644 --- a/packages/drizzle/test/resolver-sqlite.spec.gql +++ b/packages/drizzle/test/resolver-sqlite.spec.gql @@ -59,10 +59,10 @@ input PostUpdateInput { } type Query { - post(limit: Int, offset: Int, orderBy: [PostOrderBy!], where: PostFilters): [PostItem!]! - postSingle(offset: Int, orderBy: [PostOrderBy!], where: PostFilters): PostItem - user(limit: Int, offset: Int, orderBy: [UserOrderBy!], where: UserFilters): [UserItem!]! - userSingle(offset: Int, orderBy: [UserOrderBy!], where: UserFilters): UserItem + post(limit: Int, offset: Int, orderBy: PostOrderBy, where: PostFilters): [PostItem!]! + postSingle(offset: Int, orderBy: PostOrderBy, where: PostFilters): PostItem + user(limit: Int, offset: Int, orderBy: UserOrderBy, where: UserFilters): [UserItem!]! + userSingle(offset: Int, orderBy: UserOrderBy, where: UserFilters): UserItem } input SQLiteIntegerFilters { @@ -70,26 +70,26 @@ input SQLiteIntegerFilters { eq: Int gt: Int gte: Int - inArray: [Int!] + in: [Int!] isNotNull: Boolean isNull: Boolean lt: Int lte: Int ne: Int - notInArray: [Int!] + notIn: [Int!] } input SQLiteIntegerFiltersOr { eq: Int gt: Int gte: Int - inArray: [Int!] + in: [Int!] isNotNull: Boolean isNull: Boolean lt: Int lte: Int ne: Int - notInArray: [Int!] + notIn: [Int!] } input SQLiteTextFilters { @@ -98,7 +98,7 @@ input SQLiteTextFilters { gt: String gte: String ilike: String - inArray: [String!] + in: [String!] isNotNull: Boolean isNull: Boolean like: String @@ -106,7 +106,7 @@ input SQLiteTextFilters { lte: String ne: String notIlike: String - notInArray: [String!] + notIn: [String!] notLike: String } @@ -115,7 +115,7 @@ input SQLiteTextFiltersOr { gt: String gte: String ilike: String - inArray: [String!] + in: [String!] isNotNull: Boolean isNull: Boolean like: String @@ -123,7 +123,7 @@ input SQLiteTextFiltersOr { lte: String ne: String notIlike: String - notInArray: [String!] + notIn: [String!] notLike: String } diff --git a/packages/drizzle/test/resolver-sqlite.spec.ts b/packages/drizzle/test/resolver-sqlite.spec.ts index 5659c0b6..c297b4c4 100644 --- a/packages/drizzle/test/resolver-sqlite.spec.ts +++ b/packages/drizzle/test/resolver-sqlite.spec.ts @@ -1,5 +1,4 @@ import { weave } from "@gqloom/core" -import { eq } from "drizzle-orm" import { type LibSQLDatabase, drizzle } from "drizzle-orm/libsql" import { type GraphQLSchema, @@ -11,11 +10,12 @@ import { afterAll, beforeAll, describe, expect, it } from "vitest" import { drizzleResolverFactory } from "../src" import * as schema from "./schema/sqlite" import { post, user } from "./schema/sqlite" +import { relations } from "./schema/sqlite-relations" const pathToDB = new URL("./schema/sqlite-1.db", import.meta.url) describe("resolver by sqlite", () => { - let db: LibSQLDatabase + let db: LibSQLDatabase let gqlSchema: GraphQLSchema let yoga: YogaServerInstance<{}, {}> @@ -42,6 +42,7 @@ describe("resolver by sqlite", () => { beforeAll(async () => { db = drizzle({ schema, + relations, connection: { url: `file:${pathToDB.pathname}` }, }) const userFactory = drizzleResolverFactory(db, "user") @@ -53,13 +54,13 @@ describe("resolver by sqlite", () => { .insert(user) .values([{ name: "Tom" }, { name: "Tony" }, { name: "Taylor" }]) const Tom = await db.query.user.findFirst({ - where: eq(user.name, "Tom"), + where: { name: "Tom" }, }) const Tony = await db.query.user.findFirst({ - where: eq(user.name, "Tony"), + where: { name: "Tony" }, }) const Taylor = await db.query.user.findFirst({ - where: eq(user.name, "Taylor"), + where: { name: "Taylor" }, }) if (!Tom || !Tony || !Taylor) throw new Error("User not found") @@ -85,7 +86,7 @@ describe("resolver by sqlite", () => { describe.concurrent("query", () => { it("should query users correctly", async () => { const q = /* GraphQL */ ` - query user ($orderBy: [UserOrderBy!], $where: UserFilters!, $limit: Int, $offset: Int) { + query user ($orderBy: UserOrderBy, $where: UserFilters!, $limit: Int, $offset: Int) { user(orderBy: $orderBy, where: $where, limit: $limit, offset: $offset) { id name @@ -94,7 +95,7 @@ describe("resolver by sqlite", () => { ` await expect( execute(q, { - orderBy: [{ name: "asc" }], + orderBy: { name: "asc" }, where: { name: { like: "T%" } }, }) ).resolves.toMatchObject({ @@ -103,7 +104,7 @@ describe("resolver by sqlite", () => { await expect( execute(q, { - orderBy: [{ name: "asc" }], + orderBy: { name: "asc" }, where: { name: { like: "T%" } }, limit: 2, }) @@ -113,7 +114,7 @@ describe("resolver by sqlite", () => { await expect( execute(q, { - orderBy: [{ name: "asc" }], + orderBy: { name: "asc" }, where: { name: { like: "T%" } }, limit: 1, offset: 1, @@ -127,7 +128,7 @@ describe("resolver by sqlite", () => { await expect( execute( /* GraphQL */ ` - query user ($orderBy: [UserOrderBy!], $where: UserFilters!, $offset: Int) { + query user ($orderBy: UserOrderBy, $where: UserFilters!, $offset: Int) { userSingle(orderBy: $orderBy, where: $where, offset: $offset) { id name @@ -145,7 +146,7 @@ describe("resolver by sqlite", () => { it("should query user with posts correctly", async () => { const q = /* GraphQL */ ` - query user ($orderBy: [UserOrderBy!], $where: UserFilters!, $limit: Int, $offset: Int) { + query user ($orderBy: UserOrderBy, $where: UserFilters!, $limit: Int, $offset: Int) { user(orderBy: $orderBy,where: $where, limit: $limit, offset: $offset) { id name @@ -159,7 +160,7 @@ describe("resolver by sqlite", () => { await expect( execute(q, { - orderBy: [{ name: "asc" }], + orderBy: { name: "asc" }, where: { name: { like: "T%" } }, }) ).resolves.toMatchObject({ @@ -202,7 +203,7 @@ describe("resolver by sqlite", () => { // Verify the user was inserted const Tina = await db.query.user.findFirst({ - where: eq(user.name, "Tina"), + where: { name: "Tina" }, }) expect(Tina).toBeDefined() }) @@ -231,7 +232,7 @@ describe("resolver by sqlite", () => { // Verify the user was updated const updatedUser = await db.query.user.findFirst({ - where: eq(user.name, "Tiffany"), + where: { name: "Tiffany" }, }) expect(updatedUser).toBeDefined() }) @@ -247,7 +248,7 @@ describe("resolver by sqlite", () => { ` const Tony = await db.query.user.findFirst({ - where: eq(user.name, "Tony"), + where: { name: "Tony" }, }) if (!Tony) throw new Error("User not found") @@ -261,7 +262,7 @@ describe("resolver by sqlite", () => { // Verify the user was deleted const deletedUser = await db.query.user.findFirst({ - where: eq(user.name, "Tony"), + where: { name: "Tony" }, }) expect(deletedUser).toBeUndefined() }) @@ -278,7 +279,7 @@ describe("resolver by sqlite", () => { ` const Tom = await db.query.user.findFirst({ - where: eq(user.name, "Tom"), + where: { name: "Tom" }, }) if (!Tom) throw new Error("User not found") @@ -292,7 +293,7 @@ describe("resolver by sqlite", () => { // Verify the post was inserted const p = await db.query.post.findFirst({ - where: eq(post.title, "Post 5"), + where: { title: "Post 5" }, }) expect(p).toBeDefined() }) @@ -324,7 +325,7 @@ describe("resolver by sqlite", () => { // Verify the post was updated const updatedPost = await db.query.post.findFirst({ - where: eq(post.title, "Updated Post U"), + where: { title: "Updated Post U" }, }) expect(updatedPost).toBeDefined() }) @@ -355,7 +356,7 @@ describe("resolver by sqlite", () => { // Verify the post was deleted const deletedPost = await db.query.post.findFirst({ - where: eq(post.id, PostD.id), + where: { id: PostD.id }, }) expect(deletedPost).toBeUndefined() }) diff --git a/packages/drizzle/test/schema/mysql-relations.ts b/packages/drizzle/test/schema/mysql-relations.ts new file mode 100644 index 00000000..56a587b3 --- /dev/null +++ b/packages/drizzle/test/schema/mysql-relations.ts @@ -0,0 +1,14 @@ +import { defineRelations } from "drizzle-orm" +import * as schema from "./mysql" + +export const relations = defineRelations(schema, (r) => ({ + user: { + posts: r.many.post(), + }, + post: { + author: r.one.user({ + from: r.post.authorId, + to: r.user.id, + }), + }, +})) diff --git a/packages/drizzle/test/schema/mysql.ts b/packages/drizzle/test/schema/mysql.ts index f61662e9..8d9ebd7a 100644 --- a/packages/drizzle/test/schema/mysql.ts +++ b/packages/drizzle/test/schema/mysql.ts @@ -1,9 +1,8 @@ -import { relations } from "drizzle-orm" import * as t from "drizzle-orm/mysql-core" import { drizzleSilk } from "../../src" export const user = drizzleSilk( - t.mysqlTable("drizzle_user", { + t.mysqlTable("user", { id: t.int().primaryKey().autoincrement(), name: t.text().notNull(), age: t.int(), @@ -11,22 +10,11 @@ export const user = drizzleSilk( }) ) -export const usersRelations = relations(user, ({ many }) => ({ - posts: many(post), -})) - export const post = drizzleSilk( - t.mysqlTable("drizzle_post", { + t.mysqlTable("post", { id: t.int().primaryKey().autoincrement(), title: t.text().notNull(), content: t.text(), authorId: t.int().references(() => user.id, { onDelete: "cascade" }), }) ) - -export const postsRelations = relations(post, ({ one }) => ({ - author: one(user, { - fields: [post.authorId], - references: [user.id], - }), -})) diff --git a/packages/drizzle/test/schema/postgres-relations.ts b/packages/drizzle/test/schema/postgres-relations.ts new file mode 100644 index 00000000..90256a88 --- /dev/null +++ b/packages/drizzle/test/schema/postgres-relations.ts @@ -0,0 +1,14 @@ +import { defineRelations } from "drizzle-orm" +import * as schema from "./postgres" + +export const relations = defineRelations(schema, (r) => ({ + user: { + posts: r.many.post(), + }, + post: { + author: r.one.user({ + from: r.post.authorId, + to: r.user.id, + }), + }, +})) diff --git a/packages/drizzle/test/schema/postgres.ts b/packages/drizzle/test/schema/postgres.ts index f8c3f118..b97f2cca 100644 --- a/packages/drizzle/test/schema/postgres.ts +++ b/packages/drizzle/test/schema/postgres.ts @@ -1,9 +1,8 @@ -import { relations } from "drizzle-orm" import * as t from "drizzle-orm/pg-core" import { drizzleSilk } from "../../src" export const user = drizzleSilk( - t.pgTable("drizzle_user", { + t.pgTable("user", { id: t.serial().primaryKey(), name: t.text().notNull(), age: t.integer(), @@ -11,22 +10,11 @@ export const user = drizzleSilk( }) ) -export const usersRelations = relations(user, ({ many }) => ({ - posts: many(post), -})) - export const post = drizzleSilk( - t.pgTable("drizzle_post", { + t.pgTable("post", { id: t.serial().primaryKey(), title: t.text().notNull(), content: t.text(), authorId: t.integer().references(() => user.id, { onDelete: "cascade" }), }) ) - -export const postsRelations = relations(post, ({ one }) => ({ - author: one(user, { - fields: [post.authorId], - references: [user.id], - }), -})) diff --git a/packages/drizzle/test/schema/sqlite-relations.ts b/packages/drizzle/test/schema/sqlite-relations.ts new file mode 100644 index 00000000..6f37a7df --- /dev/null +++ b/packages/drizzle/test/schema/sqlite-relations.ts @@ -0,0 +1,32 @@ +import { defineRelations } from "drizzle-orm" +import * as schema from "./sqlite" + +export const relations = defineRelations(schema, (r) => ({ + user: { + posts: r.many.post(), + courses: r.many.studentToCourse(), + }, + post: { + author: r.one.user({ + from: r.post.authorId, + to: r.user.id, + }), + }, + course: { + students: r.many.studentToCourse(), + }, + studentToCourse: { + student: r.one.user({ + from: r.studentToCourse.studentId, + to: r.user.id, + }), + course: r.one.course({ + from: r.studentToCourse.courseId, + to: r.course.id, + }), + grade: r.one.studentCourseGrade({ + from: [r.studentToCourse.studentId, r.studentToCourse.courseId], + to: [r.studentCourseGrade.studentId, r.studentCourseGrade.courseId], + }), + }, +})) diff --git a/packages/drizzle/test/schema/sqlite.ts b/packages/drizzle/test/schema/sqlite.ts index 7ad36922..f81f5615 100644 --- a/packages/drizzle/test/schema/sqlite.ts +++ b/packages/drizzle/test/schema/sqlite.ts @@ -1,4 +1,4 @@ -import { relations, sql } from "drizzle-orm" +import { sql } from "drizzle-orm" import * as t from "drizzle-orm/sqlite-core" import { drizzleSilk } from "../../src" @@ -11,11 +11,6 @@ export const user = drizzleSilk( }) ) -export const usersRelations = relations(user, ({ many }) => ({ - posts: many(post), - courses: many(studentToCourse), -})) - export const post = drizzleSilk( t.sqliteTable("post", { id: t.int().primaryKey({ autoIncrement: true }), @@ -25,13 +20,6 @@ export const post = drizzleSilk( }) ) -export const postsRelations = relations(post, ({ one }) => ({ - author: one(user, { - fields: [post.authorId], - references: [user.id], - }), -})) - export const course = drizzleSilk( t.sqliteTable("course", { id: t.int().primaryKey({ autoIncrement: true }), @@ -39,10 +27,6 @@ export const course = drizzleSilk( }) ) -export const coursesRelations = relations(course, ({ many }) => ({ - students: many(studentToCourse), -})) - export const studentToCourse = drizzleSilk( t.sqliteTable("studentToCourse", { studentId: t.int().references(() => user.id), @@ -51,24 +35,6 @@ export const studentToCourse = drizzleSilk( }) ) -export const studentToCourseRelations = relations( - studentToCourse, - ({ one }) => ({ - student: one(user, { - fields: [studentToCourse.studentId], - references: [user.id], - }), - course: one(course, { - fields: [studentToCourse.courseId], - references: [course.id], - }), - grade: one(studentCourseGrade, { - fields: [studentToCourse.studentId, studentToCourse.courseId], - references: [studentCourseGrade.studentId, studentCourseGrade.courseId], - }), - }) -) - export const studentCourseGrade = drizzleSilk( t.sqliteTable("studentCourseGrade", { studentId: t.int().references(() => user.id), diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index b19a80ee..0ce899c5 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -84,22 +84,19 @@ importers: dependencies: '@gqloom/core': specifier: latest - version: 0.8.0(graphql@16.8.1(patch_hash=2p6wc42resg5at2p4utt5idamq)) + version: 0.8.3(graphql@16.8.1(patch_hash=2p6wc42resg5at2p4utt5idamq)) '@gqloom/drizzle': specifier: latest - version: 0.8.0(@gqloom/core@0.8.0(graphql@16.8.1(patch_hash=2p6wc42resg5at2p4utt5idamq)))(drizzle-orm@0.39.3(@libsql/client@0.14.0)(@prisma/client@6.1.0(prisma@6.1.0))(@types/pg@8.11.10)(better-sqlite3@11.8.1)(knex@3.1.0(better-sqlite3@11.8.1)(mysql2@3.12.0)(pg@8.13.2)(sqlite3@5.1.7))(mysql2@3.12.0)(pg@8.13.2)(prisma@6.1.0)(sqlite3@5.1.7))(graphql@16.8.1(patch_hash=2p6wc42resg5at2p4utt5idamq)) + version: 0.8.1(@gqloom/core@0.8.3(graphql@16.8.1(patch_hash=2p6wc42resg5at2p4utt5idamq)))(drizzle-orm@1.0.0-beta.1-bd417c1(@libsql/client@0.14.0)(@prisma/client@6.1.0(prisma@6.1.0))(@types/pg@8.11.10)(better-sqlite3@11.8.1)(gel@2.0.2)(knex@3.1.0(better-sqlite3@11.8.1)(mysql2@3.12.0)(pg@8.13.2)(sqlite3@5.1.7))(mysql2@3.12.0)(pg@8.13.2)(prisma@6.1.0)(sqlite3@5.1.7))(graphql@16.8.1(patch_hash=2p6wc42resg5at2p4utt5idamq)) '@gqloom/valibot': specifier: ^0.7.1 - version: 0.7.1(@gqloom/core@0.8.0(graphql@16.8.1(patch_hash=2p6wc42resg5at2p4utt5idamq)))(graphql@16.8.1(patch_hash=2p6wc42resg5at2p4utt5idamq))(valibot@1.0.0-rc.1(typescript@5.7.3)) + version: 0.7.1(@gqloom/core@0.8.3(graphql@16.8.1(patch_hash=2p6wc42resg5at2p4utt5idamq)))(graphql@16.8.1(patch_hash=2p6wc42resg5at2p4utt5idamq))(valibot@1.0.0-rc.1(typescript@5.7.3)) '@libsql/client': specifier: ^0.14.0 version: 0.14.0 dotenv: specifier: ^16.4.7 version: 16.4.7 - drizzle-orm: - specifier: ^0.39.3 - version: 0.39.3(@libsql/client@0.14.0)(@prisma/client@6.1.0(prisma@6.1.0))(@types/pg@8.11.10)(better-sqlite3@11.8.1)(knex@3.1.0(better-sqlite3@11.8.1)(mysql2@3.12.0)(pg@8.13.2)(sqlite3@5.1.7))(mysql2@3.12.0)(pg@8.13.2)(prisma@6.1.0)(sqlite3@5.1.7) graphql: specifier: ^16.8.1 version: 16.8.1(patch_hash=2p6wc42resg5at2p4utt5idamq) @@ -114,8 +111,11 @@ importers: specifier: ^22.13.4 version: 22.13.4 drizzle-kit: - specifier: ^0.30.1 - version: 0.30.1 + specifier: 1.0.0-beta.1-bd417c1 + version: 1.0.0-beta.1-bd417c1 + drizzle-orm: + specifier: 1.0.0-beta.1-bd417c1 + version: 1.0.0-beta.1-bd417c1(@libsql/client@0.14.0)(@prisma/client@6.1.0(prisma@6.1.0))(@types/pg@8.11.10)(better-sqlite3@11.8.1)(gel@2.0.2)(knex@3.1.0(better-sqlite3@11.8.1)(mysql2@3.12.0)(pg@8.13.2)(sqlite3@5.1.7))(mysql2@3.12.0)(pg@8.13.2)(prisma@6.1.0)(sqlite3@5.1.7) tsx: specifier: ^4.7.2 version: 4.7.2 @@ -127,22 +127,19 @@ importers: dependencies: '@gqloom/core': specifier: latest - version: 0.8.0(graphql@16.8.1(patch_hash=2p6wc42resg5at2p4utt5idamq)) + version: 0.8.3(graphql@16.8.1(patch_hash=2p6wc42resg5at2p4utt5idamq)) '@gqloom/drizzle': specifier: latest - version: 0.8.0(@gqloom/core@0.8.0(graphql@16.8.1(patch_hash=2p6wc42resg5at2p4utt5idamq)))(drizzle-orm@0.39.3(@libsql/client@0.14.0)(@prisma/client@6.1.0(prisma@6.1.0))(@types/pg@8.11.10)(better-sqlite3@11.8.1)(knex@3.1.0(better-sqlite3@11.8.1)(mysql2@3.12.0)(pg@8.13.2)(sqlite3@5.1.7))(mysql2@3.12.0)(pg@8.13.2)(prisma@6.1.0)(sqlite3@5.1.7))(graphql@16.8.1(patch_hash=2p6wc42resg5at2p4utt5idamq)) + version: 0.8.1(@gqloom/core@0.8.3(graphql@16.8.1(patch_hash=2p6wc42resg5at2p4utt5idamq)))(drizzle-orm@1.0.0-beta.1-bd417c1(@libsql/client@0.14.0)(@prisma/client@6.1.0(prisma@6.1.0))(@types/pg@8.11.10)(better-sqlite3@11.8.1)(gel@2.0.2)(knex@3.1.0(better-sqlite3@11.8.1)(mysql2@3.12.0)(pg@8.13.2)(sqlite3@5.1.7))(mysql2@3.12.0)(pg@8.13.2)(prisma@6.1.0)(sqlite3@5.1.7))(graphql@16.8.1(patch_hash=2p6wc42resg5at2p4utt5idamq)) '@gqloom/zod': specifier: latest - version: 0.8.0(@gqloom/core@0.8.0(graphql@16.8.1(patch_hash=2p6wc42resg5at2p4utt5idamq)))(graphql@16.8.1(patch_hash=2p6wc42resg5at2p4utt5idamq))(zod@3.24.2) + version: 0.8.1(@gqloom/core@0.8.3(graphql@16.8.1(patch_hash=2p6wc42resg5at2p4utt5idamq)))(graphql@16.8.1(patch_hash=2p6wc42resg5at2p4utt5idamq))(zod@3.24.2) '@libsql/client': specifier: ^0.14.0 version: 0.14.0 dotenv: specifier: ^16.4.7 version: 16.4.7 - drizzle-orm: - specifier: ^0.39.3 - version: 0.39.3(@libsql/client@0.14.0)(@prisma/client@6.1.0(prisma@6.1.0))(@types/pg@8.11.10)(better-sqlite3@11.8.1)(knex@3.1.0(better-sqlite3@11.8.1)(mysql2@3.12.0)(pg@8.13.2)(sqlite3@5.1.7))(mysql2@3.12.0)(pg@8.13.2)(prisma@6.1.0)(sqlite3@5.1.7) graphql: specifier: ^16.8.1 version: 16.8.1(patch_hash=2p6wc42resg5at2p4utt5idamq) @@ -157,8 +154,11 @@ importers: specifier: ^22.13.4 version: 22.13.4 drizzle-kit: - specifier: ^0.30.1 - version: 0.30.1 + specifier: 1.0.0-beta.1-bd417c1 + version: 1.0.0-beta.1-bd417c1 + drizzle-orm: + specifier: 1.0.0-beta.1-bd417c1 + version: 1.0.0-beta.1-bd417c1(@libsql/client@0.14.0)(@prisma/client@6.1.0(prisma@6.1.0))(@types/pg@8.11.10)(better-sqlite3@11.8.1)(gel@2.0.2)(knex@3.1.0(better-sqlite3@11.8.1)(mysql2@3.12.0)(pg@8.13.2)(sqlite3@5.1.7))(mysql2@3.12.0)(pg@8.13.2)(prisma@6.1.0)(sqlite3@5.1.7) tsx: specifier: ^4.7.2 version: 4.7.2 @@ -179,7 +179,7 @@ importers: version: 16.4.7 drizzle-seed: specifier: ^0.3.1 - version: 0.3.1(drizzle-orm@0.39.3(@libsql/client@0.14.0)(@prisma/client@6.1.0(prisma@6.1.0))(@types/pg@8.11.10)(better-sqlite3@11.8.1)(knex@3.1.0(better-sqlite3@11.8.1)(mysql2@3.12.0)(pg@8.13.2)(sqlite3@5.1.7))(mysql2@3.12.0)(pg@8.13.2)(prisma@6.1.0)(sqlite3@5.1.7)) + version: 0.3.1(drizzle-orm@1.0.0-beta.1-bd417c1(@libsql/client@0.14.0)(@prisma/client@6.1.0(prisma@6.1.0))(@types/pg@8.11.10)(better-sqlite3@11.8.1)(gel@2.0.2)(knex@3.1.0(better-sqlite3@11.8.1)(mysql2@3.12.0)(pg@8.13.2)(sqlite3@5.1.7))(mysql2@3.12.0)(pg@8.13.2)(prisma@6.1.0)(sqlite3@5.1.7)) graphql: specifier: ^16.8.1 version: 16.8.1(patch_hash=2p6wc42resg5at2p4utt5idamq) @@ -197,11 +197,11 @@ importers: specifier: ^8.11.10 version: 8.11.10 drizzle-kit: - specifier: ^0.30.1 - version: 0.30.1 + specifier: 1.0.0-beta.1-bd417c1 + version: 1.0.0-beta.1-bd417c1 drizzle-orm: - specifier: ^0.39.3 - version: 0.39.3(@libsql/client@0.14.0)(@prisma/client@6.1.0(prisma@6.1.0))(@types/pg@8.11.10)(better-sqlite3@11.8.1)(knex@3.1.0(better-sqlite3@11.8.1)(mysql2@3.12.0)(pg@8.13.2)(sqlite3@5.1.7))(mysql2@3.12.0)(pg@8.13.2)(prisma@6.1.0)(sqlite3@5.1.7) + specifier: 1.0.0-beta.1-bd417c1 + version: 1.0.0-beta.1-bd417c1(@libsql/client@0.14.0)(@prisma/client@6.1.0(prisma@6.1.0))(@types/pg@8.11.10)(better-sqlite3@11.8.1)(gel@2.0.2)(knex@3.1.0(better-sqlite3@11.8.1)(mysql2@3.12.0)(pg@8.13.2)(sqlite3@5.1.7))(mysql2@3.12.0)(pg@8.13.2)(prisma@6.1.0)(sqlite3@5.1.7) tsx: specifier: ^4.7.2 version: 4.7.2 @@ -338,11 +338,11 @@ importers: specifier: ^16.4.7 version: 16.4.7 drizzle-kit: - specifier: ^0.30.1 - version: 0.30.1 + specifier: 1.0.0-beta.1-bd417c1 + version: 1.0.0-beta.1-bd417c1 drizzle-orm: - specifier: ^0.39.3 - version: 0.39.3(@libsql/client@0.14.0)(@prisma/client@6.1.0(prisma@6.1.0))(@types/pg@8.11.10)(better-sqlite3@11.8.1)(knex@3.1.0(better-sqlite3@11.8.1)(mysql2@3.12.0)(pg@8.13.2)(sqlite3@5.1.7))(mysql2@3.12.0)(pg@8.13.2)(prisma@6.1.0)(sqlite3@5.1.7) + specifier: 1.0.0-beta.1-bd417c1 + version: 1.0.0-beta.1-bd417c1(@libsql/client@0.14.0)(@prisma/client@6.1.0(prisma@6.1.0))(@types/pg@8.11.10)(better-sqlite3@11.8.1)(gel@2.0.2)(knex@3.1.0(better-sqlite3@11.8.1)(mysql2@3.12.0)(pg@8.13.2)(sqlite3@5.1.7))(mysql2@3.12.0)(pg@8.13.2)(prisma@6.1.0)(sqlite3@5.1.7) graphql: specifier: ^16.8.1 version: 16.8.1(patch_hash=2p6wc42resg5at2p4utt5idamq) @@ -1454,13 +1454,13 @@ packages: peerDependencies: graphql: '>= 16.8.0' - '@gqloom/core@0.8.0': - resolution: {integrity: sha512-yEKwVXto4Ol73WNniPcUUu/lnelQin+Sy+fycEnpBFKEXHxhMfyQBd5gLNaYp2mAiwFC0C9CglEe9UGGKZmUew==} + '@gqloom/core@0.8.3': + resolution: {integrity: sha512-kV9mLi4wAt+dv6NRhfrfr5EIGbtxqEMqI2hyMef16/4KLGB/IGabDT1nMtWIrDDS8pa1ZFJ+aRpkWYQagtw7Sw==} peerDependencies: graphql: '>= 16.8.0' - '@gqloom/drizzle@0.8.0': - resolution: {integrity: sha512-6C26utloGUBCnt1ubGIGhSmjhTFkUdOQezFewPtawD8k5WdrHWu/OlxoI2J5CpomPgz/wY95r2dFAQKJVYgh0g==} + '@gqloom/drizzle@0.8.1': + resolution: {integrity: sha512-6/IAq3///OuEbNUUqm8UPV+6RCW5ZVQP1532WPsa0T6ipuCwi1YqgxICCSZudgBarzbhwdDkgWjWqddT+wkOVA==} peerDependencies: '@gqloom/core': '>= 0.8.0' drizzle-orm: '>= 0.38.0' @@ -1480,8 +1480,8 @@ packages: graphql: '>= 16.8.0' valibot: '>= 1.0.0' - '@gqloom/zod@0.8.0': - resolution: {integrity: sha512-2iJdhPhGRlyu2ejNlSGc01FpV3g8sF5NyxHyJvs/giZMKl8uojNHDjLP885lKtzoZpyN889OqUa04WdN0o39JA==} + '@gqloom/zod@0.8.1': + resolution: {integrity: sha512-C/RAMozjc0FnHqRoODaIfqvZ2ighOit7Z3UVqeWN+i2aXhQtYlDN74cG9FNMojwYmlgdwZzh31sm+tyNnctIHQ==} peerDependencies: '@gqloom/core': '>= 0.8.0' graphql: '>= 16.8.0' @@ -2003,6 +2003,9 @@ packages: cpu: [x64] os: [win32] + '@petamoriken/float16@3.9.2': + resolution: {integrity: sha512-VgffxawQde93xKxT3qap3OH+meZf7VaSB5Sqd4Rqc+FP5alWbpOyan/7tRbOAvynjpG3GpdtAuGU/NdhQpmrog==} + '@pkgjs/parseargs@0.11.0': resolution: {integrity: sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==} engines: {node: '>=14'} @@ -3406,6 +3409,10 @@ packages: resolution: {integrity: sha512-HmA/NeewvHywhJ2ENXD3KvOuM/+K2dGLJfxVfIHsGwaqKICJnS+Ke2L6UcSrSrtMJLJaT0Im1Qv4TFXfaZShyw==} hasBin: true + drizzle-kit@1.0.0-beta.1-bd417c1: + resolution: {integrity: sha512-snxKV7u3b6UXn+qo9Udg49SqiKrqyQ3Gyf/uz8zTD5QDOD5TTo4abOptYZr/afnTr5YOlZnYsLzr9q2ORyIlbg==} + hasBin: true + drizzle-orm@0.39.3: resolution: {integrity: sha512-EZ8ZpYvDIvKU9C56JYLOmUskazhad+uXZCTCRN4OnRMsL+xAJ05dv1eCpAG5xzhsm1hqiuC5kAZUCS924u2DTw==} peerDependencies: @@ -3492,6 +3499,95 @@ packages: sqlite3: optional: true + drizzle-orm@1.0.0-beta.1-bd417c1: + resolution: {integrity: sha512-WCjjODdeDAkpo4ceCCA7pp8qFiQZbc5AAJiYxFdM7rSH4Ez71VWC5oEvIcvwepACyHUCtCxYgzKOyjFITFa8CA==} + peerDependencies: + '@aws-sdk/client-rds-data': '>=3' + '@cloudflare/workers-types': '>=4' + '@electric-sql/pglite': '>=0.2.0' + '@libsql/client': '>=0.10.0' + '@libsql/client-wasm': '>=0.10.0' + '@neondatabase/serverless': '>=0.10.0' + '@op-engineering/op-sqlite': '>=2' + '@opentelemetry/api': ^1.4.1 + '@planetscale/database': '>=1' + '@prisma/client': '*' + '@tidbcloud/serverless': '*' + '@types/better-sqlite3': '*' + '@types/pg': '*' + '@types/sql.js': '*' + '@vercel/postgres': '>=0.8.0' + '@xata.io/client': '*' + better-sqlite3: '>=9.3.0' + bun-types: '*' + expo-sqlite: '>=14.0.0' + gel: '>=2' + knex: '*' + kysely: '*' + mysql2: '>=2' + pg: ^8.13.2 + postgres: '>=3' + prisma: '*' + sql.js: '>=1' + sqlite3: '>=5' + peerDependenciesMeta: + '@aws-sdk/client-rds-data': + optional: true + '@cloudflare/workers-types': + optional: true + '@electric-sql/pglite': + optional: true + '@libsql/client': + optional: true + '@libsql/client-wasm': + optional: true + '@neondatabase/serverless': + optional: true + '@op-engineering/op-sqlite': + optional: true + '@opentelemetry/api': + optional: true + '@planetscale/database': + optional: true + '@prisma/client': + optional: true + '@tidbcloud/serverless': + optional: true + '@types/better-sqlite3': + optional: true + '@types/pg': + optional: true + '@types/sql.js': + optional: true + '@vercel/postgres': + optional: true + '@xata.io/client': + optional: true + better-sqlite3: + optional: true + bun-types: + optional: true + expo-sqlite: + optional: true + gel: + optional: true + knex: + optional: true + kysely: + optional: true + mysql2: + optional: true + pg: + optional: true + postgres: + optional: true + prisma: + optional: true + sql.js: + optional: true + sqlite3: + optional: true + drizzle-seed@0.3.1: resolution: {integrity: sha512-F/0lgvfOAsqlYoHM/QAGut4xXIOXoE5VoAdv2FIl7DpGYVXlAzKuJO+IphkKUFK3Dz+rFlOsQLnMNrvoQ0cx7g==} peerDependencies: @@ -3547,6 +3643,10 @@ packages: resolution: {integrity: sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A==} engines: {node: '>=6'} + env-paths@3.0.0: + resolution: {integrity: sha512-dtJUTepzMW3Lm/NPxRf3wP4642UWhjL2sQxc+ym2YMj1m/H2zDNQOlezafzkHwn6sMstjHTwG6iQQsctDW/b1A==} + engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} + err-code@2.0.3: resolution: {integrity: sha512-2bmlRpNKBxT/CRmPOlyISQpNj+qSeYvcym/uT0Jx2bMOlKLtSy1ZmLuVxSEKKyor/N5yhvp/ZiG1oE3DEYMSFA==} @@ -3858,6 +3958,11 @@ packages: engines: {node: ^12.13.0 || ^14.15.0 || >=16.0.0} deprecated: This package is no longer supported. + gel@2.0.2: + resolution: {integrity: sha512-XTKpfNR9HZOw+k0Bl04nETZjuP5pypVAXsZADSdwr3EtyygTTe1RqvftU2FjGu7Tp9e576a9b/iIOxWrRBxMiQ==} + engines: {node: '>= 18.0.0'} + hasBin: true + generate-function@2.3.1: resolution: {integrity: sha512-eeB5GfMNeevm/GRYq20ShmsaGcmI81kIX2K9XQx5miC8KdHaC6Jm0qQ8ZNeGOi7wYB8OsdxKs+Y2oVuTFuVwKQ==} @@ -4190,6 +4295,10 @@ packages: isexe@2.0.0: resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==} + isexe@3.1.1: + resolution: {integrity: sha512-LpB/54B+/2J5hqQ7imZHfdU31OlgQqx7ZicVlkm9kzg9/w8GKLEcFfJl/t7DCEDueOyBAD6zCCwTO6Fzs0NoEQ==} + engines: {node: '>=16'} + istanbul-lib-coverage@3.2.2: resolution: {integrity: sha512-O8dpsF+r0WV/8MNRKfnmrtCWhuKjxrq2w+jpzBL5UZKTi2LeVWnWOmWRxFlesJONmc+wLAGvKQZEOanko0LFTg==} engines: {node: '>=8'} @@ -5457,6 +5566,10 @@ packages: resolution: {integrity: sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==} engines: {node: '>=8'} + shell-quote@1.8.2: + resolution: {integrity: sha512-AzqKpGKjrj7EM6rKVQEPpB288oCfnrEIuyoT9cyF4nmGa7V8Zk6f7RRqYisX8X9m+Q7bd632aZW4ky7EhbQztA==} + engines: {node: '>= 0.4'} + shiki@2.3.1: resolution: {integrity: sha512-bD1XuVAyZBVxHiPlO/m2nM2F5g8G5MwSZHNYx+ArpcOW52+fCN6peGP5gG61O0gZpzUVbImeR3ar8cF+Z5WM8g==} @@ -6089,6 +6202,11 @@ packages: engines: {node: '>= 8'} hasBin: true + which@4.0.0: + resolution: {integrity: sha512-GlaYyEb07DPxYCKhKzplCWBJtvxZcZMrL+4UkrTSJHHPyZU4mYYTv3qaOe77H7EODLSSopAUFAc6W8U4yqvscg==} + engines: {node: ^16.13.0 || >=18.0.0} + hasBin: true + why-is-node-running@2.3.0: resolution: {integrity: sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w==} engines: {node: '>=8'} @@ -6761,14 +6879,14 @@ snapshots: dependencies: graphql: 16.8.1(patch_hash=2p6wc42resg5at2p4utt5idamq) - '@gqloom/core@0.8.0(graphql@16.8.1(patch_hash=2p6wc42resg5at2p4utt5idamq))': + '@gqloom/core@0.8.3(graphql@16.8.1(patch_hash=2p6wc42resg5at2p4utt5idamq))': dependencies: graphql: 16.8.1(patch_hash=2p6wc42resg5at2p4utt5idamq) - '@gqloom/drizzle@0.8.0(@gqloom/core@0.8.0(graphql@16.8.1(patch_hash=2p6wc42resg5at2p4utt5idamq)))(drizzle-orm@0.39.3(@libsql/client@0.14.0)(@prisma/client@6.1.0(prisma@6.1.0))(@types/pg@8.11.10)(better-sqlite3@11.8.1)(knex@3.1.0(better-sqlite3@11.8.1)(mysql2@3.12.0)(pg@8.13.2)(sqlite3@5.1.7))(mysql2@3.12.0)(pg@8.13.2)(prisma@6.1.0)(sqlite3@5.1.7))(graphql@16.8.1(patch_hash=2p6wc42resg5at2p4utt5idamq))': + '@gqloom/drizzle@0.8.1(@gqloom/core@0.8.3(graphql@16.8.1(patch_hash=2p6wc42resg5at2p4utt5idamq)))(drizzle-orm@1.0.0-beta.1-bd417c1(@libsql/client@0.14.0)(@prisma/client@6.1.0(prisma@6.1.0))(@types/pg@8.11.10)(better-sqlite3@11.8.1)(gel@2.0.2)(knex@3.1.0(better-sqlite3@11.8.1)(mysql2@3.12.0)(pg@8.13.2)(sqlite3@5.1.7))(mysql2@3.12.0)(pg@8.13.2)(prisma@6.1.0)(sqlite3@5.1.7))(graphql@16.8.1(patch_hash=2p6wc42resg5at2p4utt5idamq))': dependencies: - '@gqloom/core': 0.8.0(graphql@16.8.1(patch_hash=2p6wc42resg5at2p4utt5idamq)) - drizzle-orm: 0.39.3(@libsql/client@0.14.0)(@prisma/client@6.1.0(prisma@6.1.0))(@types/pg@8.11.10)(better-sqlite3@11.8.1)(knex@3.1.0(better-sqlite3@11.8.1)(mysql2@3.12.0)(pg@8.13.2)(sqlite3@5.1.7))(mysql2@3.12.0)(pg@8.13.2)(prisma@6.1.0)(sqlite3@5.1.7) + '@gqloom/core': 0.8.3(graphql@16.8.1(patch_hash=2p6wc42resg5at2p4utt5idamq)) + drizzle-orm: 1.0.0-beta.1-bd417c1(@libsql/client@0.14.0)(@prisma/client@6.1.0(prisma@6.1.0))(@types/pg@8.11.10)(better-sqlite3@11.8.1)(gel@2.0.2)(knex@3.1.0(better-sqlite3@11.8.1)(mysql2@3.12.0)(pg@8.13.2)(sqlite3@5.1.7))(mysql2@3.12.0)(pg@8.13.2)(prisma@6.1.0)(sqlite3@5.1.7) graphql: 16.8.1(patch_hash=2p6wc42resg5at2p4utt5idamq) '@gqloom/mikro-orm@0.7.0(@gqloom/core@0.7.0(graphql@16.8.1(patch_hash=2p6wc42resg5at2p4utt5idamq)))(@mikro-orm/core@6.4.6)(graphql@16.8.1(patch_hash=2p6wc42resg5at2p4utt5idamq))': @@ -6777,15 +6895,15 @@ snapshots: '@mikro-orm/core': 6.4.6 graphql: 16.8.1(patch_hash=2p6wc42resg5at2p4utt5idamq) - '@gqloom/valibot@0.7.1(@gqloom/core@0.8.0(graphql@16.8.1(patch_hash=2p6wc42resg5at2p4utt5idamq)))(graphql@16.8.1(patch_hash=2p6wc42resg5at2p4utt5idamq))(valibot@1.0.0-rc.1(typescript@5.7.3))': + '@gqloom/valibot@0.7.1(@gqloom/core@0.8.3(graphql@16.8.1(patch_hash=2p6wc42resg5at2p4utt5idamq)))(graphql@16.8.1(patch_hash=2p6wc42resg5at2p4utt5idamq))(valibot@1.0.0-rc.1(typescript@5.7.3))': dependencies: - '@gqloom/core': 0.8.0(graphql@16.8.1(patch_hash=2p6wc42resg5at2p4utt5idamq)) + '@gqloom/core': 0.8.3(graphql@16.8.1(patch_hash=2p6wc42resg5at2p4utt5idamq)) graphql: 16.8.1(patch_hash=2p6wc42resg5at2p4utt5idamq) valibot: 1.0.0-rc.1(typescript@5.7.3) - '@gqloom/zod@0.8.0(@gqloom/core@0.8.0(graphql@16.8.1(patch_hash=2p6wc42resg5at2p4utt5idamq)))(graphql@16.8.1(patch_hash=2p6wc42resg5at2p4utt5idamq))(zod@3.24.2)': + '@gqloom/zod@0.8.1(@gqloom/core@0.8.3(graphql@16.8.1(patch_hash=2p6wc42resg5at2p4utt5idamq)))(graphql@16.8.1(patch_hash=2p6wc42resg5at2p4utt5idamq))(zod@3.24.2)': dependencies: - '@gqloom/core': 0.8.0(graphql@16.8.1(patch_hash=2p6wc42resg5at2p4utt5idamq)) + '@gqloom/core': 0.8.3(graphql@16.8.1(patch_hash=2p6wc42resg5at2p4utt5idamq)) graphql: 16.8.1(patch_hash=2p6wc42resg5at2p4utt5idamq) zod: 3.24.2 @@ -7294,6 +7412,8 @@ snapshots: '@oxc-transform/binding-win32-x64-msvc@0.48.2': optional: true + '@petamoriken/float16@3.9.2': {} + '@pkgjs/parseargs@0.11.0': optional: true @@ -8705,6 +8825,16 @@ snapshots: transitivePeerDependencies: - supports-color + drizzle-kit@1.0.0-beta.1-bd417c1: + dependencies: + '@drizzle-team/brocli': 0.10.2 + '@esbuild-kit/esm-loader': 2.6.5 + esbuild: 0.19.12 + esbuild-register: 3.6.0(esbuild@0.19.12) + gel: 2.0.2 + transitivePeerDependencies: + - supports-color + drizzle-orm@0.39.3(@libsql/client@0.14.0)(@prisma/client@6.1.0(prisma@6.1.0))(@types/pg@8.11.10)(better-sqlite3@11.8.1)(knex@3.1.0(better-sqlite3@11.8.1)(mysql2@3.12.0)(pg@8.13.2)(sqlite3@5.1.7))(mysql2@3.12.0)(pg@8.13.2)(prisma@6.1.0)(sqlite3@5.1.7): optionalDependencies: '@libsql/client': 0.14.0 @@ -8717,11 +8847,24 @@ snapshots: prisma: 6.1.0 sqlite3: 5.1.7 - drizzle-seed@0.3.1(drizzle-orm@0.39.3(@libsql/client@0.14.0)(@prisma/client@6.1.0(prisma@6.1.0))(@types/pg@8.11.10)(better-sqlite3@11.8.1)(knex@3.1.0(better-sqlite3@11.8.1)(mysql2@3.12.0)(pg@8.13.2)(sqlite3@5.1.7))(mysql2@3.12.0)(pg@8.13.2)(prisma@6.1.0)(sqlite3@5.1.7)): + drizzle-orm@1.0.0-beta.1-bd417c1(@libsql/client@0.14.0)(@prisma/client@6.1.0(prisma@6.1.0))(@types/pg@8.11.10)(better-sqlite3@11.8.1)(gel@2.0.2)(knex@3.1.0(better-sqlite3@11.8.1)(mysql2@3.12.0)(pg@8.13.2)(sqlite3@5.1.7))(mysql2@3.12.0)(pg@8.13.2)(prisma@6.1.0)(sqlite3@5.1.7): + optionalDependencies: + '@libsql/client': 0.14.0 + '@prisma/client': 6.1.0(prisma@6.1.0) + '@types/pg': 8.11.10 + better-sqlite3: 11.8.1 + gel: 2.0.2 + knex: 3.1.0(better-sqlite3@11.8.1)(mysql2@3.12.0)(pg@8.13.2)(sqlite3@5.1.7) + mysql2: 3.12.0 + pg: 8.13.2 + prisma: 6.1.0 + sqlite3: 5.1.7 + + drizzle-seed@0.3.1(drizzle-orm@1.0.0-beta.1-bd417c1(@libsql/client@0.14.0)(@prisma/client@6.1.0(prisma@6.1.0))(@types/pg@8.11.10)(better-sqlite3@11.8.1)(gel@2.0.2)(knex@3.1.0(better-sqlite3@11.8.1)(mysql2@3.12.0)(pg@8.13.2)(sqlite3@5.1.7))(mysql2@3.12.0)(pg@8.13.2)(prisma@6.1.0)(sqlite3@5.1.7)): dependencies: pure-rand: 6.1.0 optionalDependencies: - drizzle-orm: 0.39.3(@libsql/client@0.14.0)(@prisma/client@6.1.0(prisma@6.1.0))(@types/pg@8.11.10)(better-sqlite3@11.8.1)(knex@3.1.0(better-sqlite3@11.8.1)(mysql2@3.12.0)(pg@8.13.2)(sqlite3@5.1.7))(mysql2@3.12.0)(pg@8.13.2)(prisma@6.1.0)(sqlite3@5.1.7) + drizzle-orm: 1.0.0-beta.1-bd417c1(@libsql/client@0.14.0)(@prisma/client@6.1.0(prisma@6.1.0))(@types/pg@8.11.10)(better-sqlite3@11.8.1)(gel@2.0.2)(knex@3.1.0(better-sqlite3@11.8.1)(mysql2@3.12.0)(pg@8.13.2)(sqlite3@5.1.7))(mysql2@3.12.0)(pg@8.13.2)(prisma@6.1.0)(sqlite3@5.1.7) dset@3.1.3: {} @@ -8766,6 +8909,8 @@ snapshots: env-paths@2.2.1: optional: true + env-paths@3.0.0: {} + err-code@2.0.3: optional: true @@ -9320,6 +9465,17 @@ snapshots: wide-align: 1.1.5 optional: true + gel@2.0.2: + dependencies: + '@petamoriken/float16': 3.9.2 + debug: 4.4.0 + env-paths: 3.0.0 + semver: 7.7.1 + shell-quote: 1.8.2 + which: 4.0.0 + transitivePeerDependencies: + - supports-color + generate-function@2.3.1: dependencies: is-property: 1.0.2 @@ -9709,6 +9865,8 @@ snapshots: isexe@2.0.0: {} + isexe@3.1.1: {} + istanbul-lib-coverage@3.2.2: {} istanbul-lib-report@3.0.1: @@ -11304,8 +11462,7 @@ snapshots: dependencies: lru-cache: 6.0.0 - semver@7.7.1: - optional: true + semver@7.7.1: {} send@0.18.0: dependencies: @@ -11390,6 +11547,8 @@ snapshots: shebang-regex@3.0.0: {} + shell-quote@1.8.2: {} + shiki@2.3.1: dependencies: '@shikijs/core': 2.3.1 @@ -12013,6 +12172,10 @@ snapshots: dependencies: isexe: 2.0.0 + which@4.0.0: + dependencies: + isexe: 3.1.1 + why-is-node-running@2.3.0: dependencies: siginfo: 2.0.0 From 88551af13441c3c3f4ac0b23dcc3d135c8080155 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E2=9B=B5?= <809067559@qq.com> Date: Sun, 6 Apr 2025 22:11:54 +0800 Subject: [PATCH 02/54] chore(dependencies): update mysql2 to version 3.14.0 and adjust package.json formatting --- packages/drizzle/package.json | 2 +- pnpm-lock.yaml | 64 +++++++++++++++++------------------ 2 files changed, 33 insertions(+), 33 deletions(-) diff --git a/packages/drizzle/package.json b/packages/drizzle/package.json index b1dda956..2bc743c5 100644 --- a/packages/drizzle/package.json +++ b/packages/drizzle/package.json @@ -46,7 +46,7 @@ "drizzle-orm": "1.0.0-beta.1-bd417c1", "graphql": "^16.8.1", "graphql-yoga": "^5.6.0", - "mysql2": "^3.12.0", + "mysql2": "^3.14.0", "pg": "^8.13.1", "tsx": "^4.7.2", "valibot": "1.0.0-beta.12" diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 0ce899c5..8c81214e 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -87,7 +87,7 @@ importers: version: 0.8.3(graphql@16.8.1(patch_hash=2p6wc42resg5at2p4utt5idamq)) '@gqloom/drizzle': specifier: latest - version: 0.8.1(@gqloom/core@0.8.3(graphql@16.8.1(patch_hash=2p6wc42resg5at2p4utt5idamq)))(drizzle-orm@1.0.0-beta.1-bd417c1(@libsql/client@0.14.0)(@prisma/client@6.1.0(prisma@6.1.0))(@types/pg@8.11.10)(better-sqlite3@11.8.1)(gel@2.0.2)(knex@3.1.0(better-sqlite3@11.8.1)(mysql2@3.12.0)(pg@8.13.2)(sqlite3@5.1.7))(mysql2@3.12.0)(pg@8.13.2)(prisma@6.1.0)(sqlite3@5.1.7))(graphql@16.8.1(patch_hash=2p6wc42resg5at2p4utt5idamq)) + version: 0.8.1(@gqloom/core@0.8.3(graphql@16.8.1(patch_hash=2p6wc42resg5at2p4utt5idamq)))(drizzle-orm@1.0.0-beta.1-bd417c1(@libsql/client@0.14.0)(@prisma/client@6.1.0(prisma@6.1.0))(@types/pg@8.11.10)(better-sqlite3@11.8.1)(gel@2.0.2)(knex@3.1.0(better-sqlite3@11.8.1)(mysql2@3.14.0)(pg@8.13.2)(sqlite3@5.1.7))(mysql2@3.14.0)(pg@8.13.2)(prisma@6.1.0)(sqlite3@5.1.7))(graphql@16.8.1(patch_hash=2p6wc42resg5at2p4utt5idamq)) '@gqloom/valibot': specifier: ^0.7.1 version: 0.7.1(@gqloom/core@0.8.3(graphql@16.8.1(patch_hash=2p6wc42resg5at2p4utt5idamq)))(graphql@16.8.1(patch_hash=2p6wc42resg5at2p4utt5idamq))(valibot@1.0.0-rc.1(typescript@5.7.3)) @@ -115,7 +115,7 @@ importers: version: 1.0.0-beta.1-bd417c1 drizzle-orm: specifier: 1.0.0-beta.1-bd417c1 - version: 1.0.0-beta.1-bd417c1(@libsql/client@0.14.0)(@prisma/client@6.1.0(prisma@6.1.0))(@types/pg@8.11.10)(better-sqlite3@11.8.1)(gel@2.0.2)(knex@3.1.0(better-sqlite3@11.8.1)(mysql2@3.12.0)(pg@8.13.2)(sqlite3@5.1.7))(mysql2@3.12.0)(pg@8.13.2)(prisma@6.1.0)(sqlite3@5.1.7) + version: 1.0.0-beta.1-bd417c1(@libsql/client@0.14.0)(@prisma/client@6.1.0(prisma@6.1.0))(@types/pg@8.11.10)(better-sqlite3@11.8.1)(gel@2.0.2)(knex@3.1.0(better-sqlite3@11.8.1)(mysql2@3.14.0)(pg@8.13.2)(sqlite3@5.1.7))(mysql2@3.14.0)(pg@8.13.2)(prisma@6.1.0)(sqlite3@5.1.7) tsx: specifier: ^4.7.2 version: 4.7.2 @@ -130,7 +130,7 @@ importers: version: 0.8.3(graphql@16.8.1(patch_hash=2p6wc42resg5at2p4utt5idamq)) '@gqloom/drizzle': specifier: latest - version: 0.8.1(@gqloom/core@0.8.3(graphql@16.8.1(patch_hash=2p6wc42resg5at2p4utt5idamq)))(drizzle-orm@1.0.0-beta.1-bd417c1(@libsql/client@0.14.0)(@prisma/client@6.1.0(prisma@6.1.0))(@types/pg@8.11.10)(better-sqlite3@11.8.1)(gel@2.0.2)(knex@3.1.0(better-sqlite3@11.8.1)(mysql2@3.12.0)(pg@8.13.2)(sqlite3@5.1.7))(mysql2@3.12.0)(pg@8.13.2)(prisma@6.1.0)(sqlite3@5.1.7))(graphql@16.8.1(patch_hash=2p6wc42resg5at2p4utt5idamq)) + version: 0.8.1(@gqloom/core@0.8.3(graphql@16.8.1(patch_hash=2p6wc42resg5at2p4utt5idamq)))(drizzle-orm@1.0.0-beta.1-bd417c1(@libsql/client@0.14.0)(@prisma/client@6.1.0(prisma@6.1.0))(@types/pg@8.11.10)(better-sqlite3@11.8.1)(gel@2.0.2)(knex@3.1.0(better-sqlite3@11.8.1)(mysql2@3.14.0)(pg@8.13.2)(sqlite3@5.1.7))(mysql2@3.14.0)(pg@8.13.2)(prisma@6.1.0)(sqlite3@5.1.7))(graphql@16.8.1(patch_hash=2p6wc42resg5at2p4utt5idamq)) '@gqloom/zod': specifier: latest version: 0.8.1(@gqloom/core@0.8.3(graphql@16.8.1(patch_hash=2p6wc42resg5at2p4utt5idamq)))(graphql@16.8.1(patch_hash=2p6wc42resg5at2p4utt5idamq))(zod@3.24.2) @@ -158,7 +158,7 @@ importers: version: 1.0.0-beta.1-bd417c1 drizzle-orm: specifier: 1.0.0-beta.1-bd417c1 - version: 1.0.0-beta.1-bd417c1(@libsql/client@0.14.0)(@prisma/client@6.1.0(prisma@6.1.0))(@types/pg@8.11.10)(better-sqlite3@11.8.1)(gel@2.0.2)(knex@3.1.0(better-sqlite3@11.8.1)(mysql2@3.12.0)(pg@8.13.2)(sqlite3@5.1.7))(mysql2@3.12.0)(pg@8.13.2)(prisma@6.1.0)(sqlite3@5.1.7) + version: 1.0.0-beta.1-bd417c1(@libsql/client@0.14.0)(@prisma/client@6.1.0(prisma@6.1.0))(@types/pg@8.11.10)(better-sqlite3@11.8.1)(gel@2.0.2)(knex@3.1.0(better-sqlite3@11.8.1)(mysql2@3.14.0)(pg@8.13.2)(sqlite3@5.1.7))(mysql2@3.14.0)(pg@8.13.2)(prisma@6.1.0)(sqlite3@5.1.7) tsx: specifier: ^4.7.2 version: 4.7.2 @@ -179,7 +179,7 @@ importers: version: 16.4.7 drizzle-seed: specifier: ^0.3.1 - version: 0.3.1(drizzle-orm@1.0.0-beta.1-bd417c1(@libsql/client@0.14.0)(@prisma/client@6.1.0(prisma@6.1.0))(@types/pg@8.11.10)(better-sqlite3@11.8.1)(gel@2.0.2)(knex@3.1.0(better-sqlite3@11.8.1)(mysql2@3.12.0)(pg@8.13.2)(sqlite3@5.1.7))(mysql2@3.12.0)(pg@8.13.2)(prisma@6.1.0)(sqlite3@5.1.7)) + version: 0.3.1(drizzle-orm@1.0.0-beta.1-bd417c1(@libsql/client@0.14.0)(@prisma/client@6.1.0(prisma@6.1.0))(@types/pg@8.11.10)(better-sqlite3@11.8.1)(gel@2.0.2)(knex@3.1.0(better-sqlite3@11.8.1)(mysql2@3.14.0)(pg@8.13.2)(sqlite3@5.1.7))(mysql2@3.14.0)(pg@8.13.2)(prisma@6.1.0)(sqlite3@5.1.7)) graphql: specifier: ^16.8.1 version: 16.8.1(patch_hash=2p6wc42resg5at2p4utt5idamq) @@ -201,7 +201,7 @@ importers: version: 1.0.0-beta.1-bd417c1 drizzle-orm: specifier: 1.0.0-beta.1-bd417c1 - version: 1.0.0-beta.1-bd417c1(@libsql/client@0.14.0)(@prisma/client@6.1.0(prisma@6.1.0))(@types/pg@8.11.10)(better-sqlite3@11.8.1)(gel@2.0.2)(knex@3.1.0(better-sqlite3@11.8.1)(mysql2@3.12.0)(pg@8.13.2)(sqlite3@5.1.7))(mysql2@3.12.0)(pg@8.13.2)(prisma@6.1.0)(sqlite3@5.1.7) + version: 1.0.0-beta.1-bd417c1(@libsql/client@0.14.0)(@prisma/client@6.1.0(prisma@6.1.0))(@types/pg@8.11.10)(better-sqlite3@11.8.1)(gel@2.0.2)(knex@3.1.0(better-sqlite3@11.8.1)(mysql2@3.14.0)(pg@8.13.2)(sqlite3@5.1.7))(mysql2@3.14.0)(pg@8.13.2)(prisma@6.1.0)(sqlite3@5.1.7) tsx: specifier: ^4.7.2 version: 4.7.2 @@ -246,7 +246,7 @@ importers: version: 6.4.6 '@mikro-orm/postgresql': specifier: ^6.4.6 - version: 6.4.6(@mikro-orm/core@6.4.6)(better-sqlite3@11.8.1)(libsql@0.4.7)(mysql2@3.12.0)(sqlite3@5.1.7) + version: 6.4.6(@mikro-orm/core@6.4.6)(better-sqlite3@11.8.1)(libsql@0.4.7)(mysql2@3.14.0)(sqlite3@5.1.7) graphql-yoga: specifier: ^5.6.0 version: 5.6.0(graphql@16.8.1(patch_hash=2p6wc42resg5at2p4utt5idamq)) @@ -342,7 +342,7 @@ importers: version: 1.0.0-beta.1-bd417c1 drizzle-orm: specifier: 1.0.0-beta.1-bd417c1 - version: 1.0.0-beta.1-bd417c1(@libsql/client@0.14.0)(@prisma/client@6.1.0(prisma@6.1.0))(@types/pg@8.11.10)(better-sqlite3@11.8.1)(gel@2.0.2)(knex@3.1.0(better-sqlite3@11.8.1)(mysql2@3.12.0)(pg@8.13.2)(sqlite3@5.1.7))(mysql2@3.12.0)(pg@8.13.2)(prisma@6.1.0)(sqlite3@5.1.7) + version: 1.0.0-beta.1-bd417c1(@libsql/client@0.14.0)(@prisma/client@6.1.0(prisma@6.1.0))(@types/pg@8.11.10)(better-sqlite3@11.8.1)(gel@2.0.2)(knex@3.1.0(better-sqlite3@11.8.1)(mysql2@3.14.0)(pg@8.13.2)(sqlite3@5.1.7))(mysql2@3.14.0)(pg@8.13.2)(prisma@6.1.0)(sqlite3@5.1.7) graphql: specifier: ^16.8.1 version: 16.8.1(patch_hash=2p6wc42resg5at2p4utt5idamq) @@ -350,8 +350,8 @@ importers: specifier: ^5.6.0 version: 5.6.0(graphql@16.8.1(patch_hash=2p6wc42resg5at2p4utt5idamq)) mysql2: - specifier: ^3.12.0 - version: 3.12.0 + specifier: ^3.14.0 + version: 3.14.0 pg: specifier: ^8.13.2 version: 8.13.2 @@ -561,7 +561,7 @@ importers: version: 6.4.6 '@mikro-orm/postgresql': specifier: ^6.4.6 - version: 6.4.6(@mikro-orm/core@6.4.6)(better-sqlite3@11.8.1)(libsql@0.4.7)(mysql2@3.12.0)(sqlite3@5.1.7) + version: 6.4.6(@mikro-orm/core@6.4.6)(better-sqlite3@11.8.1)(libsql@0.4.7)(mysql2@3.14.0)(sqlite3@5.1.7) '@tailwindcss/postcss': specifier: ^4.0.3 version: 4.0.3 @@ -585,7 +585,7 @@ importers: version: 0.30.1 drizzle-orm: specifier: ^0.39.3 - version: 0.39.3(@libsql/client@0.14.0)(@prisma/client@6.1.0(prisma@6.1.0))(@types/pg@8.11.10)(better-sqlite3@11.8.1)(knex@3.1.0(better-sqlite3@11.8.1)(mysql2@3.12.0)(pg@8.13.2)(sqlite3@5.1.7))(mysql2@3.12.0)(pg@8.13.2)(prisma@6.1.0)(sqlite3@5.1.7) + version: 0.39.3(@libsql/client@0.14.0)(@prisma/client@6.1.0(prisma@6.1.0))(@types/pg@8.11.10)(better-sqlite3@11.8.1)(knex@3.1.0(better-sqlite3@11.8.1)(mysql2@3.14.0)(pg@8.13.2)(sqlite3@5.1.7))(mysql2@3.14.0)(pg@8.13.2)(prisma@6.1.0)(sqlite3@5.1.7) fastify: specifier: ^4.28.1 version: 4.28.1 @@ -4873,8 +4873,8 @@ packages: ms@2.1.3: resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==} - mysql2@3.12.0: - resolution: {integrity: sha512-C8fWhVysZoH63tJbX8d10IAoYCyXy4fdRFz2Ihrt9jtPILYynFEKUUzpp1U7qxzDc3tMbotvaBH+sl6bFnGZiw==} + mysql2@3.14.0: + resolution: {integrity: sha512-8eMhmG6gt/hRkU1G+8KlGOdQi2w+CgtNoD1ksXZq9gQfkfDsX4LHaBwTe1SY0Imx//t2iZA03DFnyYKPinxSRw==} engines: {node: '>= 8.0'} mz@2.7.0: @@ -6883,10 +6883,10 @@ snapshots: dependencies: graphql: 16.8.1(patch_hash=2p6wc42resg5at2p4utt5idamq) - '@gqloom/drizzle@0.8.1(@gqloom/core@0.8.3(graphql@16.8.1(patch_hash=2p6wc42resg5at2p4utt5idamq)))(drizzle-orm@1.0.0-beta.1-bd417c1(@libsql/client@0.14.0)(@prisma/client@6.1.0(prisma@6.1.0))(@types/pg@8.11.10)(better-sqlite3@11.8.1)(gel@2.0.2)(knex@3.1.0(better-sqlite3@11.8.1)(mysql2@3.12.0)(pg@8.13.2)(sqlite3@5.1.7))(mysql2@3.12.0)(pg@8.13.2)(prisma@6.1.0)(sqlite3@5.1.7))(graphql@16.8.1(patch_hash=2p6wc42resg5at2p4utt5idamq))': + '@gqloom/drizzle@0.8.1(@gqloom/core@0.8.3(graphql@16.8.1(patch_hash=2p6wc42resg5at2p4utt5idamq)))(drizzle-orm@1.0.0-beta.1-bd417c1(@libsql/client@0.14.0)(@prisma/client@6.1.0(prisma@6.1.0))(@types/pg@8.11.10)(better-sqlite3@11.8.1)(gel@2.0.2)(knex@3.1.0(better-sqlite3@11.8.1)(mysql2@3.14.0)(pg@8.13.2)(sqlite3@5.1.7))(mysql2@3.14.0)(pg@8.13.2)(prisma@6.1.0)(sqlite3@5.1.7))(graphql@16.8.1(patch_hash=2p6wc42resg5at2p4utt5idamq))': dependencies: '@gqloom/core': 0.8.3(graphql@16.8.1(patch_hash=2p6wc42resg5at2p4utt5idamq)) - drizzle-orm: 1.0.0-beta.1-bd417c1(@libsql/client@0.14.0)(@prisma/client@6.1.0(prisma@6.1.0))(@types/pg@8.11.10)(better-sqlite3@11.8.1)(gel@2.0.2)(knex@3.1.0(better-sqlite3@11.8.1)(mysql2@3.12.0)(pg@8.13.2)(sqlite3@5.1.7))(mysql2@3.12.0)(pg@8.13.2)(prisma@6.1.0)(sqlite3@5.1.7) + drizzle-orm: 1.0.0-beta.1-bd417c1(@libsql/client@0.14.0)(@prisma/client@6.1.0(prisma@6.1.0))(@types/pg@8.11.10)(better-sqlite3@11.8.1)(gel@2.0.2)(knex@3.1.0(better-sqlite3@11.8.1)(mysql2@3.14.0)(pg@8.13.2)(sqlite3@5.1.7))(mysql2@3.14.0)(pg@8.13.2)(prisma@6.1.0)(sqlite3@5.1.7) graphql: 16.8.1(patch_hash=2p6wc42resg5at2p4utt5idamq) '@gqloom/mikro-orm@0.7.0(@gqloom/core@0.7.0(graphql@16.8.1(patch_hash=2p6wc42resg5at2p4utt5idamq)))(@mikro-orm/core@6.4.6)(graphql@16.8.1(patch_hash=2p6wc42resg5at2p4utt5idamq))': @@ -7209,11 +7209,11 @@ snapshots: mikro-orm: 6.4.6 reflect-metadata: 0.2.2 - '@mikro-orm/knex@6.4.6(@mikro-orm/core@6.4.6)(better-sqlite3@11.8.1)(libsql@0.4.7)(mysql2@3.12.0)(pg@8.13.2)(sqlite3@5.1.7)': + '@mikro-orm/knex@6.4.6(@mikro-orm/core@6.4.6)(better-sqlite3@11.8.1)(libsql@0.4.7)(mysql2@3.14.0)(pg@8.13.2)(sqlite3@5.1.7)': dependencies: '@mikro-orm/core': 6.4.6 fs-extra: 11.3.0 - knex: 3.1.0(better-sqlite3@11.8.1)(mysql2@3.12.0)(pg@8.13.2)(sqlite3@5.1.7) + knex: 3.1.0(better-sqlite3@11.8.1)(mysql2@3.14.0)(pg@8.13.2)(sqlite3@5.1.7) sqlstring: 2.3.3 optionalDependencies: better-sqlite3: 11.8.1 @@ -7230,7 +7230,7 @@ snapshots: '@mikro-orm/libsql@6.4.6(@mikro-orm/core@6.4.6)(better-sqlite3@11.8.1)': dependencies: '@mikro-orm/core': 6.4.6 - '@mikro-orm/knex': 6.4.6(@mikro-orm/core@6.4.6)(better-sqlite3@11.8.1)(libsql@0.4.7)(mysql2@3.12.0)(pg@8.13.2)(sqlite3@5.1.7) + '@mikro-orm/knex': 6.4.6(@mikro-orm/core@6.4.6)(better-sqlite3@11.8.1)(libsql@0.4.7)(mysql2@3.14.0)(pg@8.13.2)(sqlite3@5.1.7) fs-extra: 11.3.0 libsql: 0.4.7 sqlstring-sqlite: 0.1.1 @@ -7245,10 +7245,10 @@ snapshots: - supports-color - tedious - '@mikro-orm/postgresql@6.4.6(@mikro-orm/core@6.4.6)(better-sqlite3@11.8.1)(libsql@0.4.7)(mysql2@3.12.0)(sqlite3@5.1.7)': + '@mikro-orm/postgresql@6.4.6(@mikro-orm/core@6.4.6)(better-sqlite3@11.8.1)(libsql@0.4.7)(mysql2@3.14.0)(sqlite3@5.1.7)': dependencies: '@mikro-orm/core': 6.4.6 - '@mikro-orm/knex': 6.4.6(@mikro-orm/core@6.4.6)(better-sqlite3@11.8.1)(libsql@0.4.7)(mysql2@3.12.0)(pg@8.13.2)(sqlite3@5.1.7) + '@mikro-orm/knex': 6.4.6(@mikro-orm/core@6.4.6)(better-sqlite3@11.8.1)(libsql@0.4.7)(mysql2@3.14.0)(pg@8.13.2)(sqlite3@5.1.7) pg: 8.13.2 postgres-array: 3.0.2 postgres-date: 2.1.0 @@ -8835,36 +8835,36 @@ snapshots: transitivePeerDependencies: - supports-color - drizzle-orm@0.39.3(@libsql/client@0.14.0)(@prisma/client@6.1.0(prisma@6.1.0))(@types/pg@8.11.10)(better-sqlite3@11.8.1)(knex@3.1.0(better-sqlite3@11.8.1)(mysql2@3.12.0)(pg@8.13.2)(sqlite3@5.1.7))(mysql2@3.12.0)(pg@8.13.2)(prisma@6.1.0)(sqlite3@5.1.7): + drizzle-orm@0.39.3(@libsql/client@0.14.0)(@prisma/client@6.1.0(prisma@6.1.0))(@types/pg@8.11.10)(better-sqlite3@11.8.1)(knex@3.1.0(better-sqlite3@11.8.1)(mysql2@3.14.0)(pg@8.13.2)(sqlite3@5.1.7))(mysql2@3.14.0)(pg@8.13.2)(prisma@6.1.0)(sqlite3@5.1.7): optionalDependencies: '@libsql/client': 0.14.0 '@prisma/client': 6.1.0(prisma@6.1.0) '@types/pg': 8.11.10 better-sqlite3: 11.8.1 - knex: 3.1.0(better-sqlite3@11.8.1)(mysql2@3.12.0)(pg@8.13.2)(sqlite3@5.1.7) - mysql2: 3.12.0 + knex: 3.1.0(better-sqlite3@11.8.1)(mysql2@3.14.0)(pg@8.13.2)(sqlite3@5.1.7) + mysql2: 3.14.0 pg: 8.13.2 prisma: 6.1.0 sqlite3: 5.1.7 - drizzle-orm@1.0.0-beta.1-bd417c1(@libsql/client@0.14.0)(@prisma/client@6.1.0(prisma@6.1.0))(@types/pg@8.11.10)(better-sqlite3@11.8.1)(gel@2.0.2)(knex@3.1.0(better-sqlite3@11.8.1)(mysql2@3.12.0)(pg@8.13.2)(sqlite3@5.1.7))(mysql2@3.12.0)(pg@8.13.2)(prisma@6.1.0)(sqlite3@5.1.7): + drizzle-orm@1.0.0-beta.1-bd417c1(@libsql/client@0.14.0)(@prisma/client@6.1.0(prisma@6.1.0))(@types/pg@8.11.10)(better-sqlite3@11.8.1)(gel@2.0.2)(knex@3.1.0(better-sqlite3@11.8.1)(mysql2@3.14.0)(pg@8.13.2)(sqlite3@5.1.7))(mysql2@3.14.0)(pg@8.13.2)(prisma@6.1.0)(sqlite3@5.1.7): optionalDependencies: '@libsql/client': 0.14.0 '@prisma/client': 6.1.0(prisma@6.1.0) '@types/pg': 8.11.10 better-sqlite3: 11.8.1 gel: 2.0.2 - knex: 3.1.0(better-sqlite3@11.8.1)(mysql2@3.12.0)(pg@8.13.2)(sqlite3@5.1.7) - mysql2: 3.12.0 + knex: 3.1.0(better-sqlite3@11.8.1)(mysql2@3.14.0)(pg@8.13.2)(sqlite3@5.1.7) + mysql2: 3.14.0 pg: 8.13.2 prisma: 6.1.0 sqlite3: 5.1.7 - drizzle-seed@0.3.1(drizzle-orm@1.0.0-beta.1-bd417c1(@libsql/client@0.14.0)(@prisma/client@6.1.0(prisma@6.1.0))(@types/pg@8.11.10)(better-sqlite3@11.8.1)(gel@2.0.2)(knex@3.1.0(better-sqlite3@11.8.1)(mysql2@3.12.0)(pg@8.13.2)(sqlite3@5.1.7))(mysql2@3.12.0)(pg@8.13.2)(prisma@6.1.0)(sqlite3@5.1.7)): + drizzle-seed@0.3.1(drizzle-orm@1.0.0-beta.1-bd417c1(@libsql/client@0.14.0)(@prisma/client@6.1.0(prisma@6.1.0))(@types/pg@8.11.10)(better-sqlite3@11.8.1)(gel@2.0.2)(knex@3.1.0(better-sqlite3@11.8.1)(mysql2@3.14.0)(pg@8.13.2)(sqlite3@5.1.7))(mysql2@3.14.0)(pg@8.13.2)(prisma@6.1.0)(sqlite3@5.1.7)): dependencies: pure-rand: 6.1.0 optionalDependencies: - drizzle-orm: 1.0.0-beta.1-bd417c1(@libsql/client@0.14.0)(@prisma/client@6.1.0(prisma@6.1.0))(@types/pg@8.11.10)(better-sqlite3@11.8.1)(gel@2.0.2)(knex@3.1.0(better-sqlite3@11.8.1)(mysql2@3.12.0)(pg@8.13.2)(sqlite3@5.1.7))(mysql2@3.12.0)(pg@8.13.2)(prisma@6.1.0)(sqlite3@5.1.7) + drizzle-orm: 1.0.0-beta.1-bd417c1(@libsql/client@0.14.0)(@prisma/client@6.1.0(prisma@6.1.0))(@types/pg@8.11.10)(better-sqlite3@11.8.1)(gel@2.0.2)(knex@3.1.0(better-sqlite3@11.8.1)(mysql2@3.14.0)(pg@8.13.2)(sqlite3@5.1.7))(mysql2@3.14.0)(pg@8.13.2)(prisma@6.1.0)(sqlite3@5.1.7) dset@3.1.3: {} @@ -9953,7 +9953,7 @@ snapshots: kind-of@6.0.3: {} - knex@3.1.0(better-sqlite3@11.8.1)(mysql2@3.12.0)(pg@8.13.2)(sqlite3@5.1.7): + knex@3.1.0(better-sqlite3@11.8.1)(mysql2@3.14.0)(pg@8.13.2)(sqlite3@5.1.7): dependencies: colorette: 2.0.19 commander: 10.0.1 @@ -9971,7 +9971,7 @@ snapshots: tildify: 2.0.0 optionalDependencies: better-sqlite3: 11.8.1 - mysql2: 3.12.0 + mysql2: 3.14.0 pg: 8.13.2 sqlite3: 5.1.7 transitivePeerDependencies: @@ -10751,7 +10751,7 @@ snapshots: ms@2.1.3: {} - mysql2@3.12.0: + mysql2@3.14.0: dependencies: aws-ssl-profiles: 1.1.2 denque: 2.1.0 From bfd502d87f94e7ccf20bf88af037ef632ad5d5ed Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E2=9B=B5?= <809067559@qq.com> Date: Sun, 6 Apr 2025 23:01:26 +0800 Subject: [PATCH 03/54] refactor(tests): rename user and post references to plural forms across MySQL, PostgreSQL, and SQLite schemas - Updated schema definitions and resolver factories to use pluralized names for users and posts. - Adjusted GraphQL mutations and queries to reflect the new naming conventions. - Ensured consistency across all database tests by renaming relevant references and updating relationships. --- .../drizzle/test/resolver-factory.spec.ts | 143 +++++++++--------- packages/drizzle/test/resolver-mysql.spec.gql | 56 +++---- packages/drizzle/test/resolver-mysql.spec.ts | 134 ++++++++-------- .../drizzle/test/resolver-postgres.spec.gql | 56 +++---- .../drizzle/test/resolver-postgres.spec.ts | 132 ++++++++-------- .../drizzle/test/resolver-sqlite.spec.gql | 60 ++++---- packages/drizzle/test/resolver-sqlite.spec.ts | 100 ++++++------ .../drizzle/test/schema/mysql-relations.ts | 12 +- packages/drizzle/test/schema/mysql.ts | 10 +- .../drizzle/test/schema/postgres-relations.ts | 12 +- packages/drizzle/test/schema/postgres.ts | 10 +- .../drizzle/test/schema/sqlite-relations.ts | 38 ++--- packages/drizzle/test/schema/sqlite.ts | 30 ++-- 13 files changed, 400 insertions(+), 393 deletions(-) diff --git a/packages/drizzle/test/resolver-factory.spec.ts b/packages/drizzle/test/resolver-factory.spec.ts index 88b4e775..cc388313 100644 --- a/packages/drizzle/test/resolver-factory.spec.ts +++ b/packages/drizzle/test/resolver-factory.spec.ts @@ -39,7 +39,7 @@ describe.concurrent("DrizzleResolverFactory", () => { let db: LibSQLDatabase let userFactory: DrizzleSQLiteResolverFactory< typeof db, - typeof sqliteSchemas.user + typeof sqliteSchemas.users > beforeAll(async () => { @@ -49,9 +49,9 @@ describe.concurrent("DrizzleResolverFactory", () => { connection: { url: `file:${pathToDB.pathname}` }, }) - userFactory = drizzleResolverFactory(db, "user") + userFactory = drizzleResolverFactory(db, "users") - await db.insert(sqliteSchemas.user).values([ + await db.insert(sqliteSchemas.users).values([ { name: "John", age: 10, @@ -73,11 +73,11 @@ describe.concurrent("DrizzleResolverFactory", () => { name: "Jill", age: 14, }, - ] satisfies (typeof sqliteSchemas.user.$inferInsert)[]) + ] satisfies (typeof sqliteSchemas.users.$inferInsert)[]) }) afterAll(async () => { - await db.delete(sqliteSchemas.user) + await db.delete(sqliteSchemas.users) }) it("should create a resolver factory", () => { @@ -94,7 +94,7 @@ describe.concurrent("DrizzleResolverFactory", () => { }) describe.concurrent("selectArrayQuery", () => { - // db.query.user.findMany({ orderBy: () => [] }) + // db.query.users.findMany({ orderBy: () => [] }) it("should be created without error", async () => { const query = userFactory.selectArrayQuery() expect(query).toBeDefined() @@ -215,7 +215,7 @@ describe.concurrent("DrizzleResolverFactory", () => { it("should be created with middlewares", async () => { type SelectArrayOptions = InferSelectArrayOptions< typeof db, - typeof sqliteSchemas.user + typeof sqliteSchemas.users > let count = 0 @@ -232,7 +232,7 @@ describe.concurrent("DrizzleResolverFactory", () => { count++ const answer = await next() expectTypeOf(answer).toEqualTypeOf< - (typeof sqliteSchemas.user.$inferSelect)[] + (typeof sqliteSchemas.users.$inferSelect)[] >() return answer }, @@ -246,7 +246,7 @@ describe.concurrent("DrizzleResolverFactory", () => { count++ const answer = await next() expectTypeOf(answer).toEqualTypeOf< - (typeof sqliteSchemas.user.$inferSelect)[] + (typeof sqliteSchemas.users.$inferSelect)[] >() return answer }) @@ -301,7 +301,7 @@ describe.concurrent("DrizzleResolverFactory", () => { it("should be created with middlewares", async () => { type SelectSingleOptions = InferSelectSingleOptions< typeof db, - typeof sqliteSchemas.user + typeof sqliteSchemas.users > let count = 0 const query = userFactory.selectSingleQuery({ @@ -316,7 +316,7 @@ describe.concurrent("DrizzleResolverFactory", () => { count++ const answer = await next() expectTypeOf(answer).toEqualTypeOf< - typeof sqliteSchemas.user.$inferSelect | undefined | null + typeof sqliteSchemas.users.$inferSelect | undefined | null >() return answer }, @@ -330,17 +330,17 @@ describe.concurrent("DrizzleResolverFactory", () => { describe("relationField", () => { afterAll(async () => { - await db.delete(sqliteSchemas.studentCourseGrade) - await db.delete(sqliteSchemas.studentToCourse) - await db.delete(sqliteSchemas.course) - await db.delete(sqliteSchemas.post) + await db.delete(sqliteSchemas.studentCourseGrades) + await db.delete(sqliteSchemas.studentToCourses) + await db.delete(sqliteSchemas.courses) + await db.delete(sqliteSchemas.posts) }) it("should be created without error", () => { const postsField = userFactory.relationField("posts").description("posts") expect(postsField).toBeDefined() - const postFactory = drizzleResolverFactory(db, "post") + const postFactory = drizzleResolverFactory(db, "posts") const authorField = postFactory .relationField("author") .description("author") @@ -348,24 +348,27 @@ describe.concurrent("DrizzleResolverFactory", () => { }) it("should resolve correctly", async () => { - const studentCourseFactory = drizzleResolverFactory(db, "studentToCourse") + const studentCourseFactory = drizzleResolverFactory( + db, + "studentToCourses" + ) const gradeField = studentCourseFactory.relationField("grade") - const John = await db.query.user.findFirst({ + const John = await db.query.users.findFirst({ where: { name: "John" }, }) if (!John) throw new Error("John not found") - const Joe = await db.query.user.findFirst({ + const Joe = await db.query.users.findFirst({ where: { name: "Joe" }, }) if (!Joe) throw new Error("Joe not found") const [math, english] = await db - .insert(sqliteSchemas.course) + .insert(sqliteSchemas.courses) .values([{ name: "Math" }, { name: "English" }]) .returning() const studentCourses = await db - .insert(sqliteSchemas.studentToCourse) + .insert(sqliteSchemas.studentToCourses) .values([ { studentId: John.id, courseId: math.id }, { studentId: John.id, courseId: english.id }, @@ -374,7 +377,7 @@ describe.concurrent("DrizzleResolverFactory", () => { ]) .returning() - await db.insert(sqliteSchemas.studentCourseGrade).values( + await db.insert(sqliteSchemas.studentCourseGrades).values( studentCourses.map((it) => ({ ...it, grade: Math.floor(Math.random() * 51) + 50, @@ -405,7 +408,7 @@ describe.concurrent("DrizzleResolverFactory", () => { ]) ) - await db.insert(sqliteSchemas.post).values([ + await db.insert(sqliteSchemas.posts).values([ { authorId: John.id, title: "Hello" }, { authorId: John.id, title: "World" }, ]) @@ -422,12 +425,12 @@ describe.concurrent("DrizzleResolverFactory", () => { it("should be created without error", () => { const userExecutor = userFactory.resolver().toExecutor() expect(userExecutor).toBeDefined() - expect(userExecutor.user).toBeDefined() - expect(userExecutor.userSingle).toBeDefined() - expect(userExecutor.insertIntoUser).toBeDefined() - expect(userExecutor.insertIntoUserSingle).toBeDefined() - expect(userExecutor.updateUser).toBeDefined() - expect(userExecutor.deleteFromUser).toBeDefined() + expect(userExecutor.users).toBeDefined() + expect(userExecutor.usersSingle).toBeDefined() + expect(userExecutor.insertIntoUsers).toBeDefined() + expect(userExecutor.insertIntoUsersSingle).toBeDefined() + expect(userExecutor.updateUsers).toBeDefined() + expect(userExecutor.deleteFromUsers).toBeDefined() expect(userExecutor.courses).toBeDefined() expect(userExecutor.posts).toBeDefined() }) @@ -436,12 +439,12 @@ describe.concurrent("DrizzleResolverFactory", () => { describe.concurrent("DrizzleMySQLResolverFactory", () => { const schema = { - user: mysqlSchemas.user, + users: mysqlSchemas.users, } let db: MySql2Database let userFactory: DrizzleMySQLResolverFactory< typeof db, - typeof mysqlSchemas.user + typeof mysqlSchemas.users > beforeAll(async () => { @@ -450,7 +453,7 @@ describe.concurrent("DrizzleMySQLResolverFactory", () => { relations: mysqlRelations, mode: "default", }) - userFactory = drizzleResolverFactory(db, "user") + userFactory = drizzleResolverFactory(db, "users") await db.execute(sql`select 1`) }) @@ -472,8 +475,8 @@ describe.concurrent("DrizzleMySQLResolverFactory", () => { ).toMatchObject({ isSuccess: true }) await db - .delete(mysqlSchemas.user) - .where(inArray(mysqlSchemas.user.age, [5, 6])) + .delete(mysqlSchemas.users) + .where(inArray(mysqlSchemas.users.age, [5, 6])) }) it("should be created with custom input", async () => { @@ -498,8 +501,8 @@ describe.concurrent("DrizzleMySQLResolverFactory", () => { }) await db - .delete(mysqlSchemas.user) - .where(inArray(mysqlSchemas.user.age, [5, 6])) + .delete(mysqlSchemas.users) + .where(inArray(mysqlSchemas.users.age, [5, 6])) }) }) @@ -516,7 +519,7 @@ describe.concurrent("DrizzleMySQLResolverFactory", () => { }) expect(answer).toMatchObject({ isSuccess: true }) - await db.delete(mysqlSchemas.user).where(eq(mysqlSchemas.user.age, 7)) + await db.delete(mysqlSchemas.users).where(eq(mysqlSchemas.users.age, 7)) }) }) @@ -527,7 +530,7 @@ describe.concurrent("DrizzleMySQLResolverFactory", () => { }) it("should resolve correctly", async () => { - await db.insert(mysqlSchemas.user).values({ name: "Bob", age: 18 }) + await db.insert(mysqlSchemas.users).values({ name: "Bob", age: 18 }) const mutation = userFactory.updateMutation() expect( await mutation["~meta"].resolve({ @@ -536,8 +539,8 @@ describe.concurrent("DrizzleMySQLResolverFactory", () => { }) ).toMatchObject({ isSuccess: true }) await db - .delete(mysqlSchemas.user) - .where(eq(mysqlSchemas.user.name, "Bob")) + .delete(mysqlSchemas.users) + .where(eq(mysqlSchemas.users.name, "Bob")) }) }) @@ -548,7 +551,7 @@ describe.concurrent("DrizzleMySQLResolverFactory", () => { }) it("should resolve correctly", async () => { - await db.insert(mysqlSchemas.user).values({ name: "Alice", age: 18 }) + await db.insert(mysqlSchemas.users).values({ name: "Alice", age: 18 }) try { const mutation = userFactory.deleteMutation() const answer = await mutation["~meta"].resolve({ @@ -557,8 +560,8 @@ describe.concurrent("DrizzleMySQLResolverFactory", () => { expect(answer).toMatchObject({ isSuccess: true }) } finally { await db - .delete(mysqlSchemas.user) - .where(eq(mysqlSchemas.user.name, "Alice")) + .delete(mysqlSchemas.users) + .where(eq(mysqlSchemas.users.name, "Alice")) } }) }) @@ -566,12 +569,12 @@ describe.concurrent("DrizzleMySQLResolverFactory", () => { describe.concurrent("DrizzlePostgresResolverFactory", () => { const schema = { - user: pgSchemas.user, + users: pgSchemas.users, } let db: NodePgDatabase let userFactory: DrizzlePostgresResolverFactory< typeof db, - typeof pgSchemas.user + typeof pgSchemas.users > beforeAll(async () => { @@ -579,7 +582,7 @@ describe.concurrent("DrizzlePostgresResolverFactory", () => { schema, relations: pgRelations, }) - userFactory = drizzleResolverFactory(db, "user") + userFactory = drizzleResolverFactory(db, "users") await db.execute(sql`select 1`) }) @@ -602,7 +605,9 @@ describe.concurrent("DrizzlePostgresResolverFactory", () => { { name: "Jane", age: 6 }, ]) - await db.delete(pgSchemas.user).where(inArray(pgSchemas.user.age, [5, 6])) + await db + .delete(pgSchemas.users) + .where(inArray(pgSchemas.users.age, [5, 6])) }) it("should be created with custom input", async () => { @@ -628,8 +633,8 @@ describe.concurrent("DrizzlePostgresResolverFactory", () => { ]) await db - .delete(pgSchemas.user) - .where(inArray(mysqlSchemas.user.age, [5, 6])) + .delete(pgSchemas.users) + .where(inArray(mysqlSchemas.users.age, [5, 6])) }) }) @@ -647,7 +652,7 @@ describe.concurrent("DrizzlePostgresResolverFactory", () => { expect(answer).toMatchObject({ name: "John", age: 7 }) - await db.delete(pgSchemas.user).where(eq(pgSchemas.user.id, answer!.id)) + await db.delete(pgSchemas.users).where(eq(pgSchemas.users.id, answer!.id)) }) }) @@ -658,7 +663,7 @@ describe.concurrent("DrizzlePostgresResolverFactory", () => { }) it("should resolve correctly", async () => { - await db.insert(pgSchemas.user).values({ name: "Bob", age: 18 }) + await db.insert(pgSchemas.users).values({ name: "Bob", age: 18 }) try { const mutation = userFactory.updateMutation() const answer = await mutation["~meta"].resolve({ @@ -667,7 +672,7 @@ describe.concurrent("DrizzlePostgresResolverFactory", () => { }) expect(answer).toMatchObject([{ name: "Bob", age: 19 }]) } finally { - await db.delete(pgSchemas.user).where(eq(pgSchemas.user.name, "Bob")) + await db.delete(pgSchemas.users).where(eq(pgSchemas.users.name, "Bob")) } }) }) @@ -679,7 +684,7 @@ describe.concurrent("DrizzlePostgresResolverFactory", () => { }) it("should resolve correctly", async () => { - await db.insert(pgSchemas.user).values({ name: "Alice", age: 18 }) + await db.insert(pgSchemas.users).values({ name: "Alice", age: 18 }) try { const mutation = userFactory.deleteMutation() const answer = await mutation["~meta"].resolve({ @@ -687,7 +692,9 @@ describe.concurrent("DrizzlePostgresResolverFactory", () => { }) expect(answer).toMatchObject([{ name: "Alice", age: 18 }]) } finally { - await db.delete(pgSchemas.user).where(eq(pgSchemas.user.name, "Alice")) + await db + .delete(pgSchemas.users) + .where(eq(pgSchemas.users.name, "Alice")) } }) }) @@ -697,7 +704,7 @@ describe.concurrent("DrizzleSQLiteResolverFactory", () => { let db: LibSQLDatabase let userFactory: DrizzleSQLiteResolverFactory< typeof db, - typeof sqliteSchemas.user + typeof sqliteSchemas.users > beforeAll(async () => { @@ -707,7 +714,7 @@ describe.concurrent("DrizzleSQLiteResolverFactory", () => { connection: { url: `file:${pathToDB.pathname}` }, }) - userFactory = drizzleResolverFactory(db, "user") + userFactory = drizzleResolverFactory(db, "users") }) describe("insertArrayMutation", () => { @@ -732,8 +739,8 @@ describe.concurrent("DrizzleSQLiteResolverFactory", () => { ]) await db - .delete(sqliteSchemas.user) - .where(inArray(sqliteSchemas.user.age, [5, 6])) + .delete(sqliteSchemas.users) + .where(inArray(sqliteSchemas.users.age, [5, 6])) }) }) @@ -751,8 +758,8 @@ describe.concurrent("DrizzleSQLiteResolverFactory", () => { expect(answer).toMatchObject({ name: "John", age: 7 }) await db - .delete(sqliteSchemas.user) - .where(eq(sqliteSchemas.user.id, answer!.id)) + .delete(sqliteSchemas.users) + .where(eq(sqliteSchemas.users.id, answer!.id)) }) it("should be created with custom input", async () => { @@ -778,8 +785,8 @@ describe.concurrent("DrizzleSQLiteResolverFactory", () => { ]) await db - .delete(sqliteSchemas.user) - .where(inArray(sqliteSchemas.user.age, [5, 6])) + .delete(sqliteSchemas.users) + .where(inArray(sqliteSchemas.users.age, [5, 6])) }) }) @@ -790,7 +797,7 @@ describe.concurrent("DrizzleSQLiteResolverFactory", () => { }) it("should resolve correctly", async () => { - await db.insert(sqliteSchemas.user).values({ name: "Bob", age: 18 }) + await db.insert(sqliteSchemas.users).values({ name: "Bob", age: 18 }) try { const mutation = userFactory.updateMutation() const answer = await mutation["~meta"].resolve({ @@ -800,8 +807,8 @@ describe.concurrent("DrizzleSQLiteResolverFactory", () => { expect(answer).toMatchObject([{ name: "Bob", age: 19 }]) } finally { await db - .delete(sqliteSchemas.user) - .where(eq(sqliteSchemas.user.name, "Bob")) + .delete(sqliteSchemas.users) + .where(eq(sqliteSchemas.users.name, "Bob")) } }) }) @@ -813,7 +820,7 @@ describe.concurrent("DrizzleSQLiteResolverFactory", () => { }) it("should resolve correctly", async () => { - await db.insert(sqliteSchemas.user).values({ name: "Alice", age: 18 }) + await db.insert(sqliteSchemas.users).values({ name: "Alice", age: 18 }) try { const mutation = userFactory.deleteMutation() const answer = await mutation["~meta"].resolve({ @@ -823,8 +830,8 @@ describe.concurrent("DrizzleSQLiteResolverFactory", () => { expect(answer).toMatchObject([{ name: "Alice", age: 18 }]) } finally { await db - .delete(sqliteSchemas.user) - .where(eq(sqliteSchemas.user.name, "Alice")) + .delete(sqliteSchemas.users) + .where(eq(sqliteSchemas.users.name, "Alice")) } }) }) diff --git a/packages/drizzle/test/resolver-mysql.spec.gql b/packages/drizzle/test/resolver-mysql.spec.gql index 8ca0d031..256eaac2 100644 --- a/packages/drizzle/test/resolver-mysql.spec.gql +++ b/packages/drizzle/test/resolver-mysql.spec.gql @@ -1,12 +1,12 @@ type Mutation { - deleteFromPost(where: PostFilters): MutationSuccessResult - deleteFromUser(where: UserFilters): MutationSuccessResult - insertIntoPost(values: [PostInsertInput!]!): MutationSuccessResult - insertIntoPostSingle(value: PostInsertInput!): MutationSuccessResult - insertIntoUser(values: [UserInsertInput!]!): MutationSuccessResult - insertIntoUserSingle(value: UserInsertInput!): MutationSuccessResult - updatePost(set: PostUpdateInput!, where: PostFilters): MutationSuccessResult - updateUser(set: UserUpdateInput!, where: UserFilters): MutationSuccessResult + deleteFromPosts(where: PostsFilters): MutationSuccessResult + deleteFromUsers(where: UsersFilters): MutationSuccessResult + insertIntoPosts(values: [PostsInsertInput!]!): MutationSuccessResult + insertIntoPostsSingle(value: PostsInsertInput!): MutationSuccessResult + insertIntoUsers(values: [UsersInsertInput!]!): MutationSuccessResult + insertIntoUsersSingle(value: UsersInsertInput!): MutationSuccessResult + updatePosts(set: PostsUpdateInput!, where: PostsFilters): MutationSuccessResult + updateUsers(set: UsersUpdateInput!, where: UsersFilters): MutationSuccessResult } type MutationSuccessResult { @@ -80,44 +80,44 @@ enum OrderDirection { desc } -input PostFilters { - OR: [PostFiltersOr!] +input PostsFilters { + OR: [PostsFiltersOr!] authorId: MySqlIntFilters content: MySqlTextFilters id: MySqlIntFilters title: MySqlTextFilters } -input PostFiltersOr { +input PostsFiltersOr { authorId: MySqlIntFilters content: MySqlTextFilters id: MySqlIntFilters title: MySqlTextFilters } -input PostInsertInput { +input PostsInsertInput { authorId: Int content: String id: Int title: String! } -type PostItem { - author: UserItem +type PostsItem { + author: UsersItem authorId: Int content: String id: Int! title: String! } -input PostOrderBy { +input PostsOrderBy { authorId: OrderDirection content: OrderDirection id: OrderDirection title: OrderDirection } -input PostUpdateInput { +input PostsUpdateInput { authorId: Int content: String id: Int @@ -125,50 +125,50 @@ input PostUpdateInput { } type Query { - post(limit: Int, offset: Int, orderBy: PostOrderBy, where: PostFilters): [PostItem!]! - postSingle(offset: Int, orderBy: PostOrderBy, where: PostFilters): PostItem - user(limit: Int, offset: Int, orderBy: UserOrderBy, where: UserFilters): [UserItem!]! - userSingle(offset: Int, orderBy: UserOrderBy, where: UserFilters): UserItem + posts(limit: Int, offset: Int, orderBy: PostsOrderBy, where: PostsFilters): [PostsItem!]! + postsSingle(offset: Int, orderBy: PostsOrderBy, where: PostsFilters): PostsItem + users(limit: Int, offset: Int, orderBy: UsersOrderBy, where: UsersFilters): [UsersItem!]! + usersSingle(offset: Int, orderBy: UsersOrderBy, where: UsersFilters): UsersItem } -input UserFilters { - OR: [UserFiltersOr!] +input UsersFilters { + OR: [UsersFiltersOr!] age: MySqlIntFilters email: MySqlTextFilters id: MySqlIntFilters name: MySqlTextFilters } -input UserFiltersOr { +input UsersFiltersOr { age: MySqlIntFilters email: MySqlTextFilters id: MySqlIntFilters name: MySqlTextFilters } -input UserInsertInput { +input UsersInsertInput { age: Int email: String id: Int name: String! } -type UserItem { +type UsersItem { age: Int email: String id: Int! name: String! - posts: [PostItem!]! + posts: [PostsItem!]! } -input UserOrderBy { +input UsersOrderBy { age: OrderDirection email: OrderDirection id: OrderDirection name: OrderDirection } -input UserUpdateInput { +input UsersUpdateInput { age: Int email: String id: Int diff --git a/packages/drizzle/test/resolver-mysql.spec.ts b/packages/drizzle/test/resolver-mysql.spec.ts index 38526119..72f368ec 100644 --- a/packages/drizzle/test/resolver-mysql.spec.ts +++ b/packages/drizzle/test/resolver-mysql.spec.ts @@ -10,12 +10,12 @@ import { type YogaServerInstance, createYoga } from "graphql-yoga" import { afterAll, beforeAll, describe, expect, it } from "vitest" import { config } from "../env.config" import { drizzleResolverFactory } from "../src" -import { post, user } from "./schema/mysql" +import { posts, users } from "./schema/mysql" import { relations } from "./schema/mysql-relations" const schema = { - user, - post, + users, + posts, } describe("resolver by mysql", () => { @@ -47,29 +47,29 @@ describe("resolver by mysql", () => { beforeAll(async () => { try { db = drizzle(config.mysqlUrl, { schema, relations, mode: "default" }) - const userFactory = drizzleResolverFactory(db, "user") - const postFactory = drizzleResolverFactory(db, "post") + const userFactory = drizzleResolverFactory(db, "users") + const postFactory = drizzleResolverFactory(db, "posts") gqlSchema = weave( - userFactory.resolver({ name: "user" }), - postFactory.resolver({ name: "post" }) + userFactory.resolver({ name: "users" }), + postFactory.resolver({ name: "posts" }) ) yoga = createYoga({ schema: gqlSchema }) await db - .insert(user) + .insert(users) .values([{ name: "Tom" }, { name: "Tony" }, { name: "Taylor" }]) - const Tom = await db.query.user.findFirst({ + const Tom = await db.query.users.findFirst({ where: { name: "Tom" }, }) - const Tony = await db.query.user.findFirst({ + const Tony = await db.query.users.findFirst({ where: { name: "Tony" }, }) - const Taylor = await db.query.user.findFirst({ + const Taylor = await db.query.users.findFirst({ where: { name: "Taylor" }, }) if (!Tom || !Tony || !Taylor) throw new Error("User not found") - await db.insert(post).values([ + await db.insert(posts).values([ { title: "Post 1", authorId: Tom.id }, { title: "Post 2", authorId: Tony.id }, { title: "Post 3", authorId: Taylor.id }, @@ -81,8 +81,8 @@ describe("resolver by mysql", () => { }) afterAll(async () => { - await db.delete(post) - await db.delete(user) + await db.delete(posts) + await db.delete(users) }) it("should weave GraphQL schema correctly", async () => { @@ -94,13 +94,13 @@ describe("resolver by mysql", () => { describe.concurrent("query", () => { it("should query users correctly", async () => { const q = /* GraphQL */ ` - query user( - $orderBy: UserOrderBy! - $where: UserFilters! + query users( + $orderBy: UsersOrderBy! + $where: UsersFilters! $limit: Int $offset: Int ) { - user( + users( orderBy: $orderBy where: $where limit: $limit @@ -117,7 +117,7 @@ describe("resolver by mysql", () => { where: { name: { like: "T%" } }, }) ).resolves.toMatchObject({ - user: [{ name: "Taylor" }, { name: "Tom" }, { name: "Tony" }], + users: [{ name: "Taylor" }, { name: "Tom" }, { name: "Tony" }], }) await expect( @@ -127,7 +127,7 @@ describe("resolver by mysql", () => { limit: 2, }) ).resolves.toMatchObject({ - user: [{ name: "Taylor" }, { name: "Tom" }], + users: [{ name: "Taylor" }, { name: "Tom" }], }) await expect( @@ -138,7 +138,7 @@ describe("resolver by mysql", () => { offset: 1, }) ).resolves.toMatchObject({ - user: [{ name: "Tom" }], + users: [{ name: "Tom" }], }) }) @@ -146,12 +146,12 @@ describe("resolver by mysql", () => { await expect( execute( /* GraphQL */ ` - query user( - $orderBy: UserOrderBy - $where: UserFilters! + query users( + $orderBy: UsersOrderBy + $where: UsersFilters! $offset: Int ) { - userSingle(orderBy: $orderBy, where: $where, offset: $offset) { + usersSingle(orderBy: $orderBy, where: $where, offset: $offset) { id name } @@ -162,19 +162,19 @@ describe("resolver by mysql", () => { } ) ).resolves.toMatchObject({ - userSingle: { name: "Taylor" }, + usersSingle: { name: "Taylor" }, }) }) it("should query user with posts correctly", async () => { const q = /* GraphQL */ ` - query user( - $orderBy: UserOrderBy! - $where: UserFilters! + query users( + $orderBy: UsersOrderBy! + $where: UsersFilters! $limit: Int $offset: Int ) { - user( + users( orderBy: $orderBy where: $where limit: $limit @@ -196,7 +196,7 @@ describe("resolver by mysql", () => { where: { name: { like: "T%" } }, }) ).resolves.toMatchObject({ - user: [ + users: [ { name: "Taylor", posts: [{ title: "Post 3" }], @@ -217,8 +217,8 @@ describe("resolver by mysql", () => { describe("mutation", () => { it("should insert a new user correctly", async () => { const q = /* GraphQL */ ` - mutation insertIntoUser($values: [UserInsertInput!]!) { - insertIntoUser(values: $values) { + mutation insertIntoUsers($values: [UsersInsertInput!]!) { + insertIntoUsers(values: $values) { isSuccess } } @@ -229,11 +229,11 @@ describe("resolver by mysql", () => { values: [{ name: "Tina" }], }) ).resolves.toMatchObject({ - insertIntoUser: { isSuccess: true }, + insertIntoUsers: { isSuccess: true }, }) // Verify the user was inserted - const Tina = await db.query.user.findFirst({ + const Tina = await db.query.users.findFirst({ where: { name: "Tina" }, }) expect(Tina).toBeDefined() @@ -241,21 +241,21 @@ describe("resolver by mysql", () => { it("should update user information correctly", async () => { const q = /* GraphQL */ ` - mutation updateUser( - $set: UserUpdateInput! - $where: UserFilters! + mutation updateUsers( + $set: UsersUpdateInput! + $where: UsersFilters! ) { - updateUser(set: $set, where: $where) { + updateUsers(set: $set, where: $where) { isSuccess } } ` const [TroyID] = await db - .insert(user) + .insert(users) .values({ name: "Troy" }) .$returningId() - const Troy = await db.query.user.findFirst({ + const Troy = await db.query.users.findFirst({ where: { id: TroyID.id }, }) if (!Troy) throw new Error("User not found") @@ -266,11 +266,11 @@ describe("resolver by mysql", () => { where: { id: { eq: Troy.id } }, }) ).resolves.toMatchObject({ - updateUser: { isSuccess: true }, + updateUsers: { isSuccess: true }, }) // Verify the user was updated - const updatedUser = await db.query.user.findFirst({ + const updatedUser = await db.query.users.findFirst({ where: { name: "Tiffany" }, }) expect(updatedUser).toBeDefined() @@ -278,14 +278,14 @@ describe("resolver by mysql", () => { it("should delete a user correctly", async () => { const q = /* GraphQL */ ` - mutation deleteFromUser($where: UserFilters!) { - deleteFromUser(where: $where) { + mutation deleteFromUsers($where: UsersFilters!) { + deleteFromUsers(where: $where) { isSuccess } } ` - const Tony = await db.query.user.findFirst({ + const Tony = await db.query.users.findFirst({ where: { name: "Tony" }, }) if (!Tony) throw new Error("User not found") @@ -295,13 +295,13 @@ describe("resolver by mysql", () => { where: { id: { eq: Tony.id } }, }) ).resolves.toMatchObject({ - deleteFromUser: { + deleteFromUsers: { isSuccess: true, }, }) // Verify the user was deleted - const deletedUser = await db.query.user.findFirst({ + const deletedUser = await db.query.users.findFirst({ where: { name: "Tony" }, }) expect(deletedUser).toBeUndefined() @@ -309,14 +309,14 @@ describe("resolver by mysql", () => { it("should insert a new post correctly", async () => { const q = /* GraphQL */ ` - mutation insertIntoPost($values: [PostInsertInput!]!) { - insertIntoPost(values: $values) { + mutation insertIntoPosts($values: [PostsInsertInput!]!) { + insertIntoPosts(values: $values) { isSuccess } } ` - const Tom = await db.query.user.findFirst({ + const Tom = await db.query.users.findFirst({ where: { name: "Tom" }, }) if (!Tom) throw new Error("User not found") @@ -326,13 +326,13 @@ describe("resolver by mysql", () => { values: [{ title: "Post 5", authorId: Tom.id }], }) ).resolves.toMatchObject({ - insertIntoPost: { + insertIntoPosts: { isSuccess: true, }, }) // Verify the post was inserted - const p = await db.query.post.findFirst({ + const p = await db.query.posts.findFirst({ where: { title: "Post 5" }, }) expect(p).toBeDefined() @@ -340,22 +340,22 @@ describe("resolver by mysql", () => { it("should update post information correctly", async () => { const q = /* GraphQL */ ` - mutation updatePost( - $set: PostUpdateInput! - $where: PostFilters! + mutation updatePosts( + $set: PostsUpdateInput! + $where: PostsFilters! ) { - updatePost(set: $set, where: $where) { + updatePosts(set: $set, where: $where) { isSuccess } } ` const [PostUID] = await db - .insert(post) + .insert(posts) .values({ title: "Post U" }) .$returningId() - const PostU = await db.query.post.findFirst({ + const PostU = await db.query.posts.findFirst({ where: { id: PostUID.id }, }) if (!PostU) throw new Error("Post not found") @@ -366,11 +366,11 @@ describe("resolver by mysql", () => { where: { id: { eq: PostU.id } }, }) ).resolves.toMatchObject({ - updatePost: { isSuccess: true }, + updatePosts: { isSuccess: true }, }) // Verify the post was updated - const updatedPost = await db.query.post.findFirst({ + const updatedPost = await db.query.posts.findFirst({ where: { title: "Updated Post U" }, }) expect(updatedPost).toBeDefined() @@ -378,19 +378,19 @@ describe("resolver by mysql", () => { it("should delete a post correctly", async () => { const q = /* GraphQL */ ` - mutation deleteFromPost($where: PostFilters!) { - deleteFromPost(where: $where) { + mutation deleteFromPosts($where: PostsFilters!) { + deleteFromPosts(where: $where) { isSuccess } } ` const [PostDID] = await db - .insert(post) + .insert(posts) .values({ title: "Post D" }) .$returningId() - const PostD = await db.query.post.findFirst({ + const PostD = await db.query.posts.findFirst({ where: { id: PostDID.id }, }) if (!PostD) throw new Error("Post not found") @@ -400,11 +400,11 @@ describe("resolver by mysql", () => { where: { id: { eq: PostD.id } }, }) ).resolves.toMatchObject({ - deleteFromPost: { isSuccess: true }, + deleteFromPosts: { isSuccess: true }, }) // Verify the post was deleted - const deletedPost = await db.query.post.findFirst({ + const deletedPost = await db.query.posts.findFirst({ where: { id: PostD.id }, }) expect(deletedPost).toBeUndefined() diff --git a/packages/drizzle/test/resolver-postgres.spec.gql b/packages/drizzle/test/resolver-postgres.spec.gql index 057be801..13e9ac0b 100644 --- a/packages/drizzle/test/resolver-postgres.spec.gql +++ b/packages/drizzle/test/resolver-postgres.spec.gql @@ -1,12 +1,12 @@ type Mutation { - deleteFromPost(where: PostFilters): [PostItem!]! - deleteFromUser(where: UserFilters): [UserItem!]! - insertIntoPost(values: [PostInsertInput!]!): [PostItem!]! - insertIntoPostSingle(value: PostInsertInput!): PostItem - insertIntoUser(values: [UserInsertInput!]!): [UserItem!]! - insertIntoUserSingle(value: UserInsertInput!): UserItem - updatePost(set: PostUpdateInput!, where: PostFilters): [PostItem!]! - updateUser(set: UserUpdateInput!, where: UserFilters): [UserItem!]! + deleteFromPosts(where: PostsFilters): [PostsItem!]! + deleteFromUsers(where: UsersFilters): [UsersItem!]! + insertIntoPosts(values: [PostsInsertInput!]!): [PostsItem!]! + insertIntoPostsSingle(value: PostsInsertInput!): PostsItem + insertIntoUsers(values: [UsersInsertInput!]!): [UsersItem!]! + insertIntoUsersSingle(value: UsersInsertInput!): UsersItem + updatePosts(set: PostsUpdateInput!, where: PostsFilters): [PostsItem!]! + updateUsers(set: UsersUpdateInput!, where: UsersFilters): [UsersItem!]! } enum OrderDirection { @@ -103,44 +103,44 @@ input PgTextFiltersOr { notLike: String } -input PostFilters { - OR: [PostFiltersOr!] +input PostsFilters { + OR: [PostsFiltersOr!] authorId: PgIntegerFilters content: PgTextFilters id: PgSerialFilters title: PgTextFilters } -input PostFiltersOr { +input PostsFiltersOr { authorId: PgIntegerFilters content: PgTextFilters id: PgSerialFilters title: PgTextFilters } -input PostInsertInput { +input PostsInsertInput { authorId: Int content: String id: Int title: String! } -type PostItem { - author: UserItem +type PostsItem { + author: UsersItem authorId: Int content: String id: Int! title: String! } -input PostOrderBy { +input PostsOrderBy { authorId: OrderDirection content: OrderDirection id: OrderDirection title: OrderDirection } -input PostUpdateInput { +input PostsUpdateInput { authorId: Int content: String id: Int @@ -148,50 +148,50 @@ input PostUpdateInput { } type Query { - post(limit: Int, offset: Int, orderBy: PostOrderBy, where: PostFilters): [PostItem!]! - postSingle(offset: Int, orderBy: PostOrderBy, where: PostFilters): PostItem - user(limit: Int, offset: Int, orderBy: UserOrderBy, where: UserFilters): [UserItem!]! - userSingle(offset: Int, orderBy: UserOrderBy, where: UserFilters): UserItem + posts(limit: Int, offset: Int, orderBy: PostsOrderBy, where: PostsFilters): [PostsItem!]! + postsSingle(offset: Int, orderBy: PostsOrderBy, where: PostsFilters): PostsItem + users(limit: Int, offset: Int, orderBy: UsersOrderBy, where: UsersFilters): [UsersItem!]! + usersSingle(offset: Int, orderBy: UsersOrderBy, where: UsersFilters): UsersItem } -input UserFilters { - OR: [UserFiltersOr!] +input UsersFilters { + OR: [UsersFiltersOr!] age: PgIntegerFilters email: PgTextFilters id: PgSerialFilters name: PgTextFilters } -input UserFiltersOr { +input UsersFiltersOr { age: PgIntegerFilters email: PgTextFilters id: PgSerialFilters name: PgTextFilters } -input UserInsertInput { +input UsersInsertInput { age: Int email: String id: Int name: String! } -type UserItem { +type UsersItem { age: Int email: String id: Int! name: String! - posts: [PostItem!]! + posts: [PostsItem!]! } -input UserOrderBy { +input UsersOrderBy { age: OrderDirection email: OrderDirection id: OrderDirection name: OrderDirection } -input UserUpdateInput { +input UsersUpdateInput { age: Int email: String id: Int diff --git a/packages/drizzle/test/resolver-postgres.spec.ts b/packages/drizzle/test/resolver-postgres.spec.ts index 77980218..3766acc7 100644 --- a/packages/drizzle/test/resolver-postgres.spec.ts +++ b/packages/drizzle/test/resolver-postgres.spec.ts @@ -9,10 +9,10 @@ import { type YogaServerInstance, createYoga } from "graphql-yoga" import { afterAll, beforeAll, describe, expect, it } from "vitest" import { config } from "../env.config" import { drizzleResolverFactory } from "../src" -import { post, user } from "./schema/postgres" +import { posts, users } from "./schema/postgres" import { relations } from "./schema/postgres-relations" -const schema = { user, post } +const schema = { users, posts } describe("resolver by postgres", () => { let db: NodePgDatabase @@ -43,29 +43,29 @@ describe("resolver by postgres", () => { beforeAll(async () => { try { db = drizzle(config.postgresUrl, { schema, relations }) - const userFactory = drizzleResolverFactory(db, "user") - const postFactory = drizzleResolverFactory(db, "post") + const userFactory = drizzleResolverFactory(db, "users") + const postFactory = drizzleResolverFactory(db, "posts") gqlSchema = weave( - userFactory.resolver({ name: "user" }), - postFactory.resolver({ name: "post" }) + userFactory.resolver({ name: "users" }), + postFactory.resolver({ name: "posts" }) ) yoga = createYoga({ schema: gqlSchema }) await db - .insert(user) + .insert(users) .values([{ name: "Tom" }, { name: "Tony" }, { name: "Taylor" }]) - const Tom = await db.query.user.findFirst({ + const Tom = await db.query.users.findFirst({ where: { name: "Tom" }, }) - const Tony = await db.query.user.findFirst({ + const Tony = await db.query.users.findFirst({ where: { name: "Tony" }, }) - const Taylor = await db.query.user.findFirst({ + const Taylor = await db.query.users.findFirst({ where: { name: "Taylor" }, }) if (!Tom || !Tony || !Taylor) throw new Error("User not found") - await db.insert(post).values([ + await db.insert(posts).values([ { title: "Post 1", authorId: Tom.id }, { title: "Post 2", authorId: Tony.id }, { title: "Post 3", authorId: Taylor.id }, @@ -77,8 +77,8 @@ describe("resolver by postgres", () => { }) afterAll(async () => { - await db.delete(post) - await db.delete(user) + await db.delete(posts) + await db.delete(users) }) it("should weave GraphQL schema correctly", async () => { @@ -90,13 +90,13 @@ describe("resolver by postgres", () => { describe.concurrent("query", () => { it("should query users correctly", async () => { const q = /* GraphQL */ ` - query user( - $orderBy: UserOrderBy - $where: UserFilters! + query users( + $orderBy: UsersOrderBy + $where: UsersFilters! $limit: Int $offset: Int ) { - user( + users( orderBy: $orderBy where: $where limit: $limit @@ -113,7 +113,7 @@ describe("resolver by postgres", () => { where: { name: { like: "T%" } }, }) ).resolves.toMatchObject({ - user: [{ name: "Taylor" }, { name: "Tom" }, { name: "Tony" }], + users: [{ name: "Taylor" }, { name: "Tom" }, { name: "Tony" }], }) await expect( @@ -123,7 +123,7 @@ describe("resolver by postgres", () => { limit: 2, }) ).resolves.toMatchObject({ - user: [{ name: "Taylor" }, { name: "Tom" }], + users: [{ name: "Taylor" }, { name: "Tom" }], }) await expect( @@ -134,7 +134,7 @@ describe("resolver by postgres", () => { offset: 1, }) ).resolves.toMatchObject({ - user: [{ name: "Tom" }], + users: [{ name: "Tom" }], }) }) @@ -142,12 +142,12 @@ describe("resolver by postgres", () => { await expect( execute( /* GraphQL */ ` - query user( - $orderBy: UserOrderBy - $where: UserFilters! + query users( + $orderBy: UsersOrderBy + $where: UsersFilters! $offset: Int ) { - userSingle(orderBy: $orderBy, where: $where, offset: $offset) { + usersSingle(orderBy: $orderBy, where: $where, offset: $offset) { id name } @@ -158,19 +158,19 @@ describe("resolver by postgres", () => { } ) ).resolves.toMatchObject({ - userSingle: { name: "Taylor" }, + usersSingle: { name: "Taylor" }, }) }) it("should query user with posts correctly", async () => { const q = /* GraphQL */ ` - query user( - $orderBy: UserOrderBy - $where: UserFilters! + query users( + $orderBy: UsersOrderBy + $where: UsersFilters! $limit: Int $offset: Int ) { - user( + users( orderBy: $orderBy where: $where limit: $limit @@ -192,7 +192,7 @@ describe("resolver by postgres", () => { where: { name: { like: "T%" } }, }) ).resolves.toMatchObject({ - user: [ + users: [ { name: "Taylor", posts: [{ title: "Post 3" }], @@ -213,8 +213,8 @@ describe("resolver by postgres", () => { describe("mutation", () => { it("should insert a new user correctly", async () => { const q = /* GraphQL */ ` - mutation insertIntoUser($values: [UserInsertInput!]!) { - insertIntoUser(values: $values) { + mutation insertIntoUsers($values: [UsersInsertInput!]!) { + insertIntoUsers(values: $values) { id name } @@ -226,11 +226,11 @@ describe("resolver by postgres", () => { values: [{ name: "Tina" }], }) ).resolves.toMatchObject({ - insertIntoUser: [{ name: "Tina" }], + insertIntoUsers: [{ name: "Tina" }], }) // Verify the user was inserted - const Tina = await db.query.user.findFirst({ + const Tina = await db.query.users.findFirst({ where: { name: "Tina" }, }) expect(Tina).toBeDefined() @@ -238,11 +238,11 @@ describe("resolver by postgres", () => { it("should update user information correctly", async () => { const q = /* GraphQL */ ` - mutation updateUser( - $set: UserUpdateInput! - $where: UserFilters! + mutation updateUsers( + $set: UsersUpdateInput! + $where: UsersFilters! ) { - updateUser(set: $set, where: $where) { + updateUsers(set: $set, where: $where) { id name } @@ -250,10 +250,10 @@ describe("resolver by postgres", () => { ` const [TroyID] = await db - .insert(user) + .insert(users) .values({ name: "Troy" }) .returning() - const Troy = await db.query.user.findFirst({ + const Troy = await db.query.users.findFirst({ where: { id: TroyID.id }, }) if (!Troy) throw new Error("User not found") @@ -264,11 +264,11 @@ describe("resolver by postgres", () => { where: { id: { eq: Troy.id } }, }) ).resolves.toMatchObject({ - updateUser: [{ id: Troy.id, name: "Tiffany" }], + updateUsers: [{ id: Troy.id, name: "Tiffany" }], }) // Verify the user was updated - const updatedUser = await db.query.user.findFirst({ + const updatedUser = await db.query.users.findFirst({ where: { name: "Tiffany" }, }) expect(updatedUser).toBeDefined() @@ -276,15 +276,15 @@ describe("resolver by postgres", () => { it("should delete a user correctly", async () => { const q = /* GraphQL */ ` - mutation deleteFromUser($where: UserFilters!) { - deleteFromUser(where: $where) { + mutation deleteFromUsers($where: UsersFilters!) { + deleteFromUsers(where: $where) { id name } } ` - const Tony = await db.query.user.findFirst({ + const Tony = await db.query.users.findFirst({ where: { name: "Tony" }, }) if (!Tony) throw new Error("User not found") @@ -294,11 +294,11 @@ describe("resolver by postgres", () => { where: { id: { eq: Tony.id } }, }) ).resolves.toMatchObject({ - deleteFromUser: [{ id: Tony.id, name: "Tony" }], + deleteFromUsers: [{ id: Tony.id, name: "Tony" }], }) // Verify the user was deleted - const deletedUser = await db.query.user.findFirst({ + const deletedUser = await db.query.users.findFirst({ where: { name: "Tony" }, }) expect(deletedUser).toBeUndefined() @@ -306,8 +306,8 @@ describe("resolver by postgres", () => { it("should insert a new post correctly", async () => { const q = /* GraphQL */ ` - mutation insertIntoPost($values: [PostInsertInput!]!) { - insertIntoPost(values: $values) { + mutation insertIntoPosts($values: [PostsInsertInput!]!) { + insertIntoPosts(values: $values) { id title authorId @@ -315,7 +315,7 @@ describe("resolver by postgres", () => { } ` - const Tom = await db.query.user.findFirst({ + const Tom = await db.query.users.findFirst({ where: { name: "Tom" }, }) if (!Tom) throw new Error("User not found") @@ -325,11 +325,11 @@ describe("resolver by postgres", () => { values: [{ title: "Post 5", authorId: Tom.id }], }) ).resolves.toMatchObject({ - insertIntoPost: [{ title: "Post 5", authorId: Tom.id }], + insertIntoPosts: [{ title: "Post 5", authorId: Tom.id }], }) // Verify the post was inserted - const p = await db.query.post.findFirst({ + const p = await db.query.posts.findFirst({ where: { title: "Post 5" }, }) expect(p).toBeDefined() @@ -337,11 +337,11 @@ describe("resolver by postgres", () => { it("should update post information correctly", async () => { const q = /* GraphQL */ ` - mutation updatePost( - $set: PostUpdateInput! - $where: PostFilters! + mutation updatePosts( + $set: PostsUpdateInput! + $where: PostsFilters! ) { - updatePost(set: $set, where: $where) { + updatePosts(set: $set, where: $where) { id title } @@ -349,11 +349,11 @@ describe("resolver by postgres", () => { ` const [PostUID] = await db - .insert(post) + .insert(posts) .values({ title: "Post U" }) .returning() - const PostU = await db.query.post.findFirst({ + const PostU = await db.query.posts.findFirst({ where: { id: PostUID.id }, }) if (!PostU) throw new Error("Post not found") @@ -364,11 +364,11 @@ describe("resolver by postgres", () => { where: { id: { eq: PostU.id } }, }) ).resolves.toMatchObject({ - updatePost: [{ id: PostU.id, title: "Updated Post U" }], + updatePosts: [{ id: PostU.id, title: "Updated Post U" }], }) // Verify the post was updated - const updatedPost = await db.query.post.findFirst({ + const updatedPost = await db.query.posts.findFirst({ where: { title: "Updated Post U" }, }) expect(updatedPost).toBeDefined() @@ -376,8 +376,8 @@ describe("resolver by postgres", () => { it("should delete a post correctly", async () => { const q = /* GraphQL */ ` - mutation deleteFromPost($where: PostFilters!) { - deleteFromPost(where: $where) { + mutation deleteFromPosts($where: PostsFilters!) { + deleteFromPosts(where: $where) { id title } @@ -385,11 +385,11 @@ describe("resolver by postgres", () => { ` const [PostDID] = await db - .insert(post) + .insert(posts) .values({ title: "Post D" }) .returning() - const PostD = await db.query.post.findFirst({ + const PostD = await db.query.posts.findFirst({ where: { id: PostDID.id }, }) if (!PostD) throw new Error("Post not found") @@ -399,11 +399,11 @@ describe("resolver by postgres", () => { where: { id: { eq: PostD.id } }, }) ).resolves.toMatchObject({ - deleteFromPost: [{ id: PostD.id, title: "Post D" }], + deleteFromPosts: [{ id: PostD.id, title: "Post D" }], }) // Verify the post was deleted - const deletedPost = await db.query.post.findFirst({ + const deletedPost = await db.query.posts.findFirst({ where: { id: PostD.id }, }) expect(deletedPost).toBeUndefined() diff --git a/packages/drizzle/test/resolver-sqlite.spec.gql b/packages/drizzle/test/resolver-sqlite.spec.gql index e2c1aed6..1936f95d 100644 --- a/packages/drizzle/test/resolver-sqlite.spec.gql +++ b/packages/drizzle/test/resolver-sqlite.spec.gql @@ -1,12 +1,12 @@ type Mutation { - deleteFromPost(where: PostFilters): [PostItem!]! - deleteFromUser(where: UserFilters): [UserItem!]! - insertIntoPost(values: [PostInsertInput!]!): [PostItem!]! - insertIntoPostSingle(value: PostInsertInput!): PostItem - insertIntoUser(values: [UserInsertInput!]!): [UserItem!]! - insertIntoUserSingle(value: UserInsertInput!): UserItem - updatePost(set: PostUpdateInput!, where: PostFilters): [PostItem!]! - updateUser(set: UserUpdateInput!, where: UserFilters): [UserItem!]! + deleteFromPosts(where: PostsFilters): [PostsItem!]! + deleteFromUsers(where: UsersFilters): [UsersItem!]! + insertIntoPosts(values: [PostsInsertInput!]!): [PostsItem!]! + insertIntoPostsSingle(value: PostsInsertInput!): PostsItem + insertIntoUsers(values: [UsersInsertInput!]!): [UsersItem!]! + insertIntoUsersSingle(value: UsersInsertInput!): UsersItem + updatePosts(set: PostsUpdateInput!, where: PostsFilters): [PostsItem!]! + updateUsers(set: UsersUpdateInput!, where: UsersFilters): [UsersItem!]! } enum OrderDirection { @@ -14,44 +14,44 @@ enum OrderDirection { desc } -input PostFilters { - OR: [PostFiltersOr!] +input PostsFilters { + OR: [PostsFiltersOr!] authorId: SQLiteIntegerFilters content: SQLiteTextFilters id: SQLiteIntegerFilters title: SQLiteTextFilters } -input PostFiltersOr { +input PostsFiltersOr { authorId: SQLiteIntegerFilters content: SQLiteTextFilters id: SQLiteIntegerFilters title: SQLiteTextFilters } -input PostInsertInput { +input PostsInsertInput { authorId: Int content: String id: Int title: String! } -type PostItem { - author: UserItem +type PostsItem { + author: UsersItem authorId: Int content: String id: Int! title: String! } -input PostOrderBy { +input PostsOrderBy { authorId: OrderDirection content: OrderDirection id: OrderDirection title: OrderDirection } -input PostUpdateInput { +input PostsUpdateInput { authorId: Int content: String id: Int @@ -59,10 +59,10 @@ input PostUpdateInput { } type Query { - post(limit: Int, offset: Int, orderBy: PostOrderBy, where: PostFilters): [PostItem!]! - postSingle(offset: Int, orderBy: PostOrderBy, where: PostFilters): PostItem - user(limit: Int, offset: Int, orderBy: UserOrderBy, where: UserFilters): [UserItem!]! - userSingle(offset: Int, orderBy: UserOrderBy, where: UserFilters): UserItem + posts(limit: Int, offset: Int, orderBy: PostsOrderBy, where: PostsFilters): [PostsItem!]! + postsSingle(offset: Int, orderBy: PostsOrderBy, where: PostsFilters): PostsItem + users(limit: Int, offset: Int, orderBy: UsersOrderBy, where: UsersFilters): [UsersItem!]! + usersSingle(offset: Int, orderBy: UsersOrderBy, where: UsersFilters): UsersItem } input SQLiteIntegerFilters { @@ -127,51 +127,51 @@ input SQLiteTextFiltersOr { notLike: String } -type StudentToCourseItem { +type StudentToCoursesItem { courseId: Int createdAt: String studentId: Int } -input UserFilters { - OR: [UserFiltersOr!] +input UsersFilters { + OR: [UsersFiltersOr!] age: SQLiteIntegerFilters email: SQLiteTextFilters id: SQLiteIntegerFilters name: SQLiteTextFilters } -input UserFiltersOr { +input UsersFiltersOr { age: SQLiteIntegerFilters email: SQLiteTextFilters id: SQLiteIntegerFilters name: SQLiteTextFilters } -input UserInsertInput { +input UsersInsertInput { age: Int email: String id: Int name: String! } -type UserItem { +type UsersItem { age: Int - courses: [StudentToCourseItem!]! + courses: [StudentToCoursesItem!]! email: String id: Int! name: String! - posts: [PostItem!]! + posts: [PostsItem!]! } -input UserOrderBy { +input UsersOrderBy { age: OrderDirection email: OrderDirection id: OrderDirection name: OrderDirection } -input UserUpdateInput { +input UsersUpdateInput { age: Int email: String id: Int diff --git a/packages/drizzle/test/resolver-sqlite.spec.ts b/packages/drizzle/test/resolver-sqlite.spec.ts index c297b4c4..3054f1d6 100644 --- a/packages/drizzle/test/resolver-sqlite.spec.ts +++ b/packages/drizzle/test/resolver-sqlite.spec.ts @@ -9,7 +9,7 @@ import { type YogaServerInstance, createYoga } from "graphql-yoga" import { afterAll, beforeAll, describe, expect, it } from "vitest" import { drizzleResolverFactory } from "../src" import * as schema from "./schema/sqlite" -import { post, user } from "./schema/sqlite" +import { posts, users } from "./schema/sqlite" import { relations } from "./schema/sqlite-relations" const pathToDB = new URL("./schema/sqlite-1.db", import.meta.url) @@ -45,26 +45,26 @@ describe("resolver by sqlite", () => { relations, connection: { url: `file:${pathToDB.pathname}` }, }) - const userFactory = drizzleResolverFactory(db, "user") - const postFactory = drizzleResolverFactory(db, "post") + const userFactory = drizzleResolverFactory(db, "users") + const postFactory = drizzleResolverFactory(db, "posts") gqlSchema = weave(userFactory.resolver(), postFactory.resolver()) yoga = createYoga({ schema: gqlSchema }) await db - .insert(user) + .insert(users) .values([{ name: "Tom" }, { name: "Tony" }, { name: "Taylor" }]) - const Tom = await db.query.user.findFirst({ + const Tom = await db.query.users.findFirst({ where: { name: "Tom" }, }) - const Tony = await db.query.user.findFirst({ + const Tony = await db.query.users.findFirst({ where: { name: "Tony" }, }) - const Taylor = await db.query.user.findFirst({ + const Taylor = await db.query.users.findFirst({ where: { name: "Taylor" }, }) if (!Tom || !Tony || !Taylor) throw new Error("User not found") - await db.insert(post).values([ + await db.insert(posts).values([ { title: "Post 1", authorId: Tom.id }, { title: "Post 2", authorId: Tony.id }, { title: "Post 3", authorId: Taylor.id }, @@ -73,8 +73,8 @@ describe("resolver by sqlite", () => { }) afterAll(async () => { - await db.delete(post) - await db.delete(user) + await db.delete(posts) + await db.delete(users) }) it("should weave GraphQL schema correctly", async () => { @@ -86,8 +86,8 @@ describe("resolver by sqlite", () => { describe.concurrent("query", () => { it("should query users correctly", async () => { const q = /* GraphQL */ ` - query user ($orderBy: UserOrderBy, $where: UserFilters!, $limit: Int, $offset: Int) { - user(orderBy: $orderBy, where: $where, limit: $limit, offset: $offset) { + query users ($orderBy: UsersOrderBy, $where: UsersFilters!, $limit: Int, $offset: Int) { + users(orderBy: $orderBy, where: $where, limit: $limit, offset: $offset) { id name } @@ -99,7 +99,7 @@ describe("resolver by sqlite", () => { where: { name: { like: "T%" } }, }) ).resolves.toMatchObject({ - user: [{ name: "Taylor" }, { name: "Tom" }, { name: "Tony" }], + users: [{ name: "Taylor" }, { name: "Tom" }, { name: "Tony" }], }) await expect( @@ -109,7 +109,7 @@ describe("resolver by sqlite", () => { limit: 2, }) ).resolves.toMatchObject({ - user: [{ name: "Taylor" }, { name: "Tom" }], + users: [{ name: "Taylor" }, { name: "Tom" }], }) await expect( @@ -120,7 +120,7 @@ describe("resolver by sqlite", () => { offset: 1, }) ).resolves.toMatchObject({ - user: [{ name: "Tom" }], + users: [{ name: "Tom" }], }) }) @@ -128,8 +128,8 @@ describe("resolver by sqlite", () => { await expect( execute( /* GraphQL */ ` - query user ($orderBy: UserOrderBy, $where: UserFilters!, $offset: Int) { - userSingle(orderBy: $orderBy, where: $where, offset: $offset) { + query users ($orderBy: UsersOrderBy, $where: UsersFilters!, $offset: Int) { + usersSingle(orderBy: $orderBy, where: $where, offset: $offset) { id name } @@ -140,14 +140,14 @@ describe("resolver by sqlite", () => { } ) ).resolves.toMatchObject({ - userSingle: { name: "Taylor" }, + usersSingle: { name: "Taylor" }, }) }) it("should query user with posts correctly", async () => { const q = /* GraphQL */ ` - query user ($orderBy: UserOrderBy, $where: UserFilters!, $limit: Int, $offset: Int) { - user(orderBy: $orderBy,where: $where, limit: $limit, offset: $offset) { + query users ($orderBy: UsersOrderBy, $where: UsersFilters!, $limit: Int, $offset: Int) { + users(orderBy: $orderBy,where: $where, limit: $limit, offset: $offset) { id name posts { @@ -164,7 +164,7 @@ describe("resolver by sqlite", () => { where: { name: { like: "T%" } }, }) ).resolves.toMatchObject({ - user: [ + users: [ { name: "Taylor", posts: [{ title: "Post 3" }], @@ -185,8 +185,8 @@ describe("resolver by sqlite", () => { describe("mutation", () => { it("should insert a new user correctly", async () => { const q = /* GraphQL */ ` - mutation insertIntoUser($values: [UserInsertInput!]!) { - insertIntoUser(values: $values) { + mutation insertIntoUsers($values: [UsersInsertInput!]!) { + insertIntoUsers(values: $values) { id name } @@ -198,11 +198,11 @@ describe("resolver by sqlite", () => { values: [{ name: "Tina" }], }) ).resolves.toMatchObject({ - insertIntoUser: [{ name: "Tina" }], + insertIntoUsers: [{ name: "Tina" }], }) // Verify the user was inserted - const Tina = await db.query.user.findFirst({ + const Tina = await db.query.users.findFirst({ where: { name: "Tina" }, }) expect(Tina).toBeDefined() @@ -210,15 +210,15 @@ describe("resolver by sqlite", () => { it("should update user information correctly", async () => { const q = /* GraphQL */ ` - mutation updateUser($set: UserUpdateInput!, $where: UserFilters!) { - updateUser(set: $set, where: $where) { + mutation updateUsers($set: UsersUpdateInput!, $where: UsersFilters!) { + updateUsers(set: $set, where: $where) { id name } } ` - const [Troy] = await db.insert(user).values({ name: "Troy" }).returning() + const [Troy] = await db.insert(users).values({ name: "Troy" }).returning() if (!Troy) throw new Error("User not found") await expect( @@ -227,11 +227,11 @@ describe("resolver by sqlite", () => { where: { id: { eq: Troy.id } }, }) ).resolves.toMatchObject({ - updateUser: [{ id: Troy.id, name: "Tiffany" }], + updateUsers: [{ id: Troy.id, name: "Tiffany" }], }) // Verify the user was updated - const updatedUser = await db.query.user.findFirst({ + const updatedUser = await db.query.users.findFirst({ where: { name: "Tiffany" }, }) expect(updatedUser).toBeDefined() @@ -239,15 +239,15 @@ describe("resolver by sqlite", () => { it("should delete a user correctly", async () => { const q = /* GraphQL */ ` - mutation deleteFromUser($where: UserFilters!) { - deleteFromUser(where: $where) { + mutation deleteFromUsers($where: UsersFilters!) { + deleteFromUsers(where: $where) { id name } } ` - const Tony = await db.query.user.findFirst({ + const Tony = await db.query.users.findFirst({ where: { name: "Tony" }, }) if (!Tony) throw new Error("User not found") @@ -257,11 +257,11 @@ describe("resolver by sqlite", () => { where: { id: { eq: Tony.id } }, }) ).resolves.toMatchObject({ - deleteFromUser: [{ id: Tony.id, name: "Tony" }], + deleteFromUsers: [{ id: Tony.id, name: "Tony" }], }) // Verify the user was deleted - const deletedUser = await db.query.user.findFirst({ + const deletedUser = await db.query.users.findFirst({ where: { name: "Tony" }, }) expect(deletedUser).toBeUndefined() @@ -269,8 +269,8 @@ describe("resolver by sqlite", () => { it("should insert a new post correctly", async () => { const q = /* GraphQL */ ` - mutation insertIntoPost($values: [PostInsertInput!]!) { - insertIntoPost(values: $values) { + mutation insertIntoPosts($values: [PostsInsertInput!]!) { + insertIntoPosts(values: $values) { id title authorId @@ -278,7 +278,7 @@ describe("resolver by sqlite", () => { } ` - const Tom = await db.query.user.findFirst({ + const Tom = await db.query.users.findFirst({ where: { name: "Tom" }, }) if (!Tom) throw new Error("User not found") @@ -288,11 +288,11 @@ describe("resolver by sqlite", () => { values: [{ title: "Post 5", authorId: Tom.id }], }) ).resolves.toMatchObject({ - insertIntoPost: [{ title: "Post 5", authorId: Tom.id }], + insertIntoPosts: [{ title: "Post 5", authorId: Tom.id }], }) // Verify the post was inserted - const p = await db.query.post.findFirst({ + const p = await db.query.posts.findFirst({ where: { title: "Post 5" }, }) expect(p).toBeDefined() @@ -300,8 +300,8 @@ describe("resolver by sqlite", () => { it("should update post information correctly", async () => { const q = /* GraphQL */ ` - mutation updatePost($set: PostUpdateInput!, $where: PostFilters!) { - updatePost(set: $set, where: $where) { + mutation updatePosts($set: PostsUpdateInput!, $where: PostsFilters!) { + updatePosts(set: $set, where: $where) { id title } @@ -309,7 +309,7 @@ describe("resolver by sqlite", () => { ` const [PostU] = await db - .insert(post) + .insert(posts) .values({ title: "Post U" }) .returning() if (!PostU) throw new Error("Post not found") @@ -320,11 +320,11 @@ describe("resolver by sqlite", () => { where: { id: { eq: PostU.id } }, }) ).resolves.toMatchObject({ - updatePost: [{ id: PostU.id, title: "Updated Post U" }], + updatePosts: [{ id: PostU.id, title: "Updated Post U" }], }) // Verify the post was updated - const updatedPost = await db.query.post.findFirst({ + const updatedPost = await db.query.posts.findFirst({ where: { title: "Updated Post U" }, }) expect(updatedPost).toBeDefined() @@ -332,8 +332,8 @@ describe("resolver by sqlite", () => { it("should delete a post correctly", async () => { const q = /* GraphQL */ ` - mutation deleteFromPost($where: PostFilters!) { - deleteFromPost(where: $where) { + mutation deleteFromPosts($where: PostsFilters!) { + deleteFromPosts(where: $where) { id title } @@ -341,7 +341,7 @@ describe("resolver by sqlite", () => { ` const [PostD] = await db - .insert(post) + .insert(posts) .values({ title: "Post D" }) .returning() if (!PostD) throw new Error("Post not found") @@ -351,11 +351,11 @@ describe("resolver by sqlite", () => { where: { id: { eq: PostD.id } }, }) ).resolves.toMatchObject({ - deleteFromPost: [{ id: PostD.id, title: "Post D" }], + deleteFromPosts: [{ id: PostD.id, title: "Post D" }], }) // Verify the post was deleted - const deletedPost = await db.query.post.findFirst({ + const deletedPost = await db.query.posts.findFirst({ where: { id: PostD.id }, }) expect(deletedPost).toBeUndefined() diff --git a/packages/drizzle/test/schema/mysql-relations.ts b/packages/drizzle/test/schema/mysql-relations.ts index 56a587b3..77888663 100644 --- a/packages/drizzle/test/schema/mysql-relations.ts +++ b/packages/drizzle/test/schema/mysql-relations.ts @@ -2,13 +2,13 @@ import { defineRelations } from "drizzle-orm" import * as schema from "./mysql" export const relations = defineRelations(schema, (r) => ({ - user: { - posts: r.many.post(), + users: { + posts: r.many.posts(), }, - post: { - author: r.one.user({ - from: r.post.authorId, - to: r.user.id, + posts: { + author: r.one.users({ + from: r.posts.authorId, + to: r.users.id, }), }, })) diff --git a/packages/drizzle/test/schema/mysql.ts b/packages/drizzle/test/schema/mysql.ts index 8d9ebd7a..d14607bd 100644 --- a/packages/drizzle/test/schema/mysql.ts +++ b/packages/drizzle/test/schema/mysql.ts @@ -1,8 +1,8 @@ import * as t from "drizzle-orm/mysql-core" import { drizzleSilk } from "../../src" -export const user = drizzleSilk( - t.mysqlTable("user", { +export const users = drizzleSilk( + t.mysqlTable("users", { id: t.int().primaryKey().autoincrement(), name: t.text().notNull(), age: t.int(), @@ -10,11 +10,11 @@ export const user = drizzleSilk( }) ) -export const post = drizzleSilk( - t.mysqlTable("post", { +export const posts = drizzleSilk( + t.mysqlTable("posts", { id: t.int().primaryKey().autoincrement(), title: t.text().notNull(), content: t.text(), - authorId: t.int().references(() => user.id, { onDelete: "cascade" }), + authorId: t.int().references(() => users.id, { onDelete: "cascade" }), }) ) diff --git a/packages/drizzle/test/schema/postgres-relations.ts b/packages/drizzle/test/schema/postgres-relations.ts index 90256a88..16383569 100644 --- a/packages/drizzle/test/schema/postgres-relations.ts +++ b/packages/drizzle/test/schema/postgres-relations.ts @@ -2,13 +2,13 @@ import { defineRelations } from "drizzle-orm" import * as schema from "./postgres" export const relations = defineRelations(schema, (r) => ({ - user: { - posts: r.many.post(), + users: { + posts: r.many.posts(), }, - post: { - author: r.one.user({ - from: r.post.authorId, - to: r.user.id, + posts: { + author: r.one.users({ + from: r.posts.authorId, + to: r.users.id, }), }, })) diff --git a/packages/drizzle/test/schema/postgres.ts b/packages/drizzle/test/schema/postgres.ts index b97f2cca..df00a2ae 100644 --- a/packages/drizzle/test/schema/postgres.ts +++ b/packages/drizzle/test/schema/postgres.ts @@ -1,8 +1,8 @@ import * as t from "drizzle-orm/pg-core" import { drizzleSilk } from "../../src" -export const user = drizzleSilk( - t.pgTable("user", { +export const users = drizzleSilk( + t.pgTable("users", { id: t.serial().primaryKey(), name: t.text().notNull(), age: t.integer(), @@ -10,11 +10,11 @@ export const user = drizzleSilk( }) ) -export const post = drizzleSilk( - t.pgTable("post", { +export const posts = drizzleSilk( + t.pgTable("posts", { id: t.serial().primaryKey(), title: t.text().notNull(), content: t.text(), - authorId: t.integer().references(() => user.id, { onDelete: "cascade" }), + authorId: t.integer().references(() => users.id, { onDelete: "cascade" }), }) ) diff --git a/packages/drizzle/test/schema/sqlite-relations.ts b/packages/drizzle/test/schema/sqlite-relations.ts index 6f37a7df..651c40b9 100644 --- a/packages/drizzle/test/schema/sqlite-relations.ts +++ b/packages/drizzle/test/schema/sqlite-relations.ts @@ -2,31 +2,31 @@ import { defineRelations } from "drizzle-orm" import * as schema from "./sqlite" export const relations = defineRelations(schema, (r) => ({ - user: { - posts: r.many.post(), - courses: r.many.studentToCourse(), + users: { + posts: r.many.posts(), + courses: r.many.studentToCourses(), }, - post: { - author: r.one.user({ - from: r.post.authorId, - to: r.user.id, + posts: { + author: r.one.users({ + from: r.posts.authorId, + to: r.users.id, }), }, - course: { - students: r.many.studentToCourse(), + courses: { + students: r.many.studentToCourses(), }, - studentToCourse: { - student: r.one.user({ - from: r.studentToCourse.studentId, - to: r.user.id, + studentToCourses: { + student: r.one.users({ + from: r.studentToCourses.studentId, + to: r.users.id, }), - course: r.one.course({ - from: r.studentToCourse.courseId, - to: r.course.id, + course: r.one.courses({ + from: r.studentToCourses.courseId, + to: r.courses.id, }), - grade: r.one.studentCourseGrade({ - from: [r.studentToCourse.studentId, r.studentToCourse.courseId], - to: [r.studentCourseGrade.studentId, r.studentCourseGrade.courseId], + grade: r.one.studentCourseGrades({ + from: [r.studentToCourses.studentId, r.studentToCourses.courseId], + to: [r.studentCourseGrades.studentId, r.studentCourseGrades.courseId], }), }, })) diff --git a/packages/drizzle/test/schema/sqlite.ts b/packages/drizzle/test/schema/sqlite.ts index f81f5615..86188f6b 100644 --- a/packages/drizzle/test/schema/sqlite.ts +++ b/packages/drizzle/test/schema/sqlite.ts @@ -2,8 +2,8 @@ import { sql } from "drizzle-orm" import * as t from "drizzle-orm/sqlite-core" import { drizzleSilk } from "../../src" -export const user = drizzleSilk( - t.sqliteTable("user", { +export const users = drizzleSilk( + t.sqliteTable("users", { id: t.int().primaryKey({ autoIncrement: true }), name: t.text().notNull(), age: t.int(), @@ -11,34 +11,34 @@ export const user = drizzleSilk( }) ) -export const post = drizzleSilk( - t.sqliteTable("post", { +export const posts = drizzleSilk( + t.sqliteTable("posts", { id: t.int().primaryKey({ autoIncrement: true }), title: t.text().notNull(), content: t.text(), - authorId: t.int().references(() => user.id, { onDelete: "cascade" }), + authorId: t.int().references(() => users.id, { onDelete: "cascade" }), }) ) -export const course = drizzleSilk( - t.sqliteTable("course", { +export const courses = drizzleSilk( + t.sqliteTable("courses", { id: t.int().primaryKey({ autoIncrement: true }), name: t.text().notNull(), }) ) -export const studentToCourse = drizzleSilk( - t.sqliteTable("studentToCourse", { - studentId: t.int().references(() => user.id), - courseId: t.int().references(() => course.id), +export const studentToCourses = drizzleSilk( + t.sqliteTable("studentToCourses", { + studentId: t.int().references(() => users.id), + courseId: t.int().references(() => courses.id), createdAt: t.int({ mode: "timestamp" }).default(sql`(CURRENT_TIMESTAMP)`), }) ) -export const studentCourseGrade = drizzleSilk( - t.sqliteTable("studentCourseGrade", { - studentId: t.int().references(() => user.id), - courseId: t.int().references(() => course.id), +export const studentCourseGrades = drizzleSilk( + t.sqliteTable("studentCourseGrades", { + studentId: t.int().references(() => users.id), + courseId: t.int().references(() => courses.id), grade: t.int(), }) ) From bb824b283a9606ca8f451461c53da985f143336b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E2=9B=B5?= <809067559@qq.com> Date: Sun, 6 Apr 2025 23:20:22 +0800 Subject: [PATCH 04/54] refactor(drizzle): enhance filter extraction in DrizzleResolverFactory - Updated the `where` clause in select arguments to utilize a RAW function for improved filter extraction. - Modified `extractFilters` and `extractFiltersColumn` methods to accept an optional `table` parameter, allowing for more flexible filter handling. - Ensured that operators are applied to the correct table columns, enhancing the overall query construction process. --- packages/drizzle/src/factory/resolver.ts | 37 +++++++++++++++++------- 1 file changed, 26 insertions(+), 11 deletions(-) diff --git a/packages/drizzle/src/factory/resolver.ts b/packages/drizzle/src/factory/resolver.ts index 608cc6f9..5632ac47 100644 --- a/packages/drizzle/src/factory/resolver.ts +++ b/packages/drizzle/src/factory/resolver.ts @@ -123,7 +123,9 @@ export abstract class DrizzleResolverFactory< () => this.inputFactory.selectArrayArgs(), (args) => ({ value: { - where: args.where, + where: { + RAW: (table: Table) => this.extractFilters(args.where, table), + }, orderBy: args.orderBy, limit: args.limit, offset: args.offset, @@ -155,7 +157,9 @@ export abstract class DrizzleResolverFactory< () => this.inputFactory.selectSingleArgs(), (args) => ({ value: { - where: args.where, + where: { + RAW: (table: Table) => this.extractFilters(args.where, table), + }, orderBy: args.orderBy, offset: args.offset, }, @@ -172,7 +176,8 @@ export abstract class DrizzleResolverFactory< } protected extractFilters( - filters: SelectArrayArgs["where"] + filters: SelectArrayArgs["where"], + table?: any ): SQL | undefined { if (filters == null) return const tableName = getTableName(this.table) @@ -191,7 +196,7 @@ export abstract class DrizzleResolverFactory< const variants = [] as SQL[] for (const variant of filters.OR) { - const extracted = this.extractFilters(variant) + const extracted = this.extractFilters(variant, table) if (extracted) variants.push(extracted) } @@ -203,7 +208,9 @@ export abstract class DrizzleResolverFactory< if (operators == null) continue const column = getTableColumns(this.table)[columnName]! - variants.push(this.extractFiltersColumn(column, columnName, operators)!) + variants.push( + this.extractFiltersColumn(column, columnName, operators, table)! + ) } return and(...variants) @@ -212,7 +219,8 @@ export abstract class DrizzleResolverFactory< protected extractFiltersColumn( column: TColumn, columnName: string, - operators: ColumnFilters + operators: ColumnFilters, + table?: any ): SQL | undefined { if (!operators.OR?.length) delete operators.OR @@ -228,7 +236,12 @@ export abstract class DrizzleResolverFactory< const variants = [] as SQL[] for (const variant of operators.OR) { - const extracted = this.extractFiltersColumn(column, columnName, variant) + const extracted = this.extractFiltersColumn( + column, + columnName, + variant, + table + ) if (extracted) variants.push(extracted) } @@ -242,25 +255,27 @@ export abstract class DrizzleResolverFactory< const arrayOperators = { in: inArray, notIn: notInArray } const nullOperators = { isNull, isNotNull } + const tableColumn = table ? table[columnName] : column + for (const [operatorName, operatorValue] of entries) { if (operatorValue === null || operatorValue === false) continue if (operatorName in binaryOperators) { const operator = binaryOperators[operatorName as keyof typeof binaryOperators] - variants.push(operator(column, operatorValue)) + variants.push(operator(tableColumn, operatorValue)) } else if (operatorName in textOperators) { const operator = textOperators[operatorName as keyof typeof textOperators] - variants.push(operator(column, operatorValue)) + variants.push(operator(tableColumn, operatorValue)) } else if (operatorName in arrayOperators) { const operator = arrayOperators[operatorName as keyof typeof arrayOperators] - variants.push(operator(column, operatorValue)) + variants.push(operator(tableColumn, operatorValue)) } else if (operatorName in nullOperators) { const operator = nullOperators[operatorName as keyof typeof nullOperators] - if (operatorValue === true) variants.push(operator(column)) + if (operatorValue === true) variants.push(operator(tableColumn)) } } From ffba6c63b93075e608997e31a630bf31292ccb17 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E2=9B=B5?= <809067559@qq.com> Date: Sun, 6 Apr 2025 23:35:36 +0800 Subject: [PATCH 05/54] refactor(drizzle): update filter structure in DrizzleInputFactory - Renamed filter types from `FiltersOr` to `FiltersNested` for improved clarity. - Introduced `AND` and `NOT` fields in filter definitions to enhance query capabilities. - Updated related test cases and GraphQL schemas to reflect the new filter structure across MySQL, PostgreSQL, and SQLite. --- packages/drizzle/src/factory/input.ts | 22 ++++++++++++------- packages/drizzle/test/input-factory.spec.ts | 4 +++- packages/drizzle/test/resolver-mysql.spec.gql | 12 ++++++---- .../drizzle/test/resolver-postgres.spec.gql | 12 ++++++---- .../drizzle/test/resolver-sqlite.spec.gql | 12 ++++++---- 5 files changed, 41 insertions(+), 21 deletions(-) diff --git a/packages/drizzle/src/factory/input.ts b/packages/drizzle/src/factory/input.ts index c74ac14e..a999e1fe 100644 --- a/packages/drizzle/src/factory/input.ts +++ b/packages/drizzle/src/factory/input.ts @@ -181,8 +181,8 @@ export class DrizzleInputFactory { type: DrizzleInputFactory.columnFilters(column), })) - const filtersOr = new GraphQLObjectType({ - name: `${pascalCase(getTableName(this.table))}FiltersOr`, + const filtersNested = new GraphQLObjectType({ + name: `${pascalCase(getTableName(this.table))}FiltersNested`, fields: { ...filterFields }, }) return weaverContext.memoNamedType( @@ -190,7 +190,9 @@ export class DrizzleInputFactory { name, fields: { ...filterFields, - OR: { type: new GraphQLList(new GraphQLNonNull(filtersOr)) }, + OR: { type: new GraphQLList(new GraphQLNonNull(filtersNested)) }, + AND: { type: new GraphQLList(new GraphQLNonNull(filtersNested)) }, + NOT: { type: filtersNested }, }, }) ) @@ -325,27 +327,31 @@ export type FiltersCore = Partial<{ export type Filters = FiltersCore & { OR?: FiltersCore[] + AND?: FiltersCore[] + NOT?: FiltersCore } export interface ColumnFiltersCore { eq?: TType ne?: TType - lt?: TType - lte?: TType gt?: TType gte?: TType + lt?: TType + lte?: TType + in?: TType[] + notIn?: TType[] like?: TType extends string ? string : never - notLike?: TType extends string ? string : never ilike?: TType extends string ? string : never + notLike?: TType extends string ? string : never notIlike?: TType extends string ? string : never - in?: TType[] - notIn?: TType[] isNull?: boolean isNotNull?: boolean } export interface ColumnFilters extends ColumnFiltersCore { OR?: ColumnFiltersCore[] + AND?: ColumnFiltersCore[] + NOT?: ColumnFiltersCore } export interface MutationResult { diff --git a/packages/drizzle/test/input-factory.spec.ts b/packages/drizzle/test/input-factory.spec.ts index fb4a4e0c..48c5779e 100644 --- a/packages/drizzle/test/input-factory.spec.ts +++ b/packages/drizzle/test/input-factory.spec.ts @@ -40,7 +40,9 @@ describe("DrizzleInputFactory", () => { id: PgSerialFilters name: PgTextFilters email: PgTextFilters - OR: [UsersFiltersOr!] + OR: [UsersFiltersNested!] + AND: [UsersFiltersNested!] + NOT: UsersFiltersNested }" `) }) diff --git a/packages/drizzle/test/resolver-mysql.spec.gql b/packages/drizzle/test/resolver-mysql.spec.gql index 256eaac2..11e76ffa 100644 --- a/packages/drizzle/test/resolver-mysql.spec.gql +++ b/packages/drizzle/test/resolver-mysql.spec.gql @@ -81,14 +81,16 @@ enum OrderDirection { } input PostsFilters { - OR: [PostsFiltersOr!] + AND: [PostsFiltersNested!] + NOT: PostsFiltersNested + OR: [PostsFiltersNested!] authorId: MySqlIntFilters content: MySqlTextFilters id: MySqlIntFilters title: MySqlTextFilters } -input PostsFiltersOr { +input PostsFiltersNested { authorId: MySqlIntFilters content: MySqlTextFilters id: MySqlIntFilters @@ -132,14 +134,16 @@ type Query { } input UsersFilters { - OR: [UsersFiltersOr!] + AND: [UsersFiltersNested!] + NOT: UsersFiltersNested + OR: [UsersFiltersNested!] age: MySqlIntFilters email: MySqlTextFilters id: MySqlIntFilters name: MySqlTextFilters } -input UsersFiltersOr { +input UsersFiltersNested { age: MySqlIntFilters email: MySqlTextFilters id: MySqlIntFilters diff --git a/packages/drizzle/test/resolver-postgres.spec.gql b/packages/drizzle/test/resolver-postgres.spec.gql index 13e9ac0b..58144615 100644 --- a/packages/drizzle/test/resolver-postgres.spec.gql +++ b/packages/drizzle/test/resolver-postgres.spec.gql @@ -104,14 +104,16 @@ input PgTextFiltersOr { } input PostsFilters { - OR: [PostsFiltersOr!] + AND: [PostsFiltersNested!] + NOT: PostsFiltersNested + OR: [PostsFiltersNested!] authorId: PgIntegerFilters content: PgTextFilters id: PgSerialFilters title: PgTextFilters } -input PostsFiltersOr { +input PostsFiltersNested { authorId: PgIntegerFilters content: PgTextFilters id: PgSerialFilters @@ -155,14 +157,16 @@ type Query { } input UsersFilters { - OR: [UsersFiltersOr!] + AND: [UsersFiltersNested!] + NOT: UsersFiltersNested + OR: [UsersFiltersNested!] age: PgIntegerFilters email: PgTextFilters id: PgSerialFilters name: PgTextFilters } -input UsersFiltersOr { +input UsersFiltersNested { age: PgIntegerFilters email: PgTextFilters id: PgSerialFilters diff --git a/packages/drizzle/test/resolver-sqlite.spec.gql b/packages/drizzle/test/resolver-sqlite.spec.gql index 1936f95d..98ef5fbb 100644 --- a/packages/drizzle/test/resolver-sqlite.spec.gql +++ b/packages/drizzle/test/resolver-sqlite.spec.gql @@ -15,14 +15,16 @@ enum OrderDirection { } input PostsFilters { - OR: [PostsFiltersOr!] + AND: [PostsFiltersNested!] + NOT: PostsFiltersNested + OR: [PostsFiltersNested!] authorId: SQLiteIntegerFilters content: SQLiteTextFilters id: SQLiteIntegerFilters title: SQLiteTextFilters } -input PostsFiltersOr { +input PostsFiltersNested { authorId: SQLiteIntegerFilters content: SQLiteTextFilters id: SQLiteIntegerFilters @@ -134,14 +136,16 @@ type StudentToCoursesItem { } input UsersFilters { - OR: [UsersFiltersOr!] + AND: [UsersFiltersNested!] + NOT: UsersFiltersNested + OR: [UsersFiltersNested!] age: SQLiteIntegerFilters email: SQLiteTextFilters id: SQLiteIntegerFilters name: SQLiteTextFilters } -input UsersFiltersOr { +input UsersFiltersNested { age: SQLiteIntegerFilters email: SQLiteTextFilters id: SQLiteIntegerFilters From 38e9eaaf84e3fd651476b63c98405330de96e29c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E2=9B=B5?= <809067559@qq.com> Date: Mon, 7 Apr 2025 00:13:11 +0800 Subject: [PATCH 06/54] refactor(drizzle): enhance filter capabilities in DrizzleResolverFactory - Introduced support for `AND`, `OR`, and `NOT` operators in filter processing to improve query flexibility. - Updated the `extractFilters` method to handle new logical operators and ensure correct extraction of conditions. - Added corresponding test cases to validate the functionality of the new operators in various query scenarios. --- packages/drizzle/src/factory/resolver.ts | 59 ++++++++++++------- .../drizzle/test/resolver-factory.spec.ts | 43 +++++++++++++- 2 files changed, 80 insertions(+), 22 deletions(-) diff --git a/packages/drizzle/src/factory/resolver.ts b/packages/drizzle/src/factory/resolver.ts index 5632ac47..cde97e31 100644 --- a/packages/drizzle/src/factory/resolver.ts +++ b/packages/drizzle/src/factory/resolver.ts @@ -36,6 +36,7 @@ import { lt, lte, ne, + not, notIlike, notInArray, notLike, @@ -180,37 +181,53 @@ export abstract class DrizzleResolverFactory< table?: any ): SQL | undefined { if (filters == null) return - const tableName = getTableName(this.table) - - if (!filters.OR?.length) delete filters.OR const entries = Object.entries(filters as FiltersCore) + const variants: (SQL | undefined)[] = [] - if (filters.OR) { - if (entries.length > 1) { - throw new GraphQLError( - `WHERE ${tableName}: Cannot specify both fields and 'OR' in table filters!` - ) - } - - const variants = [] as SQL[] + for (const [columnName, operators] of entries) { + if (operators == null) continue - for (const variant of filters.OR) { - const extracted = this.extractFilters(variant, table) - if (extracted) variants.push(extracted) + if (columnName === "OR" && Array.isArray(operators)) { + const orConditions: SQL[] = [] + for (const variant of operators) { + const extracted = this.extractFilters(variant, table) + if (extracted) orConditions.push(extracted) + } + if (orConditions.length > 0) { + variants.push(or(...orConditions)) + } + continue } - return or(...variants) - } + if (columnName === "AND" && Array.isArray(operators)) { + const andConditions: SQL[] = [] + for (const variant of operators) { + const extracted = this.extractFilters(variant, table) + if (extracted) andConditions.push(extracted) + } + if (andConditions.length > 0) { + variants.push(and(...andConditions)) + } + continue + } - const variants: SQL[] = [] - for (const [columnName, operators] of entries) { - if (operators == null) continue + if (columnName === "NOT" && operators) { + const extracted = this.extractFilters(operators as any, table) + if (extracted) { + variants.push(not(extracted)) + } + continue + } const column = getTableColumns(this.table)[columnName]! - variants.push( - this.extractFiltersColumn(column, columnName, operators, table)! + const extractedColumn = this.extractFiltersColumn( + column, + columnName, + operators, + table ) + if (extractedColumn) variants.push(extractedColumn) } return and(...variants) diff --git a/packages/drizzle/test/resolver-factory.spec.ts b/packages/drizzle/test/resolver-factory.spec.ts index cc388313..32d3f754 100644 --- a/packages/drizzle/test/resolver-factory.spec.ts +++ b/packages/drizzle/test/resolver-factory.spec.ts @@ -94,7 +94,6 @@ describe.concurrent("DrizzleResolverFactory", () => { }) describe.concurrent("selectArrayQuery", () => { - // db.query.users.findMany({ orderBy: () => [] }) it("should be created without error", async () => { const query = userFactory.selectArrayQuery() expect(query).toBeDefined() @@ -254,6 +253,48 @@ describe.concurrent("DrizzleResolverFactory", () => { await query["~meta"].resolve({}) expect(count).toBe(2) }) + + it("should work with AND operators", async () => { + const query = userFactory.selectArrayQuery() + let answer + answer = await query["~meta"].resolve({ + where: { AND: [{ name: { eq: "John" } }, { age: { gt: 10 } }] }, + }) + expect(answer).toHaveLength(0) + + answer = await query["~meta"].resolve({ + where: { AND: [{ name: { eq: "John" } }, { age: { gte: 10 } }] }, + }) + expect(answer).toHaveLength(1) + }) + + it("should work with OR operators", async () => { + const query = userFactory.selectArrayQuery() + let answer + answer = await query["~meta"].resolve({ + where: { OR: [{ name: { eq: "John" } }, { age: { gt: 12 } }] }, + }) + expect(answer).toHaveLength(3) + + answer = await query["~meta"].resolve({ + where: { OR: [{ age: { gte: 14 } }, { age: { lte: 10 } }] }, + }) + expect(answer).toHaveLength(2) + }) + + it("should work with NOT operators", async () => { + const query = userFactory.selectArrayQuery() + let answer + answer = await query["~meta"].resolve({ + where: { NOT: { name: { eq: "John" } } }, + }) + expect(answer).toHaveLength(4) + + answer = await query["~meta"].resolve({ + where: { NOT: { age: { lte: 10 } } }, + }) + expect(answer).toHaveLength(4) + }) }) describe.concurrent("selectSingleQuery", () => { From a3fd243c7c4488be0960112fb7ab806e788bde71 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E2=9B=B5?= <809067559@qq.com> Date: Mon, 7 Apr 2025 00:15:51 +0800 Subject: [PATCH 07/54] refactor(drizzle): remove OR operator handling from DrizzleResolverFactory - Eliminated the handling of the `OR` operator in the `extractFiltersColumn` method to streamline filter processing. - Updated related test cases to reflect the removal of `OR` operator support, ensuring consistency in query behavior. --- packages/drizzle/src/factory/resolver.ts | 24 ------------------- .../drizzle/test/resolver-factory.spec.ts | 5 ---- 2 files changed, 29 deletions(-) diff --git a/packages/drizzle/src/factory/resolver.ts b/packages/drizzle/src/factory/resolver.ts index cde97e31..7289699f 100644 --- a/packages/drizzle/src/factory/resolver.ts +++ b/packages/drizzle/src/factory/resolver.ts @@ -42,7 +42,6 @@ import { notLike, or, } from "drizzle-orm" -import { GraphQLError } from "graphql" import { DrizzleWeaver, type TableSilk } from ".." import { inArrayMultiple } from "../helper" import { @@ -243,29 +242,6 @@ export abstract class DrizzleResolverFactory< const entries = Object.entries(operators) - if (operators.OR) { - if (entries.length > 1) { - throw new GraphQLError( - `WHERE ${columnName}: Cannot specify both fields and 'OR' in column operators!` - ) - } - - const variants = [] as SQL[] - - for (const variant of operators.OR) { - const extracted = this.extractFiltersColumn( - column, - columnName, - variant, - table - ) - - if (extracted) variants.push(extracted) - } - - return or(...variants) - } - const variants: SQL[] = [] const binaryOperators = { eq, ne, gt, gte, lt, lte } const textOperators = { like, notLike, ilike, notIlike } diff --git a/packages/drizzle/test/resolver-factory.spec.ts b/packages/drizzle/test/resolver-factory.spec.ts index 32d3f754..b90de6c4 100644 --- a/packages/drizzle/test/resolver-factory.spec.ts +++ b/packages/drizzle/test/resolver-factory.spec.ts @@ -154,11 +154,6 @@ describe.concurrent("DrizzleResolverFactory", () => { new Set([{ age: 12 }, { age: 13 }, { age: 14 }]) ) - answer = await query["~meta"].resolve({ - where: { age: { OR: [{ eq: 10 }, { eq: 11 }] } }, - }) - expect(new Set(answer)).toMatchObject(new Set([{ age: 10 }, { age: 11 }])) - answer = await query["~meta"].resolve({ where: { OR: [{ age: { eq: 10 } }, { age: { eq: 11 } }] }, }) From faee3382b6af5c246aa1c1a8f49a07f241b3b9fd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E2=9B=B5?= <809067559@qq.com> Date: Mon, 7 Apr 2025 00:41:04 +0800 Subject: [PATCH 08/54] refactor(drizzle): enhance logical operator support in DrizzleResolverFactory - Improved handling of `AND`, `OR`, and `NOT` operators in the `extractFilters` method to allow for more complex query conditions. - Updated the `extractFiltersColumn` method to accommodate new logical operator processing. - Added comprehensive test cases to validate the functionality of the enhanced logical operators in various query scenarios. --- packages/drizzle/src/factory/resolver.ts | 52 ++- .../drizzle/test/resolver-factory.spec.ts | 315 ++++++++++++++++++ 2 files changed, 363 insertions(+), 4 deletions(-) diff --git a/packages/drizzle/src/factory/resolver.ts b/packages/drizzle/src/factory/resolver.ts index 7289699f..683e913e 100644 --- a/packages/drizzle/src/factory/resolver.ts +++ b/packages/drizzle/src/factory/resolver.ts @@ -212,7 +212,7 @@ export abstract class DrizzleResolverFactory< } if (columnName === "NOT" && operators) { - const extracted = this.extractFilters(operators as any, table) + const extracted = this.extractFilters(operators, table) if (extracted) { variants.push(not(extracted)) } @@ -238,11 +238,9 @@ export abstract class DrizzleResolverFactory< operators: ColumnFilters, table?: any ): SQL | undefined { - if (!operators.OR?.length) delete operators.OR - const entries = Object.entries(operators) - const variants: SQL[] = [] + const variants: (SQL | undefined)[] = [] const binaryOperators = { eq, ne, gt, gte, lt, lte } const textOperators = { like, notLike, ilike, notIlike } const arrayOperators = { in: inArray, notIn: notInArray } @@ -250,6 +248,52 @@ export abstract class DrizzleResolverFactory< const tableColumn = table ? table[columnName] : column + if (operators.OR) { + const orVariants = [] as SQL[] + + for (const variant of operators.OR) { + const extracted = this.extractFiltersColumn( + column, + columnName, + variant, + table + ) + + if (extracted) orVariants.push(extracted) + } + + variants.push(or(...orVariants)) + } + + if (operators.AND) { + const andVariants = [] as SQL[] + + for (const variant of operators.AND) { + const extracted = this.extractFiltersColumn( + column, + columnName, + variant, + table + ) + + if (extracted) andVariants.push(extracted) + } + + variants.push(and(...andVariants)) + } + + if (operators.NOT) { + const extracted = this.extractFiltersColumn( + column, + columnName, + operators.NOT, + table + ) + if (extracted) { + variants.push(not(extracted)) + } + } + for (const [operatorName, operatorValue] of entries) { if (operatorValue === null || operatorValue === false) continue diff --git a/packages/drizzle/test/resolver-factory.spec.ts b/packages/drizzle/test/resolver-factory.spec.ts index b90de6c4..e91bf9c9 100644 --- a/packages/drizzle/test/resolver-factory.spec.ts +++ b/packages/drizzle/test/resolver-factory.spec.ts @@ -154,6 +154,11 @@ describe.concurrent("DrizzleResolverFactory", () => { new Set([{ age: 12 }, { age: 13 }, { age: 14 }]) ) + answer = await query["~meta"].resolve({ + where: { age: { OR: [{ eq: 10 }, { eq: 11 }] } }, + }) + expect(new Set(answer)).toMatchObject(new Set([{ age: 10 }, { age: 11 }])) + answer = await query["~meta"].resolve({ where: { OR: [{ age: { eq: 10 } }, { age: { eq: 11 } }] }, }) @@ -290,6 +295,78 @@ describe.concurrent("DrizzleResolverFactory", () => { }) expect(answer).toHaveLength(4) }) + + it("should work with complex NOT conditions", async () => { + const query = userFactory.selectArrayQuery() + let answer + + // Test NOT with OR condition + answer = await query["~meta"].resolve({ + where: { + NOT: { + OR: [{ name: { eq: "John" } }, { name: { eq: "Jane" } }], + } as any, + }, + }) + expect(answer).toHaveLength(3) // Should exclude both John and Jane + + // Test NOT with AND condition + answer = await query["~meta"].resolve({ + where: { + NOT: { + AND: [{ age: { gte: 10 } }, { age: { lte: 12 } }], + } as any, + }, + }) + // Should exclude ages 10, 11, 12 + expect(answer.map((user) => user.age).sort()).toEqual([13, 14]) + + // Test nested NOT conditions + answer = await query["~meta"].resolve({ + where: { + age: { + NOT: { + lte: 12, + }, + }, + }, + }) + // Double negation: NOT(NOT(age > 12)) = age > 12 + expect(answer.map((user) => user.age).sort()).toEqual([13, 14]) + }) + + it("should work with column-level NOT operator", async () => { + const query = userFactory.selectArrayQuery() + + // Test NOT applied to a column filter + const answer = await query["~meta"].resolve({ + where: { + age: { + NOT: { lte: 12 } as any, + }, + }, + }) + + // Should only include ages > 12 + expect(answer.map((user) => (user as any).age).sort()).toEqual([13, 14]) + }) + + it("should work with column-level operators (OR, AND)", async () => { + const query = userFactory.selectArrayQuery() + let answer + + // Test column-level OR operator + answer = await query["~meta"].resolve({ + where: { age: { OR: [{ eq: 10 }, { eq: 11 }] } }, + }) + expect(new Set(answer)).toMatchObject(new Set([{ age: 10 }, { age: 11 }])) + + // Test column-level AND operator + answer = await query["~meta"].resolve({ + where: { age: { AND: [{ gte: 10 }, { lte: 11 }] } }, + }) + expect(new Set(answer)).toMatchObject(new Set([{ age: 10 }, { age: 11 }])) + }) }) describe.concurrent("selectSingleQuery", () => { @@ -457,6 +534,244 @@ describe.concurrent("DrizzleResolverFactory", () => { }) }) + describe("relationField with multiple field relations", () => { + afterAll(async () => { + await db.delete(sqliteSchemas.studentCourseGrades) + await db.delete(sqliteSchemas.studentToCourses) + await db.delete(sqliteSchemas.courses) + }) + + it("should handle multi-field relations correctly", async () => { + // This test specifically targets the multi-field relation handling in relationField + const studentCourseFactory = drizzleResolverFactory( + db, + "studentToCourses" + ) + + // Setup test data + const John = await db.query.users.findFirst({ + where: { name: "John" }, + }) + if (!John) throw new Error("John not found") + + const [math, english] = await db + .insert(sqliteSchemas.courses) + .values([{ name: "Math" }, { name: "English" }]) + .returning() + + // Insert multiple student-course relationships for the same student + const studentCourses = await db + .insert(sqliteSchemas.studentToCourses) + .values([ + { studentId: John.id, courseId: math.id }, + { studentId: John.id, courseId: english.id }, + ]) + .returning() + + // Test loading multiple relations at once + const courseField = studentCourseFactory.relationField("course") + const results = await Promise.all( + studentCourses.map((sc) => courseField["~meta"].resolve(sc, undefined)) + ) + + expect(results).toMatchObject([ + { id: math.id, name: "Math" }, + { id: english.id, name: "English" }, + ]) + + // Test with batch loading multiple parents + const studentField = studentCourseFactory.relationField("student") + const studentResults = await Promise.all( + studentCourses.map((sc) => studentField["~meta"].resolve(sc, undefined)) + ) + + expect(studentResults).toMatchObject([ + { id: John.id, name: "John" }, + { id: John.id, name: "John" }, + ]) + }) + + it("should handle loading relation data correctly when using multiple fields", async () => { + const studentCourseFactory = drizzleResolverFactory( + db, + "studentToCourses" + ) + + // Setup test data for multiple students + const John = await db.query.users.findFirst({ + where: { name: "John" }, + }) + if (!John) throw new Error("John not found") + + const Joe = await db.query.users.findFirst({ + where: { name: "Joe" }, + }) + if (!Joe) throw new Error("Joe not found") + + const [math, english] = await db + .insert(sqliteSchemas.courses) + .values([{ name: "Math" }, { name: "English" }]) + .returning() + + // Insert relationships for multiple students + const studentCourses = await db + .insert(sqliteSchemas.studentToCourses) + .values([ + { studentId: John.id, courseId: math.id }, + { studentId: John.id, courseId: english.id }, + { studentId: Joe.id, courseId: math.id }, + { studentId: Joe.id, courseId: english.id }, + ]) + .returning() + + // Use the loader to fetch multiple course relations at once + const courseField = studentCourseFactory.relationField("course") + + // Load all courses for all student-course relationships at once + const allResults = await Promise.all( + studentCourses.map((sc) => courseField["~meta"].resolve(sc, undefined)) + ) + + // Verify results include both Math and English courses + expect(allResults.map((course) => (course as any).name).sort()).toEqual([ + "English", + "English", + "Math", + "Math", + ]) + + // Test the student relationship in same batch + const studentField = studentCourseFactory.relationField("student") + const studentResults = await Promise.all( + studentCourses.map((sc) => studentField["~meta"].resolve(sc, undefined)) + ) + + // Verify results show both John and Joe + const studentNames = studentResults + .map((student) => (student as any).name) + .sort() + expect(studentNames).toEqual(["Joe", "Joe", "John", "John"]) + }) + + it("should handle composite key relations correctly", async () => { + // Create a mock implementation to test the multi-field relation handling directly + + // First, we'll manually mock the relation data to test the specific code paths + const mockRelation = { + sourceColumns: [{ name: "studentId" }, { name: "courseId" }], + targetColumns: [{ name: "studentId" }, { name: "courseId" }], + } + + // Manual test for getKeyByField with multiple fields + const getKeyByField = (parent: any) => { + const fieldsLength = mockRelation.sourceColumns.length + if (fieldsLength === 1) { + return parent[mockRelation.sourceColumns[0].name] + } + return mockRelation.sourceColumns + .map((field) => parent[field.name]) + .join("-") + } + + // Test with composite keys + const parentWithCompositeKey = { studentId: 1, courseId: 2 } + expect(getKeyByField(parentWithCompositeKey)).toBe("1-2") + + // Manual test for getKeyByReference with multiple fields + const getKeyByReference = (item: any) => { + const fieldsLength = mockRelation.targetColumns.length + if (fieldsLength === 1) { + return item[mockRelation.targetColumns[0].name] + } + return mockRelation.targetColumns + .map((reference) => item[reference.name]) + .join("-") + } + + // Test with composite keys for reference + const itemWithCompositeKey = { studentId: 1, courseId: 2, grade: 95 } + expect(getKeyByReference(itemWithCompositeKey)).toBe("1-2") + + // Now, create and test real data to verify the full flow + const studentCourseFactory = drizzleResolverFactory( + db, + "studentToCourses" + ) + + // Setup test data for a composite key scenario + // First create some test data + const John = await db.query.users.findFirst({ + where: { name: "John" }, + }) + if (!John) throw new Error("John not found") + + const Jane = await db.query.users.findFirst({ + where: { name: "Jane" }, + }) + if (!Jane) throw new Error("Jane not found") + + // Insert courses if needed + const [math, english] = await db + .insert(sqliteSchemas.courses) + .values([{ name: "Math" }, { name: "English" }]) + .returning() + + // Create student-to-course mappings with composite keys + await db + .insert(sqliteSchemas.studentToCourses) + .values([ + { studentId: John.id, courseId: math.id }, + { studentId: John.id, courseId: english.id }, + { studentId: Jane.id, courseId: math.id }, + ]) + .returning() + + // Insert grades using the composite keys + await db.insert(sqliteSchemas.studentCourseGrades).values([ + { studentId: John.id, courseId: math.id, grade: 90 }, + { studentId: John.id, courseId: english.id, grade: 85 }, + { studentId: Jane.id, courseId: math.id, grade: 95 }, + ]) + + // Now test loading data with composite keys + const gradeField = studentCourseFactory.relationField("grade") + + // Load grades for all student-course pairs + const studentCourses = await db.query.studentToCourses.findMany({ + where: { + OR: [ + { studentId: { eq: John.id }, courseId: { eq: math.id } }, + { studentId: { eq: John.id }, courseId: { eq: english.id } }, + { studentId: { eq: Jane.id }, courseId: { eq: math.id } }, + ], + }, + }) + + expect(studentCourses.length).toBe(3) + + const grades = await Promise.all( + studentCourses.map((sc) => gradeField["~meta"].resolve(sc, undefined)) + ) + + // Verify we got all the grades back + expect(grades.length).toBe(3) + + // Check that each student-course pair got the correct grade + const gradeMap = new Map() + grades.forEach((g: any) => { + if (g) { + const key = `${g.studentId}-${g.courseId}` + gradeMap.set(key, g.grade) + } + }) + + expect(gradeMap.size).toBe(3) + expect(gradeMap.has(`${John.id}-${math.id}`)).toBe(true) + expect(gradeMap.has(`${John.id}-${english.id}`)).toBe(true) + expect(gradeMap.has(`${Jane.id}-${math.id}`)).toBe(true) + }) + }) + describe("resolver", () => { it("should be created without error", () => { const userExecutor = userFactory.resolver().toExecutor() From 945c8d52123809a65feaff09b6ca2b8e4e16d4bc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E2=9B=B5?= <809067559@qq.com> Date: Mon, 7 Apr 2025 00:45:12 +0800 Subject: [PATCH 09/54] refactor(drizzle): improve relation error handling in DrizzleResolverFactory - Enhanced error handling in the `relationField` method to throw a specific error when a relation is not found or is not a table. - Added a new test case to verify that the appropriate error is thrown for non-existent relations, ensuring robustness in relation management. --- packages/drizzle/src/factory/resolver.ts | 11 ++--------- packages/drizzle/test/resolver-factory.spec.ts | 8 ++++++++ 2 files changed, 10 insertions(+), 9 deletions(-) diff --git a/packages/drizzle/src/factory/resolver.ts b/packages/drizzle/src/factory/resolver.ts index 683e913e..64a7ac48 100644 --- a/packages/drizzle/src/factory/resolver.ts +++ b/packages/drizzle/src/factory/resolver.ts @@ -354,21 +354,14 @@ export abstract class DrizzleResolverFactory< const relation = this.db._.relations["config"]?.[this.tableName]?.[ relationName ] as Relation - if (!relation) { + const targetTable = relation?.targetTable + if (!relation || !(targetTable instanceof Table)) { throw new Error( `GQLoom-Drizzle Error: Relation ${this.tableName}.${String( relationName )} not found in drizzle instance. Did you forget to pass relations to drizzle constructor?` ) } - const targetTable = relation.targetTable - if (!(targetTable instanceof Table)) { - throw new Error( - `GQLoom-Drizzle Error: Relation ${this.tableName}.${String( - relationName - )} is not a table relation!` - ) - } const output = DrizzleWeaver.unravel(targetTable) const tableName = getTableName(targetTable) diff --git a/packages/drizzle/test/resolver-factory.spec.ts b/packages/drizzle/test/resolver-factory.spec.ts index e91bf9c9..c241980b 100644 --- a/packages/drizzle/test/resolver-factory.spec.ts +++ b/packages/drizzle/test/resolver-factory.spec.ts @@ -532,6 +532,14 @@ describe.concurrent("DrizzleResolverFactory", () => { { authorId: John.id, title: "World" }, ]) }) + + it("should throw an error when relation is not found", () => { + expect(() => { + userFactory.relationField("nonExistentRelation" as any) + }).toThrow( + "GQLoom-Drizzle Error: Relation users.nonExistentRelation not found in drizzle instance" + ) + }) }) describe("relationField with multiple field relations", () => { From dec48b0d76c498b6c7bc3ed97ea2f4f4807fe30d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E2=9B=B5?= <809067559@qq.com> Date: Mon, 7 Apr 2025 01:01:45 +0800 Subject: [PATCH 10/54] refactor(drizzle): update filter structure in DrizzleInputFactory - Renamed filter types from `FiltersOr` to `FiltersNested` for improved clarity. - Introduced `AND` and `NOT` fields in filter definitions to enhance query capabilities. - Updated related test cases and GraphQL schemas for MySQL, PostgreSQL, and SQLite to reflect the new filter structure. --- packages/drizzle/src/factory/input.ts | 8 +++++--- packages/drizzle/test/resolver-mysql.spec.gql | 12 ++++++++---- .../drizzle/test/resolver-postgres.spec.gql | 18 ++++++++++++------ packages/drizzle/test/resolver-sqlite.spec.gql | 12 ++++++++---- 4 files changed, 33 insertions(+), 17 deletions(-) diff --git a/packages/drizzle/src/factory/input.ts b/packages/drizzle/src/factory/input.ts index a999e1fe..d0ed5555 100644 --- a/packages/drizzle/src/factory/input.ts +++ b/packages/drizzle/src/factory/input.ts @@ -224,8 +224,8 @@ export class DrizzleInputFactory { isNotNull: { type: GraphQLBoolean }, } - const filtersOr = new GraphQLObjectType({ - name: `${pascalCase(column.columnType)}FiltersOr`, + const filtersNested = new GraphQLObjectType({ + name: `${pascalCase(column.columnType)}FiltersNested`, fields: { ...baseFields }, }) @@ -234,7 +234,9 @@ export class DrizzleInputFactory { name, fields: { ...baseFields, - OR: { type: new GraphQLList(new GraphQLNonNull(filtersOr)) }, + OR: { type: new GraphQLList(new GraphQLNonNull(filtersNested)) }, + AND: { type: new GraphQLList(new GraphQLNonNull(filtersNested)) }, + NOT: { type: filtersNested }, }, }) ) diff --git a/packages/drizzle/test/resolver-mysql.spec.gql b/packages/drizzle/test/resolver-mysql.spec.gql index 11e76ffa..f4f874e5 100644 --- a/packages/drizzle/test/resolver-mysql.spec.gql +++ b/packages/drizzle/test/resolver-mysql.spec.gql @@ -14,7 +14,9 @@ type MutationSuccessResult { } input MySqlIntFilters { - OR: [MySqlIntFiltersOr!] + AND: [MySqlIntFiltersNested!] + NOT: MySqlIntFiltersNested + OR: [MySqlIntFiltersNested!] eq: Int gt: Int gte: Int @@ -27,7 +29,7 @@ input MySqlIntFilters { notIn: [Int!] } -input MySqlIntFiltersOr { +input MySqlIntFiltersNested { eq: Int gt: Int gte: Int @@ -41,7 +43,9 @@ input MySqlIntFiltersOr { } input MySqlTextFilters { - OR: [MySqlTextFiltersOr!] + AND: [MySqlTextFiltersNested!] + NOT: MySqlTextFiltersNested + OR: [MySqlTextFiltersNested!] eq: String gt: String gte: String @@ -58,7 +62,7 @@ input MySqlTextFilters { notLike: String } -input MySqlTextFiltersOr { +input MySqlTextFiltersNested { eq: String gt: String gte: String diff --git a/packages/drizzle/test/resolver-postgres.spec.gql b/packages/drizzle/test/resolver-postgres.spec.gql index 58144615..b5acf5e5 100644 --- a/packages/drizzle/test/resolver-postgres.spec.gql +++ b/packages/drizzle/test/resolver-postgres.spec.gql @@ -15,7 +15,9 @@ enum OrderDirection { } input PgIntegerFilters { - OR: [PgIntegerFiltersOr!] + AND: [PgIntegerFiltersNested!] + NOT: PgIntegerFiltersNested + OR: [PgIntegerFiltersNested!] eq: Int gt: Int gte: Int @@ -28,7 +30,7 @@ input PgIntegerFilters { notIn: [Int!] } -input PgIntegerFiltersOr { +input PgIntegerFiltersNested { eq: Int gt: Int gte: Int @@ -42,7 +44,9 @@ input PgIntegerFiltersOr { } input PgSerialFilters { - OR: [PgSerialFiltersOr!] + AND: [PgSerialFiltersNested!] + NOT: PgSerialFiltersNested + OR: [PgSerialFiltersNested!] eq: Int gt: Int gte: Int @@ -55,7 +59,7 @@ input PgSerialFilters { notIn: [Int!] } -input PgSerialFiltersOr { +input PgSerialFiltersNested { eq: Int gt: Int gte: Int @@ -69,7 +73,9 @@ input PgSerialFiltersOr { } input PgTextFilters { - OR: [PgTextFiltersOr!] + AND: [PgTextFiltersNested!] + NOT: PgTextFiltersNested + OR: [PgTextFiltersNested!] eq: String gt: String gte: String @@ -86,7 +92,7 @@ input PgTextFilters { notLike: String } -input PgTextFiltersOr { +input PgTextFiltersNested { eq: String gt: String gte: String diff --git a/packages/drizzle/test/resolver-sqlite.spec.gql b/packages/drizzle/test/resolver-sqlite.spec.gql index 98ef5fbb..00168538 100644 --- a/packages/drizzle/test/resolver-sqlite.spec.gql +++ b/packages/drizzle/test/resolver-sqlite.spec.gql @@ -68,7 +68,9 @@ type Query { } input SQLiteIntegerFilters { - OR: [SQLiteIntegerFiltersOr!] + AND: [SQLiteIntegerFiltersNested!] + NOT: SQLiteIntegerFiltersNested + OR: [SQLiteIntegerFiltersNested!] eq: Int gt: Int gte: Int @@ -81,7 +83,7 @@ input SQLiteIntegerFilters { notIn: [Int!] } -input SQLiteIntegerFiltersOr { +input SQLiteIntegerFiltersNested { eq: Int gt: Int gte: Int @@ -95,7 +97,9 @@ input SQLiteIntegerFiltersOr { } input SQLiteTextFilters { - OR: [SQLiteTextFiltersOr!] + AND: [SQLiteTextFiltersNested!] + NOT: SQLiteTextFiltersNested + OR: [SQLiteTextFiltersNested!] eq: String gt: String gte: String @@ -112,7 +116,7 @@ input SQLiteTextFilters { notLike: String } -input SQLiteTextFiltersOr { +input SQLiteTextFiltersNested { eq: String gt: String gte: String From 8aa3a01668056b00ca1de61b0c3ede95c866c1aa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E2=9B=B5?= <809067559@qq.com> Date: Wed, 9 Apr 2025 02:41:19 +0800 Subject: [PATCH 11/54] feat(website): update dependencies and refactor user service integration --- examples/cattery-valibot/package.json | 4 +- .../cattery-valibot/src/contexts/index.ts | 4 +- .../cattery-valibot/src/resolvers/user.ts | 10 +- .../cattery-valibot/src/services/index.ts | 1 - examples/cattery-valibot/src/services/user.ts | 19 - examples/cattery-zod/package.json | 4 +- examples/cattery-zod/src/contexts/index.ts | 5 +- examples/cattery-zod/src/resolvers/user.ts | 10 +- examples/cattery-zod/src/services/index.ts | 1 - examples/cattery-zod/src/services/user.ts | 19 - examples/drizzle/package.json | 4 +- packages/drizzle/package.json | 4 +- pnpm-lock.yaml | 205 +++-------- website/content/docs/dataloader.mdx | 97 ++++-- website/content/docs/dataloader.zh.mdx | 97 ++++-- website/content/docs/schema/drizzle.md | 328 ++++++++++-------- website/content/docs/schema/drizzle.zh.md | 328 ++++++++++-------- website/content/home/drizzle.md | 101 +++++- website/package.json | 4 +- 19 files changed, 679 insertions(+), 566 deletions(-) delete mode 100644 examples/cattery-valibot/src/services/index.ts delete mode 100644 examples/cattery-valibot/src/services/user.ts delete mode 100644 examples/cattery-zod/src/services/index.ts delete mode 100644 examples/cattery-zod/src/services/user.ts diff --git a/examples/cattery-valibot/package.json b/examples/cattery-valibot/package.json index 3572fdbd..e7e00f1a 100644 --- a/examples/cattery-valibot/package.json +++ b/examples/cattery-valibot/package.json @@ -11,8 +11,8 @@ "description": "", "devDependencies": { "@types/node": "^22.13.4", - "drizzle-kit": "1.0.0-beta.1-bd417c1", - "drizzle-orm": "1.0.0-beta.1-bd417c1", + "drizzle-kit": "1.0.0-beta.1-7946562", + "drizzle-orm": "1.0.0-beta.1-7946562", "tsx": "^4.7.2", "typescript": "^5.7.3" }, diff --git a/examples/cattery-valibot/src/contexts/index.ts b/examples/cattery-valibot/src/contexts/index.ts index 7844dc51..d9b45628 100644 --- a/examples/cattery-valibot/src/contexts/index.ts +++ b/examples/cattery-valibot/src/contexts/index.ts @@ -1,14 +1,14 @@ import { createMemoization, useContext } from "@gqloom/core" import { GraphQLError } from "graphql" import type { YogaInitialContext } from "graphql-yoga" -import { userService } from "../services" +import { db } from "../providers" export const useCurrentUser = createMemoization(async () => { const phone = useContext().request.headers.get("authorization") if (phone == null) throw new GraphQLError("Unauthorized") - const user = await userService.findUserByPhone(phone) + const user = await db.query.users.findFirst({ where: { phone } }) if (user == null) throw new GraphQLError("Unauthorized") return user }) diff --git a/examples/cattery-valibot/src/resolvers/user.ts b/examples/cattery-valibot/src/resolvers/user.ts index ab20d0e1..adf0e90e 100644 --- a/examples/cattery-valibot/src/resolvers/user.ts +++ b/examples/cattery-valibot/src/resolvers/user.ts @@ -4,7 +4,6 @@ import * as v from "valibot" import { useCurrentUser } from "../contexts" import { db } from "../providers" import { users } from "../schema" -import { userService } from "../services" const userResolverFactory = drizzleResolverFactory(db, "users") @@ -15,11 +14,11 @@ export const userResolver = resolver.of(users, { usersByName: query(users.$list()) .input({ name: v.string() }) - .resolve(({ name }) => userService.findUsersByName(name)), + .resolve(({ name }) => db.query.users.findMany({ where: { name } })), userByPhone: query(users.$nullable()) .input({ phone: v.string() }) - .resolve(({ phone }) => userService.findUserByPhone(phone)), + .resolve(({ phone }) => db.query.users.findFirst({ where: { phone } })), createUser: mutation(users) .input({ @@ -28,5 +27,8 @@ export const userResolver = resolver.of(users, { phone: v.string(), }), }) - .resolve(async ({ data }) => userService.createUser(data)), + .resolve(async ({ data }) => { + const [user] = await db.insert(users).values(data).returning() + return user + }), }) diff --git a/examples/cattery-valibot/src/services/index.ts b/examples/cattery-valibot/src/services/index.ts deleted file mode 100644 index 1fb51aa5..00000000 --- a/examples/cattery-valibot/src/services/index.ts +++ /dev/null @@ -1 +0,0 @@ -export * as userService from "./user" diff --git a/examples/cattery-valibot/src/services/user.ts b/examples/cattery-valibot/src/services/user.ts deleted file mode 100644 index af5ced0f..00000000 --- a/examples/cattery-valibot/src/services/user.ts +++ /dev/null @@ -1,19 +0,0 @@ -import { db } from "../providers" -import { users } from "../schema" - -export async function createUser(input: typeof users.$inferInsert) { - const [user] = await db.insert(users).values(input).returning() - return user -} - -export async function findUsersByName(name: string) { - return await db.query.users.findMany({ - where: { name }, - }) -} - -export async function findUserByPhone(phone: string) { - return await db.query.users.findFirst({ - where: { phone }, - }) -} diff --git a/examples/cattery-zod/package.json b/examples/cattery-zod/package.json index c6eebcda..a7a7b3c3 100644 --- a/examples/cattery-zod/package.json +++ b/examples/cattery-zod/package.json @@ -11,8 +11,8 @@ "description": "", "devDependencies": { "@types/node": "^22.13.4", - "drizzle-kit": "1.0.0-beta.1-bd417c1", - "drizzle-orm": "1.0.0-beta.1-bd417c1", + "drizzle-kit": "1.0.0-beta.1-7946562", + "drizzle-orm": "1.0.0-beta.1-7946562", "tsx": "^4.7.2", "typescript": "^5.7.3" }, diff --git a/examples/cattery-zod/src/contexts/index.ts b/examples/cattery-zod/src/contexts/index.ts index 7844dc51..6310260c 100644 --- a/examples/cattery-zod/src/contexts/index.ts +++ b/examples/cattery-zod/src/contexts/index.ts @@ -1,14 +1,15 @@ import { createMemoization, useContext } from "@gqloom/core" import { GraphQLError } from "graphql" import type { YogaInitialContext } from "graphql-yoga" -import { userService } from "../services" +import { db } from "../providers" export const useCurrentUser = createMemoization(async () => { const phone = useContext().request.headers.get("authorization") if (phone == null) throw new GraphQLError("Unauthorized") - const user = await userService.findUserByPhone(phone) + const user = await db.query.users.findFirst({ where: { phone } }) + if (user == null) throw new GraphQLError("Unauthorized") return user }) diff --git a/examples/cattery-zod/src/resolvers/user.ts b/examples/cattery-zod/src/resolvers/user.ts index db552713..303305c8 100644 --- a/examples/cattery-zod/src/resolvers/user.ts +++ b/examples/cattery-zod/src/resolvers/user.ts @@ -4,7 +4,6 @@ import { z } from "zod" import { useCurrentUser } from "../contexts" import { db } from "../providers" import { users } from "../schema" -import { userService } from "../services" const userResolverFactory = drizzleResolverFactory(db, "users") @@ -15,11 +14,11 @@ export const userResolver = resolver.of(users, { usersByName: query(users.$list()) .input({ name: z.string() }) - .resolve(({ name }) => userService.findUsersByName(name)), + .resolve(({ name }) => db.query.users.findMany({ where: { name } })), userByPhone: query(users.$nullable()) .input({ phone: z.string() }) - .resolve(({ phone }) => userService.findUserByPhone(phone)), + .resolve(({ phone }) => db.query.users.findFirst({ where: { phone } })), createUser: mutation(users) .input({ @@ -28,5 +27,8 @@ export const userResolver = resolver.of(users, { phone: z.string(), }), }) - .resolve(async ({ data }) => userService.createUser(data)), + .resolve(async ({ data }) => { + const [user] = await db.insert(users).values(data).returning() + return user + }), }) diff --git a/examples/cattery-zod/src/services/index.ts b/examples/cattery-zod/src/services/index.ts deleted file mode 100644 index 1fb51aa5..00000000 --- a/examples/cattery-zod/src/services/index.ts +++ /dev/null @@ -1 +0,0 @@ -export * as userService from "./user" diff --git a/examples/cattery-zod/src/services/user.ts b/examples/cattery-zod/src/services/user.ts deleted file mode 100644 index af5ced0f..00000000 --- a/examples/cattery-zod/src/services/user.ts +++ /dev/null @@ -1,19 +0,0 @@ -import { db } from "../providers" -import { users } from "../schema" - -export async function createUser(input: typeof users.$inferInsert) { - const [user] = await db.insert(users).values(input).returning() - return user -} - -export async function findUsersByName(name: string) { - return await db.query.users.findMany({ - where: { name }, - }) -} - -export async function findUserByPhone(phone: string) { - return await db.query.users.findFirst({ - where: { phone }, - }) -} diff --git a/examples/drizzle/package.json b/examples/drizzle/package.json index cf208390..48f683de 100644 --- a/examples/drizzle/package.json +++ b/examples/drizzle/package.json @@ -15,8 +15,8 @@ "devDependencies": { "@types/node": "^22.13.1", "@types/pg": "^8.11.10", - "drizzle-kit": "1.0.0-beta.1-bd417c1", - "drizzle-orm": "1.0.0-beta.1-bd417c1", + "drizzle-kit": "1.0.0-beta.1-7946562", + "drizzle-orm": "1.0.0-beta.1-7946562", "tsx": "^4.7.2", "typescript": "^5.7.3" }, diff --git a/packages/drizzle/package.json b/packages/drizzle/package.json index c659c54e..9975b39e 100644 --- a/packages/drizzle/package.json +++ b/packages/drizzle/package.json @@ -42,8 +42,8 @@ "@libsql/client": "^0.14.0", "@types/pg": "^8.11.10", "dotenv": "^16.4.7", - "drizzle-kit": "1.0.0-beta.1-bd417c1", - "drizzle-orm": "1.0.0-beta.1-bd417c1", + "drizzle-kit": "1.0.0-beta.1-7946562", + "drizzle-orm": "1.0.0-beta.1-7946562", "graphql": "^16.8.1", "graphql-yoga": "^5.6.0", "mysql2": "^3.14.0", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 8c81214e..f4298da9 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -84,13 +84,13 @@ importers: dependencies: '@gqloom/core': specifier: latest - version: 0.8.3(graphql@16.8.1(patch_hash=2p6wc42resg5at2p4utt5idamq)) + version: 0.8.4(graphql@16.8.1(patch_hash=2p6wc42resg5at2p4utt5idamq)) '@gqloom/drizzle': specifier: latest - version: 0.8.1(@gqloom/core@0.8.3(graphql@16.8.1(patch_hash=2p6wc42resg5at2p4utt5idamq)))(drizzle-orm@1.0.0-beta.1-bd417c1(@libsql/client@0.14.0)(@prisma/client@6.1.0(prisma@6.1.0))(@types/pg@8.11.10)(better-sqlite3@11.8.1)(gel@2.0.2)(knex@3.1.0(better-sqlite3@11.8.1)(mysql2@3.14.0)(pg@8.13.2)(sqlite3@5.1.7))(mysql2@3.14.0)(pg@8.13.2)(prisma@6.1.0)(sqlite3@5.1.7))(graphql@16.8.1(patch_hash=2p6wc42resg5at2p4utt5idamq)) + version: 0.8.2(@gqloom/core@0.8.4(graphql@16.8.1(patch_hash=2p6wc42resg5at2p4utt5idamq)))(drizzle-orm@1.0.0-beta.1-7946562(@libsql/client@0.14.0)(@prisma/client@6.1.0(prisma@6.1.0))(@types/pg@8.11.10)(better-sqlite3@11.8.1)(gel@2.0.2)(knex@3.1.0(better-sqlite3@11.8.1)(mysql2@3.14.0)(pg@8.13.2)(sqlite3@5.1.7))(mysql2@3.14.0)(pg@8.13.2)(prisma@6.1.0)(sqlite3@5.1.7))(graphql@16.8.1(patch_hash=2p6wc42resg5at2p4utt5idamq)) '@gqloom/valibot': specifier: ^0.7.1 - version: 0.7.1(@gqloom/core@0.8.3(graphql@16.8.1(patch_hash=2p6wc42resg5at2p4utt5idamq)))(graphql@16.8.1(patch_hash=2p6wc42resg5at2p4utt5idamq))(valibot@1.0.0-rc.1(typescript@5.7.3)) + version: 0.7.1(@gqloom/core@0.8.4(graphql@16.8.1(patch_hash=2p6wc42resg5at2p4utt5idamq)))(graphql@16.8.1(patch_hash=2p6wc42resg5at2p4utt5idamq))(valibot@1.0.0-rc.1(typescript@5.7.3)) '@libsql/client': specifier: ^0.14.0 version: 0.14.0 @@ -111,11 +111,11 @@ importers: specifier: ^22.13.4 version: 22.13.4 drizzle-kit: - specifier: 1.0.0-beta.1-bd417c1 - version: 1.0.0-beta.1-bd417c1 + specifier: 1.0.0-beta.1-7946562 + version: 1.0.0-beta.1-7946562 drizzle-orm: - specifier: 1.0.0-beta.1-bd417c1 - version: 1.0.0-beta.1-bd417c1(@libsql/client@0.14.0)(@prisma/client@6.1.0(prisma@6.1.0))(@types/pg@8.11.10)(better-sqlite3@11.8.1)(gel@2.0.2)(knex@3.1.0(better-sqlite3@11.8.1)(mysql2@3.14.0)(pg@8.13.2)(sqlite3@5.1.7))(mysql2@3.14.0)(pg@8.13.2)(prisma@6.1.0)(sqlite3@5.1.7) + specifier: 1.0.0-beta.1-7946562 + version: 1.0.0-beta.1-7946562(@libsql/client@0.14.0)(@prisma/client@6.1.0(prisma@6.1.0))(@types/pg@8.11.10)(better-sqlite3@11.8.1)(gel@2.0.2)(knex@3.1.0(better-sqlite3@11.8.1)(mysql2@3.14.0)(pg@8.13.2)(sqlite3@5.1.7))(mysql2@3.14.0)(pg@8.13.2)(prisma@6.1.0)(sqlite3@5.1.7) tsx: specifier: ^4.7.2 version: 4.7.2 @@ -127,13 +127,13 @@ importers: dependencies: '@gqloom/core': specifier: latest - version: 0.8.3(graphql@16.8.1(patch_hash=2p6wc42resg5at2p4utt5idamq)) + version: 0.8.4(graphql@16.8.1(patch_hash=2p6wc42resg5at2p4utt5idamq)) '@gqloom/drizzle': specifier: latest - version: 0.8.1(@gqloom/core@0.8.3(graphql@16.8.1(patch_hash=2p6wc42resg5at2p4utt5idamq)))(drizzle-orm@1.0.0-beta.1-bd417c1(@libsql/client@0.14.0)(@prisma/client@6.1.0(prisma@6.1.0))(@types/pg@8.11.10)(better-sqlite3@11.8.1)(gel@2.0.2)(knex@3.1.0(better-sqlite3@11.8.1)(mysql2@3.14.0)(pg@8.13.2)(sqlite3@5.1.7))(mysql2@3.14.0)(pg@8.13.2)(prisma@6.1.0)(sqlite3@5.1.7))(graphql@16.8.1(patch_hash=2p6wc42resg5at2p4utt5idamq)) + version: 0.8.2(@gqloom/core@0.8.4(graphql@16.8.1(patch_hash=2p6wc42resg5at2p4utt5idamq)))(drizzle-orm@1.0.0-beta.1-7946562(@libsql/client@0.14.0)(@prisma/client@6.1.0(prisma@6.1.0))(@types/pg@8.11.10)(better-sqlite3@11.8.1)(gel@2.0.2)(knex@3.1.0(better-sqlite3@11.8.1)(mysql2@3.14.0)(pg@8.13.2)(sqlite3@5.1.7))(mysql2@3.14.0)(pg@8.13.2)(prisma@6.1.0)(sqlite3@5.1.7))(graphql@16.8.1(patch_hash=2p6wc42resg5at2p4utt5idamq)) '@gqloom/zod': specifier: latest - version: 0.8.1(@gqloom/core@0.8.3(graphql@16.8.1(patch_hash=2p6wc42resg5at2p4utt5idamq)))(graphql@16.8.1(patch_hash=2p6wc42resg5at2p4utt5idamq))(zod@3.24.2) + version: 0.8.1(@gqloom/core@0.8.4(graphql@16.8.1(patch_hash=2p6wc42resg5at2p4utt5idamq)))(graphql@16.8.1(patch_hash=2p6wc42resg5at2p4utt5idamq))(zod@3.24.2) '@libsql/client': specifier: ^0.14.0 version: 0.14.0 @@ -154,11 +154,11 @@ importers: specifier: ^22.13.4 version: 22.13.4 drizzle-kit: - specifier: 1.0.0-beta.1-bd417c1 - version: 1.0.0-beta.1-bd417c1 + specifier: 1.0.0-beta.1-7946562 + version: 1.0.0-beta.1-7946562 drizzle-orm: - specifier: 1.0.0-beta.1-bd417c1 - version: 1.0.0-beta.1-bd417c1(@libsql/client@0.14.0)(@prisma/client@6.1.0(prisma@6.1.0))(@types/pg@8.11.10)(better-sqlite3@11.8.1)(gel@2.0.2)(knex@3.1.0(better-sqlite3@11.8.1)(mysql2@3.14.0)(pg@8.13.2)(sqlite3@5.1.7))(mysql2@3.14.0)(pg@8.13.2)(prisma@6.1.0)(sqlite3@5.1.7) + specifier: 1.0.0-beta.1-7946562 + version: 1.0.0-beta.1-7946562(@libsql/client@0.14.0)(@prisma/client@6.1.0(prisma@6.1.0))(@types/pg@8.11.10)(better-sqlite3@11.8.1)(gel@2.0.2)(knex@3.1.0(better-sqlite3@11.8.1)(mysql2@3.14.0)(pg@8.13.2)(sqlite3@5.1.7))(mysql2@3.14.0)(pg@8.13.2)(prisma@6.1.0)(sqlite3@5.1.7) tsx: specifier: ^4.7.2 version: 4.7.2 @@ -179,7 +179,7 @@ importers: version: 16.4.7 drizzle-seed: specifier: ^0.3.1 - version: 0.3.1(drizzle-orm@1.0.0-beta.1-bd417c1(@libsql/client@0.14.0)(@prisma/client@6.1.0(prisma@6.1.0))(@types/pg@8.11.10)(better-sqlite3@11.8.1)(gel@2.0.2)(knex@3.1.0(better-sqlite3@11.8.1)(mysql2@3.14.0)(pg@8.13.2)(sqlite3@5.1.7))(mysql2@3.14.0)(pg@8.13.2)(prisma@6.1.0)(sqlite3@5.1.7)) + version: 0.3.1(drizzle-orm@1.0.0-beta.1-7946562(@libsql/client@0.14.0)(@prisma/client@6.1.0(prisma@6.1.0))(@types/pg@8.11.10)(better-sqlite3@11.8.1)(gel@2.0.2)(knex@3.1.0(better-sqlite3@11.8.1)(mysql2@3.14.0)(pg@8.13.2)(sqlite3@5.1.7))(mysql2@3.14.0)(pg@8.13.2)(prisma@6.1.0)(sqlite3@5.1.7)) graphql: specifier: ^16.8.1 version: 16.8.1(patch_hash=2p6wc42resg5at2p4utt5idamq) @@ -197,11 +197,11 @@ importers: specifier: ^8.11.10 version: 8.11.10 drizzle-kit: - specifier: 1.0.0-beta.1-bd417c1 - version: 1.0.0-beta.1-bd417c1 + specifier: 1.0.0-beta.1-7946562 + version: 1.0.0-beta.1-7946562 drizzle-orm: - specifier: 1.0.0-beta.1-bd417c1 - version: 1.0.0-beta.1-bd417c1(@libsql/client@0.14.0)(@prisma/client@6.1.0(prisma@6.1.0))(@types/pg@8.11.10)(better-sqlite3@11.8.1)(gel@2.0.2)(knex@3.1.0(better-sqlite3@11.8.1)(mysql2@3.14.0)(pg@8.13.2)(sqlite3@5.1.7))(mysql2@3.14.0)(pg@8.13.2)(prisma@6.1.0)(sqlite3@5.1.7) + specifier: 1.0.0-beta.1-7946562 + version: 1.0.0-beta.1-7946562(@libsql/client@0.14.0)(@prisma/client@6.1.0(prisma@6.1.0))(@types/pg@8.11.10)(better-sqlite3@11.8.1)(gel@2.0.2)(knex@3.1.0(better-sqlite3@11.8.1)(mysql2@3.14.0)(pg@8.13.2)(sqlite3@5.1.7))(mysql2@3.14.0)(pg@8.13.2)(prisma@6.1.0)(sqlite3@5.1.7) tsx: specifier: ^4.7.2 version: 4.7.2 @@ -338,11 +338,11 @@ importers: specifier: ^16.4.7 version: 16.4.7 drizzle-kit: - specifier: 1.0.0-beta.1-bd417c1 - version: 1.0.0-beta.1-bd417c1 + specifier: 1.0.0-beta.1-7946562 + version: 1.0.0-beta.1-7946562 drizzle-orm: - specifier: 1.0.0-beta.1-bd417c1 - version: 1.0.0-beta.1-bd417c1(@libsql/client@0.14.0)(@prisma/client@6.1.0(prisma@6.1.0))(@types/pg@8.11.10)(better-sqlite3@11.8.1)(gel@2.0.2)(knex@3.1.0(better-sqlite3@11.8.1)(mysql2@3.14.0)(pg@8.13.2)(sqlite3@5.1.7))(mysql2@3.14.0)(pg@8.13.2)(prisma@6.1.0)(sqlite3@5.1.7) + specifier: 1.0.0-beta.1-7946562 + version: 1.0.0-beta.1-7946562(@libsql/client@0.14.0)(@prisma/client@6.1.0(prisma@6.1.0))(@types/pg@8.11.10)(better-sqlite3@11.8.1)(gel@2.0.2)(knex@3.1.0(better-sqlite3@11.8.1)(mysql2@3.14.0)(pg@8.13.2)(sqlite3@5.1.7))(mysql2@3.14.0)(pg@8.13.2)(prisma@6.1.0)(sqlite3@5.1.7) graphql: specifier: ^16.8.1 version: 16.8.1(patch_hash=2p6wc42resg5at2p4utt5idamq) @@ -581,11 +581,11 @@ importers: specifier: ^16.4.7 version: 16.4.7 drizzle-kit: - specifier: ^0.30.1 - version: 0.30.1 + specifier: 1.0.0-beta.1-7946562 + version: 1.0.0-beta.1-7946562 drizzle-orm: - specifier: ^0.39.3 - version: 0.39.3(@libsql/client@0.14.0)(@prisma/client@6.1.0(prisma@6.1.0))(@types/pg@8.11.10)(better-sqlite3@11.8.1)(knex@3.1.0(better-sqlite3@11.8.1)(mysql2@3.14.0)(pg@8.13.2)(sqlite3@5.1.7))(mysql2@3.14.0)(pg@8.13.2)(prisma@6.1.0)(sqlite3@5.1.7) + specifier: 1.0.0-beta.1-7946562 + version: 1.0.0-beta.1-7946562(@libsql/client@0.14.0)(@prisma/client@6.1.0(prisma@6.1.0))(@types/pg@8.11.10)(better-sqlite3@11.8.1)(gel@2.0.2)(knex@3.1.0(better-sqlite3@11.8.1)(mysql2@3.14.0)(pg@8.13.2)(sqlite3@5.1.7))(mysql2@3.14.0)(pg@8.13.2)(prisma@6.1.0)(sqlite3@5.1.7) fastify: specifier: ^4.28.1 version: 4.28.1 @@ -1454,13 +1454,13 @@ packages: peerDependencies: graphql: '>= 16.8.0' - '@gqloom/core@0.8.3': - resolution: {integrity: sha512-kV9mLi4wAt+dv6NRhfrfr5EIGbtxqEMqI2hyMef16/4KLGB/IGabDT1nMtWIrDDS8pa1ZFJ+aRpkWYQagtw7Sw==} + '@gqloom/core@0.8.4': + resolution: {integrity: sha512-pRogXmgHyZkqXz7m7PONrHmkyBuJT07nd4YZaHZaHYT6GQ/UVMnoHp1Iw+0RiYpB/ZWy65BsNReFVF8oyyK9VQ==} peerDependencies: graphql: '>= 16.8.0' - '@gqloom/drizzle@0.8.1': - resolution: {integrity: sha512-6/IAq3///OuEbNUUqm8UPV+6RCW5ZVQP1532WPsa0T6ipuCwi1YqgxICCSZudgBarzbhwdDkgWjWqddT+wkOVA==} + '@gqloom/drizzle@0.8.2': + resolution: {integrity: sha512-DYNtTYFlU/9cDCYyr2rfJMx2GGbJA7GgihPcIXCwmLb5Eq5T5eNSux22dlZ1XJ/KQB5R4ONpyxIKR3uginFcjw==} peerDependencies: '@gqloom/core': '>= 0.8.0' drizzle-orm: '>= 0.38.0' @@ -3405,102 +3405,12 @@ packages: resolution: {integrity: sha512-47qPchRCykZC03FhkYAhrvwU4xDBFIj1QPqaarj6mdM/hgUzfPHcpkHJOn3mJAufFeeAxAzeGsr5X0M4k6fLZQ==} engines: {node: '>=12'} - drizzle-kit@0.30.1: - resolution: {integrity: sha512-HmA/NeewvHywhJ2ENXD3KvOuM/+K2dGLJfxVfIHsGwaqKICJnS+Ke2L6UcSrSrtMJLJaT0Im1Qv4TFXfaZShyw==} + drizzle-kit@1.0.0-beta.1-7946562: + resolution: {integrity: sha512-Gkq8UQGKpOjEe2zRDNFknabOFX1y6v+6F3D7fnK2iEdbWcDlDkrRhiitpMVB8+B0K+5Y6j7rBMHtWFdt+YrSug==} hasBin: true - drizzle-kit@1.0.0-beta.1-bd417c1: - resolution: {integrity: sha512-snxKV7u3b6UXn+qo9Udg49SqiKrqyQ3Gyf/uz8zTD5QDOD5TTo4abOptYZr/afnTr5YOlZnYsLzr9q2ORyIlbg==} - hasBin: true - - drizzle-orm@0.39.3: - resolution: {integrity: sha512-EZ8ZpYvDIvKU9C56JYLOmUskazhad+uXZCTCRN4OnRMsL+xAJ05dv1eCpAG5xzhsm1hqiuC5kAZUCS924u2DTw==} - peerDependencies: - '@aws-sdk/client-rds-data': '>=3' - '@cloudflare/workers-types': '>=4' - '@electric-sql/pglite': '>=0.2.0' - '@libsql/client': '>=0.10.0' - '@libsql/client-wasm': '>=0.10.0' - '@neondatabase/serverless': '>=0.10.0' - '@op-engineering/op-sqlite': '>=2' - '@opentelemetry/api': ^1.4.1 - '@planetscale/database': '>=1' - '@prisma/client': '*' - '@tidbcloud/serverless': '*' - '@types/better-sqlite3': '*' - '@types/pg': '*' - '@types/sql.js': '*' - '@vercel/postgres': '>=0.8.0' - '@xata.io/client': '*' - better-sqlite3: '>=7' - bun-types: '*' - expo-sqlite: '>=14.0.0' - knex: '*' - kysely: '*' - mysql2: '>=2' - pg: ^8.13.2 - postgres: '>=3' - prisma: '*' - sql.js: '>=1' - sqlite3: '>=5' - peerDependenciesMeta: - '@aws-sdk/client-rds-data': - optional: true - '@cloudflare/workers-types': - optional: true - '@electric-sql/pglite': - optional: true - '@libsql/client': - optional: true - '@libsql/client-wasm': - optional: true - '@neondatabase/serverless': - optional: true - '@op-engineering/op-sqlite': - optional: true - '@opentelemetry/api': - optional: true - '@planetscale/database': - optional: true - '@prisma/client': - optional: true - '@tidbcloud/serverless': - optional: true - '@types/better-sqlite3': - optional: true - '@types/pg': - optional: true - '@types/sql.js': - optional: true - '@vercel/postgres': - optional: true - '@xata.io/client': - optional: true - better-sqlite3: - optional: true - bun-types: - optional: true - expo-sqlite: - optional: true - knex: - optional: true - kysely: - optional: true - mysql2: - optional: true - pg: - optional: true - postgres: - optional: true - prisma: - optional: true - sql.js: - optional: true - sqlite3: - optional: true - - drizzle-orm@1.0.0-beta.1-bd417c1: - resolution: {integrity: sha512-WCjjODdeDAkpo4ceCCA7pp8qFiQZbc5AAJiYxFdM7rSH4Ez71VWC5oEvIcvwepACyHUCtCxYgzKOyjFITFa8CA==} + drizzle-orm@1.0.0-beta.1-7946562: + resolution: {integrity: sha512-cjMiZZ9Dw6dFsIGmZ2nJNz4vRfEoy2xfxBgSagQKmw27IfNxN4GngENXWdZSP4hDU/+1OHXlS2JxquTw7fJh9Q==} peerDependencies: '@aws-sdk/client-rds-data': '>=3' '@cloudflare/workers-types': '>=4' @@ -6879,14 +6789,14 @@ snapshots: dependencies: graphql: 16.8.1(patch_hash=2p6wc42resg5at2p4utt5idamq) - '@gqloom/core@0.8.3(graphql@16.8.1(patch_hash=2p6wc42resg5at2p4utt5idamq))': + '@gqloom/core@0.8.4(graphql@16.8.1(patch_hash=2p6wc42resg5at2p4utt5idamq))': dependencies: graphql: 16.8.1(patch_hash=2p6wc42resg5at2p4utt5idamq) - '@gqloom/drizzle@0.8.1(@gqloom/core@0.8.3(graphql@16.8.1(patch_hash=2p6wc42resg5at2p4utt5idamq)))(drizzle-orm@1.0.0-beta.1-bd417c1(@libsql/client@0.14.0)(@prisma/client@6.1.0(prisma@6.1.0))(@types/pg@8.11.10)(better-sqlite3@11.8.1)(gel@2.0.2)(knex@3.1.0(better-sqlite3@11.8.1)(mysql2@3.14.0)(pg@8.13.2)(sqlite3@5.1.7))(mysql2@3.14.0)(pg@8.13.2)(prisma@6.1.0)(sqlite3@5.1.7))(graphql@16.8.1(patch_hash=2p6wc42resg5at2p4utt5idamq))': + '@gqloom/drizzle@0.8.2(@gqloom/core@0.8.4(graphql@16.8.1(patch_hash=2p6wc42resg5at2p4utt5idamq)))(drizzle-orm@1.0.0-beta.1-7946562(@libsql/client@0.14.0)(@prisma/client@6.1.0(prisma@6.1.0))(@types/pg@8.11.10)(better-sqlite3@11.8.1)(gel@2.0.2)(knex@3.1.0(better-sqlite3@11.8.1)(mysql2@3.14.0)(pg@8.13.2)(sqlite3@5.1.7))(mysql2@3.14.0)(pg@8.13.2)(prisma@6.1.0)(sqlite3@5.1.7))(graphql@16.8.1(patch_hash=2p6wc42resg5at2p4utt5idamq))': dependencies: - '@gqloom/core': 0.8.3(graphql@16.8.1(patch_hash=2p6wc42resg5at2p4utt5idamq)) - drizzle-orm: 1.0.0-beta.1-bd417c1(@libsql/client@0.14.0)(@prisma/client@6.1.0(prisma@6.1.0))(@types/pg@8.11.10)(better-sqlite3@11.8.1)(gel@2.0.2)(knex@3.1.0(better-sqlite3@11.8.1)(mysql2@3.14.0)(pg@8.13.2)(sqlite3@5.1.7))(mysql2@3.14.0)(pg@8.13.2)(prisma@6.1.0)(sqlite3@5.1.7) + '@gqloom/core': 0.8.4(graphql@16.8.1(patch_hash=2p6wc42resg5at2p4utt5idamq)) + drizzle-orm: 1.0.0-beta.1-7946562(@libsql/client@0.14.0)(@prisma/client@6.1.0(prisma@6.1.0))(@types/pg@8.11.10)(better-sqlite3@11.8.1)(gel@2.0.2)(knex@3.1.0(better-sqlite3@11.8.1)(mysql2@3.14.0)(pg@8.13.2)(sqlite3@5.1.7))(mysql2@3.14.0)(pg@8.13.2)(prisma@6.1.0)(sqlite3@5.1.7) graphql: 16.8.1(patch_hash=2p6wc42resg5at2p4utt5idamq) '@gqloom/mikro-orm@0.7.0(@gqloom/core@0.7.0(graphql@16.8.1(patch_hash=2p6wc42resg5at2p4utt5idamq)))(@mikro-orm/core@6.4.6)(graphql@16.8.1(patch_hash=2p6wc42resg5at2p4utt5idamq))': @@ -6895,15 +6805,15 @@ snapshots: '@mikro-orm/core': 6.4.6 graphql: 16.8.1(patch_hash=2p6wc42resg5at2p4utt5idamq) - '@gqloom/valibot@0.7.1(@gqloom/core@0.8.3(graphql@16.8.1(patch_hash=2p6wc42resg5at2p4utt5idamq)))(graphql@16.8.1(patch_hash=2p6wc42resg5at2p4utt5idamq))(valibot@1.0.0-rc.1(typescript@5.7.3))': + '@gqloom/valibot@0.7.1(@gqloom/core@0.8.4(graphql@16.8.1(patch_hash=2p6wc42resg5at2p4utt5idamq)))(graphql@16.8.1(patch_hash=2p6wc42resg5at2p4utt5idamq))(valibot@1.0.0-rc.1(typescript@5.7.3))': dependencies: - '@gqloom/core': 0.8.3(graphql@16.8.1(patch_hash=2p6wc42resg5at2p4utt5idamq)) + '@gqloom/core': 0.8.4(graphql@16.8.1(patch_hash=2p6wc42resg5at2p4utt5idamq)) graphql: 16.8.1(patch_hash=2p6wc42resg5at2p4utt5idamq) valibot: 1.0.0-rc.1(typescript@5.7.3) - '@gqloom/zod@0.8.1(@gqloom/core@0.8.3(graphql@16.8.1(patch_hash=2p6wc42resg5at2p4utt5idamq)))(graphql@16.8.1(patch_hash=2p6wc42resg5at2p4utt5idamq))(zod@3.24.2)': + '@gqloom/zod@0.8.1(@gqloom/core@0.8.4(graphql@16.8.1(patch_hash=2p6wc42resg5at2p4utt5idamq)))(graphql@16.8.1(patch_hash=2p6wc42resg5at2p4utt5idamq))(zod@3.24.2)': dependencies: - '@gqloom/core': 0.8.3(graphql@16.8.1(patch_hash=2p6wc42resg5at2p4utt5idamq)) + '@gqloom/core': 0.8.4(graphql@16.8.1(patch_hash=2p6wc42resg5at2p4utt5idamq)) graphql: 16.8.1(patch_hash=2p6wc42resg5at2p4utt5idamq) zod: 3.24.2 @@ -8816,16 +8726,7 @@ snapshots: dotenv@16.4.7: {} - drizzle-kit@0.30.1: - dependencies: - '@drizzle-team/brocli': 0.10.2 - '@esbuild-kit/esm-loader': 2.6.5 - esbuild: 0.19.12 - esbuild-register: 3.6.0(esbuild@0.19.12) - transitivePeerDependencies: - - supports-color - - drizzle-kit@1.0.0-beta.1-bd417c1: + drizzle-kit@1.0.0-beta.1-7946562: dependencies: '@drizzle-team/brocli': 0.10.2 '@esbuild-kit/esm-loader': 2.6.5 @@ -8835,19 +8736,7 @@ snapshots: transitivePeerDependencies: - supports-color - drizzle-orm@0.39.3(@libsql/client@0.14.0)(@prisma/client@6.1.0(prisma@6.1.0))(@types/pg@8.11.10)(better-sqlite3@11.8.1)(knex@3.1.0(better-sqlite3@11.8.1)(mysql2@3.14.0)(pg@8.13.2)(sqlite3@5.1.7))(mysql2@3.14.0)(pg@8.13.2)(prisma@6.1.0)(sqlite3@5.1.7): - optionalDependencies: - '@libsql/client': 0.14.0 - '@prisma/client': 6.1.0(prisma@6.1.0) - '@types/pg': 8.11.10 - better-sqlite3: 11.8.1 - knex: 3.1.0(better-sqlite3@11.8.1)(mysql2@3.14.0)(pg@8.13.2)(sqlite3@5.1.7) - mysql2: 3.14.0 - pg: 8.13.2 - prisma: 6.1.0 - sqlite3: 5.1.7 - - drizzle-orm@1.0.0-beta.1-bd417c1(@libsql/client@0.14.0)(@prisma/client@6.1.0(prisma@6.1.0))(@types/pg@8.11.10)(better-sqlite3@11.8.1)(gel@2.0.2)(knex@3.1.0(better-sqlite3@11.8.1)(mysql2@3.14.0)(pg@8.13.2)(sqlite3@5.1.7))(mysql2@3.14.0)(pg@8.13.2)(prisma@6.1.0)(sqlite3@5.1.7): + drizzle-orm@1.0.0-beta.1-7946562(@libsql/client@0.14.0)(@prisma/client@6.1.0(prisma@6.1.0))(@types/pg@8.11.10)(better-sqlite3@11.8.1)(gel@2.0.2)(knex@3.1.0(better-sqlite3@11.8.1)(mysql2@3.14.0)(pg@8.13.2)(sqlite3@5.1.7))(mysql2@3.14.0)(pg@8.13.2)(prisma@6.1.0)(sqlite3@5.1.7): optionalDependencies: '@libsql/client': 0.14.0 '@prisma/client': 6.1.0(prisma@6.1.0) @@ -8860,11 +8749,11 @@ snapshots: prisma: 6.1.0 sqlite3: 5.1.7 - drizzle-seed@0.3.1(drizzle-orm@1.0.0-beta.1-bd417c1(@libsql/client@0.14.0)(@prisma/client@6.1.0(prisma@6.1.0))(@types/pg@8.11.10)(better-sqlite3@11.8.1)(gel@2.0.2)(knex@3.1.0(better-sqlite3@11.8.1)(mysql2@3.14.0)(pg@8.13.2)(sqlite3@5.1.7))(mysql2@3.14.0)(pg@8.13.2)(prisma@6.1.0)(sqlite3@5.1.7)): + drizzle-seed@0.3.1(drizzle-orm@1.0.0-beta.1-7946562(@libsql/client@0.14.0)(@prisma/client@6.1.0(prisma@6.1.0))(@types/pg@8.11.10)(better-sqlite3@11.8.1)(gel@2.0.2)(knex@3.1.0(better-sqlite3@11.8.1)(mysql2@3.14.0)(pg@8.13.2)(sqlite3@5.1.7))(mysql2@3.14.0)(pg@8.13.2)(prisma@6.1.0)(sqlite3@5.1.7)): dependencies: pure-rand: 6.1.0 optionalDependencies: - drizzle-orm: 1.0.0-beta.1-bd417c1(@libsql/client@0.14.0)(@prisma/client@6.1.0(prisma@6.1.0))(@types/pg@8.11.10)(better-sqlite3@11.8.1)(gel@2.0.2)(knex@3.1.0(better-sqlite3@11.8.1)(mysql2@3.14.0)(pg@8.13.2)(sqlite3@5.1.7))(mysql2@3.14.0)(pg@8.13.2)(prisma@6.1.0)(sqlite3@5.1.7) + drizzle-orm: 1.0.0-beta.1-7946562(@libsql/client@0.14.0)(@prisma/client@6.1.0(prisma@6.1.0))(@types/pg@8.11.10)(better-sqlite3@11.8.1)(gel@2.0.2)(knex@3.1.0(better-sqlite3@11.8.1)(mysql2@3.14.0)(pg@8.13.2)(sqlite3@5.1.7))(mysql2@3.14.0)(pg@8.13.2)(prisma@6.1.0)(sqlite3@5.1.7) dset@3.1.3: {} diff --git a/website/content/docs/dataloader.mdx b/website/content/docs/dataloader.mdx index 6822ee7a..5397ce0b 100644 --- a/website/content/docs/dataloader.mdx +++ b/website/content/docs/dataloader.mdx @@ -14,9 +14,9 @@ This leads to the well-known N+1 query problem. To solve this problem, we can us Consider that we have two tables, `users` and `posts`, where `posts` is associated with the `id` of `users` through `posts.authorId`: -```ts twoslash +```ts twoslash tab="schema.ts" import { drizzleSilk } from "@gqloom/drizzle" -import { relations } from "drizzle-orm" +import { defineRelations } from "drizzle-orm" import * as t from "drizzle-orm/pg-core" export const roleEnum = t.pgEnum("role", ["user", "admin"]) @@ -31,10 +31,6 @@ export const users = drizzleSilk( }) ) -export const usersRelations = relations(users, ({ many }) => ({ - posts: many(posts), -})) - export const posts = drizzleSilk( t.pgTable("posts", { id: t.serial().primaryKey(), @@ -48,9 +44,25 @@ export const posts = drizzleSilk( authorId: t.integer().notNull(), }) ) +``` + +```ts twoslash tab="relations.ts" +import { defineRelations } from "drizzle-orm" +import * as tables from "./schema" -export const postsRelations = relations(posts, ({ one }) => ({ - author: one(users, { fields: [posts.authorId], references: [users.id] }), +export const relations = defineRelations(tables, (r) => ({ + users: { + posts: r.many.posts({ + from: r.users.id, + to: r.posts.authorId, + }), + }, + posts: { + author: r.one.users({ + from: r.posts.authorId, + to: r.users.id, + }), + }, })) ``` @@ -61,7 +73,7 @@ Let's use [drizzle-seed](https://orm.drizzle.team/docs/seed-overview) to populat ```ts import { drizzle } from "drizzle-orm/node-postgres" import { reset, seed } from "drizzle-seed" -import { config } from "../env.config" +import { config } from "./env.config" import * as schema from "./schema" async function main() { @@ -91,7 +103,6 @@ Let's write a simple resolver for the `User` object: ```ts twoslash // @filename: schema.ts import { drizzleSilk } from "@gqloom/drizzle" -import { relations } from "drizzle-orm" import * as t from "drizzle-orm/pg-core" export const roleEnum = t.pgEnum("role", ["user", "admin"]) @@ -106,10 +117,6 @@ export const users = drizzleSilk( }) ) -export const usersRelations = relations(users, ({ many }) => ({ - posts: many(posts), -})) - export const posts = drizzleSilk( t.pgTable("posts", { id: t.serial().primaryKey(), @@ -124,8 +131,23 @@ export const posts = drizzleSilk( }) ) -export const postsRelations = relations(posts, ({ one }) => ({ - author: one(users, { fields: [posts.authorId], references: [users.id] }), +// @filename: relations.ts +import { defineRelations } from "drizzle-orm" +import * as tables from "./schema" + +export const relations = defineRelations(tables, (r) => ({ + users: { + posts: r.many.posts({ + from: r.users.id, + to: r.posts.authorId, + }), + }, + posts: { + author: r.one.users({ + from: r.posts.authorId, + to: r.users.id, + }), + }, })) // @filename: env.config.ts export const config = { databaseUrl: "" } @@ -135,10 +157,15 @@ import { field, query, resolver, weave } from "@gqloom/core" import { eq, inArray } from "drizzle-orm" import { drizzle } from "drizzle-orm/node-postgres" import { config } from "./env.config" +import { relations } from "./relations" import * as tables from "./schema" import { posts, users } from "./schema" -const db = drizzle(config.databaseUrl, { schema: tables, logger: true }) +const db = drizzle(config.databaseUrl, { + schema: tables, + relations, + logger: true, +}) const userResolver = resolver.of(users, { users: query(users.$list()).resolve(() => db.select().from(users)), @@ -179,7 +206,6 @@ Next, we will use DataLoader to optimize our query. ```ts twoslash // @filename: schema.ts import { drizzleSilk } from "@gqloom/drizzle" -import { relations } from "drizzle-orm" import * as t from "drizzle-orm/pg-core" export const roleEnum = t.pgEnum("role", ["user", "admin"]) @@ -194,10 +220,6 @@ export const users = drizzleSilk( }) ) -export const usersRelations = relations(users, ({ many }) => ({ - posts: many(posts), -})) - export const posts = drizzleSilk( t.pgTable("posts", { id: t.serial().primaryKey(), @@ -212,9 +234,27 @@ export const posts = drizzleSilk( }) ) -export const postsRelations = relations(posts, ({ one }) => ({ - author: one(users, { fields: [posts.authorId], references: [users.id] }), -})) +// @filename: relations.ts +import { defineRelations } from "drizzle-orm" +import * as tables from "./schema" + +export const relations = defineRelations( + { users: tables.users, posts: tables.posts }, + (r) => ({ + users: { + posts: r.many.posts({ + from: r.users.id, + to: r.posts.authorId, + }), + }, + posts: { + author: r.one.users({ + from: r.posts.authorId, + to: r.users.id, + }), + }, + }) +) // @filename: env.config.ts export const config = { databaseUrl: "" } // @filename: index.ts @@ -223,10 +263,15 @@ import { field, query, resolver, weave } from "@gqloom/core" import { eq, inArray } from "drizzle-orm" import { drizzle } from "drizzle-orm/node-postgres" import { config } from "./env.config" +import { relations } from "./relations" import * as tables from "./schema" import { posts, users } from "./schema" -const db = drizzle(config.databaseUrl, { schema: tables, logger: true }) +const db = drizzle(config.databaseUrl, { + schema: tables, + relations, + logger: true, +}) const userResolver = resolver.of(users, { users: query(users.$list()).resolve(() => db.select().from(users)), diff --git a/website/content/docs/dataloader.zh.mdx b/website/content/docs/dataloader.zh.mdx index df69e862..729cba84 100644 --- a/website/content/docs/dataloader.zh.mdx +++ b/website/content/docs/dataloader.zh.mdx @@ -14,9 +14,9 @@ icon: HardDriveDownload 考虑我们有两张表 `users` 和 `posts`,其中 `posts` 通过 `posts.authorId` 关联到 `users` 的 `id`: -```ts twoslash +```ts twoslash tab="schema.ts" import { drizzleSilk } from "@gqloom/drizzle" -import { relations } from "drizzle-orm" +import { defineRelations } from "drizzle-orm" import * as t from "drizzle-orm/pg-core" export const roleEnum = t.pgEnum("role", ["user", "admin"]) @@ -31,10 +31,6 @@ export const users = drizzleSilk( }) ) -export const usersRelations = relations(users, ({ many }) => ({ - posts: many(posts), -})) - export const posts = drizzleSilk( t.pgTable("posts", { id: t.serial().primaryKey(), @@ -48,9 +44,25 @@ export const posts = drizzleSilk( authorId: t.integer().notNull(), }) ) +``` + +```ts twoslash tab="relations.ts" +import { defineRelations } from "drizzle-orm" +import * as tables from "./schema" -export const postsRelations = relations(posts, ({ one }) => ({ - author: one(users, { fields: [posts.authorId], references: [users.id] }), +export const relations = defineRelations(tables, (r) => ({ + users: { + posts: r.many.posts({ + from: r.users.id, + to: r.posts.authorId, + }), + }, + posts: { + author: r.one.users({ + from: r.posts.authorId, + to: r.users.id, + }), + }, })) ``` @@ -61,7 +73,7 @@ export const postsRelations = relations(posts, ({ one }) => ({ ```ts import { drizzle } from "drizzle-orm/node-postgres" import { reset, seed } from "drizzle-seed" -import { config } from "../env.config" +import { config } from "./env.config" import * as schema from "./schema" async function main() { @@ -91,7 +103,6 @@ main() ```ts twoslash // @filename: schema.ts import { drizzleSilk } from "@gqloom/drizzle" -import { relations } from "drizzle-orm" import * as t from "drizzle-orm/pg-core" export const roleEnum = t.pgEnum("role", ["user", "admin"]) @@ -106,10 +117,6 @@ export const users = drizzleSilk( }) ) -export const usersRelations = relations(users, ({ many }) => ({ - posts: many(posts), -})) - export const posts = drizzleSilk( t.pgTable("posts", { id: t.serial().primaryKey(), @@ -124,8 +131,23 @@ export const posts = drizzleSilk( }) ) -export const postsRelations = relations(posts, ({ one }) => ({ - author: one(users, { fields: [posts.authorId], references: [users.id] }), +// @filename: relations.ts +import { defineRelations } from "drizzle-orm" +import * as tables from "./schema" + +export const relations = defineRelations(tables, (r) => ({ + users: { + posts: r.many.posts({ + from: r.users.id, + to: r.posts.authorId, + }), + }, + posts: { + author: r.one.users({ + from: r.posts.authorId, + to: r.users.id, + }), + }, })) // @filename: env.config.ts export const config = { databaseUrl: "" } @@ -135,10 +157,15 @@ import { field, query, resolver, weave } from "@gqloom/core" import { eq, inArray } from "drizzle-orm" import { drizzle } from "drizzle-orm/node-postgres" import { config } from "./env.config" +import { relations } from "./relations" import * as tables from "./schema" import { posts, users } from "./schema" -const db = drizzle(config.databaseUrl, { schema: tables, logger: true }) +const db = drizzle(config.databaseUrl, { + schema: tables, + relations, + logger: true, +}) const userResolver = resolver.of(users, { users: query(users.$list()).resolve(() => db.select().from(users)), @@ -179,7 +206,6 @@ query usersWithPosts { ```ts twoslash // @filename: schema.ts import { drizzleSilk } from "@gqloom/drizzle" -import { relations } from "drizzle-orm" import * as t from "drizzle-orm/pg-core" export const roleEnum = t.pgEnum("role", ["user", "admin"]) @@ -194,10 +220,6 @@ export const users = drizzleSilk( }) ) -export const usersRelations = relations(users, ({ many }) => ({ - posts: many(posts), -})) - export const posts = drizzleSilk( t.pgTable("posts", { id: t.serial().primaryKey(), @@ -212,9 +234,27 @@ export const posts = drizzleSilk( }) ) -export const postsRelations = relations(posts, ({ one }) => ({ - author: one(users, { fields: [posts.authorId], references: [users.id] }), -})) +// @filename: relations.ts +import { defineRelations } from "drizzle-orm" +import * as tables from "./schema" + +export const relations = defineRelations( + { users: tables.users, posts: tables.posts }, + (r) => ({ + users: { + posts: r.many.posts({ + from: r.users.id, + to: r.posts.authorId, + }), + }, + posts: { + author: r.one.users({ + from: r.posts.authorId, + to: r.users.id, + }), + }, + }) +) // @filename: env.config.ts export const config = { databaseUrl: "" } // @filename: index.ts @@ -223,10 +263,15 @@ import { field, query, resolver, weave } from "@gqloom/core" import { eq, inArray } from "drizzle-orm" import { drizzle } from "drizzle-orm/node-postgres" import { config } from "./env.config" +import { relations } from "./relations" import * as tables from "./schema" import { posts, users } from "./schema" -const db = drizzle(config.databaseUrl, { schema: tables, logger: true }) +const db = drizzle(config.databaseUrl, { + schema: tables, + relations, + logger: true, +}) const userResolver = resolver.of(users, { users: query(users.$list()).resolve(() => db.select().from(users)), diff --git a/website/content/docs/schema/drizzle.md b/website/content/docs/schema/drizzle.md index 63cfed4d..a6f16397 100644 --- a/website/content/docs/schema/drizzle.md +++ b/website/content/docs/schema/drizzle.md @@ -32,9 +32,8 @@ bun add @gqloom/core @gqloom/drizzle We can easily use Drizzle Schemas as [Silk](../silk) by simply wrapping them with `drizzleSilk`. -```ts twoslash title="schema.ts" +```ts twoslash title="schema.ts" tab="schema.ts" import { drizzleSilk } from "@gqloom/drizzle" -import { relations } from "drizzle-orm" import * as t from "drizzle-orm/sqlite-core" export const users = drizzleSilk( @@ -47,10 +46,6 @@ export const users = drizzleSilk( }) ) -export const usersRelations = relations(users, ({ many }) => ({ - posts: many(posts), -})) - export const posts = drizzleSilk( t.sqliteTable("posts", { id: t.int().primaryKey({ autoIncrement: true }), @@ -59,12 +54,25 @@ export const posts = drizzleSilk( authorId: t.int().references(() => users.id, { onDelete: "cascade" }), }) ) +``` -export const postsRelations = relations(posts, ({ one }) => ({ - author: one(users, { - fields: [posts.authorId], - references: [users.id], - }), +```ts twoslash title="relations.ts" tab="relations.ts" +import { defineRelations } from "drizzle-orm" +import * as tables from "./schema" + +export const relations = defineRelations(tables, (r) => ({ + users: { + posts: r.many.posts({ + from: r.users.id, + to: r.posts.authorId, + }), + }, + posts: { + author: r.one.users({ + from: r.posts.authorId, + to: r.users.id, + }), + }, })) ``` @@ -73,7 +81,6 @@ Let's use them in the resolver: ```ts twoslash title="resolver.ts" // @filename: schema.ts import { drizzleSilk } from "@gqloom/drizzle" -import { relations } from "drizzle-orm" import * as t from "drizzle-orm/sqlite-core" export const users = drizzleSilk( @@ -86,10 +93,6 @@ export const users = drizzleSilk( }) ) -export const usersRelations = relations(users, ({ many }) => ({ - posts: many(posts), -})) - export const posts = drizzleSilk( t.sqliteTable("posts", { id: t.int().primaryKey({ autoIncrement: true }), @@ -98,73 +101,63 @@ export const posts = drizzleSilk( authorId: t.int().references(() => users.id, { onDelete: "cascade" }), }) ) - -export const postsRelations = relations(posts, ({ one }) => ({ - author: one(users, { - fields: [posts.authorId], - references: [users.id], - }), +// @filename: relations.ts +import { defineRelations } from "drizzle-orm" +import * as tables from "./schema" + +export const relations = defineRelations(tables, (r) => ({ + users: { + posts: r.many.posts({ + from: r.users.id, + to: r.posts.authorId, + }), + }, + posts: { + author: r.one.users({ + from: r.posts.authorId, + to: r.users.id, + }), + }, })) // @filename: resolver.ts // ---cut--- -import { - EasyDataLoader, - createMemoization, - field, - query, - resolver, -} from "@gqloom/core" +import { field, query, resolver } from "@gqloom/core" import { eq, inArray } from "drizzle-orm" import { drizzle } from "drizzle-orm/libsql" import * as v from "valibot" +import { relations } from "./relations" import * as schema from "./schema" import { posts, users } from "./schema" const db = drizzle({ schema, + relations, connection: { url: process.env.DB_FILE_NAME! }, }) -const usePostsLoader = createMemoization( - () => - new EasyDataLoader< - typeof users.$inferSelect, - (typeof posts.$inferSelect)[] - >(async (userList) => { - const postList = await db - .select() - .from(posts) - .where( - inArray( - users.id, - userList.map((user) => user.id) - ) - ) - const groups = new Map() - - for (const post of postList) { - const key = post.authorId - if (key == null) continue - groups.set(key, [...(groups.get(key) ?? []), post]) - } - return userList.map((user) => groups.get(user.id) ?? []) - }) -) - export const usersResolver = resolver.of(users, { - user: query - .output(users.$nullable()) + user: query(users.$nullable()) .input({ id: v.number() }) .resolve(({ id }) => { return db.select().from(users).where(eq(users.id, id)).get() }), - users: query.output(users.$list()).resolve(() => { + users: query(users.$list()).resolve(() => { return db.select().from(users).all() }), - posts: field.output(posts.$list()).resolve((user) => { - return usePostsLoader().load(user) + posts: field(posts.$list()).load(async (userList) => { + const postList = await db + .select() + .from(posts) + .where( + inArray( + posts.authorId, + userList.map((user) => user.id) + ) + ) + const postMap = Map.groupBy(postList, (p) => p.authorId) + return userList.map((u) => postMap.get(u.id) ?? []) }), }) ``` @@ -225,7 +218,6 @@ export const usersResolver = resolver.of(users, { ```ts twoslash title="resolver.ts" // @filename: schema.ts import { drizzleSilk } from "@gqloom/drizzle" -import { relations } from "drizzle-orm" import * as t from "drizzle-orm/sqlite-core" export const users = drizzleSilk( @@ -238,10 +230,6 @@ export const users = drizzleSilk( }) ) -export const usersRelations = relations(users, ({ many }) => ({ - posts: many(posts), -})) - export const posts = drizzleSilk( t.sqliteTable("posts", { id: t.int().primaryKey({ autoIncrement: true }), @@ -250,21 +238,34 @@ export const posts = drizzleSilk( authorId: t.int().references(() => users.id, { onDelete: "cascade" }), }) ) - -export const postsRelations = relations(posts, ({ one }) => ({ - author: one(users, { - fields: [posts.authorId], - references: [users.id], - }), +// @filename: relations.ts +import { defineRelations } from "drizzle-orm" +import * as tables from "./schema" + +export const relations = defineRelations(tables, (r) => ({ + users: { + posts: r.many.posts({ + from: r.users.id, + to: r.posts.authorId, + }), + }, + posts: { + author: r.one.users({ + from: r.posts.authorId, + to: r.users.id, + }), + }, })) // @filename: resolver.ts // ---cut--- import { drizzleResolverFactory } from "@gqloom/drizzle" import { drizzle } from "drizzle-orm/libsql" +import { relations } from "./relations" import * as schema from "./schema" const db = drizzle({ schema, + relations, connection: { url: process.env.DB_FILE_NAME! }, }) @@ -278,7 +279,6 @@ In Drizzle Table, we can easily create [relationships](https://orm.drizzle.team/ ```ts twoslash title="resolver.ts" // @filename: schema.ts import { drizzleSilk } from "@gqloom/drizzle" -import { relations } from "drizzle-orm" import * as t from "drizzle-orm/sqlite-core" export const users = drizzleSilk( @@ -291,10 +291,6 @@ export const users = drizzleSilk( }) ) -export const usersRelations = relations(users, ({ many }) => ({ - posts: many(posts), -})) - export const posts = drizzleSilk( t.sqliteTable("posts", { id: t.int().primaryKey({ autoIncrement: true }), @@ -303,12 +299,23 @@ export const posts = drizzleSilk( authorId: t.int().references(() => users.id, { onDelete: "cascade" }), }) ) - -export const postsRelations = relations(posts, ({ one }) => ({ - author: one(users, { - fields: [posts.authorId], - references: [users.id], - }), +// @filename: relations.ts +import { defineRelations } from "drizzle-orm" +import * as tables from "./schema" + +export const relations = defineRelations(tables, (r) => ({ + users: { + posts: r.many.posts({ + from: r.users.id, + to: r.posts.authorId, + }), + }, + posts: { + author: r.one.users({ + from: r.posts.authorId, + to: r.users.id, + }), + }, })) // @filename: resolver.ts import { field, EasyDataLoader, createMemoization } from "@gqloom/core" @@ -319,11 +326,13 @@ import { drizzleResolverFactory } from "@gqloom/drizzle" import { eq, inArray } from "drizzle-orm" import { drizzle } from "drizzle-orm/libsql" import * as v from "valibot" +import { relations } from "./relations" import * as schema from "./schema" import { users } from "./schema" const db = drizzle({ schema, + relations, connection: { url: process.env.DB_FILE_NAME! }, }) @@ -387,7 +396,6 @@ We can use the queries from the resolver factory in the resolver: ```ts twoslash // @filename: schema.ts import { drizzleSilk } from "@gqloom/drizzle" -import { relations } from "drizzle-orm" import * as t from "drizzle-orm/sqlite-core" export const users = drizzleSilk( @@ -400,10 +408,6 @@ export const users = drizzleSilk( }) ) -export const usersRelations = relations(users, ({ many }) => ({ - posts: many(posts), -})) - export const posts = drizzleSilk( t.sqliteTable("posts", { id: t.int().primaryKey({ autoIncrement: true }), @@ -412,12 +416,23 @@ export const posts = drizzleSilk( authorId: t.int().references(() => users.id, { onDelete: "cascade" }), }) ) - -export const postsRelations = relations(posts, ({ one }) => ({ - author: one(users, { - fields: [posts.authorId], - references: [users.id], - }), +// @filename: relations.ts +import { defineRelations } from "drizzle-orm" +import * as tables from "./schema" + +export const relations = defineRelations(tables, (r) => ({ + users: { + posts: r.many.posts({ + from: r.users.id, + to: r.posts.authorId, + }), + }, + posts: { + author: r.one.users({ + from: r.posts.authorId, + to: r.users.id, + }), + }, })) // @filename: resolver.ts import { query, resolver } from "@gqloom/core" @@ -425,11 +440,13 @@ import { drizzleResolverFactory } from "@gqloom/drizzle" import { eq } from "drizzle-orm" import { drizzle } from "drizzle-orm/libsql" import * as v from "valibot" +import { relations } from "./relations" import * as schema from "./schema" import { users } from "./schema" const db = drizzle({ schema, + relations, connection: { url: process.env.DB_FILE_NAME! }, }) @@ -469,7 +486,6 @@ We can use the mutations from the resolver factory in the resolver: ```ts twoslash // @filename: schema.ts import { drizzleSilk } from "@gqloom/drizzle" -import { relations } from "drizzle-orm" import * as t from "drizzle-orm/sqlite-core" export const users = drizzleSilk( @@ -482,10 +498,6 @@ export const users = drizzleSilk( }) ) -export const usersRelations = relations(users, ({ many }) => ({ - posts: many(posts), -})) - export const posts = drizzleSilk( t.sqliteTable("posts", { id: t.int().primaryKey({ autoIncrement: true }), @@ -494,23 +506,36 @@ export const posts = drizzleSilk( authorId: t.int().references(() => users.id, { onDelete: "cascade" }), }) ) - -export const postsRelations = relations(posts, ({ one }) => ({ - author: one(users, { - fields: [posts.authorId], - references: [users.id], - }), +// @filename: relations.ts +import { defineRelations } from "drizzle-orm" +import * as tables from "./schema" + +export const relations = defineRelations(tables, (r) => ({ + users: { + posts: r.many.posts({ + from: r.users.id, + to: r.posts.authorId, + }), + }, + posts: { + author: r.one.users({ + from: r.posts.authorId, + to: r.users.id, + }), + }, })) // @filename: resolver.ts import { resolver } from "@gqloom/core" import { drizzleResolverFactory } from "@gqloom/drizzle" import { drizzle } from "drizzle-orm/libsql" import * as v from "valibot" +import { relations } from "./relations" import * as schema from "./schema" import { users } from "./schema" const db = drizzle({ schema, + relations, connection: { url: process.env.DB_FILE_NAME! }, }) @@ -536,7 +561,6 @@ The pre-defined queries and mutations of the resolver factory support custom inp ```ts twoslash // @filename: schema.ts import { drizzleSilk } from "@gqloom/drizzle" -import { relations } from "drizzle-orm" import * as t from "drizzle-orm/sqlite-core" export const users = drizzleSilk( @@ -549,10 +573,6 @@ export const users = drizzleSilk( }) ) -export const usersRelations = relations(users, ({ many }) => ({ - posts: many(posts), -})) - export const posts = drizzleSilk( t.sqliteTable("posts", { id: t.int().primaryKey({ autoIncrement: true }), @@ -561,24 +581,36 @@ export const posts = drizzleSilk( authorId: t.int().references(() => users.id, { onDelete: "cascade" }), }) ) - -export const postsRelations = relations(posts, ({ one }) => ({ - author: one(users, { - fields: [posts.authorId], - references: [users.id], - }), +// @filename: relations.ts +import { defineRelations } from "drizzle-orm" +import * as tables from "./schema" + +export const relations = defineRelations(tables, (r) => ({ + users: { + posts: r.many.posts({ + from: r.users.id, + to: r.posts.authorId, + }), + }, + posts: { + author: r.one.users({ + from: r.posts.authorId, + to: r.users.id, + }), + }, })) // @filename: resolver.ts import { query, resolver } from "@gqloom/core" import { drizzleResolverFactory } from "@gqloom/drizzle" -import { eq } from "drizzle-orm" import { drizzle } from "drizzle-orm/libsql" import * as v from "valibot" +import { relations } from "./relations" import * as schema from "./schema" import { users } from "./schema" const db = drizzle({ schema, + relations, connection: { url: process.env.DB_FILE_NAME! }, }) @@ -588,7 +620,7 @@ export const usersResolver = resolver.of(users, { user: usersResolverFactory.selectSingleQuery().input( v.pipe( // [!code hl] v.object({ id: v.number() }), // [!code hl] - v.transform(({ id }) => ({ where: eq(users.id, id) })) // [!code hl] + v.transform(({ id }) => ({ where: { id } })) // [!code hl] ) // [!code hl] ), @@ -598,7 +630,7 @@ export const usersResolver = resolver.of(users, { }) ``` -In the above code, we use `valibot` to define the input type. `v.object({ id: v.number() })` defines the type of the input object, and `v.transform(({ id }) => ({ where: eq(users.id, id) }))` converts the input parameters into Prisma query parameters. +In the above code, we use `valibot` to define the input type. `v.object({ id: v.number() })` defines the type of the input object, and `v.transform(({ id }) => ({ where: { id } }))` converts the input parameters into Prisma query parameters. ### Adding Middleware @@ -607,7 +639,6 @@ The pre-defined queries, mutations, and fields of the resolver factory support a ```ts twoslash // @filename: schema.ts import { drizzleSilk } from "@gqloom/drizzle" -import { relations } from "drizzle-orm" import * as t from "drizzle-orm/sqlite-core" export const users = drizzleSilk( @@ -620,10 +651,6 @@ export const users = drizzleSilk( }) ) -export const usersRelations = relations(users, ({ many }) => ({ - posts: many(posts), -})) - export const posts = drizzleSilk( t.sqliteTable("posts", { id: t.int().primaryKey({ autoIncrement: true }), @@ -632,12 +659,23 @@ export const posts = drizzleSilk( authorId: t.int().references(() => users.id, { onDelete: "cascade" }), }) ) - -export const postsRelations = relations(posts, ({ one }) => ({ - author: one(users, { - fields: [posts.authorId], - references: [users.id], - }), +// @filename: relations.ts +import { defineRelations } from "drizzle-orm" +import * as tables from "./schema" + +export const relations = defineRelations(tables, (r) => ({ + users: { + posts: r.many.posts({ + from: r.users.id, + to: r.posts.authorId, + }), + }, + posts: { + author: r.one.users({ + from: r.posts.authorId, + to: r.users.id, + }), + }, })) // @filename: resolver.ts import { query, field, resolver, createMemoization } from "@gqloom/core" @@ -646,11 +684,13 @@ import { eq } from "drizzle-orm" import { drizzle } from "drizzle-orm/libsql" import { GraphQLError } from "graphql" import * as v from "valibot" +import { relations } from "./relations" import * as schema from "./schema" import { users, posts } from "./schema" const db = drizzle({ schema, + relations, connection: { url: process.env.DB_FILE_NAME! }, }) @@ -684,7 +724,6 @@ We can directly create a complete Resolver with the resolver factory: ```ts twoslash // @filename: schema.ts import { drizzleSilk } from "@gqloom/drizzle" -import { relations } from "drizzle-orm" import * as t from "drizzle-orm/sqlite-core" export const users = drizzleSilk( @@ -697,10 +736,6 @@ export const users = drizzleSilk( }) ) -export const usersRelations = relations(users, ({ many }) => ({ - posts: many(posts), -})) - export const posts = drizzleSilk( t.sqliteTable("posts", { id: t.int().primaryKey({ autoIncrement: true }), @@ -709,12 +744,23 @@ export const posts = drizzleSilk( authorId: t.int().references(() => users.id, { onDelete: "cascade" }), }) ) - -export const postsRelations = relations(posts, ({ one }) => ({ - author: one(users, { - fields: [posts.authorId], - references: [users.id], - }), +// @filename: relations.ts +import { defineRelations } from "drizzle-orm" +import * as tables from "./schema" + +export const relations = defineRelations(tables, (r) => ({ + users: { + posts: r.many.posts({ + from: r.users.id, + to: r.posts.authorId, + }), + }, + posts: { + author: r.one.users({ + from: r.posts.authorId, + to: r.users.id, + }), + }, })) // @filename: resolver.ts import { query, resolver } from "@gqloom/core" @@ -722,11 +768,13 @@ import { drizzleResolverFactory } from "@gqloom/drizzle" import { eq } from "drizzle-orm" import { drizzle } from "drizzle-orm/libsql" import * as v from "valibot" +import { relations } from "./relations" import * as schema from "./schema" import { users } from "./schema" const db = drizzle({ schema, + relations, connection: { url: process.env.DB_FILE_NAME! }, }) diff --git a/website/content/docs/schema/drizzle.zh.md b/website/content/docs/schema/drizzle.zh.md index a69ab80d..c531fe3c 100644 --- a/website/content/docs/schema/drizzle.zh.md +++ b/website/content/docs/schema/drizzle.zh.md @@ -32,9 +32,8 @@ bun add @gqloom/core @gqloom/drizzle 只需要使用 `drizzleSilk` 包裹 Drizzle Table,我们就可以轻松地将它们作为[丝线](../silk)使用。 -```ts twoslash title="schema.ts" +```ts twoslash title="schema.ts" tab="schema.ts" import { drizzleSilk } from "@gqloom/drizzle" -import { relations } from "drizzle-orm" import * as t from "drizzle-orm/sqlite-core" export const users = drizzleSilk( @@ -47,10 +46,6 @@ export const users = drizzleSilk( }) ) -export const usersRelations = relations(users, ({ many }) => ({ - posts: many(posts), -})) - export const posts = drizzleSilk( t.sqliteTable("posts", { id: t.int().primaryKey({ autoIncrement: true }), @@ -59,12 +54,25 @@ export const posts = drizzleSilk( authorId: t.int().references(() => users.id, { onDelete: "cascade" }), }) ) +``` -export const postsRelations = relations(posts, ({ one }) => ({ - author: one(users, { - fields: [posts.authorId], - references: [users.id], - }), +```ts twoslash title="relations.ts" tab="relations.ts" +import { defineRelations } from "drizzle-orm" +import * as tables from "./schema" + +export const relations = defineRelations(tables, (r) => ({ + users: { + posts: r.many.posts({ + from: r.users.id, + to: r.posts.authorId, + }), + }, + posts: { + author: r.one.users({ + from: r.posts.authorId, + to: r.users.id, + }), + }, })) ``` @@ -73,7 +81,6 @@ export const postsRelations = relations(posts, ({ one }) => ({ ```ts twoslash title="resolver.ts" // @filename: schema.ts import { drizzleSilk } from "@gqloom/drizzle" -import { relations } from "drizzle-orm" import * as t from "drizzle-orm/sqlite-core" export const users = drizzleSilk( @@ -86,10 +93,6 @@ export const users = drizzleSilk( }) ) -export const usersRelations = relations(users, ({ many }) => ({ - posts: many(posts), -})) - export const posts = drizzleSilk( t.sqliteTable("posts", { id: t.int().primaryKey({ autoIncrement: true }), @@ -98,73 +101,63 @@ export const posts = drizzleSilk( authorId: t.int().references(() => users.id, { onDelete: "cascade" }), }) ) - -export const postsRelations = relations(posts, ({ one }) => ({ - author: one(users, { - fields: [posts.authorId], - references: [users.id], - }), +// @filename: relations.ts +import { defineRelations } from "drizzle-orm" +import * as tables from "./schema" + +export const relations = defineRelations(tables, (r) => ({ + users: { + posts: r.many.posts({ + from: r.users.id, + to: r.posts.authorId, + }), + }, + posts: { + author: r.one.users({ + from: r.posts.authorId, + to: r.users.id, + }), + }, })) // @filename: resolver.ts // ---cut--- -import { - EasyDataLoader, - createMemoization, - field, - query, - resolver, -} from "@gqloom/core" +import { field, query, resolver } from "@gqloom/core" import { eq, inArray } from "drizzle-orm" import { drizzle } from "drizzle-orm/libsql" import * as v from "valibot" +import { relations } from "./relations" import * as schema from "./schema" import { posts, users } from "./schema" const db = drizzle({ schema, + relations, connection: { url: process.env.DB_FILE_NAME! }, }) -const usePostsLoader = createMemoization( - () => - new EasyDataLoader< - typeof users.$inferSelect, - (typeof posts.$inferSelect)[] - >(async (userList) => { - const postList = await db - .select() - .from(posts) - .where( - inArray( - users.id, - userList.map((user) => user.id) - ) - ) - const groups = new Map() - - for (const post of postList) { - const key = post.authorId - if (key == null) continue - groups.set(key, [...(groups.get(key) ?? []), post]) - } - return userList.map((user) => groups.get(user.id) ?? []) - }) -) - export const usersResolver = resolver.of(users, { - user: query - .output(users.$nullable()) + user: query(users.$nullable()) .input({ id: v.number() }) .resolve(({ id }) => { return db.select().from(users).where(eq(users.id, id)).get() }), - users: query.output(users.$list()).resolve(() => { + users: query(users.$list()).resolve(() => { return db.select().from(users).all() }), - posts: field.output(posts.$list()).resolve((user) => { - return usePostsLoader().load(user) + posts: field(posts.$list()).load(async (userList) => { + const postList = await db + .select() + .from(posts) + .where( + inArray( + posts.authorId, + userList.map((user) => user.id) + ) + ) + const postMap = Map.groupBy(postList, (p) => p.authorId) + return userList.map((u) => postMap.get(u.id) ?? []) }), }) ``` @@ -225,7 +218,6 @@ export const usersResolver = resolver.of(users, { ```ts twoslash title="resolver.ts" // @filename: schema.ts import { drizzleSilk } from "@gqloom/drizzle" -import { relations } from "drizzle-orm" import * as t from "drizzle-orm/sqlite-core" export const users = drizzleSilk( @@ -238,10 +230,6 @@ export const users = drizzleSilk( }) ) -export const usersRelations = relations(users, ({ many }) => ({ - posts: many(posts), -})) - export const posts = drizzleSilk( t.sqliteTable("posts", { id: t.int().primaryKey({ autoIncrement: true }), @@ -250,21 +238,34 @@ export const posts = drizzleSilk( authorId: t.int().references(() => users.id, { onDelete: "cascade" }), }) ) - -export const postsRelations = relations(posts, ({ one }) => ({ - author: one(users, { - fields: [posts.authorId], - references: [users.id], - }), +// @filename: relations.ts +import { defineRelations } from "drizzle-orm" +import * as tables from "./schema" + +export const relations = defineRelations(tables, (r) => ({ + users: { + posts: r.many.posts({ + from: r.users.id, + to: r.posts.authorId, + }), + }, + posts: { + author: r.one.users({ + from: r.posts.authorId, + to: r.users.id, + }), + }, })) // @filename: resolver.ts // ---cut--- import { drizzleResolverFactory } from "@gqloom/drizzle" import { drizzle } from "drizzle-orm/libsql" +import { relations } from "./relations" import * as schema from "./schema" const db = drizzle({ schema, + relations, connection: { url: process.env.DB_FILE_NAME! }, }) @@ -278,7 +279,6 @@ const usersResolverFactory = drizzleResolverFactory(db, "users") ```ts twoslash title="resolver.ts" // @filename: schema.ts import { drizzleSilk } from "@gqloom/drizzle" -import { relations } from "drizzle-orm" import * as t from "drizzle-orm/sqlite-core" export const users = drizzleSilk( @@ -291,10 +291,6 @@ export const users = drizzleSilk( }) ) -export const usersRelations = relations(users, ({ many }) => ({ - posts: many(posts), -})) - export const posts = drizzleSilk( t.sqliteTable("posts", { id: t.int().primaryKey({ autoIncrement: true }), @@ -303,12 +299,23 @@ export const posts = drizzleSilk( authorId: t.int().references(() => users.id, { onDelete: "cascade" }), }) ) - -export const postsRelations = relations(posts, ({ one }) => ({ - author: one(users, { - fields: [posts.authorId], - references: [users.id], - }), +// @filename: relations.ts +import { defineRelations } from "drizzle-orm" +import * as tables from "./schema" + +export const relations = defineRelations(tables, (r) => ({ + users: { + posts: r.many.posts({ + from: r.users.id, + to: r.posts.authorId, + }), + }, + posts: { + author: r.one.users({ + from: r.posts.authorId, + to: r.users.id, + }), + }, })) // @filename: resolver.ts import { field, EasyDataLoader, createMemoization } from "@gqloom/core" @@ -319,11 +326,13 @@ import { drizzleResolverFactory } from "@gqloom/drizzle" import { eq, inArray } from "drizzle-orm" import { drizzle } from "drizzle-orm/libsql" import * as v from "valibot" +import { relations } from "./relations" import * as schema from "./schema" import { users } from "./schema" const db = drizzle({ schema, + relations, connection: { url: process.env.DB_FILE_NAME! }, }) @@ -387,7 +396,6 @@ Drizzle 解析器工厂预置了一些常用的查询: ```ts twoslash // @filename: schema.ts import { drizzleSilk } from "@gqloom/drizzle" -import { relations } from "drizzle-orm" import * as t from "drizzle-orm/sqlite-core" export const users = drizzleSilk( @@ -400,10 +408,6 @@ export const users = drizzleSilk( }) ) -export const usersRelations = relations(users, ({ many }) => ({ - posts: many(posts), -})) - export const posts = drizzleSilk( t.sqliteTable("posts", { id: t.int().primaryKey({ autoIncrement: true }), @@ -412,12 +416,23 @@ export const posts = drizzleSilk( authorId: t.int().references(() => users.id, { onDelete: "cascade" }), }) ) - -export const postsRelations = relations(posts, ({ one }) => ({ - author: one(users, { - fields: [posts.authorId], - references: [users.id], - }), +// @filename: relations.ts +import { defineRelations } from "drizzle-orm" +import * as tables from "./schema" + +export const relations = defineRelations(tables, (r) => ({ + users: { + posts: r.many.posts({ + from: r.users.id, + to: r.posts.authorId, + }), + }, + posts: { + author: r.one.users({ + from: r.posts.authorId, + to: r.users.id, + }), + }, })) // @filename: resolver.ts import { query, resolver } from "@gqloom/core" @@ -425,11 +440,13 @@ import { drizzleResolverFactory } from "@gqloom/drizzle" import { eq } from "drizzle-orm" import { drizzle } from "drizzle-orm/libsql" import * as v from "valibot" +import { relations } from "./relations" import * as schema from "./schema" import { users } from "./schema" const db = drizzle({ schema, + relations, connection: { url: process.env.DB_FILE_NAME! }, }) @@ -469,7 +486,6 @@ Drizzle 解析器工厂预置了一些常用的变更: ```ts twoslash // @filename: schema.ts import { drizzleSilk } from "@gqloom/drizzle" -import { relations } from "drizzle-orm" import * as t from "drizzle-orm/sqlite-core" export const users = drizzleSilk( @@ -482,10 +498,6 @@ export const users = drizzleSilk( }) ) -export const usersRelations = relations(users, ({ many }) => ({ - posts: many(posts), -})) - export const posts = drizzleSilk( t.sqliteTable("posts", { id: t.int().primaryKey({ autoIncrement: true }), @@ -494,23 +506,36 @@ export const posts = drizzleSilk( authorId: t.int().references(() => users.id, { onDelete: "cascade" }), }) ) - -export const postsRelations = relations(posts, ({ one }) => ({ - author: one(users, { - fields: [posts.authorId], - references: [users.id], - }), +// @filename: relations.ts +import { defineRelations } from "drizzle-orm" +import * as tables from "./schema" + +export const relations = defineRelations(tables, (r) => ({ + users: { + posts: r.many.posts({ + from: r.users.id, + to: r.posts.authorId, + }), + }, + posts: { + author: r.one.users({ + from: r.posts.authorId, + to: r.users.id, + }), + }, })) // @filename: resolver.ts import { resolver } from "@gqloom/core" import { drizzleResolverFactory } from "@gqloom/drizzle" import { drizzle } from "drizzle-orm/libsql" import * as v from "valibot" +import { relations } from "./relations" import * as schema from "./schema" import { users } from "./schema" const db = drizzle({ schema, + relations, connection: { url: process.env.DB_FILE_NAME! }, }) @@ -536,7 +561,6 @@ export const usersResolver = resolver.of(users, { ```ts twoslash // @filename: schema.ts import { drizzleSilk } from "@gqloom/drizzle" -import { relations } from "drizzle-orm" import * as t from "drizzle-orm/sqlite-core" export const users = drizzleSilk( @@ -549,10 +573,6 @@ export const users = drizzleSilk( }) ) -export const usersRelations = relations(users, ({ many }) => ({ - posts: many(posts), -})) - export const posts = drizzleSilk( t.sqliteTable("posts", { id: t.int().primaryKey({ autoIncrement: true }), @@ -561,24 +581,36 @@ export const posts = drizzleSilk( authorId: t.int().references(() => users.id, { onDelete: "cascade" }), }) ) - -export const postsRelations = relations(posts, ({ one }) => ({ - author: one(users, { - fields: [posts.authorId], - references: [users.id], - }), +// @filename: relations.ts +import { defineRelations } from "drizzle-orm" +import * as tables from "./schema" + +export const relations = defineRelations(tables, (r) => ({ + users: { + posts: r.many.posts({ + from: r.users.id, + to: r.posts.authorId, + }), + }, + posts: { + author: r.one.users({ + from: r.posts.authorId, + to: r.users.id, + }), + }, })) // @filename: resolver.ts import { query, resolver } from "@gqloom/core" import { drizzleResolverFactory } from "@gqloom/drizzle" -import { eq } from "drizzle-orm" import { drizzle } from "drizzle-orm/libsql" import * as v from "valibot" +import { relations } from "./relations" import * as schema from "./schema" import { users } from "./schema" const db = drizzle({ schema, + relations, connection: { url: process.env.DB_FILE_NAME! }, }) @@ -588,7 +620,7 @@ export const usersResolver = resolver.of(users, { user: usersResolverFactory.selectSingleQuery().input( v.pipe( // [!code hl] v.object({ id: v.number() }), // [!code hl] - v.transform(({ id }) => ({ where: eq(users.id, id) })) // [!code hl] + v.transform(({ id }) => ({ where: { id } })) // [!code hl] ) // [!code hl] ), @@ -598,7 +630,7 @@ export const usersResolver = resolver.of(users, { }) ``` -在上面的代码中,我们使用 `valibot` 来定义输入类型, `v.object({ id: v.number() })` 定义了输入对象的类型,`v.transform(({ id }) => ({ where: eq(users.id, id) }))` 将输入参数转换为 Prisma 的查询参数。 +在上面的代码中,我们使用 `valibot` 来定义输入类型, `v.object({ id: v.number() })` 定义了输入对象的类型,`v.transform(({ id }) => ({ where: { id } }))` 将输入参数转换为 Prisma 的查询参数。 ### 添加中间件 @@ -607,7 +639,6 @@ export const usersResolver = resolver.of(users, { ```ts twoslash // @filename: schema.ts import { drizzleSilk } from "@gqloom/drizzle" -import { relations } from "drizzle-orm" import * as t from "drizzle-orm/sqlite-core" export const users = drizzleSilk( @@ -620,10 +651,6 @@ export const users = drizzleSilk( }) ) -export const usersRelations = relations(users, ({ many }) => ({ - posts: many(posts), -})) - export const posts = drizzleSilk( t.sqliteTable("posts", { id: t.int().primaryKey({ autoIncrement: true }), @@ -632,12 +659,23 @@ export const posts = drizzleSilk( authorId: t.int().references(() => users.id, { onDelete: "cascade" }), }) ) - -export const postsRelations = relations(posts, ({ one }) => ({ - author: one(users, { - fields: [posts.authorId], - references: [users.id], - }), +// @filename: relations.ts +import { defineRelations } from "drizzle-orm" +import * as tables from "./schema" + +export const relations = defineRelations(tables, (r) => ({ + users: { + posts: r.many.posts({ + from: r.users.id, + to: r.posts.authorId, + }), + }, + posts: { + author: r.one.users({ + from: r.posts.authorId, + to: r.users.id, + }), + }, })) // @filename: resolver.ts import { query, field, resolver, createMemoization } from "@gqloom/core" @@ -646,11 +684,13 @@ import { eq } from "drizzle-orm" import { drizzle } from "drizzle-orm/libsql" import { GraphQLError } from "graphql" import * as v from "valibot" +import { relations } from "./relations" import * as schema from "./schema" import { users, posts } from "./schema" const db = drizzle({ schema, + relations, connection: { url: process.env.DB_FILE_NAME! }, }) @@ -684,7 +724,6 @@ const postResolver = resolver.of(posts, { ```ts twoslash // @filename: schema.ts import { drizzleSilk } from "@gqloom/drizzle" -import { relations } from "drizzle-orm" import * as t from "drizzle-orm/sqlite-core" export const users = drizzleSilk( @@ -697,10 +736,6 @@ export const users = drizzleSilk( }) ) -export const usersRelations = relations(users, ({ many }) => ({ - posts: many(posts), -})) - export const posts = drizzleSilk( t.sqliteTable("posts", { id: t.int().primaryKey({ autoIncrement: true }), @@ -709,12 +744,23 @@ export const posts = drizzleSilk( authorId: t.int().references(() => users.id, { onDelete: "cascade" }), }) ) - -export const postsRelations = relations(posts, ({ one }) => ({ - author: one(users, { - fields: [posts.authorId], - references: [users.id], - }), +// @filename: relations.ts +import { defineRelations } from "drizzle-orm" +import * as tables from "./schema" + +export const relations = defineRelations(tables, (r) => ({ + users: { + posts: r.many.posts({ + from: r.users.id, + to: r.posts.authorId, + }), + }, + posts: { + author: r.one.users({ + from: r.posts.authorId, + to: r.users.id, + }), + }, })) // @filename: resolver.ts import { query, resolver } from "@gqloom/core" @@ -722,11 +768,13 @@ import { drizzleResolverFactory } from "@gqloom/drizzle" import { eq } from "drizzle-orm" import { drizzle } from "drizzle-orm/libsql" import * as v from "valibot" +import { relations } from "./relations" import * as schema from "./schema" import { users } from "./schema" const db = drizzle({ schema, + relations, connection: { url: process.env.DB_FILE_NAME! }, }) diff --git a/website/content/home/drizzle.md b/website/content/home/drizzle.md index b44c2067..a0f8779b 100644 --- a/website/content/home/drizzle.md +++ b/website/content/home/drizzle.md @@ -1,7 +1,5 @@ ```ts twoslash title="src/index.ts" tab="index.ts" -// @filename: schema.ts import { drizzleSilk } from "@gqloom/drizzle" -import { relations } from "drizzle-orm" import * as t from "drizzle-orm/pg-core" export const roleEnum = t.pgEnum("role", ["user", "admin"]) @@ -16,9 +14,37 @@ export const users = drizzleSilk( }) ) -export const usersRelations = relations(users, ({ many }) => ({ - posts: many(posts), -})) +export const posts = drizzleSilk( + t.pgTable("posts", { + id: t.serial().primaryKey(), + createdAt: t.timestamp().defaultNow(), + updatedAt: t + .timestamp() + .defaultNow() + .$onUpdateFn(() => new Date()), + published: t.boolean().default(false), + title: t.varchar({ length: 255 }).notNull(), + authorId: t.integer(), + }) +) +``` + +```ts twoslash title="src/relations.ts" tab="relations.ts" +// @filename: schema.ts +import { drizzleSilk } from "@gqloom/drizzle" +import * as t from "drizzle-orm/pg-core" + +export const roleEnum = t.pgEnum("role", ["user", "admin"]) + +export const users = drizzleSilk( + t.pgTable("users", { + id: t.serial().primaryKey(), + createdAt: t.timestamp().defaultNow(), + email: t.text().unique().notNull(), + name: t.text(), + role: roleEnum().default("user"), + }) +) export const posts = drizzleSilk( t.pgTable("posts", { @@ -34,8 +60,17 @@ export const posts = drizzleSilk( }) ) -export const postsRelations = relations(posts, ({ one }) => ({ - author: one(users, { fields: [posts.authorId], references: [users.id] }), +// @filename: relations.ts +import { defineRelations } from "drizzle-orm" +import * as tables from "./schema" + +export const relations = defineRelations(tables, (r) => ({ + users: { + posts: r.many.posts({ from: r.users.id, to: r.posts.authorId }), + }, + posts: { + author: r.one.users({ from: r.posts.authorId, to: r.users.id }), + }, })) // @filename: index.ts // ---cut--- @@ -44,9 +79,10 @@ import { weave } from "@gqloom/core" import { drizzleResolverFactory } from "@gqloom/drizzle" import { drizzle } from "drizzle-orm/node-postgres" import { createYoga } from "graphql-yoga" +import { relations } from "./relations" import * as tables from "./schema" -const db = drizzle(process.env.DATABASE_URL!, { schema: tables }) +const db = drizzle(process.env.DATABASE_URL!, { schema: tables, relations }) const userResolver = drizzleResolverFactory(db, "users").resolver() const postResolver = drizzleResolverFactory(db, "posts").resolver() @@ -62,7 +98,6 @@ server.listen(4000, () => { ```ts twoslash title="src/schema.ts" tab="schema.ts" import { drizzleSilk } from "@gqloom/drizzle" -import { relations } from "drizzle-orm" import * as t from "drizzle-orm/pg-core" export const roleEnum = t.pgEnum("role", ["user", "admin"]) @@ -77,9 +112,37 @@ export const users = drizzleSilk( }) ) -export const usersRelations = relations(users, ({ many }) => ({ - posts: many(posts), -})) +export const posts = drizzleSilk( + t.pgTable("posts", { + id: t.serial().primaryKey(), + createdAt: t.timestamp().defaultNow(), + updatedAt: t + .timestamp() + .defaultNow() + .$onUpdateFn(() => new Date()), + published: t.boolean().default(false), + title: t.varchar({ length: 255 }).notNull(), + authorId: t.integer(), + }) +) +``` + +```ts twoslash title="src/relations.ts" tab="relations.ts" +// @filename: schema.ts +import { drizzleSilk } from "@gqloom/drizzle" +import * as t from "drizzle-orm/pg-core" + +export const roleEnum = t.pgEnum("role", ["user", "admin"]) + +export const users = drizzleSilk( + t.pgTable("users", { + id: t.serial().primaryKey(), + createdAt: t.timestamp().defaultNow(), + email: t.text().unique().notNull(), + name: t.text(), + role: roleEnum().default("user"), + }) +) export const posts = drizzleSilk( t.pgTable("posts", { @@ -95,8 +158,18 @@ export const posts = drizzleSilk( }) ) -export const postsRelations = relations(posts, ({ one }) => ({ - author: one(users, { fields: [posts.authorId], references: [users.id] }), +// @filename: relations.ts +// ---cut--- +import { defineRelations } from "drizzle-orm" +import * as tables from "./schema" + +export const relations = defineRelations(tables, (r) => ({ + users: { + posts: r.many.posts({ from: r.users.id, to: r.posts.authorId }), + }, + posts: { + author: r.one.users({ from: r.posts.authorId, to: r.users.id }), + }, })) ``` diff --git a/website/package.json b/website/package.json index b29bf2b5..3c46db99 100644 --- a/website/package.json +++ b/website/package.json @@ -43,8 +43,8 @@ "@types/react": "^19.0.8", "@types/react-dom": "^19.0.3", "dotenv": "^16.4.7", - "drizzle-kit": "^0.30.1", - "drizzle-orm": "^0.39.3", + "drizzle-kit": "1.0.0-beta.1-7946562", + "drizzle-orm": "1.0.0-beta.1-7946562", "fastify": "^4.28.1", "graphql": "^16.8.1", "graphql-scalars": "^1.24.1", From 8aff1e253de306b5a1004d7f2c1bfce461801247 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E2=9B=B5?= <809067559@qq.com> Date: Wed, 9 Apr 2025 14:02:58 +0800 Subject: [PATCH 12/54] docs(getting-started): update database schema and remove user service --- website/content/docs/getting-started.mdx | 682 +++++++++----------- website/content/docs/getting-started.zh.mdx | 682 +++++++++----------- 2 files changed, 612 insertions(+), 752 deletions(-) diff --git a/website/content/docs/getting-started.mdx b/website/content/docs/getting-started.mdx index 8f295e0d..23d8039a 100644 --- a/website/content/docs/getting-started.mdx +++ b/website/content/docs/getting-started.mdx @@ -44,10 +44,7 @@ Our application will have the following structure: - - - - + @@ -291,40 +288,68 @@ yarn add -D drizzle-kit Next, define the database tables in the `src/schema/index.ts` file. We will define two tables, `users` and `cats`, and establish the relationship between them: -```ts twoslash title="src/schema/index.ts" +```ts twoslash title="src/schema/index.ts" tab="src/schema/index.ts" import { drizzleSilk } from "@gqloom/drizzle" -import { relations } from "drizzle-orm" -import * as t from "drizzle-orm/sqlite-core" +import { integer, sqliteTable, text } from "drizzle-orm/sqlite-core" export const users = drizzleSilk( - t.sqliteTable("users", { - id: t.int().primaryKey({ autoIncrement: true }), - name: t.text().notNull(), - phone: t.text().notNull().unique(), + sqliteTable("users", { + id: integer().primaryKey({ autoIncrement: true }), + name: text().notNull(), + phone: text().notNull().unique(), }) ) -export const usersRelations = relations(users, ({ many }) => ({ - cats: many(cats), -})) +export const cats = drizzleSilk( + sqliteTable("cats", { + id: integer().primaryKey({ autoIncrement: true }), + name: text().notNull(), + birthday: integer({ mode: "timestamp" }).notNull(), + ownerId: integer() + .notNull() + .references(() => users.id), + }) +) +``` + +```ts twoslash title="src/schema/relations.ts" tab="src/schema/relations.ts" +// @filename: index.ts +import { drizzleSilk } from "@gqloom/drizzle" +import { integer, sqliteTable, text } from "drizzle-orm/sqlite-core" + +export const users = drizzleSilk( + sqliteTable("users", { + id: integer().primaryKey({ autoIncrement: true }), + name: text().notNull(), + phone: text().notNull().unique(), + }) +) export const cats = drizzleSilk( - t.sqliteTable("cats", { - id: t.integer().primaryKey({ autoIncrement: true }), - name: t.text().notNull(), - birthday: t.integer({ mode: "timestamp" }).notNull(), - ownerId: t - .integer() + sqliteTable("cats", { + id: integer().primaryKey({ autoIncrement: true }), + name: text().notNull(), + birthday: integer({ mode: "timestamp" }).notNull(), + ownerId: integer() .notNull() .references(() => users.id), }) ) +// @filename: relations.ts +// ---cut--- +import { defineRelations } from "drizzle-orm" +import * as schema from "./index" -export const catsRelations = relations(cats, ({ one }) => ({ - owner: one(users, { - fields: [cats.ownerId], - references: [users.id], - }), +export const relations = defineRelations(schema, (r) => ({ + users: { + cats: r.many.cats(), + }, + cats: { + owner: r.one.users({ + from: r.cats.ownerId, + to: r.users.id, + }), + }, })) ``` @@ -353,45 +378,18 @@ npx drizzle-kit push ### Use the Database To use the database in the application, we need to create a database instance: + ```ts title="src/providers/index.ts" import { drizzle } from "drizzle-orm/libsql" import * as schema from "../schema" +import { relations } from "../schema/relations" export const db = drizzle(process.env.DB_FILE_NAME ?? "file:local.db", { schema, + relations, }) ``` -Let's first create a user service, which will contain a series of operations on the user table. -We will implement the user service in the `src/services/user.ts` file and export the entire `user.ts` as `userService` in the `src/resolvers/index.ts` file: - -```ts title="src/services/user.ts" tab="src/services/user.ts" -import { eq } from "drizzle-orm" -import { db } from "../providers" -import { users } from "../schema" - -export async function createUser(input: typeof users.$inferInsert) { - const [user] = await db.insert(users).values(input).returning() - return user -} - -export async function findUsersByName(name: string) { - return await db.query.users.findMany({ - where: eq(users.name, name), - }) -} - -export async function findUserByPhone(phone: string) { - return await db.query.users.findFirst({ - where: eq(users.phone, phone), - }) -} -``` - -```ts title="src/services/index.ts" tab="src/services/index.ts" -export * as userService from "./user" -``` - ## Resolvers Now, we can use the user service in the resolver. We will create a user resolver and add the following operations: @@ -407,86 +405,67 @@ After completing the user resolver, we also need to add it to the `resolvers` in ```ts twoslash title="src/resolvers/user.ts" -// @filename: schema.ts +// @filename: schema/index.ts import { drizzleSilk } from "@gqloom/drizzle" -import { relations } from "drizzle-orm" -import * as t from "drizzle-orm/sqlite-core" +import { integer, sqliteTable, text } from "drizzle-orm/sqlite-core" export const users = drizzleSilk( - t.sqliteTable("users", { - id: t.int().primaryKey({ autoIncrement: true }), - name: t.text().notNull(), - phone: t.text().notNull().unique(), + sqliteTable("users", { + id: integer().primaryKey({ autoIncrement: true }), + name: text().notNull(), + phone: text().notNull().unique(), }) ) -export const usersRelations = relations(users, ({ many }) => ({ - cats: many(cats), -})) - export const cats = drizzleSilk( - t.sqliteTable("cats", { - id: t.integer().primaryKey({ autoIncrement: true }), - name: t.text().notNull(), - birthday: t.integer({ mode: "timestamp" }).notNull(), - ownerId: t - .integer() + sqliteTable("cats", { + id: integer().primaryKey({ autoIncrement: true }), + name: text().notNull(), + birthday: integer({ mode: "timestamp" }).notNull(), + ownerId: integer() .notNull() .references(() => users.id), }) ) +// @filename: schema/relations.ts +import { defineRelations } from "drizzle-orm" +import * as schema from "./index" -export const catsRelations = relations(cats, ({ one }) => ({ - owner: one(users, { - fields: [cats.ownerId], - references: [users.id], - }), +export const relations = defineRelations(schema, (r) => ({ + users: { + cats: r.many.cats(), + }, + cats: { + owner: r.one.users({ + from: r.cats.ownerId, + to: r.users.id, + }), + }, })) // @filename: providers/index.ts import { drizzle } from "drizzle-orm/libsql" import * as schema from "../schema" +import { relations } from "../schema/relations" export const db = drizzle(process.env.DB_FILE_NAME ?? "file:local.db", { schema, + relations, }) -// @filename: services/user.ts -import { eq } from "drizzle-orm" -import { db } from "../providers" -import { users } from "../schema" - -export async function createUser(input: typeof users.$inferInsert) { - const [user] = await db.insert(users).values(input).returning() - return user -} - -export async function findUsersByName(name: string) { - return await db.query.users.findMany({ - where: eq(users.name, name), - }) -} - -export async function findUserByPhone(phone: string) { - return await db.query.users.findFirst({ - where: eq(users.phone, phone), - }) -} -// @filename: services/index.ts -export * as userService from "./user" // @filename: resolvers/user.ts // ---cut--- import { mutation, query, resolver } from "@gqloom/core" import * as v from "valibot" +import { db } from "../providers" import { users } from "../schema" -import { userService } from "../services" export const userResolver = resolver.of(users, { usersByName: query(users.$list()) .input({ name: v.string() }) - .resolve(({ name }) => userService.findUsersByName(name)), + .resolve(({ name }) => db.query.users.findMany({ where: { name } })), userByPhone: query(users.$nullable()) .input({ phone: v.string() }) - .resolve(({ phone }) => userService.findUserByPhone(phone)), + .resolve(({ phone }) => db.query.users.findFirst({ where: { phone } })), createUser: mutation(users) .input({ @@ -495,92 +474,76 @@ export const userResolver = resolver.of(users, { phone: v.string(), }), }) - .resolve(async ({ data }) => userService.createUser(data)), + .resolve(async ({ data }) => { + const [user] = await db.insert(users).values(data).returning() + return user + }), }) ``` ```ts twoslash title="src/resolvers/index.ts" -// @filename: schema.ts +// @filename: schema/index.ts import { drizzleSilk } from "@gqloom/drizzle" -import { relations } from "drizzle-orm" -import * as t from "drizzle-orm/sqlite-core" +import { integer, sqliteTable, text } from "drizzle-orm/sqlite-core" export const users = drizzleSilk( - t.sqliteTable("users", { - id: t.int().primaryKey({ autoIncrement: true }), - name: t.text().notNull(), - phone: t.text().notNull().unique(), + sqliteTable("users", { + id: integer().primaryKey({ autoIncrement: true }), + name: text().notNull(), + phone: text().notNull().unique(), }) ) -export const usersRelations = relations(users, ({ many }) => ({ - cats: many(cats), -})) - export const cats = drizzleSilk( - t.sqliteTable("cats", { - id: t.integer().primaryKey({ autoIncrement: true }), - name: t.text().notNull(), - birthday: t.integer({ mode: "timestamp" }).notNull(), - ownerId: t - .integer() + sqliteTable("cats", { + id: integer().primaryKey({ autoIncrement: true }), + name: text().notNull(), + birthday: integer({ mode: "timestamp" }).notNull(), + ownerId: integer() .notNull() .references(() => users.id), }) ) +// @filename: schema/relations.ts +import { defineRelations } from "drizzle-orm" +import * as schema from "./index" -export const catsRelations = relations(cats, ({ one }) => ({ - owner: one(users, { - fields: [cats.ownerId], - references: [users.id], - }), +export const relations = defineRelations(schema, (r) => ({ + users: { + cats: r.many.cats(), + }, + cats: { + owner: r.one.users({ + from: r.cats.ownerId, + to: r.users.id, + }), + }, })) // @filename: providers/index.ts import { drizzle } from "drizzle-orm/libsql" import * as schema from "../schema" +import { relations } from "../schema/relations" export const db = drizzle(process.env.DB_FILE_NAME ?? "file:local.db", { schema, + relations, }) -// @filename: services/user.ts -import { eq } from "drizzle-orm" -import { db } from "../providers" -import { users } from "../schema" - -export async function createUser(input: typeof users.$inferInsert) { - const [user] = await db.insert(users).values(input).returning() - return user -} - -export async function findUsersByName(name: string) { - return await db.query.users.findMany({ - where: eq(users.name, name), - }) -} - -export async function findUserByPhone(phone: string) { - return await db.query.users.findFirst({ - where: eq(users.phone, phone), - }) -} -// @filename: services/index.ts -export * as userService from "./user" -// ---cut--- // @filename: resolvers/user.ts +// ---cut--- import { mutation, query, resolver } from "@gqloom/core" import { z } from "zod" +import { db } from "../providers" import { users } from "../schema" -import { userService } from "../services" -export const userResolver = resolver({ +export const userResolver = resolver.of(users, { usersByName: query(users.$list()) .input({ name: z.string() }) - .resolve(({ name }) => userService.findUsersByName(name)), + .resolve(({ name }) => db.query.users.findMany({ where: { name } })), userByPhone: query(users.$nullable()) .input({ phone: z.string() }) - .resolve(({ phone }) => userService.findUserByPhone(phone)), + .resolve(({ phone }) => db.query.users.findFirst({ where: { phone } })), createUser: mutation(users) .input({ @@ -589,7 +552,10 @@ export const userResolver = resolver({ phone: z.string(), }), }) - .resolve(async ({ data }) => userService.createUser(data)), + .resolve(async ({ data }) => { + const [user] = await db.insert(users).values(data).returning() + return user + }), }) ``` @@ -600,36 +566,36 @@ export const userResolver = resolver({ ```ts title="src/resolvers/index.ts" import { query, resolver } from "@gqloom/core" -import { z } from "zod" -import { userResolver } from "./user" // [!code ++] +import * as v from "valibot" +import { userResolver } from "./user" // [!code ++] const helloResolver = resolver({ - hello: query(z.string()) - .input({ - name: z - .string() - .nullish() - .transform((x) => x ?? "World"), - }) + hello: query(v.string()) + .input({ name: v.nullish(v.string(), "World") }) .resolve(({ name }) => `Hello ${name}!`), }) -export const resolvers = [helloResolver, userResolver] // [!code ++] +export const resolvers = [helloResolver, userResolver] // [!code ++] ``` ```ts title="src/resolvers/index.ts" import { query, resolver } from "@gqloom/core" -import * as v from "valibot" -import { userResolver } from "./user" // [!code ++] +import { z } from "zod" +import { userResolver } from "./user" // [!code ++] const helloResolver = resolver({ - hello: query(v.string()) - .input({ name: v.nullish(v.string(), "World") }) + hello: query(z.string()) + .input({ + name: z + .string() + .nullish() + .transform((x) => x ?? "World"), + }) .resolve(({ name }) => `Hello ${name}!`), }) -export const resolvers = [helloResolver, userResolver] // [!code ++] +export const resolvers = [helloResolver, userResolver] // [!code ++] ``` @@ -693,84 +659,66 @@ Next, let's try to add a simple login function and add a query operation to the To implement this query, we first need to have a login function. Let's write a simple one: ```ts twoslash title="src/contexts/index.ts" -// @filename: schema.ts +// @filename: schema/index.ts import { drizzleSilk } from "@gqloom/drizzle" -import { relations } from "drizzle-orm" -import * as t from "drizzle-orm/sqlite-core" +import { integer, sqliteTable, text } from "drizzle-orm/sqlite-core" export const users = drizzleSilk( - t.sqliteTable("users", { - id: t.int().primaryKey({ autoIncrement: true }), - name: t.text().notNull(), - phone: t.text().notNull().unique(), + sqliteTable("users", { + id: integer().primaryKey({ autoIncrement: true }), + name: text().notNull(), + phone: text().notNull().unique(), }) ) -export const usersRelations = relations(users, ({ many }) => ({ - cats: many(cats), -})) - export const cats = drizzleSilk( - t.sqliteTable("cats", { - id: t.integer().primaryKey({ autoIncrement: true }), - name: t.text().notNull(), - birthday: t.integer({ mode: "timestamp" }).notNull(), - ownerId: t - .integer() + sqliteTable("cats", { + id: integer().primaryKey({ autoIncrement: true }), + name: text().notNull(), + birthday: integer({ mode: "timestamp" }).notNull(), + ownerId: integer() .notNull() .references(() => users.id), }) ) +// @filename: schema/relations.ts +import { defineRelations } from "drizzle-orm" +import * as schema from "./index" -export const catsRelations = relations(cats, ({ one }) => ({ - owner: one(users, { - fields: [cats.ownerId], - references: [users.id], - }), +export const relations = defineRelations(schema, (r) => ({ + users: { + cats: r.many.cats(), + }, + cats: { + owner: r.one.users({ + from: r.cats.ownerId, + to: r.users.id, + }), + }, })) // @filename: providers/index.ts import { drizzle } from "drizzle-orm/libsql" import * as schema from "../schema" +import { relations } from "../schema/relations" export const db = drizzle(process.env.DB_FILE_NAME ?? "file:local.db", { schema, + relations, }) -// @filename: services/user.ts -import { eq } from "drizzle-orm" -import { db } from "../providers" -import { users } from "../schema" - -export async function createUser(input: typeof users.$inferInsert) { - const [user] = await db.insert(users).values(input).returning() - return user -} - -export async function findUsersByName(name: string) { - return await db.query.users.findMany({ - where: eq(users.name, name), - }) -} - -export async function findUserByPhone(phone: string) { - return await db.query.users.findFirst({ - where: eq(users.phone, phone), - }) -} -// @filename: services/index.ts -export * as userService from "./user" // @filename: contexts/index.ts // ---cut--- import { createMemoization, useContext } from "@gqloom/core" import { GraphQLError } from "graphql" import type { YogaInitialContext } from "graphql-yoga" -import { userService } from "../services" +import { db } from "../providers" export const useCurrentUser = createMemoization(async () => { const phone = useContext().request.headers.get("authorization") if (phone == null) throw new GraphQLError("Unauthorized") - const user = await userService.findUserByPhone(phone) + const user = await db.query.users.findFirst({ where: { phone } }) + if (user == null) throw new GraphQLError("Unauthorized") return user }) @@ -791,20 +739,20 @@ Now, we add the new query operation in the resolver: ```ts title="src/resolvers/user.ts" import { mutation, query, resolver } from "@gqloom/core" import * as v from "valibot" -import { useCurrentUser } from "../contexts" // [!code ++] +import { useCurrentUser } from "../contexts" +import { db } from "../providers" import { users } from "../schema" -import { userService } from "../services" -export const userResolver = resolver({ - mine: query(users).resolve(() => useCurrentUser()), // [!code ++] +export const userResolver = resolver.of(users, { + mine: query(users).resolve(() => useCurrentUser()), usersByName: query(users.$list()) .input({ name: v.string() }) - .resolve(({ name }) => userService.findUsersByName(name)), + .resolve(({ name }) => db.query.users.findMany({ where: { name } })), userByPhone: query(users.$nullable()) .input({ phone: v.string() }) - .resolve(({ phone }) => userService.findUserByPhone(phone)), + .resolve(({ phone }) => db.query.users.findFirst({ where: { phone } })), createUser: mutation(users) .input({ @@ -813,7 +761,10 @@ export const userResolver = resolver({ phone: v.string(), }), }) - .resolve(async ({ data }) => userService.createUser(data)), + .resolve(async ({ data }) => { + const [user] = await db.insert(users).values(data).returning() + return user + }), }) ``` @@ -821,20 +772,20 @@ export const userResolver = resolver({ ```ts title="src/resolvers/user.ts" import { mutation, query, resolver } from "@gqloom/core" import { z } from "zod" -import { useCurrentUser } from "../contexts" // [!code ++] +import { useCurrentUser } from "../contexts" +import { db } from "../providers" import { users } from "../schema" -import { userService } from "../services" -export const userResolver = resolver({ - mine: query(users).resolve(() => useCurrentUser()), // [!code ++] +export const userResolver = resolver.of(users, { + mine: query(users).resolve(() => useCurrentUser()), usersByName: query(users.$list()) .input({ name: z.string() }) - .resolve(({ name }) => userService.findUsersByName(name)), + .resolve(({ name }) => db.query.users.findMany({ where: { name } })), userByPhone: query(users.$nullable()) .input({ phone: z.string() }) - .resolve(({ phone }) => userService.findUserByPhone(phone)), + .resolve(({ phone }) => db.query.users.findFirst({ where: { phone } })), createUser: mutation(users) .input({ @@ -843,7 +794,10 @@ export const userResolver = resolver({ phone: z.string(), }), }) - .resolve(async ({ data }) => userService.createUser(data)), + .resolve(async ({ data }) => { + const [user] = await db.insert(users).values(data).returning() + return user + }), }) ``` @@ -920,49 +874,54 @@ We use the [resolver factory](./schema/drizzle#resolver-factory) to quickly crea ```ts twoslash title="src/resolvers/cat.ts" -// @filename: schema.ts +// @filename: schema/index.ts import { drizzleSilk } from "@gqloom/drizzle" -import { relations } from "drizzle-orm" -import * as t from "drizzle-orm/sqlite-core" +import { integer, sqliteTable, text } from "drizzle-orm/sqlite-core" export const users = drizzleSilk( - t.sqliteTable("users", { - id: t.int().primaryKey({ autoIncrement: true }), - name: t.text().notNull(), - phone: t.text().notNull().unique(), + sqliteTable("users", { + id: integer().primaryKey({ autoIncrement: true }), + name: text().notNull(), + phone: text().notNull().unique(), }) ) -export const usersRelations = relations(users, ({ many }) => ({ - cats: many(cats), -})) - export const cats = drizzleSilk( - t.sqliteTable("cats", { - id: t.integer().primaryKey({ autoIncrement: true }), - name: t.text().notNull(), - birthday: t.integer({ mode: "timestamp" }).notNull(), - ownerId: t - .integer() + sqliteTable("cats", { + id: integer().primaryKey({ autoIncrement: true }), + name: text().notNull(), + birthday: integer({ mode: "timestamp" }).notNull(), + ownerId: integer() .notNull() .references(() => users.id), }) ) +// @filename: schema/relations.ts +import { defineRelations } from "drizzle-orm" +import * as schema from "./index" -export const catsRelations = relations(cats, ({ one }) => ({ - owner: one(users, { - fields: [cats.ownerId], - references: [users.id], - }), +export const relations = defineRelations(schema, (r) => ({ + users: { + cats: r.many.cats(), + }, + cats: { + owner: r.one.users({ + from: r.cats.ownerId, + to: r.users.id, + }), + }, })) // @filename: providers/index.ts import { drizzle } from "drizzle-orm/libsql" import * as schema from "../schema" +import { relations } from "../schema/relations" export const db = drizzle(process.env.DB_FILE_NAME ?? "file:local.db", { schema, + relations, }) // @filename: resolvers/cat.ts +// ---cut--- import { field, resolver } from "@gqloom/core" import { drizzleResolverFactory } from "@gqloom/drizzle" import * as v from "valibot" @@ -988,47 +947,51 @@ export const catResolver = resolver.of(cats, { ```ts twoslash title="src/resolvers/cat.ts" -// @filename: schema.ts +// @filename: schema/index.ts import { drizzleSilk } from "@gqloom/drizzle" -import { relations } from "drizzle-orm" -import * as t from "drizzle-orm/sqlite-core" +import { integer, sqliteTable, text } from "drizzle-orm/sqlite-core" export const users = drizzleSilk( - t.sqliteTable("users", { - id: t.int().primaryKey({ autoIncrement: true }), - name: t.text().notNull(), - phone: t.text().notNull().unique(), + sqliteTable("users", { + id: integer().primaryKey({ autoIncrement: true }), + name: text().notNull(), + phone: text().notNull().unique(), }) ) -export const usersRelations = relations(users, ({ many }) => ({ - cats: many(cats), -})) - export const cats = drizzleSilk( - t.sqliteTable("cats", { - id: t.integer().primaryKey({ autoIncrement: true }), - name: t.text().notNull(), - birthday: t.integer({ mode: "timestamp" }).notNull(), - ownerId: t - .integer() + sqliteTable("cats", { + id: integer().primaryKey({ autoIncrement: true }), + name: text().notNull(), + birthday: integer({ mode: "timestamp" }).notNull(), + ownerId: integer() .notNull() .references(() => users.id), }) ) +// @filename: schema/relations.ts +import { defineRelations } from "drizzle-orm" +import * as schema from "./index" -export const catsRelations = relations(cats, ({ one }) => ({ - owner: one(users, { - fields: [cats.ownerId], - references: [users.id], - }), +export const relations = defineRelations(schema, (r) => ({ + users: { + cats: r.many.cats(), + }, + cats: { + owner: r.one.users({ + from: r.cats.ownerId, + to: r.users.id, + }), + }, })) // @filename: providers/index.ts import { drizzle } from "drizzle-orm/libsql" import * as schema from "../schema" +import { relations } from "../schema/relations" export const db = drizzle(process.env.DB_FILE_NAME ?? "file:local.db", { schema, + relations, }) // @filename: resolvers/cat.ts // ---cut--- @@ -1112,83 +1075,64 @@ Next, let's try to add a `createCat` mutation. We want only logged-in users to a ```ts twoslash title="src/resolvers/cat.ts" -// @filename: schema.ts +// @filename: schema/index.ts import { drizzleSilk } from "@gqloom/drizzle" -import { relations } from "drizzle-orm" -import * as t from "drizzle-orm/sqlite-core" +import { integer, sqliteTable, text } from "drizzle-orm/sqlite-core" export const users = drizzleSilk( - t.sqliteTable("users", { - id: t.int().primaryKey({ autoIncrement: true }), - name: t.text().notNull(), - phone: t.text().notNull().unique(), + sqliteTable("users", { + id: integer().primaryKey({ autoIncrement: true }), + name: text().notNull(), + phone: text().notNull().unique(), }) ) -export const usersRelations = relations(users, ({ many }) => ({ - cats: many(cats), -})) - export const cats = drizzleSilk( - t.sqliteTable("cats", { - id: t.integer().primaryKey({ autoIncrement: true }), - name: t.text().notNull(), - birthday: t.integer({ mode: "timestamp" }).notNull(), - ownerId: t - .integer() + sqliteTable("cats", { + id: integer().primaryKey({ autoIncrement: true }), + name: text().notNull(), + birthday: integer({ mode: "timestamp" }).notNull(), + ownerId: integer() .notNull() .references(() => users.id), }) ) +// @filename: schema/relations.ts +import { defineRelations } from "drizzle-orm" +import * as schema from "./index" -export const catsRelations = relations(cats, ({ one }) => ({ - owner: one(users, { - fields: [cats.ownerId], - references: [users.id], - }), +export const relations = defineRelations(schema, (r) => ({ + users: { + cats: r.many.cats(), + }, + cats: { + owner: r.one.users({ + from: r.cats.ownerId, + to: r.users.id, + }), + }, })) // @filename: providers/index.ts import { drizzle } from "drizzle-orm/libsql" import * as schema from "../schema" +import { relations } from "../schema/relations" export const db = drizzle(process.env.DB_FILE_NAME ?? "file:local.db", { schema, + relations, }) -// @filename: services/user.ts -import { eq } from "drizzle-orm" -import { db } from "../providers" -import { users } from "../schema" - -export async function createUser(input: typeof users.$inferInsert) { - const [user] = await db.insert(users).values(input).returning() - return user -} - -export async function findUsersByName(name: string) { - return await db.query.users.findMany({ - where: eq(users.name, name), - }) -} - -export async function findUserByPhone(phone: string) { - return await db.query.users.findFirst({ - where: eq(users.phone, phone), - }) -} -// @filename: services/index.ts -export * as userService from "./user" // @filename: contexts/index.ts import { createMemoization, useContext } from "@gqloom/core" import { GraphQLError } from "graphql" import type { YogaInitialContext } from "graphql-yoga" -import { userService } from "../services" +import { db } from "../providers" export const useCurrentUser = createMemoization(async () => { const phone = useContext().request.headers.get("authorization") if (phone == null) throw new GraphQLError("Unauthorized") - const user = await userService.findUserByPhone(phone) + const user = await db.query.users.findFirst({ where: { phone } }) if (user == null) throw new GraphQLError("Unauthorized") return user }) @@ -1243,83 +1187,64 @@ export const catResolver = resolver.of(cats, { ```ts twoslash title="src/resolvers/cat.ts" -// @filename: schema.ts +// @filename: schema/index.ts import { drizzleSilk } from "@gqloom/drizzle" -import { relations } from "drizzle-orm" -import * as t from "drizzle-orm/sqlite-core" +import { integer, sqliteTable, text } from "drizzle-orm/sqlite-core" export const users = drizzleSilk( - t.sqliteTable("users", { - id: t.int().primaryKey({ autoIncrement: true }), - name: t.text().notNull(), - phone: t.text().notNull().unique(), + sqliteTable("users", { + id: integer().primaryKey({ autoIncrement: true }), + name: text().notNull(), + phone: text().notNull().unique(), }) ) -export const usersRelations = relations(users, ({ many }) => ({ - cats: many(cats), -})) - export const cats = drizzleSilk( - t.sqliteTable("cats", { - id: t.integer().primaryKey({ autoIncrement: true }), - name: t.text().notNull(), - birthday: t.integer({ mode: "timestamp" }).notNull(), - ownerId: t - .integer() + sqliteTable("cats", { + id: integer().primaryKey({ autoIncrement: true }), + name: text().notNull(), + birthday: integer({ mode: "timestamp" }).notNull(), + ownerId: integer() .notNull() .references(() => users.id), }) ) +// @filename: schema/relations.ts +import { defineRelations } from "drizzle-orm" +import * as schema from "./index" -export const catsRelations = relations(cats, ({ one }) => ({ - owner: one(users, { - fields: [cats.ownerId], - references: [users.id], - }), +export const relations = defineRelations(schema, (r) => ({ + users: { + cats: r.many.cats(), + }, + cats: { + owner: r.one.users({ + from: r.cats.ownerId, + to: r.users.id, + }), + }, })) // @filename: providers/index.ts import { drizzle } from "drizzle-orm/libsql" import * as schema from "../schema" +import { relations } from "../schema/relations" export const db = drizzle(process.env.DB_FILE_NAME ?? "file:local.db", { schema, + relations, }) -// @filename: services/user.ts -import { eq } from "drizzle-orm" -import { db } from "../providers" -import { users } from "../schema" - -export async function createUser(input: typeof users.$inferInsert) { - const [user] = await db.insert(users).values(input).returning() - return user -} - -export async function findUsersByName(name: string) { - return await db.query.users.findMany({ - where: eq(users.name, name), - }) -} - -export async function findUserByPhone(phone: string) { - return await db.query.users.findFirst({ - where: eq(users.phone, phone), - }) -} -// @filename: services/index.ts -export * as userService from "./user" // @filename: contexts/index.ts import { createMemoization, useContext } from "@gqloom/core" import { GraphQLError } from "graphql" import type { YogaInitialContext } from "graphql-yoga" -import { userService } from "../services" +import { db } from "../providers" export const useCurrentUser = createMemoization(async () => { const phone = useContext().request.headers.get("authorization") if (phone == null) throw new GraphQLError("Unauthorized") - const user = await userService.findUserByPhone(phone) + const user = await db.query.users.findFirst({ where: { phone } }) if (user == null) throw new GraphQLError("Unauthorized") return user }) @@ -1559,7 +1484,6 @@ import * as v from "valibot" import { useCurrentUser } from "../contexts" import { db } from "../providers" // [!code ++] import { users } from "../schema" -import { userService } from "../services" const userResolverFactory = drizzleResolverFactory(db, "users") // [!code ++] @@ -1570,11 +1494,11 @@ export const userResolver = resolver.of(users, { usersByName: query(users.$list()) .input({ name: v.string() }) - .resolve(({ name }) => userService.findUsersByName(name)), + .resolve(({ name }) => db.query.users.findMany({ where: { name } })), userByPhone: query(users.$nullable()) .input({ phone: v.string() }) - .resolve(({ phone }) => userService.findUserByPhone(phone)), + .resolve(({ phone }) => db.query.users.findFirst({ where: { phone } })), createUser: mutation(users) .input({ @@ -1583,7 +1507,10 @@ export const userResolver = resolver.of(users, { phone: v.string(), }), }) - .resolve(async ({ data }) => userService.createUser(data)), + .resolve(async ({ data }) => { + const [user] = await db.insert(users).values(data).returning() + return user + }), }) ``` @@ -1606,11 +1533,11 @@ export const userResolver = resolver.of(users, { usersByName: query(users.$list()) .input({ name: z.string() }) - .resolve(({ name }) => userService.findUsersByName(name)), + .resolve(({ name }) => db.query.users.findMany({ where: { name } })), userByPhone: query(users.$nullable()) .input({ phone: z.string() }) - .resolve(({ phone }) => userService.findUserByPhone(phone)), + .resolve(({ phone }) => db.query.users.findFirst({ where: { phone } })), createUser: mutation(users) .input({ @@ -1619,7 +1546,10 @@ export const userResolver = resolver.of(users, { phone: z.string(), }), }) - .resolve(async ({ data }) => userService.createUser(data)), + .resolve(async ({ data }) => { + const [user] = await db.insert(users).values(data).returning() + return user + }), }) ``` diff --git a/website/content/docs/getting-started.zh.mdx b/website/content/docs/getting-started.zh.mdx index 067b0007..331bef9f 100644 --- a/website/content/docs/getting-started.zh.mdx +++ b/website/content/docs/getting-started.zh.mdx @@ -44,10 +44,7 @@ import { File, Folder, Files } from 'fumadocs-ui/components/files'; - - - - + @@ -291,40 +288,68 @@ yarn add -D drizzle-kit 接下来在 `src/schema/index.ts` 文件中定义数据库表格,我们将定义 `users` 和 `cats` 两个表格,并建立它们之间的关系: -```ts twoslash title="src/schema/index.ts" +```ts twoslash title="src/schema/index.ts" tab="src/schema/index.ts" import { drizzleSilk } from "@gqloom/drizzle" -import { relations } from "drizzle-orm" -import * as t from "drizzle-orm/sqlite-core" +import { integer, sqliteTable, text } from "drizzle-orm/sqlite-core" export const users = drizzleSilk( - t.sqliteTable("users", { - id: t.int().primaryKey({ autoIncrement: true }), - name: t.text().notNull(), - phone: t.text().notNull().unique(), + sqliteTable("users", { + id: integer().primaryKey({ autoIncrement: true }), + name: text().notNull(), + phone: text().notNull().unique(), }) ) -export const usersRelations = relations(users, ({ many }) => ({ - cats: many(cats), -})) +export const cats = drizzleSilk( + sqliteTable("cats", { + id: integer().primaryKey({ autoIncrement: true }), + name: text().notNull(), + birthday: integer({ mode: "timestamp" }).notNull(), + ownerId: integer() + .notNull() + .references(() => users.id), + }) +) +``` + +```ts twoslash title="src/schema/relations.ts" tab="src/schema/relations.ts" +// @filename: index.ts +import { drizzleSilk } from "@gqloom/drizzle" +import { integer, sqliteTable, text } from "drizzle-orm/sqlite-core" + +export const users = drizzleSilk( + sqliteTable("users", { + id: integer().primaryKey({ autoIncrement: true }), + name: text().notNull(), + phone: text().notNull().unique(), + }) +) export const cats = drizzleSilk( - t.sqliteTable("cats", { - id: t.integer().primaryKey({ autoIncrement: true }), - name: t.text().notNull(), - birthday: t.integer({ mode: "timestamp" }).notNull(), - ownerId: t - .integer() + sqliteTable("cats", { + id: integer().primaryKey({ autoIncrement: true }), + name: text().notNull(), + birthday: integer({ mode: "timestamp" }).notNull(), + ownerId: integer() .notNull() .references(() => users.id), }) ) +// @filename: relations.ts +// ---cut--- +import { defineRelations } from "drizzle-orm" +import * as schema from "./index" -export const catsRelations = relations(cats, ({ one }) => ({ - owner: one(users, { - fields: [cats.ownerId], - references: [users.id], - }), +export const relations = defineRelations(schema, (r) => ({ + users: { + cats: r.many.cats(), + }, + cats: { + owner: r.one.users({ + from: r.cats.ownerId, + to: r.users.id, + }), + }, })) ``` @@ -353,45 +378,18 @@ npx drizzle-kit push ### 使用数据库 为了在应用中使用数据库,我们需要创建一个数据库实例: + ```ts title="src/providers/index.ts" import { drizzle } from "drizzle-orm/libsql" import * as schema from "../schema" +import { relations } from "../schema/relations" export const db = drizzle(process.env.DB_FILE_NAME ?? "file:local.db", { schema, + relations, }) ``` -让我们先创建一个用户服务,其中将包含一系列对用户表的操作。 -我们将在 `src/services/user.ts` 文件中实现用户服务,并在 `src/resolvers/index.ts` 文件中将整个 `user.ts` 作为 `userService` 导出: - -```ts title="src/services/user.ts" tab="src/services/user.ts" -import { eq } from "drizzle-orm" -import { db } from "../providers" -import { users } from "../schema" - -export async function createUser(input: typeof users.$inferInsert) { - const [user] = await db.insert(users).values(input).returning() - return user -} - -export async function findUsersByName(name: string) { - return await db.query.users.findMany({ - where: eq(users.name, name), - }) -} - -export async function findUserByPhone(phone: string) { - return await db.query.users.findFirst({ - where: eq(users.phone, phone), - }) -} -``` - -```ts title="src/services/index.ts" tab="src/services/index.ts" -export * as userService from "./user" -``` - ## 解析器 现在,我们可以在解析器中使用用户服务,我们将创建一个用户解析器添加以下操作: @@ -407,86 +405,67 @@ export * as userService from "./user" ```ts twoslash title="src/resolvers/user.ts" -// @filename: schema.ts +// @filename: schema/index.ts import { drizzleSilk } from "@gqloom/drizzle" -import { relations } from "drizzle-orm" -import * as t from "drizzle-orm/sqlite-core" +import { integer, sqliteTable, text } from "drizzle-orm/sqlite-core" export const users = drizzleSilk( - t.sqliteTable("users", { - id: t.int().primaryKey({ autoIncrement: true }), - name: t.text().notNull(), - phone: t.text().notNull().unique(), + sqliteTable("users", { + id: integer().primaryKey({ autoIncrement: true }), + name: text().notNull(), + phone: text().notNull().unique(), }) ) -export const usersRelations = relations(users, ({ many }) => ({ - cats: many(cats), -})) - export const cats = drizzleSilk( - t.sqliteTable("cats", { - id: t.integer().primaryKey({ autoIncrement: true }), - name: t.text().notNull(), - birthday: t.integer({ mode: "timestamp" }).notNull(), - ownerId: t - .integer() + sqliteTable("cats", { + id: integer().primaryKey({ autoIncrement: true }), + name: text().notNull(), + birthday: integer({ mode: "timestamp" }).notNull(), + ownerId: integer() .notNull() .references(() => users.id), }) ) +// @filename: schema/relations.ts +import { defineRelations } from "drizzle-orm" +import * as schema from "./index" -export const catsRelations = relations(cats, ({ one }) => ({ - owner: one(users, { - fields: [cats.ownerId], - references: [users.id], - }), +export const relations = defineRelations(schema, (r) => ({ + users: { + cats: r.many.cats(), + }, + cats: { + owner: r.one.users({ + from: r.cats.ownerId, + to: r.users.id, + }), + }, })) // @filename: providers/index.ts import { drizzle } from "drizzle-orm/libsql" import * as schema from "../schema" +import { relations } from "../schema/relations" export const db = drizzle(process.env.DB_FILE_NAME ?? "file:local.db", { schema, + relations, }) -// @filename: services/user.ts -import { eq } from "drizzle-orm" -import { db } from "../providers" -import { users } from "../schema" - -export async function createUser(input: typeof users.$inferInsert) { - const [user] = await db.insert(users).values(input).returning() - return user -} - -export async function findUsersByName(name: string) { - return await db.query.users.findMany({ - where: eq(users.name, name), - }) -} - -export async function findUserByPhone(phone: string) { - return await db.query.users.findFirst({ - where: eq(users.phone, phone), - }) -} -// @filename: services/index.ts -export * as userService from "./user" // @filename: resolvers/user.ts // ---cut--- import { mutation, query, resolver } from "@gqloom/core" import * as v from "valibot" +import { db } from "../providers" import { users } from "../schema" -import { userService } from "../services" export const userResolver = resolver.of(users, { usersByName: query(users.$list()) .input({ name: v.string() }) - .resolve(({ name }) => userService.findUsersByName(name)), + .resolve(({ name }) => db.query.users.findMany({ where: { name } })), userByPhone: query(users.$nullable()) .input({ phone: v.string() }) - .resolve(({ phone }) => userService.findUserByPhone(phone)), + .resolve(({ phone }) => db.query.users.findFirst({ where: { phone } })), createUser: mutation(users) .input({ @@ -495,92 +474,76 @@ export const userResolver = resolver.of(users, { phone: v.string(), }), }) - .resolve(async ({ data }) => userService.createUser(data)), + .resolve(async ({ data }) => { + const [user] = await db.insert(users).values(data).returning() + return user + }), }) ``` ```ts twoslash title="src/resolvers/index.ts" -// @filename: schema.ts +// @filename: schema/index.ts import { drizzleSilk } from "@gqloom/drizzle" -import { relations } from "drizzle-orm" -import * as t from "drizzle-orm/sqlite-core" +import { integer, sqliteTable, text } from "drizzle-orm/sqlite-core" export const users = drizzleSilk( - t.sqliteTable("users", { - id: t.int().primaryKey({ autoIncrement: true }), - name: t.text().notNull(), - phone: t.text().notNull().unique(), + sqliteTable("users", { + id: integer().primaryKey({ autoIncrement: true }), + name: text().notNull(), + phone: text().notNull().unique(), }) ) -export const usersRelations = relations(users, ({ many }) => ({ - cats: many(cats), -})) - export const cats = drizzleSilk( - t.sqliteTable("cats", { - id: t.integer().primaryKey({ autoIncrement: true }), - name: t.text().notNull(), - birthday: t.integer({ mode: "timestamp" }).notNull(), - ownerId: t - .integer() + sqliteTable("cats", { + id: integer().primaryKey({ autoIncrement: true }), + name: text().notNull(), + birthday: integer({ mode: "timestamp" }).notNull(), + ownerId: integer() .notNull() .references(() => users.id), }) ) +// @filename: schema/relations.ts +import { defineRelations } from "drizzle-orm" +import * as schema from "./index" -export const catsRelations = relations(cats, ({ one }) => ({ - owner: one(users, { - fields: [cats.ownerId], - references: [users.id], - }), +export const relations = defineRelations(schema, (r) => ({ + users: { + cats: r.many.cats(), + }, + cats: { + owner: r.one.users({ + from: r.cats.ownerId, + to: r.users.id, + }), + }, })) // @filename: providers/index.ts import { drizzle } from "drizzle-orm/libsql" import * as schema from "../schema" +import { relations } from "../schema/relations" export const db = drizzle(process.env.DB_FILE_NAME ?? "file:local.db", { schema, + relations, }) -// @filename: services/user.ts -import { eq } from "drizzle-orm" -import { db } from "../providers" -import { users } from "../schema" - -export async function createUser(input: typeof users.$inferInsert) { - const [user] = await db.insert(users).values(input).returning() - return user -} - -export async function findUsersByName(name: string) { - return await db.query.users.findMany({ - where: eq(users.name, name), - }) -} - -export async function findUserByPhone(phone: string) { - return await db.query.users.findFirst({ - where: eq(users.phone, phone), - }) -} -// @filename: services/index.ts -export * as userService from "./user" -// ---cut--- // @filename: resolvers/user.ts +// ---cut--- import { mutation, query, resolver } from "@gqloom/core" import { z } from "zod" +import { db } from "../providers" import { users } from "../schema" -import { userService } from "../services" -export const userResolver = resolver({ +export const userResolver = resolver.of(users, { usersByName: query(users.$list()) .input({ name: z.string() }) - .resolve(({ name }) => userService.findUsersByName(name)), + .resolve(({ name }) => db.query.users.findMany({ where: { name } })), userByPhone: query(users.$nullable()) .input({ phone: z.string() }) - .resolve(({ phone }) => userService.findUserByPhone(phone)), + .resolve(({ phone }) => db.query.users.findFirst({ where: { phone } })), createUser: mutation(users) .input({ @@ -589,7 +552,10 @@ export const userResolver = resolver({ phone: z.string(), }), }) - .resolve(async ({ data }) => userService.createUser(data)), + .resolve(async ({ data }) => { + const [user] = await db.insert(users).values(data).returning() + return user + }), }) ``` @@ -600,36 +566,36 @@ export const userResolver = resolver({ ```ts title="src/resolvers/index.ts" import { query, resolver } from "@gqloom/core" -import { z } from "zod" -import { userResolver } from "./user" // [!code ++] +import * as v from "valibot" +import { userResolver } from "./user" // [!code ++] const helloResolver = resolver({ - hello: query(z.string()) - .input({ - name: z - .string() - .nullish() - .transform((x) => x ?? "World"), - }) + hello: query(v.string()) + .input({ name: v.nullish(v.string(), "World") }) .resolve(({ name }) => `Hello ${name}!`), }) -export const resolvers = [helloResolver, userResolver] // [!code ++] +export const resolvers = [helloResolver, userResolver] // [!code ++] ``` ```ts title="src/resolvers/index.ts" import { query, resolver } from "@gqloom/core" -import * as v from "valibot" -import { userResolver } from "./user" // [!code ++] +import { z } from "zod" +import { userResolver } from "./user" // [!code ++] const helloResolver = resolver({ - hello: query(v.string()) - .input({ name: v.nullish(v.string(), "World") }) + hello: query(z.string()) + .input({ + name: z + .string() + .nullish() + .transform((x) => x ?? "World"), + }) .resolve(({ name }) => `Hello ${name}!`), }) -export const resolvers = [helloResolver, userResolver] // [!code ++] +export const resolvers = [helloResolver, userResolver] // [!code ++] ``` @@ -693,84 +659,66 @@ mutation { 为了实现这个查询,首先得有登录功能,让我们来简单写一个: ```ts twoslash title="src/contexts/index.ts" -// @filename: schema.ts +// @filename: schema/index.ts import { drizzleSilk } from "@gqloom/drizzle" -import { relations } from "drizzle-orm" -import * as t from "drizzle-orm/sqlite-core" +import { integer, sqliteTable, text } from "drizzle-orm/sqlite-core" export const users = drizzleSilk( - t.sqliteTable("users", { - id: t.int().primaryKey({ autoIncrement: true }), - name: t.text().notNull(), - phone: t.text().notNull().unique(), + sqliteTable("users", { + id: integer().primaryKey({ autoIncrement: true }), + name: text().notNull(), + phone: text().notNull().unique(), }) ) -export const usersRelations = relations(users, ({ many }) => ({ - cats: many(cats), -})) - export const cats = drizzleSilk( - t.sqliteTable("cats", { - id: t.integer().primaryKey({ autoIncrement: true }), - name: t.text().notNull(), - birthday: t.integer({ mode: "timestamp" }).notNull(), - ownerId: t - .integer() + sqliteTable("cats", { + id: integer().primaryKey({ autoIncrement: true }), + name: text().notNull(), + birthday: integer({ mode: "timestamp" }).notNull(), + ownerId: integer() .notNull() .references(() => users.id), }) ) +// @filename: schema/relations.ts +import { defineRelations } from "drizzle-orm" +import * as schema from "./index" -export const catsRelations = relations(cats, ({ one }) => ({ - owner: one(users, { - fields: [cats.ownerId], - references: [users.id], - }), +export const relations = defineRelations(schema, (r) => ({ + users: { + cats: r.many.cats(), + }, + cats: { + owner: r.one.users({ + from: r.cats.ownerId, + to: r.users.id, + }), + }, })) // @filename: providers/index.ts import { drizzle } from "drizzle-orm/libsql" import * as schema from "../schema" +import { relations } from "../schema/relations" export const db = drizzle(process.env.DB_FILE_NAME ?? "file:local.db", { schema, + relations, }) -// @filename: services/user.ts -import { eq } from "drizzle-orm" -import { db } from "../providers" -import { users } from "../schema" - -export async function createUser(input: typeof users.$inferInsert) { - const [user] = await db.insert(users).values(input).returning() - return user -} - -export async function findUsersByName(name: string) { - return await db.query.users.findMany({ - where: eq(users.name, name), - }) -} - -export async function findUserByPhone(phone: string) { - return await db.query.users.findFirst({ - where: eq(users.phone, phone), - }) -} -// @filename: services/index.ts -export * as userService from "./user" // @filename: contexts/index.ts // ---cut--- import { createMemoization, useContext } from "@gqloom/core" import { GraphQLError } from "graphql" import type { YogaInitialContext } from "graphql-yoga" -import { userService } from "../services" +import { db } from "../providers" export const useCurrentUser = createMemoization(async () => { const phone = useContext().request.headers.get("authorization") if (phone == null) throw new GraphQLError("Unauthorized") - const user = await userService.findUserByPhone(phone) + const user = await db.query.users.findFirst({ where: { phone } }) + if (user == null) throw new GraphQLError("Unauthorized") return user }) @@ -791,20 +739,20 @@ export const useCurrentUser = createMemoization(async () => { ```ts title="src/resolvers/user.ts" import { mutation, query, resolver } from "@gqloom/core" import * as v from "valibot" -import { useCurrentUser } from "../contexts" // [!code ++] +import { useCurrentUser } from "../contexts" +import { db } from "../providers" import { users } from "../schema" -import { userService } from "../services" -export const userResolver = resolver({ - mine: query(users).resolve(() => useCurrentUser()), // [!code ++] +export const userResolver = resolver.of(users, { + mine: query(users).resolve(() => useCurrentUser()), usersByName: query(users.$list()) .input({ name: v.string() }) - .resolve(({ name }) => userService.findUsersByName(name)), + .resolve(({ name }) => db.query.users.findMany({ where: { name } })), userByPhone: query(users.$nullable()) .input({ phone: v.string() }) - .resolve(({ phone }) => userService.findUserByPhone(phone)), + .resolve(({ phone }) => db.query.users.findFirst({ where: { phone } })), createUser: mutation(users) .input({ @@ -813,7 +761,10 @@ export const userResolver = resolver({ phone: v.string(), }), }) - .resolve(async ({ data }) => userService.createUser(data)), + .resolve(async ({ data }) => { + const [user] = await db.insert(users).values(data).returning() + return user + }), }) ``` @@ -821,20 +772,20 @@ export const userResolver = resolver({ ```ts title="src/resolvers/user.ts" import { mutation, query, resolver } from "@gqloom/core" import { z } from "zod" -import { useCurrentUser } from "../contexts" // [!code ++] +import { useCurrentUser } from "../contexts" +import { db } from "../providers" import { users } from "../schema" -import { userService } from "../services" -export const userResolver = resolver({ - mine: query(users).resolve(() => useCurrentUser()), // [!code ++] +export const userResolver = resolver.of(users, { + mine: query(users).resolve(() => useCurrentUser()), usersByName: query(users.$list()) .input({ name: z.string() }) - .resolve(({ name }) => userService.findUsersByName(name)), + .resolve(({ name }) => db.query.users.findMany({ where: { name } })), userByPhone: query(users.$nullable()) .input({ phone: z.string() }) - .resolve(({ phone }) => userService.findUserByPhone(phone)), + .resolve(({ phone }) => db.query.users.findFirst({ where: { phone } })), createUser: mutation(users) .input({ @@ -843,7 +794,10 @@ export const userResolver = resolver({ phone: z.string(), }), }) - .resolve(async ({ data }) => userService.createUser(data)), + .resolve(async ({ data }) => { + const [user] = await db.insert(users).values(data).returning() + return user + }), }) ``` @@ -920,49 +874,54 @@ export const userResolver = resolver({ ```ts twoslash title="src/resolvers/cat.ts" -// @filename: schema.ts +// @filename: schema/index.ts import { drizzleSilk } from "@gqloom/drizzle" -import { relations } from "drizzle-orm" -import * as t from "drizzle-orm/sqlite-core" +import { integer, sqliteTable, text } from "drizzle-orm/sqlite-core" export const users = drizzleSilk( - t.sqliteTable("users", { - id: t.int().primaryKey({ autoIncrement: true }), - name: t.text().notNull(), - phone: t.text().notNull().unique(), + sqliteTable("users", { + id: integer().primaryKey({ autoIncrement: true }), + name: text().notNull(), + phone: text().notNull().unique(), }) ) -export const usersRelations = relations(users, ({ many }) => ({ - cats: many(cats), -})) - export const cats = drizzleSilk( - t.sqliteTable("cats", { - id: t.integer().primaryKey({ autoIncrement: true }), - name: t.text().notNull(), - birthday: t.integer({ mode: "timestamp" }).notNull(), - ownerId: t - .integer() + sqliteTable("cats", { + id: integer().primaryKey({ autoIncrement: true }), + name: text().notNull(), + birthday: integer({ mode: "timestamp" }).notNull(), + ownerId: integer() .notNull() .references(() => users.id), }) ) +// @filename: schema/relations.ts +import { defineRelations } from "drizzle-orm" +import * as schema from "./index" -export const catsRelations = relations(cats, ({ one }) => ({ - owner: one(users, { - fields: [cats.ownerId], - references: [users.id], - }), +export const relations = defineRelations(schema, (r) => ({ + users: { + cats: r.many.cats(), + }, + cats: { + owner: r.one.users({ + from: r.cats.ownerId, + to: r.users.id, + }), + }, })) // @filename: providers/index.ts import { drizzle } from "drizzle-orm/libsql" import * as schema from "../schema" +import { relations } from "../schema/relations" export const db = drizzle(process.env.DB_FILE_NAME ?? "file:local.db", { schema, + relations, }) // @filename: resolvers/cat.ts +// ---cut--- import { field, resolver } from "@gqloom/core" import { drizzleResolverFactory } from "@gqloom/drizzle" import * as v from "valibot" @@ -988,47 +947,51 @@ export const catResolver = resolver.of(cats, { ```ts twoslash title="src/resolvers/cat.ts" -// @filename: schema.ts +// @filename: schema/index.ts import { drizzleSilk } from "@gqloom/drizzle" -import { relations } from "drizzle-orm" -import * as t from "drizzle-orm/sqlite-core" +import { integer, sqliteTable, text } from "drizzle-orm/sqlite-core" export const users = drizzleSilk( - t.sqliteTable("users", { - id: t.int().primaryKey({ autoIncrement: true }), - name: t.text().notNull(), - phone: t.text().notNull().unique(), + sqliteTable("users", { + id: integer().primaryKey({ autoIncrement: true }), + name: text().notNull(), + phone: text().notNull().unique(), }) ) -export const usersRelations = relations(users, ({ many }) => ({ - cats: many(cats), -})) - export const cats = drizzleSilk( - t.sqliteTable("cats", { - id: t.integer().primaryKey({ autoIncrement: true }), - name: t.text().notNull(), - birthday: t.integer({ mode: "timestamp" }).notNull(), - ownerId: t - .integer() + sqliteTable("cats", { + id: integer().primaryKey({ autoIncrement: true }), + name: text().notNull(), + birthday: integer({ mode: "timestamp" }).notNull(), + ownerId: integer() .notNull() .references(() => users.id), }) ) +// @filename: schema/relations.ts +import { defineRelations } from "drizzle-orm" +import * as schema from "./index" -export const catsRelations = relations(cats, ({ one }) => ({ - owner: one(users, { - fields: [cats.ownerId], - references: [users.id], - }), +export const relations = defineRelations(schema, (r) => ({ + users: { + cats: r.many.cats(), + }, + cats: { + owner: r.one.users({ + from: r.cats.ownerId, + to: r.users.id, + }), + }, })) // @filename: providers/index.ts import { drizzle } from "drizzle-orm/libsql" import * as schema from "../schema" +import { relations } from "../schema/relations" export const db = drizzle(process.env.DB_FILE_NAME ?? "file:local.db", { schema, + relations, }) // @filename: resolvers/cat.ts // ---cut--- @@ -1112,83 +1075,64 @@ export const resolvers = [helloResolver, userResolver, catResolver] // [!code ++ ```ts twoslash title="src/resolvers/cat.ts" -// @filename: schema.ts +// @filename: schema/index.ts import { drizzleSilk } from "@gqloom/drizzle" -import { relations } from "drizzle-orm" -import * as t from "drizzle-orm/sqlite-core" +import { integer, sqliteTable, text } from "drizzle-orm/sqlite-core" export const users = drizzleSilk( - t.sqliteTable("users", { - id: t.int().primaryKey({ autoIncrement: true }), - name: t.text().notNull(), - phone: t.text().notNull().unique(), + sqliteTable("users", { + id: integer().primaryKey({ autoIncrement: true }), + name: text().notNull(), + phone: text().notNull().unique(), }) ) -export const usersRelations = relations(users, ({ many }) => ({ - cats: many(cats), -})) - export const cats = drizzleSilk( - t.sqliteTable("cats", { - id: t.integer().primaryKey({ autoIncrement: true }), - name: t.text().notNull(), - birthday: t.integer({ mode: "timestamp" }).notNull(), - ownerId: t - .integer() + sqliteTable("cats", { + id: integer().primaryKey({ autoIncrement: true }), + name: text().notNull(), + birthday: integer({ mode: "timestamp" }).notNull(), + ownerId: integer() .notNull() .references(() => users.id), }) ) +// @filename: schema/relations.ts +import { defineRelations } from "drizzle-orm" +import * as schema from "./index" -export const catsRelations = relations(cats, ({ one }) => ({ - owner: one(users, { - fields: [cats.ownerId], - references: [users.id], - }), +export const relations = defineRelations(schema, (r) => ({ + users: { + cats: r.many.cats(), + }, + cats: { + owner: r.one.users({ + from: r.cats.ownerId, + to: r.users.id, + }), + }, })) // @filename: providers/index.ts import { drizzle } from "drizzle-orm/libsql" import * as schema from "../schema" +import { relations } from "../schema/relations" export const db = drizzle(process.env.DB_FILE_NAME ?? "file:local.db", { schema, + relations, }) -// @filename: services/user.ts -import { eq } from "drizzle-orm" -import { db } from "../providers" -import { users } from "../schema" - -export async function createUser(input: typeof users.$inferInsert) { - const [user] = await db.insert(users).values(input).returning() - return user -} - -export async function findUsersByName(name: string) { - return await db.query.users.findMany({ - where: eq(users.name, name), - }) -} - -export async function findUserByPhone(phone: string) { - return await db.query.users.findFirst({ - where: eq(users.phone, phone), - }) -} -// @filename: services/index.ts -export * as userService from "./user" // @filename: contexts/index.ts import { createMemoization, useContext } from "@gqloom/core" import { GraphQLError } from "graphql" import type { YogaInitialContext } from "graphql-yoga" -import { userService } from "../services" +import { db } from "../providers" export const useCurrentUser = createMemoization(async () => { const phone = useContext().request.headers.get("authorization") if (phone == null) throw new GraphQLError("Unauthorized") - const user = await userService.findUserByPhone(phone) + const user = await db.query.users.findFirst({ where: { phone } }) if (user == null) throw new GraphQLError("Unauthorized") return user }) @@ -1243,83 +1187,64 @@ export const catResolver = resolver.of(cats, { ```ts twoslash title="src/resolvers/cat.ts" -// @filename: schema.ts +// @filename: schema/index.ts import { drizzleSilk } from "@gqloom/drizzle" -import { relations } from "drizzle-orm" -import * as t from "drizzle-orm/sqlite-core" +import { integer, sqliteTable, text } from "drizzle-orm/sqlite-core" export const users = drizzleSilk( - t.sqliteTable("users", { - id: t.int().primaryKey({ autoIncrement: true }), - name: t.text().notNull(), - phone: t.text().notNull().unique(), + sqliteTable("users", { + id: integer().primaryKey({ autoIncrement: true }), + name: text().notNull(), + phone: text().notNull().unique(), }) ) -export const usersRelations = relations(users, ({ many }) => ({ - cats: many(cats), -})) - export const cats = drizzleSilk( - t.sqliteTable("cats", { - id: t.integer().primaryKey({ autoIncrement: true }), - name: t.text().notNull(), - birthday: t.integer({ mode: "timestamp" }).notNull(), - ownerId: t - .integer() + sqliteTable("cats", { + id: integer().primaryKey({ autoIncrement: true }), + name: text().notNull(), + birthday: integer({ mode: "timestamp" }).notNull(), + ownerId: integer() .notNull() .references(() => users.id), }) ) +// @filename: schema/relations.ts +import { defineRelations } from "drizzle-orm" +import * as schema from "./index" -export const catsRelations = relations(cats, ({ one }) => ({ - owner: one(users, { - fields: [cats.ownerId], - references: [users.id], - }), +export const relations = defineRelations(schema, (r) => ({ + users: { + cats: r.many.cats(), + }, + cats: { + owner: r.one.users({ + from: r.cats.ownerId, + to: r.users.id, + }), + }, })) // @filename: providers/index.ts import { drizzle } from "drizzle-orm/libsql" import * as schema from "../schema" +import { relations } from "../schema/relations" export const db = drizzle(process.env.DB_FILE_NAME ?? "file:local.db", { schema, + relations, }) -// @filename: services/user.ts -import { eq } from "drizzle-orm" -import { db } from "../providers" -import { users } from "../schema" - -export async function createUser(input: typeof users.$inferInsert) { - const [user] = await db.insert(users).values(input).returning() - return user -} - -export async function findUsersByName(name: string) { - return await db.query.users.findMany({ - where: eq(users.name, name), - }) -} - -export async function findUserByPhone(phone: string) { - return await db.query.users.findFirst({ - where: eq(users.phone, phone), - }) -} -// @filename: services/index.ts -export * as userService from "./user" // @filename: contexts/index.ts import { createMemoization, useContext } from "@gqloom/core" import { GraphQLError } from "graphql" import type { YogaInitialContext } from "graphql-yoga" -import { userService } from "../services" +import { db } from "../providers" export const useCurrentUser = createMemoization(async () => { const phone = useContext().request.headers.get("authorization") if (phone == null) throw new GraphQLError("Unauthorized") - const user = await userService.findUserByPhone(phone) + const user = await db.query.users.findFirst({ where: { phone } }) if (user == null) throw new GraphQLError("Unauthorized") return user }) @@ -1559,7 +1484,6 @@ import * as v from "valibot" import { useCurrentUser } from "../contexts" import { db } from "../providers" // [!code ++] import { users } from "../schema" -import { userService } from "../services" const userResolverFactory = drizzleResolverFactory(db, "users") // [!code ++] @@ -1570,11 +1494,11 @@ export const userResolver = resolver.of(users, { usersByName: query(users.$list()) .input({ name: v.string() }) - .resolve(({ name }) => userService.findUsersByName(name)), + .resolve(({ name }) => db.query.users.findMany({ where: { name } })), userByPhone: query(users.$nullable()) .input({ phone: v.string() }) - .resolve(({ phone }) => userService.findUserByPhone(phone)), + .resolve(({ phone }) => db.query.users.findFirst({ where: { phone } })), createUser: mutation(users) .input({ @@ -1583,7 +1507,10 @@ export const userResolver = resolver.of(users, { phone: v.string(), }), }) - .resolve(async ({ data }) => userService.createUser(data)), + .resolve(async ({ data }) => { + const [user] = await db.insert(users).values(data).returning() + return user + }), }) ``` @@ -1606,11 +1533,11 @@ export const userResolver = resolver.of(users, { usersByName: query(users.$list()) .input({ name: z.string() }) - .resolve(({ name }) => userService.findUsersByName(name)), + .resolve(({ name }) => db.query.users.findMany({ where: { name } })), userByPhone: query(users.$nullable()) .input({ phone: z.string() }) - .resolve(({ phone }) => userService.findUserByPhone(phone)), + .resolve(({ phone }) => db.query.users.findFirst({ where: { phone } })), createUser: mutation(users) .input({ @@ -1619,7 +1546,10 @@ export const userResolver = resolver.of(users, { phone: z.string(), }), }) - .resolve(async ({ data }) => userService.createUser(data)), + .resolve(async ({ data }) => { + const [user] = await db.insert(users).values(data).returning() + return user + }), }) ``` From 1f4f77a57d328fad62c0469073e7474bbb7002d3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E2=9B=B5?= <809067559@qq.com> Date: Wed, 9 Apr 2025 14:04:23 +0800 Subject: [PATCH 13/54] docs(getting-started): remove mention of 'services' directory --- website/content/docs/getting-started.mdx | 1 - website/content/docs/getting-started.zh.mdx | 1 - 2 files changed, 2 deletions(-) diff --git a/website/content/docs/getting-started.mdx b/website/content/docs/getting-started.mdx index 23d8039a..b0bd2781 100644 --- a/website/content/docs/getting-started.mdx +++ b/website/content/docs/getting-started.mdx @@ -59,7 +59,6 @@ Among them, the functions of each folder or file under the `src` directory are a - `providers`: Store functions that need to interact with external services, such as database connections and Redis connections; - `resolvers`: Store GraphQL resolvers; - `schema`: Store the schema, mainly the database table structure; -- `services`: Store business logic, such as user login, user registration, etc.; - `index.ts`: Used to run the GraphQL application in the form of an HTTP service; diff --git a/website/content/docs/getting-started.zh.mdx b/website/content/docs/getting-started.zh.mdx index 331bef9f..1989ecfe 100644 --- a/website/content/docs/getting-started.zh.mdx +++ b/website/content/docs/getting-started.zh.mdx @@ -59,7 +59,6 @@ import { File, Folder, Files } from 'fumadocs-ui/components/files'; - `providers`: 存放需要与外部服务交互的功能,如数据库连接、Redis 连接; - `resolvers`: 存放 GraphQL 解析器; - `schema`: 存放 schema,主要是数据库表结构; -- `services`: 存放业务逻辑,如用户登录、用户注册等; - `index.ts`: 用于以 HTTP 服务的形式运行 GraphQL 应用; From 5b769cb71cdb3db8ce18715a4ff79f25f2d9189c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E2=9B=B5?= <809067559@qq.com> Date: Wed, 9 Apr 2025 16:29:54 +0800 Subject: [PATCH 14/54] build(drizzle): Bump package version to 0.9.0-beta.1 --- packages/drizzle/CHANGELOG.md | 2 ++ packages/drizzle/package.json | 4 ++-- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/packages/drizzle/CHANGELOG.md b/packages/drizzle/CHANGELOG.md index abc0a6d1..b629ecb3 100644 --- a/packages/drizzle/CHANGELOG.md +++ b/packages/drizzle/CHANGELOG.md @@ -4,6 +4,8 @@ All notable changes to this project will be documented in this file. ## next (YYYY-MM-DD) +- Refactor: Migrating to Relational Queries version 2 + ## 0.8.2 (2025-04-08) - Feat: support drizzle text enum diff --git a/packages/drizzle/package.json b/packages/drizzle/package.json index 9975b39e..ea42c90a 100644 --- a/packages/drizzle/package.json +++ b/packages/drizzle/package.json @@ -1,6 +1,6 @@ { "name": "@gqloom/drizzle", - "version": "0.8.2", + "version": "0.9.0-beta.1", "description": "GQLoom integration with Drizzle ORM", "type": "module", "main": "./dist/index.js", @@ -34,7 +34,7 @@ "license": "MIT", "peerDependencies": { "@gqloom/core": ">= 0.8.0", - "drizzle-orm": ">= 0.38.0", + "drizzle-orm": ">= 1.0.0-beta.1", "graphql": ">= 16.8.0" }, "devDependencies": { From abb59e7eed4b6ffa9d8dd7791df8d0959f795875 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E2=9B=B5?= <809067559@qq.com> Date: Mon, 21 Apr 2025 19:37:43 +0800 Subject: [PATCH 15/54] refactor(drizzle): deprecate `tableName` in favor of `table` in resolver factory - Updated the `drizzleResolverFactory` function to deprecate the use of `tableName`, encouraging users to directly use `table` instead. - Added detailed deprecation warnings and examples in the code comments to guide users on the preferred usage. - Modified test cases across MySQL, PostgreSQL, and SQLite to reflect the new usage of `table` instead of `tableName`. --- packages/drizzle/src/factory/index.ts | 39 +++++++++++++++++++ .../drizzle/test/resolver-factory.spec.ts | 22 +++++------ packages/drizzle/test/resolver-mysql.spec.ts | 6 +-- .../drizzle/test/resolver-postgres.spec.ts | 6 +-- packages/drizzle/test/resolver-sqlite.spec.ts | 7 ++-- 5 files changed, 57 insertions(+), 23 deletions(-) diff --git a/packages/drizzle/src/factory/index.ts b/packages/drizzle/src/factory/index.ts index 991c1b1c..a91720c6 100644 --- a/packages/drizzle/src/factory/index.ts +++ b/packages/drizzle/src/factory/index.ts @@ -8,6 +8,19 @@ import { DrizzlePostgresResolverFactory } from "./resolver-postgres" import { DrizzleSQLiteResolverFactory } from "./resolver-sqlite" import type { BaseDatabase } from "./types" +/** + * @deprecated directly use `table` instead of `tableName`. + * + * ## Example + * ⛔️ Don't do this + * ```ts + * const userFactory = drizzleResolverFactory(db, "users") + * ``` + * ✅ Do this + * ```ts + * const userFactory = drizzleResolverFactory(db, users) + * ``` + */ export function drizzleResolverFactory< TDatabase extends BaseSQLiteDatabase, TTableName extends keyof NonNullable, @@ -30,6 +43,19 @@ export function drizzleResolverFactory< options?: DrizzleResolverFactoryOptions ): DrizzleSQLiteResolverFactory +/** + * @deprecated directly use `table` instead of `tableName`. + * + * ## Example + * ⛔️ Don't do this + * ```ts + * const userFactory = drizzleResolverFactory(db, "users") + * ``` + * ✅ Do this + * ```ts + * const userFactory = drizzleResolverFactory(db, users) + * ``` + */ export function drizzleResolverFactory< TDatabase extends PgDatabase, TTableName extends keyof NonNullable, @@ -52,6 +78,19 @@ export function drizzleResolverFactory< options?: DrizzleResolverFactoryOptions ): DrizzlePostgresResolverFactory +/** + * @deprecated directly use `table` instead of `tableName`. + * + * ## Example + * ⛔️ Don't do this + * ```ts + * const userFactory = drizzleResolverFactory(db, "users") + * ``` + * ✅ Do this + * ```ts + * const userFactory = drizzleResolverFactory(db, users) + * ``` + */ export function drizzleResolverFactory< TDatabase extends MySqlDatabase, TTableName extends keyof NonNullable, diff --git a/packages/drizzle/test/resolver-factory.spec.ts b/packages/drizzle/test/resolver-factory.spec.ts index 86071852..c3e9226a 100644 --- a/packages/drizzle/test/resolver-factory.spec.ts +++ b/packages/drizzle/test/resolver-factory.spec.ts @@ -44,12 +44,11 @@ describe.concurrent("DrizzleResolverFactory", () => { beforeAll(async () => { db = sqliteDrizzle({ - schema: sqliteSchemas, relations: sqliteRelations, connection: { url: `file:${pathToDB.pathname}` }, }) - userFactory = drizzleResolverFactory(db, "users") + userFactory = drizzleResolverFactory(db, sqliteSchemas.users) await db.insert(sqliteSchemas.users).values([ { @@ -524,7 +523,7 @@ describe.concurrent("DrizzleResolverFactory", () => { const postsField = userFactory.relationField("posts").description("posts") expect(postsField).toBeDefined() - const postFactory = drizzleResolverFactory(db, "posts") + const postFactory = drizzleResolverFactory(db, sqliteSchemas.posts) const authorField = postFactory .relationField("author") .description("author") @@ -534,7 +533,7 @@ describe.concurrent("DrizzleResolverFactory", () => { it("should resolve correctly", async () => { const studentCourseFactory = drizzleResolverFactory( db, - "studentToCourses" + sqliteSchemas.studentToCourses ) const gradeField = studentCourseFactory.relationField("grade") const John = await db.query.users.findFirst({ @@ -624,7 +623,7 @@ describe.concurrent("DrizzleResolverFactory", () => { // This test specifically targets the multi-field relation handling in relationField const studentCourseFactory = drizzleResolverFactory( db, - "studentToCourses" + sqliteSchemas.studentToCourses ) // Setup test data @@ -673,7 +672,7 @@ describe.concurrent("DrizzleResolverFactory", () => { it("should handle loading relation data correctly when using multiple fields", async () => { const studentCourseFactory = drizzleResolverFactory( db, - "studentToCourses" + sqliteSchemas.studentToCourses ) // Setup test data for multiple students @@ -774,7 +773,7 @@ describe.concurrent("DrizzleResolverFactory", () => { // Now, create and test real data to verify the full flow const studentCourseFactory = drizzleResolverFactory( db, - "studentToCourses" + sqliteSchemas.studentToCourses ) // Setup test data for a composite key scenario @@ -879,11 +878,10 @@ describe.concurrent("DrizzleMySQLResolverFactory", () => { beforeAll(async () => { db = mysqlDrizzle(config.mysqlUrl, { - schema, relations: mysqlRelations, mode: "default", }) - userFactory = drizzleResolverFactory(db, "users") + userFactory = drizzleResolverFactory(db, mysqlSchemas.users) await db.execute(sql`select 1`) }) @@ -1009,10 +1007,9 @@ describe.concurrent("DrizzlePostgresResolverFactory", () => { beforeAll(async () => { db = pgDrizzle(config.postgresUrl, { - schema, relations: pgRelations, }) - userFactory = drizzleResolverFactory(db, "users") + userFactory = drizzleResolverFactory(db, pgSchemas.users) await db.execute(sql`select 1`) }) @@ -1139,12 +1136,11 @@ describe.concurrent("DrizzleSQLiteResolverFactory", () => { beforeAll(async () => { db = sqliteDrizzle({ - schema: sqliteSchemas, relations: sqliteRelations, connection: { url: `file:${pathToDB.pathname}` }, }) - userFactory = drizzleResolverFactory(db, "users") + userFactory = drizzleResolverFactory(db, sqliteSchemas.users) }) describe("insertArrayMutation", () => { diff --git a/packages/drizzle/test/resolver-mysql.spec.ts b/packages/drizzle/test/resolver-mysql.spec.ts index 72f368ec..543b25cf 100644 --- a/packages/drizzle/test/resolver-mysql.spec.ts +++ b/packages/drizzle/test/resolver-mysql.spec.ts @@ -46,9 +46,9 @@ describe("resolver by mysql", () => { beforeAll(async () => { try { - db = drizzle(config.mysqlUrl, { schema, relations, mode: "default" }) - const userFactory = drizzleResolverFactory(db, "users") - const postFactory = drizzleResolverFactory(db, "posts") + db = drizzle(config.mysqlUrl, { relations, mode: "default" }) + const userFactory = drizzleResolverFactory(db, users) + const postFactory = drizzleResolverFactory(db, posts) gqlSchema = weave( userFactory.resolver({ name: "users" }), postFactory.resolver({ name: "posts" }) diff --git a/packages/drizzle/test/resolver-postgres.spec.ts b/packages/drizzle/test/resolver-postgres.spec.ts index 3766acc7..52b33ab2 100644 --- a/packages/drizzle/test/resolver-postgres.spec.ts +++ b/packages/drizzle/test/resolver-postgres.spec.ts @@ -42,9 +42,9 @@ describe("resolver by postgres", () => { beforeAll(async () => { try { - db = drizzle(config.postgresUrl, { schema, relations }) - const userFactory = drizzleResolverFactory(db, "users") - const postFactory = drizzleResolverFactory(db, "posts") + db = drizzle(config.postgresUrl, { relations }) + const userFactory = drizzleResolverFactory(db, users) + const postFactory = drizzleResolverFactory(db, posts) gqlSchema = weave( userFactory.resolver({ name: "users" }), postFactory.resolver({ name: "posts" }) diff --git a/packages/drizzle/test/resolver-sqlite.spec.ts b/packages/drizzle/test/resolver-sqlite.spec.ts index 3054f1d6..295fc513 100644 --- a/packages/drizzle/test/resolver-sqlite.spec.ts +++ b/packages/drizzle/test/resolver-sqlite.spec.ts @@ -8,7 +8,7 @@ import { import { type YogaServerInstance, createYoga } from "graphql-yoga" import { afterAll, beforeAll, describe, expect, it } from "vitest" import { drizzleResolverFactory } from "../src" -import * as schema from "./schema/sqlite" +import type * as schema from "./schema/sqlite" import { posts, users } from "./schema/sqlite" import { relations } from "./schema/sqlite-relations" @@ -41,12 +41,11 @@ describe("resolver by sqlite", () => { beforeAll(async () => { db = drizzle({ - schema, relations, connection: { url: `file:${pathToDB.pathname}` }, }) - const userFactory = drizzleResolverFactory(db, "users") - const postFactory = drizzleResolverFactory(db, "posts") + const userFactory = drizzleResolverFactory(db, users) + const postFactory = drizzleResolverFactory(db, posts) gqlSchema = weave(userFactory.resolver(), postFactory.resolver()) yoga = createYoga({ schema: gqlSchema }) From f947cf37e941f5fd80ed826a20e44adc59a58003 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E2=9B=B5?= <809067559@qq.com> Date: Mon, 21 Apr 2025 19:38:08 +0800 Subject: [PATCH 16/54] chore(drizzle): bump version to 0.9.0-beta.3 --- packages/drizzle/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/drizzle/package.json b/packages/drizzle/package.json index ba12e69d..af276159 100644 --- a/packages/drizzle/package.json +++ b/packages/drizzle/package.json @@ -1,6 +1,6 @@ { "name": "@gqloom/drizzle", - "version": "0.9.0-beta.2", + "version": "0.9.0-beta.3", "description": "GQLoom integration with Drizzle ORM", "type": "module", "main": "./dist/index.js", From 905b1554c7915bfe411917466483246b936c1c94 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E2=9B=B5?= <809067559@qq.com> Date: Tue, 22 Apr 2025 23:25:10 +0800 Subject: [PATCH 17/54] refactor(drizzle): enhance resolver factory to support multiple database types - Added support for creating resolver factories for SQLite, PostgreSQL, and MySQL databases. - Updated the `drizzleResolverFactory` function signatures to accommodate the new database types. - Included detailed documentation for each resolver factory function to clarify usage and options. - Removed deprecated function signatures for PostgreSQL and MySQL to streamline the API. --- packages/drizzle/src/factory/index.ts | 63 +++++++++++++++++++-------- 1 file changed, 44 insertions(+), 19 deletions(-) diff --git a/packages/drizzle/src/factory/index.ts b/packages/drizzle/src/factory/index.ts index a91720c6..fd2a6509 100644 --- a/packages/drizzle/src/factory/index.ts +++ b/packages/drizzle/src/factory/index.ts @@ -8,6 +8,22 @@ import { DrizzlePostgresResolverFactory } from "./resolver-postgres" import { DrizzleSQLiteResolverFactory } from "./resolver-sqlite" import type { BaseDatabase } from "./types" +/** + * Create a resolver factory for SQLite databases. + * + * @param db - The SQLite database instance. + * @param table - The table to create a resolver factory for. + * @param options - The options for the resolver factory. + */ +export function drizzleResolverFactory< + TDatabase extends BaseSQLiteDatabase, + TTable extends SQLiteTable, +>( + db: TDatabase, + table: TTable, + options?: DrizzleResolverFactoryOptions +): DrizzleSQLiteResolverFactory + /** * @deprecated directly use `table` instead of `tableName`. * @@ -34,14 +50,38 @@ export function drizzleResolverFactory< TDatabase, NonNullable[TTableName] > + +/** + * Create a resolver factory for PostgreSQL databases. + * + * @param db - The PostgreSQL database instance. + * @param table - The table to create a resolver factory for. + * @param options - The options for the resolver factory. + */ export function drizzleResolverFactory< - TDatabase extends BaseSQLiteDatabase, - TTable extends SQLiteTable, + TDatabase extends PgDatabase, + TTable extends PgTable, >( db: TDatabase, table: TTable, options?: DrizzleResolverFactoryOptions -): DrizzleSQLiteResolverFactory +): DrizzlePostgresResolverFactory + +/** + * Create a resolver factory for MySQL databases. + * + * @param db - The MySQL database instance. + * @param table - The table to create a resolver factory for. + * @param options - The options for the resolver factory. + */ +export function drizzleResolverFactory< + TDatabase extends MySqlDatabase, + TTable extends MySqlTable, +>( + db: TDatabase, + table: TTable, + options?: DrizzleResolverFactoryOptions +): DrizzleMySQLResolverFactory /** * @deprecated directly use `table` instead of `tableName`. @@ -69,14 +109,6 @@ export function drizzleResolverFactory< TDatabase, NonNullable[TTableName] > -export function drizzleResolverFactory< - TDatabase extends PgDatabase, - TTable extends PgTable, ->( - db: TDatabase, - table: TTable, - options?: DrizzleResolverFactoryOptions -): DrizzlePostgresResolverFactory /** * @deprecated directly use `table` instead of `tableName`. @@ -91,6 +123,7 @@ export function drizzleResolverFactory< * const userFactory = drizzleResolverFactory(db, users) * ``` */ + export function drizzleResolverFactory< TDatabase extends MySqlDatabase, TTableName extends keyof NonNullable, @@ -104,14 +137,6 @@ export function drizzleResolverFactory< TDatabase, NonNullable[TTableName] > -export function drizzleResolverFactory< - TDatabase extends MySqlDatabase, - TTable extends MySqlTable, ->( - db: TDatabase, - table: TTable, - options?: DrizzleResolverFactoryOptions -): DrizzleMySQLResolverFactory export function drizzleResolverFactory( db: BaseDatabase, From 9f2b4c5676f86106fe3ce983e0f7fe38d9f59e18 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E2=9B=B5?= <809067559@qq.com> Date: Tue, 22 Apr 2025 23:58:04 +0800 Subject: [PATCH 18/54] chore(drizzle): bump version to 0.9.0-beta.4 --- packages/drizzle/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/drizzle/package.json b/packages/drizzle/package.json index af276159..0237547d 100644 --- a/packages/drizzle/package.json +++ b/packages/drizzle/package.json @@ -1,6 +1,6 @@ { "name": "@gqloom/drizzle", - "version": "0.9.0-beta.3", + "version": "0.9.0-beta.4", "description": "GQLoom integration with Drizzle ORM", "type": "module", "main": "./dist/index.js", From 3d6f620e8ddc4fb85ff18e159d21f6e25b5a83d1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E2=9B=B5?= <809067559@qq.com> Date: Mon, 28 Apr 2025 14:20:27 +0800 Subject: [PATCH 19/54] chore(drizzle): bump version to 0.9.0-beta.5 --- packages/drizzle/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/drizzle/package.json b/packages/drizzle/package.json index 0237547d..86a6de6a 100644 --- a/packages/drizzle/package.json +++ b/packages/drizzle/package.json @@ -1,6 +1,6 @@ { "name": "@gqloom/drizzle", - "version": "0.9.0-beta.4", + "version": "0.9.0-beta.5", "description": "GQLoom integration with Drizzle ORM", "type": "module", "main": "./dist/index.js", From 01d153aee9516db8a46b5cf645603fb427c32a15 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E2=9B=B5?= <809067559@qq.com> Date: Mon, 28 Apr 2025 21:39:44 +0800 Subject: [PATCH 20/54] chore(cattery-valibot): update dependencies and enhance GraphQL schema - Updated dependencies in package.json to specific versions for @gqloom/core and @gqloom/drizzle. - Added graphql-scalars dependency for enhanced scalar types. - Modified CatsItem schema to use DateTime type for birthday. - Refactored CatsFilters and SQLite filters to improve query capabilities. - Enhanced mutation operations for inserting cats with new input types and conflict handling. - Implemented error handling middleware in the server setup. --- examples/cattery-valibot/package.json | 5 +- examples/cattery-valibot/schema.graphql | 124 +++++++++++------- examples/cattery-valibot/src/index.ts | 35 ++++- .../cattery-valibot/src/providers/index.ts | 4 - examples/cattery-valibot/src/resolvers/cat.ts | 6 +- .../cattery-valibot/src/resolvers/user.ts | 2 +- pnpm-lock.yaml | 30 ++++- 7 files changed, 143 insertions(+), 63 deletions(-) diff --git a/examples/cattery-valibot/package.json b/examples/cattery-valibot/package.json index e7e00f1a..9f7c3ded 100644 --- a/examples/cattery-valibot/package.json +++ b/examples/cattery-valibot/package.json @@ -17,12 +17,13 @@ "typescript": "^5.7.3" }, "dependencies": { - "@gqloom/core": "latest", - "@gqloom/drizzle": "latest", + "@gqloom/core": "^0.8.4", + "@gqloom/drizzle": "0.9.0-beta.5", "@gqloom/valibot": "^0.7.1", "@libsql/client": "^0.14.0", "dotenv": "^16.4.7", "graphql": "^16.8.1", + "graphql-scalars": "^1.24.1", "graphql-yoga": "^5.6.0", "valibot": "1.0.0-rc.1" } diff --git a/examples/cattery-valibot/schema.graphql b/examples/cattery-valibot/schema.graphql index 37122c79..c26cee4e 100644 --- a/examples/cattery-valibot/schema.graphql +++ b/examples/cattery-valibot/schema.graphql @@ -8,12 +8,17 @@ type UsersItem { type CatsItem { id: Int! name: String! - birthday: String! + birthday: DateTime! ownerId: Int! age(currentYear: Int): Float! owner: UsersItem } +""" +A date-time string at UTC, such as 2007-12-03T10:15:30Z, compliant with the `date-time` format outlined in section 5.6 of the RFC 3339 profile of the ISO 8601 standard for representation of dates and times using the Gregorian calendar. +""" +scalar DateTime + type Query { hello(name: String): String! mine: UsersItem! @@ -22,7 +27,7 @@ type Query { cats( offset: Int limit: Int - orderBy: [CatsOrderBy!] + orderBy: CatsOrderBy where: CatsFilters ): [CatsItem!]! } @@ -44,7 +49,9 @@ input CatsFilters { name: SQLiteTextFilters birthday: SQLiteTimestampFilters ownerId: SQLiteIntegerFilters - OR: [CatsFiltersOr!] + OR: [CatsFiltersNested!] + AND: [CatsFiltersNested!] + NOT: CatsFiltersNested } input SQLiteIntegerFilters { @@ -54,22 +61,24 @@ input SQLiteIntegerFilters { lte: Int gt: Int gte: Int - inArray: [Int!] - notInArray: [Int!] + in: [Int!] + notIn: [Int!] isNull: Boolean isNotNull: Boolean - OR: [SQLiteIntegerFiltersOr!] + OR: [SQLiteIntegerFiltersNested!] + AND: [SQLiteIntegerFiltersNested!] + NOT: SQLiteIntegerFiltersNested } -input SQLiteIntegerFiltersOr { +input SQLiteIntegerFiltersNested { eq: Int ne: Int lt: Int lte: Int gt: Int gte: Int - inArray: [Int!] - notInArray: [Int!] + in: [Int!] + notIn: [Int!] isNull: Boolean isNotNull: Boolean } @@ -85,14 +94,16 @@ input SQLiteTextFilters { notLike: String ilike: String notIlike: String - inArray: [String!] - notInArray: [String!] + in: [String!] + notIn: [String!] isNull: Boolean isNotNull: Boolean - OR: [SQLiteTextFiltersOr!] + OR: [SQLiteTextFiltersNested!] + AND: [SQLiteTextFiltersNested!] + NOT: SQLiteTextFiltersNested } -input SQLiteTextFiltersOr { +input SQLiteTextFiltersNested { eq: String ne: String lt: String @@ -103,48 +114,42 @@ input SQLiteTextFiltersOr { notLike: String ilike: String notIlike: String - inArray: [String!] - notInArray: [String!] + in: [String!] + notIn: [String!] isNull: Boolean isNotNull: Boolean } input SQLiteTimestampFilters { - eq: String - ne: String - lt: String - lte: String - gt: String - gte: String - like: String - notLike: String - ilike: String - notIlike: String - inArray: [String!] - notInArray: [String!] + eq: DateTime + ne: DateTime + lt: DateTime + lte: DateTime + gt: DateTime + gte: DateTime + in: [DateTime!] + notIn: [DateTime!] isNull: Boolean isNotNull: Boolean - OR: [SQLiteTimestampFiltersOr!] + OR: [SQLiteTimestampFiltersNested!] + AND: [SQLiteTimestampFiltersNested!] + NOT: SQLiteTimestampFiltersNested } -input SQLiteTimestampFiltersOr { - eq: String - ne: String - lt: String - lte: String - gt: String - gte: String - like: String - notLike: String - ilike: String - notIlike: String - inArray: [String!] - notInArray: [String!] +input SQLiteTimestampFiltersNested { + eq: DateTime + ne: DateTime + lt: DateTime + lte: DateTime + gt: DateTime + gte: DateTime + in: [DateTime!] + notIn: [DateTime!] isNull: Boolean isNotNull: Boolean } -input CatsFiltersOr { +input CatsFiltersNested { id: SQLiteIntegerFilters name: SQLiteTextFilters birthday: SQLiteTimestampFilters @@ -153,7 +158,12 @@ input CatsFiltersOr { type Mutation { createUser(data: CreateUserDataInput!): UsersItem! - createCats(values: [CreateCatsValuesInput!]!): [CatsItem!]! + insertCats( + values: [CatsInsertInput!]! + onConflictDoUpdate: CatsInsertOnConflictDoUpdateInput + onConflictDoNothing: CatsInsertOnConflictDoNothingInput + ): [CatsItem!]! + createManyCats(values: [CreateManyCatsValuesInput!]!): [CatsItem!]! } input CreateUserDataInput { @@ -161,7 +171,33 @@ input CreateUserDataInput { phone: String! } -input CreateCatsValuesInput { +input CatsInsertInput { + id: Int + name: String! + birthday: DateTime! + ownerId: Int! +} + +input CatsInsertOnConflictDoUpdateInput { + target: [CatsTableColumn!]! + set: CatsInsertInput + targetWhere: CatsFilters + setWhere: CatsFilters +} + +enum CatsTableColumn { + id + name + birthday + ownerId +} + +input CatsInsertOnConflictDoNothingInput { + target: [CatsTableColumn!] + where: CatsFilters +} + +input CreateManyCatsValuesInput { name: String! birthday: String! } diff --git a/examples/cattery-valibot/src/index.ts b/examples/cattery-valibot/src/index.ts index d2317dc7..ecd26665 100644 --- a/examples/cattery-valibot/src/index.ts +++ b/examples/cattery-valibot/src/index.ts @@ -1,10 +1,38 @@ import { createServer } from "node:http" -import { weave } from "@gqloom/core" +import { type Middleware, weave } from "@gqloom/core" import { ValibotWeaver } from "@gqloom/valibot" +import { GraphQLDateTime, GraphQLJSONObject } from "graphql-scalars" import { createYoga } from "graphql-yoga" import { resolvers } from "./resolvers" -const schema = weave(ValibotWeaver, ...resolvers) +const exceptionFilter: Middleware = async (next) => { + try { + return await next() + } catch (error) { + // biome-ignore lint/suspicious/noConsole: log error + console.error(error) + if (error instanceof Error) { + throw new GraphQLError(error.message) + } + throw new GraphQLError("There has been something wrong...") + } +} + +const schema = weave( + ValibotWeaver, + ...resolvers, + exceptionFilter, + DrizzleWeaver.config({ + presetGraphQLType(column) { + if (column.dataType === "date") { + return GraphQLDateTime + } + if (column.dataType === "json") { + return GraphQLJSONObject + } + }, + }) +) const yoga = createYoga({ schema }) createServer(yoga).listen(4000, () => { @@ -13,7 +41,8 @@ createServer(yoga).listen(4000, () => { import * as fs from "fs" import * as path from "path" -import { printSchema } from "graphql" +import { DrizzleWeaver } from "@gqloom/drizzle" +import { GraphQLError, printSchema } from "graphql" if (process.env.NODE_ENV !== "production") { fs.writeFileSync( path.resolve(__dirname, "../schema.graphql"), diff --git a/examples/cattery-valibot/src/providers/index.ts b/examples/cattery-valibot/src/providers/index.ts index 5d4f1880..945cbc6b 100644 --- a/examples/cattery-valibot/src/providers/index.ts +++ b/examples/cattery-valibot/src/providers/index.ts @@ -1,10 +1,6 @@ import { drizzle } from "drizzle-orm/libsql" -import * as schema from "../schema" import { relations } from "../schema/relations" export const db = drizzle(process.env.DB_FILE_NAME ?? "file:local.db", { - schema, relations, }) - -db._.relations["config"]["cats"]["owner"] diff --git a/examples/cattery-valibot/src/resolvers/cat.ts b/examples/cattery-valibot/src/resolvers/cat.ts index 04742acd..947ed805 100644 --- a/examples/cattery-valibot/src/resolvers/cat.ts +++ b/examples/cattery-valibot/src/resolvers/cat.ts @@ -5,7 +5,7 @@ import { useCurrentUser } from "../contexts" import { db } from "../providers" import { cats } from "../schema" -const catResolverFactory = drizzleResolverFactory(db, "cats") +const catResolverFactory = drizzleResolverFactory(db, cats) export const catResolver = resolver.of(cats, { cats: catResolverFactory.selectArrayQuery(), @@ -22,7 +22,9 @@ export const catResolver = resolver.of(cats, { owner: catResolverFactory.relationField("owner"), - createCats: catResolverFactory.insertArrayMutation({ + insertCats: catResolverFactory.insertArrayMutation(), + + createManyCats: catResolverFactory.insertArrayMutation({ input: v.pipeAsync( v.objectAsync({ values: v.arrayAsync( diff --git a/examples/cattery-valibot/src/resolvers/user.ts b/examples/cattery-valibot/src/resolvers/user.ts index adf0e90e..e880b7a9 100644 --- a/examples/cattery-valibot/src/resolvers/user.ts +++ b/examples/cattery-valibot/src/resolvers/user.ts @@ -5,7 +5,7 @@ import { useCurrentUser } from "../contexts" import { db } from "../providers" import { users } from "../schema" -const userResolverFactory = drizzleResolverFactory(db, "users") +const userResolverFactory = drizzleResolverFactory(db, users) export const userResolver = resolver.of(users, { cats: userResolverFactory.relationField("cats"), diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index f4298da9..473467ad 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -83,11 +83,11 @@ importers: examples/cattery-valibot: dependencies: '@gqloom/core': - specifier: latest + specifier: ^0.8.4 version: 0.8.4(graphql@16.8.1(patch_hash=2p6wc42resg5at2p4utt5idamq)) '@gqloom/drizzle': - specifier: latest - version: 0.8.2(@gqloom/core@0.8.4(graphql@16.8.1(patch_hash=2p6wc42resg5at2p4utt5idamq)))(drizzle-orm@1.0.0-beta.1-7946562(@libsql/client@0.14.0)(@prisma/client@6.1.0(prisma@6.1.0))(@types/pg@8.11.10)(better-sqlite3@11.8.1)(gel@2.0.2)(knex@3.1.0(better-sqlite3@11.8.1)(mysql2@3.14.0)(pg@8.13.2)(sqlite3@5.1.7))(mysql2@3.14.0)(pg@8.13.2)(prisma@6.1.0)(sqlite3@5.1.7))(graphql@16.8.1(patch_hash=2p6wc42resg5at2p4utt5idamq)) + specifier: 0.9.0-beta.5 + version: 0.9.0-beta.5(@gqloom/core@0.8.4(graphql@16.8.1(patch_hash=2p6wc42resg5at2p4utt5idamq)))(drizzle-orm@1.0.0-beta.1-7946562(@libsql/client@0.14.0)(@prisma/client@6.1.0(prisma@6.1.0))(@types/pg@8.11.10)(better-sqlite3@11.8.1)(gel@2.0.2)(knex@3.1.0(better-sqlite3@11.8.1)(mysql2@3.14.0)(pg@8.13.2)(sqlite3@5.1.7))(mysql2@3.14.0)(pg@8.13.2)(prisma@6.1.0)(sqlite3@5.1.7))(graphql@16.8.1(patch_hash=2p6wc42resg5at2p4utt5idamq)) '@gqloom/valibot': specifier: ^0.7.1 version: 0.7.1(@gqloom/core@0.8.4(graphql@16.8.1(patch_hash=2p6wc42resg5at2p4utt5idamq)))(graphql@16.8.1(patch_hash=2p6wc42resg5at2p4utt5idamq))(valibot@1.0.0-rc.1(typescript@5.7.3)) @@ -100,6 +100,9 @@ importers: graphql: specifier: ^16.8.1 version: 16.8.1(patch_hash=2p6wc42resg5at2p4utt5idamq) + graphql-scalars: + specifier: ^1.24.1 + version: 1.24.1(graphql@16.8.1(patch_hash=2p6wc42resg5at2p4utt5idamq)) graphql-yoga: specifier: ^5.6.0 version: 5.6.0(graphql@16.8.1(patch_hash=2p6wc42resg5at2p4utt5idamq)) @@ -130,7 +133,7 @@ importers: version: 0.8.4(graphql@16.8.1(patch_hash=2p6wc42resg5at2p4utt5idamq)) '@gqloom/drizzle': specifier: latest - version: 0.8.2(@gqloom/core@0.8.4(graphql@16.8.1(patch_hash=2p6wc42resg5at2p4utt5idamq)))(drizzle-orm@1.0.0-beta.1-7946562(@libsql/client@0.14.0)(@prisma/client@6.1.0(prisma@6.1.0))(@types/pg@8.11.10)(better-sqlite3@11.8.1)(gel@2.0.2)(knex@3.1.0(better-sqlite3@11.8.1)(mysql2@3.14.0)(pg@8.13.2)(sqlite3@5.1.7))(mysql2@3.14.0)(pg@8.13.2)(prisma@6.1.0)(sqlite3@5.1.7))(graphql@16.8.1(patch_hash=2p6wc42resg5at2p4utt5idamq)) + version: 0.8.5(@gqloom/core@0.8.4(graphql@16.8.1(patch_hash=2p6wc42resg5at2p4utt5idamq)))(drizzle-orm@1.0.0-beta.1-7946562(@libsql/client@0.14.0)(@prisma/client@6.1.0(prisma@6.1.0))(@types/pg@8.11.10)(better-sqlite3@11.8.1)(gel@2.0.2)(knex@3.1.0(better-sqlite3@11.8.1)(mysql2@3.14.0)(pg@8.13.2)(sqlite3@5.1.7))(mysql2@3.14.0)(pg@8.13.2)(prisma@6.1.0)(sqlite3@5.1.7))(graphql@16.8.1(patch_hash=2p6wc42resg5at2p4utt5idamq)) '@gqloom/zod': specifier: latest version: 0.8.1(@gqloom/core@0.8.4(graphql@16.8.1(patch_hash=2p6wc42resg5at2p4utt5idamq)))(graphql@16.8.1(patch_hash=2p6wc42resg5at2p4utt5idamq))(zod@3.24.2) @@ -1459,13 +1462,20 @@ packages: peerDependencies: graphql: '>= 16.8.0' - '@gqloom/drizzle@0.8.2': - resolution: {integrity: sha512-DYNtTYFlU/9cDCYyr2rfJMx2GGbJA7GgihPcIXCwmLb5Eq5T5eNSux22dlZ1XJ/KQB5R4ONpyxIKR3uginFcjw==} + '@gqloom/drizzle@0.8.5': + resolution: {integrity: sha512-fBZQotNZSvm/wAy7v0fKmKL3UrKcj8SY9Q2MPUO3qi6BD0sGI25DPty7L0/YJmuG7gmxLHOfAiUI0EGEHN7o5g==} peerDependencies: '@gqloom/core': '>= 0.8.0' drizzle-orm: '>= 0.38.0' graphql: '>= 16.8.0' + '@gqloom/drizzle@0.9.0-beta.5': + resolution: {integrity: sha512-VrCStXrei9xuqO+UEVFaSOSGN1h/+gXYQLQhQ7w/GuTeh4VUeYa+HvkNfpMlmheZG6E67ee20rhhwOwv2ckmHg==} + peerDependencies: + '@gqloom/core': '>= 0.8.0' + drizzle-orm: '>= 1.0.0-beta.1' + graphql: '>= 16.8.0' + '@gqloom/mikro-orm@0.7.0': resolution: {integrity: sha512-95TcqpTGbP9qE79vpRduNFk8tbG+C3ouwiJ88jFwQXd+hFnqaleIILrPGWVhn7wx64HYcrdK+9xRqyHN+Mh0QA==} peerDependencies: @@ -6793,7 +6803,13 @@ snapshots: dependencies: graphql: 16.8.1(patch_hash=2p6wc42resg5at2p4utt5idamq) - '@gqloom/drizzle@0.8.2(@gqloom/core@0.8.4(graphql@16.8.1(patch_hash=2p6wc42resg5at2p4utt5idamq)))(drizzle-orm@1.0.0-beta.1-7946562(@libsql/client@0.14.0)(@prisma/client@6.1.0(prisma@6.1.0))(@types/pg@8.11.10)(better-sqlite3@11.8.1)(gel@2.0.2)(knex@3.1.0(better-sqlite3@11.8.1)(mysql2@3.14.0)(pg@8.13.2)(sqlite3@5.1.7))(mysql2@3.14.0)(pg@8.13.2)(prisma@6.1.0)(sqlite3@5.1.7))(graphql@16.8.1(patch_hash=2p6wc42resg5at2p4utt5idamq))': + '@gqloom/drizzle@0.8.5(@gqloom/core@0.8.4(graphql@16.8.1(patch_hash=2p6wc42resg5at2p4utt5idamq)))(drizzle-orm@1.0.0-beta.1-7946562(@libsql/client@0.14.0)(@prisma/client@6.1.0(prisma@6.1.0))(@types/pg@8.11.10)(better-sqlite3@11.8.1)(gel@2.0.2)(knex@3.1.0(better-sqlite3@11.8.1)(mysql2@3.14.0)(pg@8.13.2)(sqlite3@5.1.7))(mysql2@3.14.0)(pg@8.13.2)(prisma@6.1.0)(sqlite3@5.1.7))(graphql@16.8.1(patch_hash=2p6wc42resg5at2p4utt5idamq))': + dependencies: + '@gqloom/core': 0.8.4(graphql@16.8.1(patch_hash=2p6wc42resg5at2p4utt5idamq)) + drizzle-orm: 1.0.0-beta.1-7946562(@libsql/client@0.14.0)(@prisma/client@6.1.0(prisma@6.1.0))(@types/pg@8.11.10)(better-sqlite3@11.8.1)(gel@2.0.2)(knex@3.1.0(better-sqlite3@11.8.1)(mysql2@3.14.0)(pg@8.13.2)(sqlite3@5.1.7))(mysql2@3.14.0)(pg@8.13.2)(prisma@6.1.0)(sqlite3@5.1.7) + graphql: 16.8.1(patch_hash=2p6wc42resg5at2p4utt5idamq) + + '@gqloom/drizzle@0.9.0-beta.5(@gqloom/core@0.8.4(graphql@16.8.1(patch_hash=2p6wc42resg5at2p4utt5idamq)))(drizzle-orm@1.0.0-beta.1-7946562(@libsql/client@0.14.0)(@prisma/client@6.1.0(prisma@6.1.0))(@types/pg@8.11.10)(better-sqlite3@11.8.1)(gel@2.0.2)(knex@3.1.0(better-sqlite3@11.8.1)(mysql2@3.14.0)(pg@8.13.2)(sqlite3@5.1.7))(mysql2@3.14.0)(pg@8.13.2)(prisma@6.1.0)(sqlite3@5.1.7))(graphql@16.8.1(patch_hash=2p6wc42resg5at2p4utt5idamq))': dependencies: '@gqloom/core': 0.8.4(graphql@16.8.1(patch_hash=2p6wc42resg5at2p4utt5idamq)) drizzle-orm: 1.0.0-beta.1-7946562(@libsql/client@0.14.0)(@prisma/client@6.1.0(prisma@6.1.0))(@types/pg@8.11.10)(better-sqlite3@11.8.1)(gel@2.0.2)(knex@3.1.0(better-sqlite3@11.8.1)(mysql2@3.14.0)(pg@8.13.2)(sqlite3@5.1.7))(mysql2@3.14.0)(pg@8.13.2)(prisma@6.1.0)(sqlite3@5.1.7) From a50e533ceca957f2d916c403ce86694a489b00e4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E2=9B=B5?= <809067559@qq.com> Date: Mon, 28 Apr 2025 23:53:16 +0800 Subject: [PATCH 21/54] feat(drizzle): add matchQueryBuilder function to enhance query handling - Introduced `matchQueryBuilder` function in the helper module to facilitate matching query builders with target tables. - Updated `DrizzleResolverFactory` to utilize `matchQueryBuilder`, improving error handling for relations by ensuring the presence of a valid query builder. --- packages/drizzle/src/factory/resolver.ts | 10 +++------- packages/drizzle/src/helper.ts | 12 ++++++++++++ 2 files changed, 15 insertions(+), 7 deletions(-) diff --git a/packages/drizzle/src/factory/resolver.ts b/packages/drizzle/src/factory/resolver.ts index 68dea2ab..147bdbeb 100644 --- a/packages/drizzle/src/factory/resolver.ts +++ b/packages/drizzle/src/factory/resolver.ts @@ -48,7 +48,7 @@ import { DrizzleWeaver, type TableSilk, } from ".." -import { inArrayMultiple } from "../helper" +import { inArrayMultiple, matchQueryBuilder } from "../helper" import { type ColumnFilters, type CountArgs, @@ -62,7 +62,6 @@ import { type UpdateArgs, } from "./input" import type { - AnyQueryBuilder, BaseDatabase, CountQuery, DeleteMutation, @@ -393,7 +392,8 @@ export abstract class DrizzleResolverFactory< relationName ] as Relation const targetTable = relation?.targetTable - if (!relation || !(targetTable instanceof Table)) { + const queryBuilder = matchQueryBuilder(this.db.query, targetTable) + if (!relation || !(targetTable instanceof Table) || !queryBuilder) { throw new Error( `GQLoom-Drizzle Error: Relation ${this.tableName}.${String( relationName @@ -402,10 +402,6 @@ export abstract class DrizzleResolverFactory< } const output = DrizzleWeaver.unravel(targetTable) - const tableName = getTableName(targetTable) - const queryBuilder = this.db.query[ - tableName as keyof typeof this.db.query - ] as AnyQueryBuilder const isList = relation instanceof Many const fieldsLength = relation.sourceColumns.length diff --git a/packages/drizzle/src/helper.ts b/packages/drizzle/src/helper.ts index f2f4f814..cbdfe6c5 100644 --- a/packages/drizzle/src/helper.ts +++ b/packages/drizzle/src/helper.ts @@ -6,6 +6,7 @@ import { getTableName, sql, } from "drizzle-orm" +import type { AnyQueryBuilder } from "./factory" import type { DrizzleFactoryInputVisibilityBehaviors, ValueOrGetter, @@ -95,3 +96,14 @@ export function getValue(valueOrGetter: ValueOrGetter): T { ? (valueOrGetter as () => T)() : valueOrGetter } + +export function matchQueryBuilder( + queries: Record, + targetTable: any +): AnyQueryBuilder | undefined { + for (const qb of Object.values(queries)) { + if (qb.table != null && qb.table === targetTable) { + return qb + } + } +} From 34324010d4b43080468f6d5bdf488368137bcfe5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E2=9B=B5?= <809067559@qq.com> Date: Mon, 28 Apr 2025 23:55:19 +0800 Subject: [PATCH 22/54] chore(drizzle): bump version to 0.9.0-beta.7 --- packages/drizzle/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/drizzle/package.json b/packages/drizzle/package.json index 8aafc2b9..417e1547 100644 --- a/packages/drizzle/package.json +++ b/packages/drizzle/package.json @@ -1,6 +1,6 @@ { "name": "@gqloom/drizzle", - "version": "0.9.0-beta.6", + "version": "0.9.0-beta.7", "description": "GQLoom integration with Drizzle ORM", "type": "module", "main": "./dist/index.js", From df6e308238ab7392062cc549cfb1036b4524c508 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E2=9B=B5?= <809067559@qq.com> Date: Tue, 29 Apr 2025 22:16:39 +0800 Subject: [PATCH 23/54] feat(drizzle): update tablesFilter in database configurations - Modified `tablesFilter` in MySQL and Postgres configurations to include specific tables: `users` and `posts`. - Expanded `tablesFilter` in SQLite configurations to include additional tables: `users`, `posts`, `courses`, `studentToCourses`, and `studentCourseGrades`. - Removed unused `matchQueryBuilder` function from the helper module to streamline code. --- packages/drizzle/drizzle-mysql.config.ts | 2 +- packages/drizzle/drizzle-postgres.config.ts | 2 +- packages/drizzle/drizzle-sqlite-1.config.ts | 7 ++ packages/drizzle/drizzle-sqlite.config.ts | 7 ++ packages/drizzle/src/factory/resolver.ts | 67 ++++++++++++----- packages/drizzle/src/factory/types.ts | 49 +++++++++++-- packages/drizzle/src/helper.ts | 12 ---- .../drizzle/test/resolver-factory.spec.ts | 71 ++++++++++++++++++- 8 files changed, 177 insertions(+), 40 deletions(-) diff --git a/packages/drizzle/drizzle-mysql.config.ts b/packages/drizzle/drizzle-mysql.config.ts index ebfc0428..6b9cfe69 100644 --- a/packages/drizzle/drizzle-mysql.config.ts +++ b/packages/drizzle/drizzle-mysql.config.ts @@ -7,5 +7,5 @@ export default defineConfig({ dbCredentials: { url: config.mysqlUrl, }, - tablesFilter: ["drizzle_*"], + tablesFilter: ["users", "posts"], }) diff --git a/packages/drizzle/drizzle-postgres.config.ts b/packages/drizzle/drizzle-postgres.config.ts index c0ef3f3e..98db76c3 100644 --- a/packages/drizzle/drizzle-postgres.config.ts +++ b/packages/drizzle/drizzle-postgres.config.ts @@ -8,5 +8,5 @@ export default defineConfig({ dbCredentials: { url: config.postgresUrl, }, - tablesFilter: ["drizzle_*"], + tablesFilter: ["users", "posts"], }) diff --git a/packages/drizzle/drizzle-sqlite-1.config.ts b/packages/drizzle/drizzle-sqlite-1.config.ts index c9d5436d..32a4791f 100644 --- a/packages/drizzle/drizzle-sqlite-1.config.ts +++ b/packages/drizzle/drizzle-sqlite-1.config.ts @@ -6,4 +6,11 @@ export default defineConfig({ dbCredentials: { url: "file:./test/schema/sqlite-1.db", }, + tablesFilter: [ + "users", + "posts", + "courses", + "studentToCourses", + "studentCourseGrades", + ], }) diff --git a/packages/drizzle/drizzle-sqlite.config.ts b/packages/drizzle/drizzle-sqlite.config.ts index 31415b4a..6531b33b 100644 --- a/packages/drizzle/drizzle-sqlite.config.ts +++ b/packages/drizzle/drizzle-sqlite.config.ts @@ -6,4 +6,11 @@ export default defineConfig({ dbCredentials: { url: "file:./test/schema/sqlite.db", }, + tablesFilter: [ + "users", + "posts", + "courses", + "studentToCourses", + "studentCourseGrades", + ], }) diff --git a/packages/drizzle/src/factory/resolver.ts b/packages/drizzle/src/factory/resolver.ts index 147bdbeb..c84fbe1d 100644 --- a/packages/drizzle/src/factory/resolver.ts +++ b/packages/drizzle/src/factory/resolver.ts @@ -22,6 +22,8 @@ import { type Relation, type SQL, Table, + type TableRelationalConfig, + type TablesRelationalConfig, and, eq, getTableColumns, @@ -48,7 +50,8 @@ import { DrizzleWeaver, type TableSilk, } from ".." -import { inArrayMultiple, matchQueryBuilder } from "../helper" +import type { AnyQueryBuilder } from "../../dist/index.d.cts" +import { inArrayMultiple } from "../helper" import { type ColumnFilters, type CountArgs, @@ -86,10 +89,7 @@ export abstract class DrizzleResolverFactory< > { protected readonly inputFactory: DrizzleInputFactory protected readonly tableName: InferTableName - protected readonly queryBuilder: QueryBuilder< - TDatabase, - InferTableName - > + protected readonly queryBuilder: QueryBuilder public constructor( protected readonly db: TDatabase, protected readonly table: TTable, @@ -97,16 +97,14 @@ export abstract class DrizzleResolverFactory< ) { this.inputFactory = new DrizzleInputFactory(table, options) this.tableName = getTableName(table) - const queryBuilder = this.db.query[ - this.tableName as keyof typeof this.db.query - ] as QueryBuilder> + const queryBuilder = matchQueryBuilder(this.db.query, this.table) if (!queryBuilder) { throw new Error( `GQLoom-Drizzle Error: Table ${this.tableName} not found in drizzle instance. Did you forget to pass schema to drizzle constructor?` ) } - this.queryBuilder = queryBuilder + this.queryBuilder = queryBuilder as QueryBuilder } private _output?: TableSilk @@ -358,14 +356,14 @@ export abstract class DrizzleResolverFactory< public relationField< TRelationName extends keyof InferTableRelationalConfig< - QueryBuilder> + QueryBuilder >["relations"], >( relationName: TRelationName, options?: GraphQLFieldOptions & { middlewares?: Middleware< InferTableRelationalConfig< - QueryBuilder> + QueryBuilder >["relations"][TRelationName] extends Many ? RelationManyField< TTable, @@ -378,7 +376,7 @@ export abstract class DrizzleResolverFactory< >[] } ): InferTableRelationalConfig< - QueryBuilder> + QueryBuilder >["relations"][TRelationName] extends Many ? RelationManyField< TTable, @@ -388,12 +386,23 @@ export abstract class DrizzleResolverFactory< TTable, InferRelationTable > { - const relation = this.db._.relations["config"]?.[this.tableName]?.[ - relationName - ] as Relation - const targetTable = relation?.targetTable - const queryBuilder = matchQueryBuilder(this.db.query, targetTable) - if (!relation || !(targetTable instanceof Table) || !queryBuilder) { + const [relation, queryBuilder, targetTable] = (() => { + const tableKey = matchTableByTablesConfig( + this.db._.relations.tablesConfig, + this.table + )?.tsName + if (!tableKey) return [undefined, undefined, undefined] + const relation = this.db._.relations["config"]?.[tableKey]?.[ + relationName + ] as Relation + const targetTable = relation?.targetTable + return [ + relation, + matchQueryBuilder(this.db.query, targetTable), + targetTable, + ] + })() + if (!queryBuilder || !relation || !(targetTable instanceof Table)) { throw new Error( `GQLoom-Drizzle Error: Relation ${this.tableName}.${String( relationName @@ -529,3 +538,25 @@ export abstract class DrizzleResolverFactory< } ): DeleteMutation } + +function matchTableByTablesConfig( + tablesConfig: TablesRelationalConfig, + targetTable: Table +): TableRelationalConfig | undefined { + for (const config of Object.values(tablesConfig)) { + if (config.table === targetTable) { + return config + } + } +} + +function matchQueryBuilder( + queries: Record, + targetTable: any +): AnyQueryBuilder | undefined { + for (const qb of Object.values(queries)) { + if (qb.table != null && qb.table === targetTable) { + return qb + } + } +} diff --git a/packages/drizzle/src/factory/types.ts b/packages/drizzle/src/factory/types.ts index f618d0fa..3fe6e480 100644 --- a/packages/drizzle/src/factory/types.ts +++ b/packages/drizzle/src/factory/types.ts @@ -74,9 +74,9 @@ export type DrizzleResolverRelations< TTable extends Table, > = { [TRelationName in keyof InferTableRelationalConfig< - QueryBuilder> + QueryBuilder >["relations"]]: InferTableRelationalConfig< - QueryBuilder> + QueryBuilder >["relations"][TRelationName] extends Many ? RelationManyField< TTable, @@ -101,7 +101,7 @@ export interface SelectArrayQuery< export type InferSelectArrayOptions< TDatabase extends BaseDatabase, TTable extends Table, -> = Parameters["findMany"]>[0] +> = Parameters["findMany"]>[0] export interface CountQuery< TTable extends Table, @@ -128,7 +128,7 @@ export interface SelectSingleQuery< export type InferSelectSingleOptions< TDatabase extends BaseDatabase, TTable extends Table, -> = Parameters["findFirst"]>[0] +> = Parameters["findFirst"]>[0] export interface RelationManyField< TTable extends Table, @@ -251,9 +251,11 @@ export interface DeleteMutationReturningSuccess< export type QueryBuilder< TDatabase extends BaseDatabase, - TTableName extends keyof TDatabase["_"]["schema"], -> = TDatabase["query"] extends { [key in TTableName]: any } - ? TDatabase["query"][TTableName] + TTable extends Table, +> = TDatabase["query"] extends { + [key in InferTableTsName]: any +} + ? TDatabase["query"][InferTableTsName] : never export type AnyQueryBuilder = @@ -286,6 +288,37 @@ export type BaseDatabase = | PgDatabase | MySqlDatabase +export type InferTablesConfig = + TDatabase extends BaseSQLiteDatabase< + any, + any, + any, + any, + infer TTablesConfig, + any + > + ? TTablesConfig + : TDatabase extends PgDatabase + ? TTablesConfig + : TDatabase extends MySqlDatabase< + any, + any, + any, + any, + infer TTablesConfig, + any + > + ? TTablesConfig + : never + +export type InferTableTsName< + TDatabase extends BaseDatabase, + TTable extends Table, +> = Extract< + ValueOf>, + { dbName: TTable["_"]["name"] } +>["tsName"] + export type InferTableName = TTable["_"]["name"] export type InferRelationTable< @@ -294,3 +327,5 @@ export type InferRelationTable< TTargetTableName extends keyof TDatabase["_"]["relations"]["config"][TTable["_"]["name"]], > = TDatabase["_"]["relations"]["config"][TTable["_"]["name"]]["relations"][TTargetTableName]["targetTable"] + +type ValueOf = T[keyof T] diff --git a/packages/drizzle/src/helper.ts b/packages/drizzle/src/helper.ts index cbdfe6c5..f2f4f814 100644 --- a/packages/drizzle/src/helper.ts +++ b/packages/drizzle/src/helper.ts @@ -6,7 +6,6 @@ import { getTableName, sql, } from "drizzle-orm" -import type { AnyQueryBuilder } from "./factory" import type { DrizzleFactoryInputVisibilityBehaviors, ValueOrGetter, @@ -96,14 +95,3 @@ export function getValue(valueOrGetter: ValueOrGetter): T { ? (valueOrGetter as () => T)() : valueOrGetter } - -export function matchQueryBuilder( - queries: Record, - targetTable: any -): AnyQueryBuilder | undefined { - for (const qb of Object.values(queries)) { - if (qb.table != null && qb.table === targetTable) { - return qb - } - } -} diff --git a/packages/drizzle/test/resolver-factory.spec.ts b/packages/drizzle/test/resolver-factory.spec.ts index c3e9226a..802c0234 100644 --- a/packages/drizzle/test/resolver-factory.spec.ts +++ b/packages/drizzle/test/resolver-factory.spec.ts @@ -1,4 +1,4 @@ -import { eq, inArray, sql } from "drizzle-orm" +import { defineRelations, eq, inArray, sql } from "drizzle-orm" import { type LibSQLDatabase, drizzle as sqliteDrizzle, @@ -25,6 +25,7 @@ import { import type { InferSelectArrayOptions, InferSelectSingleOptions, + InferTableTsName, } from "../src/factory/types" import * as mysqlSchemas from "./schema/mysql" import { relations as mysqlRelations } from "./schema/mysql-relations" @@ -530,6 +531,74 @@ describe.concurrent("DrizzleResolverFactory", () => { expect(authorField).toBeDefined() }) + it("should be created with simple naming conventions", () => { + const users = sqlite.sqliteTable("users", { + id: sqlite.integer("id").primaryKey(), + name: sqlite.text("name"), + }) + const posts = sqlite.sqliteTable("posts", { + id: sqlite.integer("id").primaryKey(), + title: sqlite.text("title"), + authorId: sqlite.integer("authorId").references(() => users.id), + }) + + const relations = defineRelations({ users, posts }, (r) => ({ + users: { + posts: r.many.posts(), + }, + posts: { + author: r.one.users({ + from: r.posts.authorId, + to: r.users.id, + }), + }, + })) + const db0 = sqliteDrizzle({ + relations, + connection: ":memory:", + }) + + const userFactory = drizzleResolverFactory(db0, users) + const postsField = userFactory.relationField("posts") + expect(postsField).toBeDefined() + }) + + it("should be created with complex naming conventions", () => { + const User = sqlite.sqliteTable("user", { + id: sqlite.integer("id").primaryKey(), + name: sqlite.text("name"), + }) + const Post = sqlite.sqliteTable("post", { + id: sqlite.integer("id").primaryKey(), + title: sqlite.text("title"), + authorId: sqlite.integer("authorId").references(() => User.id), + }) + + const relations = defineRelations({ users: User, posts: Post }, (r) => ({ + users: { + posts: r.many.posts(), + }, + posts: { + author: r.one.users({ + from: r.posts.authorId, + to: r.users.id, + }), + }, + })) + const db0 = sqliteDrizzle({ + relations, + connection: ":memory:", + }) + + type postTsName = InferTableTsName + expectTypeOf("posts") + expectTypeOf>("users") + + const userFactory = drizzleResolverFactory(db0, User) + const postsField = userFactory.relationField("posts") + expect(postsField).toBeDefined() + }) + it("should resolve correctly", async () => { const studentCourseFactory = drizzleResolverFactory( db, From 76158edae2c2b58503001de559fefcad55df9abc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E2=9B=B5?= <809067559@qq.com> Date: Fri, 2 May 2025 01:03:58 +0800 Subject: [PATCH 24/54] chore(dependencies): update pnpm-lock.yaml to resolve version conflicts and ensure consistency --- pnpm-lock.yaml | 86 ++++++-------------------------------------------- 1 file changed, 10 insertions(+), 76 deletions(-) diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 8f448722..732696a5 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -83,25 +83,14 @@ importers: examples/cattery-valibot: dependencies: '@gqloom/core': -<<<<<<< HEAD specifier: ^0.8.4 - version: 0.8.4(graphql@16.8.1(patch_hash=2p6wc42resg5at2p4utt5idamq)) - '@gqloom/drizzle': - specifier: 0.9.0-beta.5 - version: 0.9.0-beta.5(@gqloom/core@0.8.4(graphql@16.8.1(patch_hash=2p6wc42resg5at2p4utt5idamq)))(drizzle-orm@1.0.0-beta.1-7946562(@libsql/client@0.14.0)(@prisma/client@6.1.0(prisma@6.1.0))(@types/pg@8.11.10)(better-sqlite3@11.8.1)(gel@2.0.2)(knex@3.1.0(better-sqlite3@11.8.1)(mysql2@3.14.0)(pg@8.13.2)(sqlite3@5.1.7))(mysql2@3.14.0)(pg@8.13.2)(prisma@6.1.0)(sqlite3@5.1.7))(graphql@16.8.1(patch_hash=2p6wc42resg5at2p4utt5idamq)) - '@gqloom/valibot': - specifier: ^0.7.1 - version: 0.7.1(@gqloom/core@0.8.4(graphql@16.8.1(patch_hash=2p6wc42resg5at2p4utt5idamq)))(graphql@16.8.1(patch_hash=2p6wc42resg5at2p4utt5idamq))(valibot@1.0.0-rc.1(typescript@5.7.3)) -======= - specifier: latest version: 0.8.4(graphql@16.8.1(patch_hash=c3a9767e3d80300c2403f5e7447d2c057178847cbc9a76669fd8e03cb87fc4a8)) '@gqloom/drizzle': - specifier: latest - version: 0.8.6(@gqloom/core@0.8.4(graphql@16.8.1(patch_hash=c3a9767e3d80300c2403f5e7447d2c057178847cbc9a76669fd8e03cb87fc4a8)))(drizzle-orm@0.39.3(@libsql/client@0.14.0)(@prisma/client@6.1.0(prisma@6.1.0))(@types/pg@8.11.10)(better-sqlite3@11.8.1)(knex@3.1.0(better-sqlite3@11.8.1)(mysql2@3.12.0)(pg@8.13.2)(sqlite3@5.1.7))(mysql2@3.12.0)(pg@8.13.2)(prisma@6.1.0)(sqlite3@5.1.7))(graphql@16.8.1(patch_hash=c3a9767e3d80300c2403f5e7447d2c057178847cbc9a76669fd8e03cb87fc4a8)) + specifier: 0.9.0-beta.5 + version: 0.9.0-beta.5(@gqloom/core@0.8.4(graphql@16.8.1(patch_hash=c3a9767e3d80300c2403f5e7447d2c057178847cbc9a76669fd8e03cb87fc4a8)))(drizzle-orm@1.0.0-beta.1-7946562(@libsql/client@0.14.0)(@prisma/client@6.1.0(prisma@6.1.0))(@types/pg@8.11.10)(better-sqlite3@11.8.1)(gel@2.0.2)(knex@3.1.0(better-sqlite3@11.8.1)(mysql2@3.14.0)(pg@8.13.2)(sqlite3@5.1.7))(mysql2@3.14.0)(pg@8.13.2)(prisma@6.1.0)(sqlite3@5.1.7))(graphql@16.8.1(patch_hash=c3a9767e3d80300c2403f5e7447d2c057178847cbc9a76669fd8e03cb87fc4a8)) '@gqloom/valibot': specifier: ^0.7.1 version: 0.7.1(@gqloom/core@0.8.4(graphql@16.8.1(patch_hash=c3a9767e3d80300c2403f5e7447d2c057178847cbc9a76669fd8e03cb87fc4a8)))(graphql@16.8.1(patch_hash=c3a9767e3d80300c2403f5e7447d2c057178847cbc9a76669fd8e03cb87fc4a8))(valibot@1.0.0-rc.1(typescript@5.7.3)) ->>>>>>> main '@libsql/client': specifier: ^0.14.0 version: 0.14.0 @@ -110,14 +99,10 @@ importers: version: 16.4.7 graphql: specifier: ^16.8.1 -<<<<<<< HEAD - version: 16.8.1(patch_hash=2p6wc42resg5at2p4utt5idamq) + version: 16.8.1(patch_hash=c3a9767e3d80300c2403f5e7447d2c057178847cbc9a76669fd8e03cb87fc4a8) graphql-scalars: specifier: ^1.24.1 - version: 1.24.1(graphql@16.8.1(patch_hash=2p6wc42resg5at2p4utt5idamq)) -======= - version: 16.8.1(patch_hash=c3a9767e3d80300c2403f5e7447d2c057178847cbc9a76669fd8e03cb87fc4a8) ->>>>>>> main + version: 1.24.1(graphql@16.8.1(patch_hash=c3a9767e3d80300c2403f5e7447d2c057178847cbc9a76669fd8e03cb87fc4a8)) graphql-yoga: specifier: ^5.6.0 version: 5.6.0(graphql@16.8.1(patch_hash=c3a9767e3d80300c2403f5e7447d2c057178847cbc9a76669fd8e03cb87fc4a8)) @@ -145,23 +130,13 @@ importers: dependencies: '@gqloom/core': specifier: latest -<<<<<<< HEAD - version: 0.8.4(graphql@16.8.1(patch_hash=2p6wc42resg5at2p4utt5idamq)) - '@gqloom/drizzle': - specifier: latest - version: 0.8.5(@gqloom/core@0.8.4(graphql@16.8.1(patch_hash=2p6wc42resg5at2p4utt5idamq)))(drizzle-orm@1.0.0-beta.1-7946562(@libsql/client@0.14.0)(@prisma/client@6.1.0(prisma@6.1.0))(@types/pg@8.11.10)(better-sqlite3@11.8.1)(gel@2.0.2)(knex@3.1.0(better-sqlite3@11.8.1)(mysql2@3.14.0)(pg@8.13.2)(sqlite3@5.1.7))(mysql2@3.14.0)(pg@8.13.2)(prisma@6.1.0)(sqlite3@5.1.7))(graphql@16.8.1(patch_hash=2p6wc42resg5at2p4utt5idamq)) - '@gqloom/zod': - specifier: latest - version: 0.8.1(@gqloom/core@0.8.4(graphql@16.8.1(patch_hash=2p6wc42resg5at2p4utt5idamq)))(graphql@16.8.1(patch_hash=2p6wc42resg5at2p4utt5idamq))(zod@3.24.2) -======= version: 0.8.4(graphql@16.8.1(patch_hash=c3a9767e3d80300c2403f5e7447d2c057178847cbc9a76669fd8e03cb87fc4a8)) '@gqloom/drizzle': specifier: latest - version: 0.8.6(@gqloom/core@0.8.4(graphql@16.8.1(patch_hash=c3a9767e3d80300c2403f5e7447d2c057178847cbc9a76669fd8e03cb87fc4a8)))(drizzle-orm@0.39.3(@libsql/client@0.14.0)(@prisma/client@6.1.0(prisma@6.1.0))(@types/pg@8.11.10)(better-sqlite3@11.8.1)(knex@3.1.0(better-sqlite3@11.8.1)(mysql2@3.12.0)(pg@8.13.2)(sqlite3@5.1.7))(mysql2@3.12.0)(pg@8.13.2)(prisma@6.1.0)(sqlite3@5.1.7))(graphql@16.8.1(patch_hash=c3a9767e3d80300c2403f5e7447d2c057178847cbc9a76669fd8e03cb87fc4a8)) + version: 0.8.6(@gqloom/core@0.8.4(graphql@16.8.1(patch_hash=c3a9767e3d80300c2403f5e7447d2c057178847cbc9a76669fd8e03cb87fc4a8)))(drizzle-orm@1.0.0-beta.1-7946562(@libsql/client@0.14.0)(@prisma/client@6.1.0(prisma@6.1.0))(@types/pg@8.11.10)(better-sqlite3@11.8.1)(gel@2.0.2)(knex@3.1.0(better-sqlite3@11.8.1)(mysql2@3.14.0)(pg@8.13.2)(sqlite3@5.1.7))(mysql2@3.14.0)(pg@8.13.2)(prisma@6.1.0)(sqlite3@5.1.7))(graphql@16.8.1(patch_hash=c3a9767e3d80300c2403f5e7447d2c057178847cbc9a76669fd8e03cb87fc4a8)) '@gqloom/zod': specifier: latest version: 0.8.1(@gqloom/core@0.8.4(graphql@16.8.1(patch_hash=c3a9767e3d80300c2403f5e7447d2c057178847cbc9a76669fd8e03cb87fc4a8)))(graphql@16.8.1(patch_hash=c3a9767e3d80300c2403f5e7447d2c057178847cbc9a76669fd8e03cb87fc4a8))(zod@3.24.3) ->>>>>>> main '@libsql/client': specifier: ^0.14.0 version: 0.14.0 @@ -1511,13 +1486,8 @@ packages: peerDependencies: graphql: '>= 16.8.0' -<<<<<<< HEAD - '@gqloom/drizzle@0.8.5': - resolution: {integrity: sha512-fBZQotNZSvm/wAy7v0fKmKL3UrKcj8SY9Q2MPUO3qi6BD0sGI25DPty7L0/YJmuG7gmxLHOfAiUI0EGEHN7o5g==} -======= '@gqloom/drizzle@0.8.6': resolution: {integrity: sha512-BNoCwn7foRiQBv2Tt3KtAcyYWPQYxM5v/FXUTZplLjOjRB+uQufaiYUGPlngh36JpnqLkRadS3kS/efAOfQh/g==} ->>>>>>> main peerDependencies: '@gqloom/core': '>= 0.8.0' drizzle-orm: '>= 0.38.0' @@ -4251,7 +4221,6 @@ packages: libsql@0.4.7: resolution: {integrity: sha512-T9eIRCs6b0J1SHKYIvD8+KCJMcWZ900iZyxdnSCdqxN12Z1ijzT+jY5nrk72Jw4B0HGzms2NgpryArlJqvc3Lw==} - cpu: [x64, arm64, wasm32] os: [darwin, linux, win32] light-my-request@5.13.0: @@ -5420,17 +5389,12 @@ packages: resolution: {integrity: sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==} engines: {node: '>=8'} -<<<<<<< HEAD shell-quote@1.8.2: resolution: {integrity: sha512-AzqKpGKjrj7EM6rKVQEPpB288oCfnrEIuyoT9cyF4nmGa7V8Zk6f7RRqYisX8X9m+Q7bd632aZW4ky7EhbQztA==} engines: {node: '>= 0.4'} - shiki@2.3.1: - resolution: {integrity: sha512-bD1XuVAyZBVxHiPlO/m2nM2F5g8G5MwSZHNYx+ArpcOW52+fCN6peGP5gG61O0gZpzUVbImeR3ar8cF+Z5WM8g==} -======= shiki@3.3.0: resolution: {integrity: sha512-j0Z1tG5vlOFGW8JVj0Cpuatzvshes7VJy5ncDmmMaYcmnGW0Js1N81TOW98ivTFNZfKRn9uwEg/aIm638o368g==} ->>>>>>> main side-channel@1.0.6: resolution: {integrity: sha512-fDW/EZ6Q9RiO8eFG8Hj+7u/oW+XrPTIChwCOM2+th2A6OblDtYYIpve9m+KvI9Z4C9qSEXlaGR6bTEYHReuglA==} @@ -6737,33 +6701,21 @@ snapshots: dependencies: graphql: 16.8.1(patch_hash=c3a9767e3d80300c2403f5e7447d2c057178847cbc9a76669fd8e03cb87fc4a8) -<<<<<<< HEAD - '@gqloom/core@0.8.4(graphql@16.8.1(patch_hash=2p6wc42resg5at2p4utt5idamq))': -======= '@gqloom/core@0.8.4(graphql@16.8.1(patch_hash=c3a9767e3d80300c2403f5e7447d2c057178847cbc9a76669fd8e03cb87fc4a8))': ->>>>>>> main dependencies: graphql: 16.8.1(patch_hash=c3a9767e3d80300c2403f5e7447d2c057178847cbc9a76669fd8e03cb87fc4a8) -<<<<<<< HEAD - '@gqloom/drizzle@0.8.5(@gqloom/core@0.8.4(graphql@16.8.1(patch_hash=2p6wc42resg5at2p4utt5idamq)))(drizzle-orm@1.0.0-beta.1-7946562(@libsql/client@0.14.0)(@prisma/client@6.1.0(prisma@6.1.0))(@types/pg@8.11.10)(better-sqlite3@11.8.1)(gel@2.0.2)(knex@3.1.0(better-sqlite3@11.8.1)(mysql2@3.14.0)(pg@8.13.2)(sqlite3@5.1.7))(mysql2@3.14.0)(pg@8.13.2)(prisma@6.1.0)(sqlite3@5.1.7))(graphql@16.8.1(patch_hash=2p6wc42resg5at2p4utt5idamq))': + '@gqloom/drizzle@0.8.6(@gqloom/core@0.8.4(graphql@16.8.1(patch_hash=c3a9767e3d80300c2403f5e7447d2c057178847cbc9a76669fd8e03cb87fc4a8)))(drizzle-orm@1.0.0-beta.1-7946562(@libsql/client@0.14.0)(@prisma/client@6.1.0(prisma@6.1.0))(@types/pg@8.11.10)(better-sqlite3@11.8.1)(gel@2.0.2)(knex@3.1.0(better-sqlite3@11.8.1)(mysql2@3.14.0)(pg@8.13.2)(sqlite3@5.1.7))(mysql2@3.14.0)(pg@8.13.2)(prisma@6.1.0)(sqlite3@5.1.7))(graphql@16.8.1(patch_hash=c3a9767e3d80300c2403f5e7447d2c057178847cbc9a76669fd8e03cb87fc4a8))': dependencies: - '@gqloom/core': 0.8.4(graphql@16.8.1(patch_hash=2p6wc42resg5at2p4utt5idamq)) + '@gqloom/core': 0.8.4(graphql@16.8.1(patch_hash=c3a9767e3d80300c2403f5e7447d2c057178847cbc9a76669fd8e03cb87fc4a8)) drizzle-orm: 1.0.0-beta.1-7946562(@libsql/client@0.14.0)(@prisma/client@6.1.0(prisma@6.1.0))(@types/pg@8.11.10)(better-sqlite3@11.8.1)(gel@2.0.2)(knex@3.1.0(better-sqlite3@11.8.1)(mysql2@3.14.0)(pg@8.13.2)(sqlite3@5.1.7))(mysql2@3.14.0)(pg@8.13.2)(prisma@6.1.0)(sqlite3@5.1.7) - graphql: 16.8.1(patch_hash=2p6wc42resg5at2p4utt5idamq) + graphql: 16.8.1(patch_hash=c3a9767e3d80300c2403f5e7447d2c057178847cbc9a76669fd8e03cb87fc4a8) - '@gqloom/drizzle@0.9.0-beta.5(@gqloom/core@0.8.4(graphql@16.8.1(patch_hash=2p6wc42resg5at2p4utt5idamq)))(drizzle-orm@1.0.0-beta.1-7946562(@libsql/client@0.14.0)(@prisma/client@6.1.0(prisma@6.1.0))(@types/pg@8.11.10)(better-sqlite3@11.8.1)(gel@2.0.2)(knex@3.1.0(better-sqlite3@11.8.1)(mysql2@3.14.0)(pg@8.13.2)(sqlite3@5.1.7))(mysql2@3.14.0)(pg@8.13.2)(prisma@6.1.0)(sqlite3@5.1.7))(graphql@16.8.1(patch_hash=2p6wc42resg5at2p4utt5idamq))': - dependencies: - '@gqloom/core': 0.8.4(graphql@16.8.1(patch_hash=2p6wc42resg5at2p4utt5idamq)) - drizzle-orm: 1.0.0-beta.1-7946562(@libsql/client@0.14.0)(@prisma/client@6.1.0(prisma@6.1.0))(@types/pg@8.11.10)(better-sqlite3@11.8.1)(gel@2.0.2)(knex@3.1.0(better-sqlite3@11.8.1)(mysql2@3.14.0)(pg@8.13.2)(sqlite3@5.1.7))(mysql2@3.14.0)(pg@8.13.2)(prisma@6.1.0)(sqlite3@5.1.7) - graphql: 16.8.1(patch_hash=2p6wc42resg5at2p4utt5idamq) -======= - '@gqloom/drizzle@0.8.6(@gqloom/core@0.8.4(graphql@16.8.1(patch_hash=c3a9767e3d80300c2403f5e7447d2c057178847cbc9a76669fd8e03cb87fc4a8)))(drizzle-orm@0.39.3(@libsql/client@0.14.0)(@prisma/client@6.1.0(prisma@6.1.0))(@types/pg@8.11.10)(better-sqlite3@11.8.1)(knex@3.1.0(better-sqlite3@11.8.1)(mysql2@3.12.0)(pg@8.13.2)(sqlite3@5.1.7))(mysql2@3.12.0)(pg@8.13.2)(prisma@6.1.0)(sqlite3@5.1.7))(graphql@16.8.1(patch_hash=c3a9767e3d80300c2403f5e7447d2c057178847cbc9a76669fd8e03cb87fc4a8))': + '@gqloom/drizzle@0.9.0-beta.5(@gqloom/core@0.8.4(graphql@16.8.1(patch_hash=c3a9767e3d80300c2403f5e7447d2c057178847cbc9a76669fd8e03cb87fc4a8)))(drizzle-orm@1.0.0-beta.1-7946562(@libsql/client@0.14.0)(@prisma/client@6.1.0(prisma@6.1.0))(@types/pg@8.11.10)(better-sqlite3@11.8.1)(gel@2.0.2)(knex@3.1.0(better-sqlite3@11.8.1)(mysql2@3.14.0)(pg@8.13.2)(sqlite3@5.1.7))(mysql2@3.14.0)(pg@8.13.2)(prisma@6.1.0)(sqlite3@5.1.7))(graphql@16.8.1(patch_hash=c3a9767e3d80300c2403f5e7447d2c057178847cbc9a76669fd8e03cb87fc4a8))': dependencies: '@gqloom/core': 0.8.4(graphql@16.8.1(patch_hash=c3a9767e3d80300c2403f5e7447d2c057178847cbc9a76669fd8e03cb87fc4a8)) - drizzle-orm: 0.39.3(@libsql/client@0.14.0)(@prisma/client@6.1.0(prisma@6.1.0))(@types/pg@8.11.10)(better-sqlite3@11.8.1)(knex@3.1.0(better-sqlite3@11.8.1)(mysql2@3.12.0)(pg@8.13.2)(sqlite3@5.1.7))(mysql2@3.12.0)(pg@8.13.2)(prisma@6.1.0)(sqlite3@5.1.7) + drizzle-orm: 1.0.0-beta.1-7946562(@libsql/client@0.14.0)(@prisma/client@6.1.0(prisma@6.1.0))(@types/pg@8.11.10)(better-sqlite3@11.8.1)(gel@2.0.2)(knex@3.1.0(better-sqlite3@11.8.1)(mysql2@3.14.0)(pg@8.13.2)(sqlite3@5.1.7))(mysql2@3.14.0)(pg@8.13.2)(prisma@6.1.0)(sqlite3@5.1.7) graphql: 16.8.1(patch_hash=c3a9767e3d80300c2403f5e7447d2c057178847cbc9a76669fd8e03cb87fc4a8) ->>>>>>> main '@gqloom/mikro-orm@0.7.0(@gqloom/core@0.7.0(graphql@16.8.1(patch_hash=c3a9767e3d80300c2403f5e7447d2c057178847cbc9a76669fd8e03cb87fc4a8)))(@mikro-orm/core@6.4.6)(graphql@16.8.1(patch_hash=c3a9767e3d80300c2403f5e7447d2c057178847cbc9a76669fd8e03cb87fc4a8))': dependencies: @@ -6771,19 +6723,6 @@ snapshots: '@mikro-orm/core': 6.4.6 graphql: 16.8.1(patch_hash=c3a9767e3d80300c2403f5e7447d2c057178847cbc9a76669fd8e03cb87fc4a8) -<<<<<<< HEAD - '@gqloom/valibot@0.7.1(@gqloom/core@0.8.4(graphql@16.8.1(patch_hash=2p6wc42resg5at2p4utt5idamq)))(graphql@16.8.1(patch_hash=2p6wc42resg5at2p4utt5idamq))(valibot@1.0.0-rc.1(typescript@5.7.3))': - dependencies: - '@gqloom/core': 0.8.4(graphql@16.8.1(patch_hash=2p6wc42resg5at2p4utt5idamq)) - graphql: 16.8.1(patch_hash=2p6wc42resg5at2p4utt5idamq) - valibot: 1.0.0-rc.1(typescript@5.7.3) - - '@gqloom/zod@0.8.1(@gqloom/core@0.8.4(graphql@16.8.1(patch_hash=2p6wc42resg5at2p4utt5idamq)))(graphql@16.8.1(patch_hash=2p6wc42resg5at2p4utt5idamq))(zod@3.24.2)': - dependencies: - '@gqloom/core': 0.8.4(graphql@16.8.1(patch_hash=2p6wc42resg5at2p4utt5idamq)) - graphql: 16.8.1(patch_hash=2p6wc42resg5at2p4utt5idamq) - zod: 3.24.2 -======= '@gqloom/valibot@0.7.1(@gqloom/core@0.8.4(graphql@16.8.1(patch_hash=c3a9767e3d80300c2403f5e7447d2c057178847cbc9a76669fd8e03cb87fc4a8)))(graphql@16.8.1(patch_hash=c3a9767e3d80300c2403f5e7447d2c057178847cbc9a76669fd8e03cb87fc4a8))(valibot@1.0.0-rc.1(typescript@5.7.3))': dependencies: '@gqloom/core': 0.8.4(graphql@16.8.1(patch_hash=c3a9767e3d80300c2403f5e7447d2c057178847cbc9a76669fd8e03cb87fc4a8)) @@ -6795,7 +6734,6 @@ snapshots: '@gqloom/core': 0.8.4(graphql@16.8.1(patch_hash=c3a9767e3d80300c2403f5e7447d2c057178847cbc9a76669fd8e03cb87fc4a8)) graphql: 16.8.1(patch_hash=c3a9767e3d80300c2403f5e7447d2c057178847cbc9a76669fd8e03cb87fc4a8) zod: 3.24.3 ->>>>>>> main '@graphql-tools/executor@1.2.8(graphql@16.8.1(patch_hash=c3a9767e3d80300c2403f5e7447d2c057178847cbc9a76669fd8e03cb87fc4a8))': dependencies: @@ -11289,13 +11227,9 @@ snapshots: shebang-regex@3.0.0: {} -<<<<<<< HEAD shell-quote@1.8.2: {} - shiki@2.3.1: -======= shiki@3.3.0: ->>>>>>> main dependencies: '@shikijs/core': 3.3.0 '@shikijs/engine-javascript': 3.3.0 From 7cae1ff5ec9674706220ac7368d6615f26d30a10 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E2=9B=B5?= <809067559@qq.com> Date: Wed, 7 May 2025 06:08:23 +0800 Subject: [PATCH 25/54] refactor(resolver): update type handling for select queries in DrizzleResolverFactory - Replaced InferSelectArrayOptions and InferSelectSingleOptions with SelectArrayOptions and SelectSingleOptions for improved clarity and consistency. - Adjusted the input handling in selectArrayQuery and selectSingleQuery methods to utilize the new types. - Updated related test cases to reflect the changes in type definitions and ensure type safety. --- packages/drizzle/src/factory/resolver.ts | 40 ++++++--------- packages/drizzle/src/factory/types.ts | 50 +++++++------------ .../drizzle/test/resolver-factory.spec.ts | 17 ++----- 3 files changed, 36 insertions(+), 71 deletions(-) diff --git a/packages/drizzle/src/factory/resolver.ts b/packages/drizzle/src/factory/resolver.ts index ab883db2..fe42a450 100644 --- a/packages/drizzle/src/factory/resolver.ts +++ b/packages/drizzle/src/factory/resolver.ts @@ -72,8 +72,6 @@ import type { CountQuery, DeleteMutation, InferRelationTable, - InferSelectArrayOptions, - InferSelectSingleOptions, InferTableName, InferTableRelationalConfig, InsertArrayMutation, @@ -81,7 +79,9 @@ import type { QueryBuilder, RelationManyField, RelationOneField, + SelectArrayOptions, SelectArrayQuery, + SelectSingleOptions, SelectSingleQuery, UpdateMutation, } from "./types" @@ -111,13 +111,10 @@ export abstract class DrizzleResolverFactory< input, ...options }: GraphQLFieldOptions & { - input?: GraphQLSilk, TInputI> - middlewares?: Middleware>[] - } = {}): SelectArrayQuery { - input ??= silk< - InferSelectArrayOptions, - SelectArrayArgs - >( + input?: GraphQLSilk + middlewares?: Middleware>[] + } = {}): SelectArrayQuery { + input ??= silk>( () => this.inputFactory.selectArrayArgs(), (args) => ({ value: { @@ -127,15 +124,12 @@ export abstract class DrizzleResolverFactory< offset: args.offset, }, }) - ) as GraphQLSilk, TInputI> + ) as GraphQLSilk return new QueryFactoryWithResolve(this.output.$list(), { input, ...options, - resolve: ( - opts: InferSelectArrayOptions | undefined, - payload - ) => { + resolve: (opts: SelectArrayOptions | undefined, payload) => { let query: any = (this.db as any) .select(getSelectedColumns(this.table, payload)) .from(this.table) @@ -152,13 +146,10 @@ export abstract class DrizzleResolverFactory< input, ...options }: GraphQLFieldOptions & { - input?: GraphQLSilk, TInputI> - middlewares?: Middleware>[] - } = {}): SelectSingleQuery { - input ??= silk< - InferSelectSingleOptions, - SelectSingleArgs - >( + input?: GraphQLSilk + middlewares?: Middleware>[] + } = {}): SelectSingleQuery { + input ??= silk>( () => this.inputFactory.selectSingleArgs(), (args) => ({ value: { @@ -167,15 +158,12 @@ export abstract class DrizzleResolverFactory< offset: args.offset, }, }) - ) as GraphQLSilk, TInputI> + ) as GraphQLSilk return new QueryFactoryWithResolve(this.output.$nullable(), { input, ...options, - resolve: ( - opts: InferSelectSingleOptions | undefined, - payload - ) => { + resolve: (opts: SelectSingleOptions | undefined, payload) => { let query: any = (this.db as any) .select(getSelectedColumns(this.table, payload)) .from(this.table) diff --git a/packages/drizzle/src/factory/types.ts b/packages/drizzle/src/factory/types.ts index f2d841ae..2733f953 100644 --- a/packages/drizzle/src/factory/types.ts +++ b/packages/drizzle/src/factory/types.ts @@ -44,9 +44,9 @@ export type DrizzleResolverReturningItems< TTable extends Table, TTableName extends string = TTable["_"]["name"], > = { - [key in TTableName]: SelectArrayQuery + [key in TTableName]: SelectArrayQuery } & { - [key in `${TTableName}Single`]: SelectArrayQuery + [key in `${TTableName}Single`]: SelectSingleQuery } & { [key in `insertInto${Capitalize}`]: InsertArrayMutationReturningItems } & { @@ -62,9 +62,9 @@ export type DrizzleResolverReturningSuccess< TTable extends Table, TTableName extends string = TTable["_"]["name"], > = { - [key in TTableName]: SelectArrayQuery + [key in TTableName]: SelectArrayQuery } & { - [key in `${TTableName}Single`]: SelectArrayQuery + [key in `${TTableName}Single`]: SelectSingleQuery } & { [key in `insertInto${Capitalize}`]: InsertArrayMutationReturningSuccess } & { @@ -95,26 +95,20 @@ export type DrizzleResolverRelations< } export interface SelectArrayQuery< - TDatabase extends BaseDatabase, TTable extends Table, TInputI = SelectArrayArgs, > extends QueryFactoryWithResolve< - InferSelectArrayOptions, + SelectArrayOptions | undefined, GraphQLSilk[], InferSelectModel[]>, - GraphQLSilk, TInputI> + GraphQLSilk > {} -export type InferSelectArrayOptions< - _TDatabase extends BaseDatabase, - _TTable extends Table, -> = - | { - where?: SQL - orderBy?: (Column | SQL | SQL.Aliased)[] - limit?: number - offset?: number - } - | undefined +export type SelectArrayOptions = { + where?: SQL + orderBy?: (Column | SQL | SQL.Aliased)[] + limit?: number + offset?: number +} export interface CountQuery< TTable extends Table, @@ -126,28 +120,22 @@ export interface CountQuery< > {} export interface SelectSingleQuery< - TDatabase extends BaseDatabase, TTable extends Table, TInputI = SelectSingleArgs, > extends QueryFactoryWithResolve< - InferSelectSingleOptions, + SelectSingleOptions | undefined, GraphQLSilk< InferSelectModel | null | undefined, InferSelectModel | null | undefined >, - GraphQLSilk, TInputI> + GraphQLSilk > {} -export type InferSelectSingleOptions< - _TDatabase extends BaseDatabase, - _TTable extends Table, -> = - | { - where?: SQL - orderBy?: (Column | SQL | SQL.Aliased)[] - offset?: number - } - | undefined +export type SelectSingleOptions = { + where?: SQL + orderBy?: (Column | SQL | SQL.Aliased)[] + offset?: number +} export interface RelationManyField< TTable extends Table, diff --git a/packages/drizzle/test/resolver-factory.spec.ts b/packages/drizzle/test/resolver-factory.spec.ts index 2a55b9cd..d28f5e08 100644 --- a/packages/drizzle/test/resolver-factory.spec.ts +++ b/packages/drizzle/test/resolver-factory.spec.ts @@ -35,9 +35,9 @@ import { drizzleResolverFactory, } from "../src" import type { - InferSelectArrayOptions, - InferSelectSingleOptions, InferTableTsName, + SelectArrayOptions, + SelectSingleOptions, } from "../src/factory/types" import * as mysqlSchemas from "./schema/mysql" import { relations as mysqlRelations } from "./schema/mysql-relations" @@ -218,11 +218,6 @@ describe.concurrent("DrizzleResolverFactory", () => { }) it("should be created with middlewares", async () => { - type SelectArrayOptions = InferSelectArrayOptions< - typeof db, - typeof sqliteSchemas.users - > - let count = 0 const query = userFactory @@ -428,10 +423,6 @@ describe.concurrent("DrizzleResolverFactory", () => { }) it("should be created with middlewares", async () => { - type SelectSingleOptions = InferSelectSingleOptions< - typeof db, - typeof sqliteSchemas.users - > let count = 0 const query = userFactory.selectSingleQuery({ middlewares: [ @@ -439,9 +430,7 @@ describe.concurrent("DrizzleResolverFactory", () => { const opts = await parseInput() if (opts.issues) throw new Error("Invalid input") - expectTypeOf(opts.value).toEqualTypeOf< - SelectSingleOptions | undefined - >() + expectTypeOf(opts.value).toEqualTypeOf() count++ const answer = await next() expectTypeOf(answer).toEqualTypeOf< From fd2b4f8ba7b0a0b917963cd631944a408a08604a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E2=9B=B5?= <809067559@qq.com> Date: Wed, 7 May 2025 06:14:14 +0800 Subject: [PATCH 26/54] test(resolver): enhance logging and query assertions in DrizzleResolverFactory tests - Added logging functionality to capture executed SQL queries during tests. - Updated the `selectArrayQuery` method to sort results by both name and age. - Introduced a `beforeEach` hook to reset the log before each test, ensuring clean test runs. - Adjusted assertions to validate the logged SQL queries against expected output. --- .../drizzle/test/resolver-factory.spec.ts | 38 ++++++++++++++----- 1 file changed, 29 insertions(+), 9 deletions(-) diff --git a/packages/drizzle/test/resolver-factory.spec.ts b/packages/drizzle/test/resolver-factory.spec.ts index d28f5e08..f58b8a60 100644 --- a/packages/drizzle/test/resolver-factory.spec.ts +++ b/packages/drizzle/test/resolver-factory.spec.ts @@ -25,7 +25,15 @@ import { } from "drizzle-orm/node-postgres" import * as sqlite from "drizzle-orm/sqlite-core" import * as v from "valibot" -import { afterAll, beforeAll, describe, expect, expectTypeOf, it } from "vitest" +import { + afterAll, + beforeAll, + beforeEach, + describe, + expect, + expectTypeOf, + it, +} from "vitest" import { config } from "../env.config" import { type DrizzleMySQLResolverFactory, @@ -54,11 +62,13 @@ describe.concurrent("DrizzleResolverFactory", () => { typeof db, typeof sqliteSchemas.users > + let log: string[] = [] beforeAll(async () => { db = sqliteDrizzle({ relations: sqliteRelations, connection: { url: `file:${pathToDB.pathname}` }, + logger: { logQuery: (query) => log.push(query) }, }) userFactory = drizzleResolverFactory(db, sqliteSchemas.users) @@ -87,6 +97,9 @@ describe.concurrent("DrizzleResolverFactory", () => { }, ] satisfies (typeof sqliteSchemas.users.$inferInsert)[]) }) + beforeEach(() => { + log = [] + }) afterAll(async () => { await db.delete(sqliteSchemas.users) @@ -96,7 +109,7 @@ describe.concurrent("DrizzleResolverFactory", () => { expect(userFactory).toBeInstanceOf(DrizzleResolverFactory) }) - describe.concurrent("selectArrayQuery", () => { + describe("selectArrayQuery", () => { it("should be created without error", async () => { const query = userFactory.selectArrayQuery() expect(query).toBeDefined() @@ -106,17 +119,17 @@ describe.concurrent("DrizzleResolverFactory", () => { const executor = userFactory.resolver().toExecutor() let answer - answer = await executor.users({ orderBy: { age: "asc" } }) + answer = await executor.users({ orderBy: { name: "asc", age: "asc" } }) expect(answer).toMatchObject([ - { age: 10 }, - { age: 11 }, - { age: 12 }, - { age: 13 }, - { age: 14 }, + { name: "Jane", age: 11 }, + { name: "Jill", age: 14 }, + { name: "Jim", age: 12 }, + { name: "Joe", age: 13 }, + { name: "John", age: 10 }, ]) - answer = await executor.users({ orderBy: { age: "desc" } }) + answer = await executor.users({ orderBy: { age: "desc", name: "asc" } }) expect(answer).toMatchObject([ { age: 14 }, { age: 13 }, @@ -124,6 +137,13 @@ describe.concurrent("DrizzleResolverFactory", () => { { age: 11 }, { age: 10 }, ]) + + expect(["", ...log, ""].join("\n")).toMatchInlineSnapshot(` + " + select "id", "name", "age", "email" from "users" order by "users"."name" asc, "users"."age" asc + select "id", "name", "age", "email" from "users" order by "users"."age" desc, "users"."name" asc + " + `) }) it("should resolve correctly with filters", async () => { From b18ba104267345aad8c072a35d1fc25faccf5da1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E2=9B=B5?= <809067559@qq.com> Date: Wed, 7 May 2025 06:14:33 +0800 Subject: [PATCH 27/54] fix(tests): remove redundant SQL query assertions in DrizzleResolverFactory tests - Cleaned up test assertions by removing duplicate SQL query logs that were no longer necessary. - Ensured that the remaining assertions accurately reflect the expected output of the queries. --- packages/drizzle/test/resolver-factory.spec.ts | 2 -- 1 file changed, 2 deletions(-) diff --git a/packages/drizzle/test/resolver-factory.spec.ts b/packages/drizzle/test/resolver-factory.spec.ts index f58b8a60..04759327 100644 --- a/packages/drizzle/test/resolver-factory.spec.ts +++ b/packages/drizzle/test/resolver-factory.spec.ts @@ -140,8 +140,6 @@ describe.concurrent("DrizzleResolverFactory", () => { expect(["", ...log, ""].join("\n")).toMatchInlineSnapshot(` " - select "id", "name", "age", "email" from "users" order by "users"."name" asc, "users"."age" asc - select "id", "name", "age", "email" from "users" order by "users"."age" desc, "users"."name" asc " `) }) From 7dda66538398ee84556116ab7c9a324e22b516c6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E2=9B=B5?= <809067559@qq.com> Date: Wed, 7 May 2025 06:28:38 +0800 Subject: [PATCH 28/54] test(resolver): refactor DrizzleResolverFactory tests to use executor for query execution - Updated test cases to utilize the new executor pattern for executing queries, improving clarity and consistency. - Replaced direct calls to resolve with executor queries for AND, OR, and NOT conditions. - Enhanced readability of test assertions by structuring query conditions in a more intuitive format. --- .../drizzle/test/resolver-factory.spec.ts | 107 +++++++++--------- 1 file changed, 54 insertions(+), 53 deletions(-) diff --git a/packages/drizzle/test/resolver-factory.spec.ts b/packages/drizzle/test/resolver-factory.spec.ts index 04759327..4d3cf85b 100644 --- a/packages/drizzle/test/resolver-factory.spec.ts +++ b/packages/drizzle/test/resolver-factory.spec.ts @@ -272,88 +272,86 @@ describe.concurrent("DrizzleResolverFactory", () => { it("should work with AND operators", async () => { const query = userFactory.selectArrayQuery() + const executor = resolver({ query }).toExecutor() let answer - answer = await query["~meta"].resolve({ - where: and( - eq(sqliteSchemas.users.name, "John"), - gt(sqliteSchemas.users.age, 10) - ), + answer = await executor.query({ + where: { + AND: [{ name: { eq: "John" } }, { age: { gt: 10 } }], + }, }) expect(answer).toHaveLength(0) - answer = await query["~meta"].resolve({ - where: and( - eq(sqliteSchemas.users.name, "John"), - gte(sqliteSchemas.users.age, 10) - ), + answer = await executor.query({ + where: { + AND: [{ name: { eq: "John" } }, { age: { gte: 10 } }], + }, }) expect(answer).toHaveLength(1) }) it("should work with OR operators", async () => { const query = userFactory.selectArrayQuery() + const executor = resolver({ query }).toExecutor() let answer - answer = await query["~meta"].resolve({ - where: or( - eq(sqliteSchemas.users.name, "John"), - gt(sqliteSchemas.users.age, 12) - ), + answer = await executor.query({ + where: { + OR: [{ name: { eq: "John" } }, { age: { gt: 12 } }], + }, }) expect(answer).toHaveLength(3) - answer = await query["~meta"].resolve({ - where: or( - gte(sqliteSchemas.users.age, 14), - lte(sqliteSchemas.users.age, 10) - ), + answer = await executor.query({ + where: { + OR: [{ age: { gte: 14 } }, { age: { lte: 10 } }], + }, }) expect(answer).toHaveLength(2) }) it("should work with NOT operators", async () => { const query = userFactory.selectArrayQuery() + const executor = resolver({ query }).toExecutor() let answer - answer = await query["~meta"].resolve({ - where: not(eq(sqliteSchemas.users.name, "John")), + answer = await executor.query({ + where: { NOT: { name: { eq: "John" } } }, }) expect(answer).toHaveLength(4) - answer = await query["~meta"].resolve({ - where: not(lte(sqliteSchemas.users.age, 10)), + answer = await executor.query({ + where: { NOT: { age: { lte: 10 } } }, }) expect(answer).toHaveLength(4) }) it("should work with complex NOT conditions", async () => { const query = userFactory.selectArrayQuery() + const executor = resolver({ query }).toExecutor() let answer // Test NOT with OR condition - answer = await query["~meta"].resolve({ - where: not( - or( - eq(sqliteSchemas.users.name, "John"), - eq(sqliteSchemas.users.name, "Jane") - ) as any - ), + answer = await executor.query({ + where: { + NOT: { + OR: [{ name: { eq: "John" } }, { name: { eq: "Jane" } }], + } as any, + }, }) expect(answer).toHaveLength(3) // Should exclude both John and Jane // Test NOT with AND condition - answer = await query["~meta"].resolve({ - where: not( - and( - gte(sqliteSchemas.users.age, 10), - lte(sqliteSchemas.users.age, 12) - ) as any - ), + answer = await executor.query({ + where: { + NOT: { + AND: [{ age: { gte: 10 } }, { age: { lte: 12 } }], + } as any, + }, }) // Should exclude ages 10, 11, 12 expect(answer.map((user) => user.age).sort()).toEqual([13, 14]) // Test nested NOT conditions - answer = await query["~meta"].resolve({ - where: not(lte(sqliteSchemas.users.age, 12)), + answer = await executor.query({ + where: { NOT: { age: { lte: 12 } } }, }) // Double negation: NOT(NOT(age > 12)) = age > 12 expect(answer.map((user) => user.age).sort()).toEqual([13, 14]) @@ -361,10 +359,10 @@ describe.concurrent("DrizzleResolverFactory", () => { it("should work with column-level NOT operator", async () => { const query = userFactory.selectArrayQuery() - + const executor = resolver({ query }).toExecutor() // Test NOT applied to a column filter - const answer = await query["~meta"].resolve({ - where: not(lte(sqliteSchemas.users.age, 12)), + const answer = await executor.query({ + where: { age: { NOT: { lte: 12 } } }, }) // Should only include ages > 12 @@ -373,23 +371,26 @@ describe.concurrent("DrizzleResolverFactory", () => { it("should work with column-level operators (OR, AND)", async () => { const query = userFactory.selectArrayQuery() + const executor = resolver({ query }).toExecutor() let answer // Test column-level OR operator - answer = await query["~meta"].resolve({ - where: or( - eq(sqliteSchemas.users.age, 10), - eq(sqliteSchemas.users.age, 11) - ), + answer = await executor.query({ + where: { + age: { + OR: [{ eq: 10 }, { eq: 11 }], + }, + }, }) expect(new Set(answer)).toMatchObject(new Set([{ age: 10 }, { age: 11 }])) // Test column-level AND operator - answer = await query["~meta"].resolve({ - where: and( - gte(sqliteSchemas.users.age, 10), - lte(sqliteSchemas.users.age, 11) - ), + answer = await executor.query({ + where: { + age: { + AND: [{ gte: 10 }, { lte: 11 }], + }, + }, }) expect(new Set(answer)).toMatchObject(new Set([{ age: 10 }, { age: 11 }])) }) From 5a8c4fe721e69787c68b8b917a99aee2f3ac0fd9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E2=9B=B5?= <809067559@qq.com> Date: Wed, 7 May 2025 06:29:19 +0800 Subject: [PATCH 29/54] refactor(tests): simplify imports in resolver-factory tests - Removed unused import statements to streamline the test file. - Retained essential imports for clarity and improved maintainability of the test cases. --- packages/drizzle/test/resolver-factory.spec.ts | 13 +------------ 1 file changed, 1 insertion(+), 12 deletions(-) diff --git a/packages/drizzle/test/resolver-factory.spec.ts b/packages/drizzle/test/resolver-factory.spec.ts index 4d3cf85b..59480cb1 100644 --- a/packages/drizzle/test/resolver-factory.spec.ts +++ b/packages/drizzle/test/resolver-factory.spec.ts @@ -1,16 +1,5 @@ import { resolver } from "@gqloom/core" -import { - and, - defineRelations, - eq, - gt, - gte, - inArray, - lte, - not, - or, - sql, -} from "drizzle-orm" +import { defineRelations, eq, inArray, sql } from "drizzle-orm" import { type LibSQLDatabase, drizzle as sqliteDrizzle, From 782f41d319dd6b827bd270640161626c1f1456f3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E2=9B=B5?= <809067559@qq.com> Date: Wed, 7 May 2025 06:34:28 +0800 Subject: [PATCH 30/54] refactor(resolver): clean up type imports in resolver factory - Removed unused type import for AnyQueryBuilder to streamline the code. - Added the AnyQueryBuilder type import back for clarity and consistency in type handling. --- packages/drizzle/src/factory/resolver.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/drizzle/src/factory/resolver.ts b/packages/drizzle/src/factory/resolver.ts index fe42a450..ba37109a 100644 --- a/packages/drizzle/src/factory/resolver.ts +++ b/packages/drizzle/src/factory/resolver.ts @@ -53,7 +53,6 @@ import { DrizzleWeaver, type TableSilk, } from ".." -import type { AnyQueryBuilder } from "../../dist/index.d.cts" import { getSelectedColumns, inArrayMultiple } from "../helper" import { type ColumnFilters, @@ -68,6 +67,7 @@ import { type UpdateArgs, } from "./input" import type { + AnyQueryBuilder, BaseDatabase, CountQuery, DeleteMutation, From 6a41051c6fc4c0e99c8133f32ff727a969b5cbaf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E2=9B=B5?= <809067559@qq.com> Date: Sun, 11 May 2025 15:43:37 +0800 Subject: [PATCH 31/54] chore(drizzle): bump version to 0.10.0-beta.1 in package.json --- packages/drizzle/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/drizzle/package.json b/packages/drizzle/package.json index 7ba50cf0..0b093c34 100644 --- a/packages/drizzle/package.json +++ b/packages/drizzle/package.json @@ -1,6 +1,6 @@ { "name": "@gqloom/drizzle", - "version": "0.10.0", + "version": "0.10.0-beta.1", "description": "GQLoom integration with Drizzle ORM", "type": "module", "main": "./dist/index.js", From c5bcf9902e6de9c21c6afea48f2f28f09dd5d690 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E2=9B=B5?= <809067559@qq.com> Date: Sun, 11 May 2025 22:24:02 +0800 Subject: [PATCH 32/54] refactor(core): clean up documentation in use-resolving-fields.ts - Removed unnecessary memoization comment from the hook documentation to enhance clarity and focus on the primary functionality of field resolution in GraphQL queries. --- packages/core/src/context/use-resolving-fields.ts | 2 -- 1 file changed, 2 deletions(-) diff --git a/packages/core/src/context/use-resolving-fields.ts b/packages/core/src/context/use-resolving-fields.ts index cd347e0e..5be5aae5 100644 --- a/packages/core/src/context/use-resolving-fields.ts +++ b/packages/core/src/context/use-resolving-fields.ts @@ -7,8 +7,6 @@ import { createContext, useResolverPayload } from "./context" /** * A hook that analyzes and processes field resolution in a GraphQL query. * - * The hook is memoized to prevent unnecessary recalculations. - * * @returns An object containing sets of different field types, * or undefined if no resolver payload is available */ From 4ffaa2354396c51e66387c368ba4b61032863a96 Mon Sep 17 00:00:00 2001 From: xcfox Date: Tue, 13 May 2025 16:37:13 +0800 Subject: [PATCH 33/54] chore(drizzle): update CHANGELOG and bump version to 0.10.0-beta.3 - Updated CHANGELOG to include new version entries and document recent changes. - Bumped package version to 0.10.0-beta.3. - Enhanced DrizzleInputFactory to support custom column types, improving flexibility in input handling. --- packages/drizzle/CHANGELOG.md | 9 ++++- packages/drizzle/package.json | 2 +- packages/drizzle/src/factory/input.ts | 7 +++- packages/drizzle/test/input-factory.spec.ts | 45 ++++++++++++++++++++- 4 files changed, 57 insertions(+), 6 deletions(-) diff --git a/packages/drizzle/CHANGELOG.md b/packages/drizzle/CHANGELOG.md index 97d1e383..00652922 100644 --- a/packages/drizzle/CHANGELOG.md +++ b/packages/drizzle/CHANGELOG.md @@ -2,9 +2,16 @@ All notable changes to this project will be documented in this file. -## next (YYYY-MM-DD) +## Next (YYYY-MM-DD) - Refactor: Migrating to Relational Queries version 2 + +## 0.9.2 (2025-05-13) + +- Fix: DrizzleInputFactory to support custom column types + +## 0.9.0 (2025-05-11) + - Feat: add `DrizzleResolverFactory.queriesResolver` to create a read-only resolver - Feat: add `getSelectedColumns` to get the selected columns from the resolver payload diff --git a/packages/drizzle/package.json b/packages/drizzle/package.json index 965c84d3..acc41620 100644 --- a/packages/drizzle/package.json +++ b/packages/drizzle/package.json @@ -1,6 +1,6 @@ { "name": "@gqloom/drizzle", - "version": "0.10.0-beta.2", + "version": "0.10.0-beta.3", "description": "GQLoom integration with Drizzle ORM", "type": "module", "main": "./dist/index.js", diff --git a/packages/drizzle/src/factory/input.ts b/packages/drizzle/src/factory/input.ts index bcef7395..31ffbf50 100644 --- a/packages/drizzle/src/factory/input.ts +++ b/packages/drizzle/src/factory/input.ts @@ -226,7 +226,8 @@ export class DrizzleInputFactory { return mapValue.SKIP } const type = (() => { - const t = DrizzleWeaver.getColumnType(column) + const fieldConfig = fieldsConfig[columnName] + const t = fieldConfig?.type ?? DrizzleWeaver.getColumnType(column) if (column.hasDefault) return t if (column.notNull && !isNonNullType(t)) return new GraphQLNonNull(t) @@ -305,7 +306,9 @@ export class DrizzleInputFactory { ) { return mapValue.SKIP } - const type = DrizzleWeaver.getColumnType(column) + const type = + fieldsConfig[columnName]?.type ?? + DrizzleWeaver.getColumnType(column) const columnConfig = fieldsConfig[columnName] return { type, description: columnConfig?.description } }), diff --git a/packages/drizzle/test/input-factory.spec.ts b/packages/drizzle/test/input-factory.spec.ts index 97b36f6c..ad1a641b 100644 --- a/packages/drizzle/test/input-factory.spec.ts +++ b/packages/drizzle/test/input-factory.spec.ts @@ -1,7 +1,7 @@ import * as pg from "drizzle-orm/pg-core" -import { printType } from "graphql" +import { GraphQLScalarType, printType } from "graphql" import { describe, expect, it } from "vitest" -import { DrizzleInputFactory } from "../src" +import { DrizzleInputFactory, drizzleSilk } from "../src" import type { DrizzleFactoryInputVisibilityBehaviors } from "../src/types" describe("DrizzleInputFactory", () => { @@ -196,4 +196,45 @@ describe("DrizzleInputFactory", () => { `) }) }) + + describe("custom column types", () => { + const EmailAddress = new GraphQLScalarType({ + name: "EmailAddress", + description: "A valid email address", + parseValue: (value) => String(value), + serialize: (value) => String(value), + }) + + const userTags = drizzleSilk( + pg.pgTable("userTags", { + id: pg.serial("id").primaryKey(), + email: pg.text("email").notNull(), + }), + { + fields: { + email: { type: EmailAddress }, + }, + } + ) + + const inputFactory = new DrizzleInputFactory(userTags) + + it("should respect column types in InsertInput", () => { + expect(printType(inputFactory.insertInput())).toMatchInlineSnapshot(` + "type UserTagsInsertInput { + id: Int + email: EmailAddress! + }" + `) + }) + + it("should respect column types in UpdateInput", () => { + expect(printType(inputFactory.updateInput())).toMatchInlineSnapshot(` + "type UserTagsUpdateInput { + id: Int + email: EmailAddress + }" + `) + }) + }) }) From 387b2051ef0a1eab76ba3de3882c3d94cea73467 Mon Sep 17 00:00:00 2001 From: xcfox Date: Sat, 17 May 2025 18:50:42 +0800 Subject: [PATCH 34/54] feat(docs): add schema definitions for users and posts in dataloader documentation - Introduced new schema definitions for `users` and `posts` in both English and Chinese documentation. - Updated the resolver logic to utilize `eq` for user ID filtering, enhancing query precision. - Ensured consistency across documentation files for better clarity and usability. --- website/content/docs/dataloader.mdx | 33 ++++++++++++++++++++++ website/content/docs/dataloader.zh.mdx | 33 ++++++++++++++++++++++ website/content/docs/schema/drizzle.mdx | 27 +++++++++++++++++- website/content/docs/schema/drizzle.zh.mdx | 27 +++++++++++++++++- 4 files changed, 118 insertions(+), 2 deletions(-) diff --git a/website/content/docs/dataloader.mdx b/website/content/docs/dataloader.mdx index 874c2cc8..bfe679ed 100644 --- a/website/content/docs/dataloader.mdx +++ b/website/content/docs/dataloader.mdx @@ -47,6 +47,39 @@ export const posts = drizzleSilk( ``` ```ts twoslash tab="relations.ts" +// @filename: schema.ts + +import { drizzleSilk } from "@gqloom/drizzle" +import { defineRelations } from "drizzle-orm" +import * as t from "drizzle-orm/pg-core" + +export const roleEnum = t.pgEnum("role", ["user", "admin"]) + +export const users = drizzleSilk( + t.pgTable("users", { + id: t.serial().primaryKey(), + createdAt: t.timestamp().defaultNow(), + email: t.text().unique().notNull(), + name: t.text(), + role: roleEnum().default("user"), + }) +) + +export const posts = drizzleSilk( + t.pgTable("posts", { + id: t.serial().primaryKey(), + createdAt: t.timestamp().defaultNow(), + updatedAt: t + .timestamp() + .defaultNow() + .$onUpdateFn(() => new Date()), + published: t.boolean().default(false), + title: t.varchar({ length: 255 }).notNull(), + authorId: t.integer().notNull(), + }) +) +// @filename: relations.ts +// ---cut--- import { defineRelations } from "drizzle-orm" import * as tables from "./schema" diff --git a/website/content/docs/dataloader.zh.mdx b/website/content/docs/dataloader.zh.mdx index bf05c515..7d293a3c 100644 --- a/website/content/docs/dataloader.zh.mdx +++ b/website/content/docs/dataloader.zh.mdx @@ -47,6 +47,39 @@ export const posts = drizzleSilk( ``` ```ts twoslash tab="relations.ts" +// @filename: schema.ts + +import { drizzleSilk } from "@gqloom/drizzle" +import { defineRelations } from "drizzle-orm" +import * as t from "drizzle-orm/pg-core" + +export const roleEnum = t.pgEnum("role", ["user", "admin"]) + +export const users = drizzleSilk( + t.pgTable("users", { + id: t.serial().primaryKey(), + createdAt: t.timestamp().defaultNow(), + email: t.text().unique().notNull(), + name: t.text(), + role: roleEnum().default("user"), + }) +) + +export const posts = drizzleSilk( + t.pgTable("posts", { + id: t.serial().primaryKey(), + createdAt: t.timestamp().defaultNow(), + updatedAt: t + .timestamp() + .defaultNow() + .$onUpdateFn(() => new Date()), + published: t.boolean().default(false), + title: t.varchar({ length: 255 }).notNull(), + authorId: t.integer().notNull(), + }) +) +// @filename: relations.ts +// ---cut--- import { defineRelations } from "drizzle-orm" import * as tables from "./schema" diff --git a/website/content/docs/schema/drizzle.mdx b/website/content/docs/schema/drizzle.mdx index 9bde4bf6..861e9ca5 100644 --- a/website/content/docs/schema/drizzle.mdx +++ b/website/content/docs/schema/drizzle.mdx @@ -81,6 +81,30 @@ export const posts = drizzleSilk( ``` ```ts twoslash title="relations.ts" tab="relations.ts" +// @filename: schema.ts +import { drizzleSilk } from "@gqloom/drizzle" +import * as t from "drizzle-orm/sqlite-core" + +export const users = drizzleSilk( + t.sqliteTable("users", { + id: t.int().primaryKey({ autoIncrement: true }), + name: t.text().notNull(), + age: t.int(), + email: t.text(), + password: t.text(), + }) +) + +export const posts = drizzleSilk( + t.sqliteTable("posts", { + id: t.int().primaryKey({ autoIncrement: true }), + title: t.text().notNull(), + content: t.text(), + authorId: t.int().references(() => users.id, { onDelete: "cascade" }), + }) +) +// @filename: relations.ts +// ---cut--- import { defineRelations } from "drizzle-orm" import * as tables from "./schema" @@ -701,6 +725,7 @@ export const relations = defineRelations(tables, (r) => ({ // @filename: resolver.ts import { query, resolver } from "@gqloom/core" import { drizzleResolverFactory } from "@gqloom/drizzle" +import { eq } from "drizzle-orm" import { drizzle } from "drizzle-orm/libsql" import * as v from "valibot" import { relations } from "./relations" @@ -719,7 +744,7 @@ export const usersResolver = resolver.of(users, { user: usersResolverFactory.selectSingleQuery().input( v.pipe( // [!code hl] v.object({ id: v.number() }), // [!code hl] - v.transform(({ id }) => ({ where: { id } })) // [!code hl] + v.transform(({ id }) => ({ where: eq(users.id, id) })) // [!code hl] ) // [!code hl] ), diff --git a/website/content/docs/schema/drizzle.zh.mdx b/website/content/docs/schema/drizzle.zh.mdx index 2c086db6..ab06848e 100644 --- a/website/content/docs/schema/drizzle.zh.mdx +++ b/website/content/docs/schema/drizzle.zh.mdx @@ -81,6 +81,30 @@ export const posts = drizzleSilk( ``` ```ts twoslash title="relations.ts" tab="relations.ts" +// @filename: schema.ts +import { drizzleSilk } from "@gqloom/drizzle" +import * as t from "drizzle-orm/sqlite-core" + +export const users = drizzleSilk( + t.sqliteTable("users", { + id: t.int().primaryKey({ autoIncrement: true }), + name: t.text().notNull(), + age: t.int(), + email: t.text(), + password: t.text(), + }) +) + +export const posts = drizzleSilk( + t.sqliteTable("posts", { + id: t.int().primaryKey({ autoIncrement: true }), + title: t.text().notNull(), + content: t.text(), + authorId: t.int().references(() => users.id, { onDelete: "cascade" }), + }) +) +// @filename: relations.ts +// ---cut--- import { defineRelations } from "drizzle-orm" import * as tables from "./schema" @@ -701,6 +725,7 @@ export const relations = defineRelations(tables, (r) => ({ // @filename: resolver.ts import { query, resolver } from "@gqloom/core" import { drizzleResolverFactory } from "@gqloom/drizzle" +import { eq } from "drizzle-orm" import { drizzle } from "drizzle-orm/libsql" import * as v from "valibot" import { relations } from "./relations" @@ -719,7 +744,7 @@ export const usersResolver = resolver.of(users, { user: usersResolverFactory.selectSingleQuery().input( v.pipe( // [!code hl] v.object({ id: v.number() }), // [!code hl] - v.transform(({ id }) => ({ where: { id } })) // [!code hl] + v.transform(({ id }) => ({ where: eq(users.id, id) })) // [!code hl] ) // [!code hl] ), From cf848980dbc214d2a453650799b2654f47ab6937 Mon Sep 17 00:00:00 2001 From: xcfox Date: Sat, 17 May 2025 19:44:14 +0800 Subject: [PATCH 35/54] chore: bump version of @gqloom/drizzle to 0.10.0-beta.4 --- packages/drizzle/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/drizzle/package.json b/packages/drizzle/package.json index 8ab47973..eeb31cc8 100644 --- a/packages/drizzle/package.json +++ b/packages/drizzle/package.json @@ -1,6 +1,6 @@ { "name": "@gqloom/drizzle", - "version": "0.10.0-beta.3", + "version": "0.10.0-beta.4", "description": "GQLoom integration with Drizzle ORM", "type": "module", "main": "./dist/index.js", From 1dd369ddefcd6785feb48eff02de7da2b74c7af6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E2=9B=B5?= <809067559@qq.com> Date: Mon, 9 Jun 2025 19:19:31 +0800 Subject: [PATCH 36/54] feat(helper): add getQueriedColumns function to retrieve queried columns from resolver payload - Implemented getQueriedColumns to extract columns from the resolver payload. - Updated tests to verify the functionality of the new method. --- packages/drizzle/src/helper.ts | 13 ++++++++++ packages/drizzle/test/helper.spec.ts | 25 +++++++++++++++++++ .../drizzle/test/resolver-factory.spec.ts | 4 +++ 3 files changed, 42 insertions(+) diff --git a/packages/drizzle/src/helper.ts b/packages/drizzle/src/helper.ts index 01ad9058..e44fa801 100644 --- a/packages/drizzle/src/helper.ts +++ b/packages/drizzle/src/helper.ts @@ -137,3 +137,16 @@ export function getSelectedColumns( return mapValue.SKIP }) as SelectedTableColumns } + +/** + * Get the queried columns from the resolver payload + * @param table - The table to get the queried columns from + * @param payload - The resolver payload + * @returns The queried columns + */ +export function getQueriedColumns( + table: Table, + payload: ResolverPayload | (ResolverPayload | undefined)[] | undefined +): Partial> { + return mapValue(getSelectedColumns(table, payload), () => true) +} diff --git a/packages/drizzle/test/helper.spec.ts b/packages/drizzle/test/helper.spec.ts index 6d3283c6..5c6a02de 100644 --- a/packages/drizzle/test/helper.spec.ts +++ b/packages/drizzle/test/helper.spec.ts @@ -8,6 +8,7 @@ import { GraphQLString, execute, parse } from "graphql" import { beforeEach, describe, expect, it } from "vitest" import { getEnumNameByColumn, + getQueriedColumns, getSelectedColumns, getValue, inArrayMultiple, @@ -94,6 +95,7 @@ describe("helper", () => { describe("getSelectedColumns", () => { const selectedColumns = new Set() + const queriedColumns = new Set() const r = resolver.of(sqliteTables.users, { users: query(sqliteTables.users.$list()).resolve((_input, payload) => { for (const column of Object.keys( @@ -104,6 +106,17 @@ describe("getSelectedColumns", () => { return [] }), + usersByQuery: query(sqliteTables.users.$list()).resolve( + (_input, payload) => { + for (const column of Object.keys( + getQueriedColumns(sqliteTables.users, payload) + )) { + queriedColumns.add(column) + } + return [] + } + ), + greeting: field(silk(GraphQLString)) .derivedFrom("name") .resolve((user) => `Hello ${user.name}`), @@ -125,6 +138,18 @@ describe("getSelectedColumns", () => { expect(selectedColumns).toEqual(new Set(["id", "name"])) }) + it("should access queried columns", async () => { + const query = parse(/* GraphQL */ ` + query { + usersByQuery { + id + } + } + `) + await execute({ schema, document: query }) + expect(queriedColumns).toEqual(new Set(["id"])) + }) + it("should handle derived fields", async () => { const query = parse(/* GraphQL */ ` query { diff --git a/packages/drizzle/test/resolver-factory.spec.ts b/packages/drizzle/test/resolver-factory.spec.ts index 94b26731..6aedcfab 100644 --- a/packages/drizzle/test/resolver-factory.spec.ts +++ b/packages/drizzle/test/resolver-factory.spec.ts @@ -721,6 +721,10 @@ describe.concurrent("DrizzleResolverFactory", () => { "GQLoom-Drizzle Error: Relation users.nonExistentRelation not found in drizzle instance" ) }) + + it.todo("should handle limit and offset") + it.todo("should handle orderBy") + it.todo("should handle where") }) describe("relationField with multiple field relations", () => { From 35a60871afaf58341490bc4eb95f593e71e4f346 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E2=9B=B5?= <809067559@qq.com> Date: Mon, 9 Jun 2025 21:24:29 +0800 Subject: [PATCH 37/54] feat(helper): add paramsAsKey function for serializing parameters into query string format - Implemented paramsAsKey to convert various data structures (objects, arrays) into a URL query string format. - Added comprehensive tests to validate the functionality of paramsAsKey, covering edge cases and nested structures. --- .../src/factory/relation-field-loader.ts | 87 +++++++++++++++++++ packages/drizzle/src/factory/resolver.ts | 73 ++-------------- packages/drizzle/src/helper.ts | 43 +++++++++ packages/drizzle/test/helper.spec.ts | 74 ++++++++++++++++ 4 files changed, 213 insertions(+), 64 deletions(-) create mode 100644 packages/drizzle/src/factory/relation-field-loader.ts diff --git a/packages/drizzle/src/factory/relation-field-loader.ts b/packages/drizzle/src/factory/relation-field-loader.ts new file mode 100644 index 00000000..176a363a --- /dev/null +++ b/packages/drizzle/src/factory/relation-field-loader.ts @@ -0,0 +1,87 @@ +import { EasyDataLoader, type ResolverPayload } from "@gqloom/core" +import { Many, type Relation, type Table, inArray } from "drizzle-orm" +import { getSelectedColumns, inArrayMultiple } from "../helper" +import type { AnyQueryBuilder, BaseDatabase } from "./types" + +export class RelationFieldLoader extends EasyDataLoader< + [any, payload: ResolverPayload | undefined], + any +> { + protected isList: boolean + protected fieldsLength: number + + public constructor( + protected db: BaseDatabase, + protected relation: Relation, + protected queryBuilder: AnyQueryBuilder, + protected targetTable: Table + ) { + super((...args) => this.batchLoad(...args)) + this.isList = relation instanceof Many + this.fieldsLength = relation.sourceColumns.length + } + + protected async batchLoad( + inputs: [any, payload: ResolverPayload | undefined][] + ) { + const where = (() => { + if (this.fieldsLength === 1) { + const values = inputs.map( + ([parent]) => parent[this.relation.sourceColumns[0].name] + ) + return inArray(this.relation.targetColumns[0], values) + } + const values = inputs.map((input) => + this.relation.sourceColumns.map((field) => input[0][field.name]) + ) + return inArrayMultiple( + this.relation.targetColumns, + values, + this.targetTable + ) + })() + const selectedColumns = getSelectedColumns( + this.targetTable, + inputs.map((input) => input[1]) + ) + + const referenceColumns = Object.fromEntries( + this.relation.targetColumns.map((col) => [col.name, col]) + ) + + const list = await (this.db as any) + .select({ ...selectedColumns, ...referenceColumns }) + .from(this.targetTable) + .where(where) + + const groups = new Map() + for (const item of list) { + const key = this.getKeyByReference(item) + this.isList + ? groups.set(key, [...(groups.get(key) ?? []), item]) + : groups.set(key, item) + } + return inputs.map(([parent]) => { + const key = this.getKeyByField(parent) + return groups.get(key) ?? (this.isList ? [] : null) + }) + } + + protected getKeyByField(parent: any) { + if (this.fieldsLength === 1) { + return parent[this.relation.sourceColumns[0].name] + } + return this.relation.sourceColumns + .map((field) => parent[field.name]) + .join("-") + } + + protected getKeyByReference(item: any) { + if (this.fieldsLength === 1) { + return item[this.relation.targetColumns[0].name] + } + return this.relation.targetColumns + .map((reference) => item[reference.name]) + .join("-") + } +} diff --git a/packages/drizzle/src/factory/resolver.ts b/packages/drizzle/src/factory/resolver.ts index a351816a..1237ae16 100644 --- a/packages/drizzle/src/factory/resolver.ts +++ b/packages/drizzle/src/factory/resolver.ts @@ -1,5 +1,4 @@ import { - EasyDataLoader, FieldFactoryWithResolve, type FieldOptions, type GraphQLFieldOptions, @@ -9,7 +8,6 @@ import { type ObjectChainResolver, QueryFactoryWithResolve, type QueryOptions, - type ResolverPayload, capitalize, getMemoizationMap, loom, @@ -54,7 +52,7 @@ import { type SelectiveTable, type TableSilk, } from ".." -import { getSelectedColumns, inArrayMultiple } from "../helper" +import { getSelectedColumns } from "../helper" import { type ColumnFilters, type CountArgs, @@ -67,6 +65,7 @@ import { type SelectSingleArgs, type UpdateArgs, } from "./input" +import { RelationFieldLoader } from "./relation-field-loader" import type { AnyQueryBuilder, BaseDatabase, @@ -417,7 +416,7 @@ export abstract class DrizzleResolverFactory< targetTable, ] })() - if (!queryBuilder || !relation || !(targetTable instanceof Table)) { + if (!relation || !queryBuilder || !(targetTable instanceof Table)) { throw new Error( `GQLoom-Drizzle Error: Relation ${this.tableName}.${String( relationName @@ -427,73 +426,19 @@ export abstract class DrizzleResolverFactory< const output = DrizzleWeaver.unravel(targetTable) const isList = relation instanceof Many - const fieldsLength = relation.sourceColumns.length - const getKeyByField = (parent: any) => { - if (fieldsLength === 1) { - return parent[relation.sourceColumns[0].name] - } - return relation.sourceColumns.map((field) => parent[field.name]).join("-") - } - - const referenceColumns = Object.fromEntries( - relation.targetColumns.map((col) => [col.name, col]) + const columns = Object.entries(getTableColumns(targetTable)) + const dependencies = relation.sourceColumns.map( + (col) => columns.find(([_, value]) => value === col)?.[0] ?? col.name ) - - const getKeyByReference = (item: any) => { - if (fieldsLength === 1) { - return item[relation.targetColumns[0].name] - } - return relation.targetColumns - .map((reference) => item[reference.name]) - .join("-") - } - - const initLoader = () => { - return new EasyDataLoader( - async (inputs: [any, payload: ResolverPayload | undefined][]) => { - const where = (() => { - if (fieldsLength === 1) { - const values = inputs.map( - ([parent]) => parent[relation.sourceColumns[0].name] - ) - return inArray(relation.targetColumns[0], values) - } - const values = inputs.map((input) => - relation.sourceColumns.map((field) => input[0][field.name]) - ) - return inArrayMultiple(relation.targetColumns, values, targetTable) - })() - const selectedColumns = getSelectedColumns( - targetTable, - inputs.map((input) => input[1]) - ) - - const list = await (this.db as any) - .select({ ...selectedColumns, ...referenceColumns }) - .from(targetTable) - .where(where) - - const groups = new Map() - for (const item of list) { - const key = getKeyByReference(item) - isList - ? groups.set(key, [...(groups.get(key) ?? []), item]) - : groups.set(key, item) - } - return inputs.map(([parent]) => { - const key = getKeyByField(parent) - return groups.get(key) ?? (isList ? [] : null) - }) - } - ) - } + const initLoader = () => + new RelationFieldLoader(this.db, relation, queryBuilder, targetTable) return new FieldFactoryWithResolve( isList ? output.$list() : output.$nullable(), { ...options, - dependencies: ["tableName"], + dependencies, resolve: (parent, _input, payload) => { const loader = (() => { if (!payload) return initLoader() diff --git a/packages/drizzle/src/helper.ts b/packages/drizzle/src/helper.ts index e44fa801..e1936eb5 100644 --- a/packages/drizzle/src/helper.ts +++ b/packages/drizzle/src/helper.ts @@ -150,3 +150,46 @@ export function getQueriedColumns( ): Partial> { return mapValue(getSelectedColumns(table, payload), () => true) } + +export function paramsAsKey(params: any): string { + if (typeof params !== "object" || params === null) return String(params) + + const searchParams = new URLSearchParams() + + function addToParams(obj: unknown, prefix = "") { + if (Array.isArray(obj)) { + obj.forEach((value, index) => { + const key = prefix ? `${prefix}.${index}` : String(index) + if (value != null && typeof value === "object") { + addToParams(value, key) + } else { + searchParams.set(key, String(value)) + } + }) + return + } + + if (typeof obj !== "object" || obj === null) { + searchParams.set(prefix, String(obj)) + return + } + + for (const [key, value] of Object.entries(obj)) { + const newPrefix = prefix ? `${prefix}.${key}` : key + + if (value == null) { + searchParams.set(newPrefix, "") + } else if (Array.isArray(value)) { + addToParams(value, newPrefix) + } else if (typeof value === "object") { + addToParams(value, newPrefix) + } else { + searchParams.set(newPrefix, String(value)) + } + } + } + + addToParams(params) + searchParams.sort() + return searchParams.toString() +} diff --git a/packages/drizzle/test/helper.spec.ts b/packages/drizzle/test/helper.spec.ts index 5c6a02de..cd0368f3 100644 --- a/packages/drizzle/test/helper.spec.ts +++ b/packages/drizzle/test/helper.spec.ts @@ -13,6 +13,7 @@ import { getValue, inArrayMultiple, isColumnVisible, + paramsAsKey, } from "../src/helper" import type { DrizzleFactoryInputVisibilityBehaviors } from "../src/types" import * as sqliteTables from "./schema/sqlite" @@ -163,3 +164,76 @@ describe("getSelectedColumns", () => { expect(selectedColumns).toEqual(new Set(["id", "name"])) }) }) + +describe("paramsAsKey", () => { + it("should handle null and undefined", () => { + expect(paramsAsKey(null)).toBe("null") + expect(paramsAsKey(undefined)).toBe("undefined") + }) + + it("should handle primitive values", () => { + expect(paramsAsKey(42)).toBe("42") + expect(paramsAsKey("hello")).toBe("hello") + expect(paramsAsKey(true)).toBe("true") + }) + + it("should handle flat objects", () => { + const obj = { a: 1, b: "test", c: true } + expect(paramsAsKey(obj)).toBe("a=1&b=test&c=true") + }) + + it("should handle nested objects", () => { + const obj = { + a: 1, + b: { + c: "test", + d: { + e: true, + }, + }, + } + expect(paramsAsKey(obj)).toBe("a=1&b.c=test&b.d.e=true") + }) + + it("should handle arrays", () => { + const obj = { + a: [1, 2, 3], + b: { + c: ["x", "y"], + }, + } + expect(paramsAsKey(obj)).toBe("a.0=1&a.1=2&a.2=3&b.c.0=x&b.c.1=y") + }) + + it("should generate same key for objects with different key order", () => { + const obj1 = { a: 1, b: 2, c: 3 } + const obj2 = { c: 3, a: 1, b: 2 } + expect(paramsAsKey(obj1)).toBe(paramsAsKey(obj2)) + }) + + it("should handle null values in objects", () => { + const obj = { a: null, b: undefined, c: 1 } + expect(paramsAsKey(obj)).toBe("a=&b=&c=1") + }) + + it("should handle array of objects", () => { + const arr = [ + { a: 1, b: 2 }, + { a: 3, b: 4 }, + ] + expect(paramsAsKey(arr)).toBe("0.a=1&0.b=2&1.a=3&1.b=4") + }) + + it("should handle object with array of objects", () => { + const obj = { + group: [ + { id: 1, name: "foo" }, + { id: 2, name: "bar" }, + ], + } + + expect(paramsAsKey(obj)).toBe( + "group.0.id=1&group.0.name=foo&group.1.id=2&group.1.name=bar" + ) + }) +}) From cdb53827e1f43c98c67e877de73df974d0323dc7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E2=9B=B5?= <809067559@qq.com> Date: Tue, 10 Jun 2025 20:46:44 +0800 Subject: [PATCH 38/54] feat(relations): implement relation field loading with support for one-to-many and many-to-one relationships - Added methods for handling relation arguments in the DrizzleInputFactory. - Enhanced RelationFieldLoader to support batch loading for both one-to-many and many-to-one relationships. - Updated resolver factory to utilize new relation argument structures. - Removed unused getQueriedColumns function from helper. - Added tests to validate the new relation loading functionality. --- packages/drizzle/src/factory/input.ts | 64 +++- .../src/factory/relation-field-loader.ts | 300 ++++++++++++++---- packages/drizzle/src/factory/resolver.ts | 137 ++++---- packages/drizzle/src/factory/types.ts | 71 ++++- packages/drizzle/src/helper.ts | 13 - packages/drizzle/test/helper.spec.ts | 25 -- .../drizzle/test/resolver-factory.spec.ts | 87 +++-- packages/drizzle/test/resolver-mysql.spec.gql | 4 +- packages/drizzle/test/resolver-mysql.spec.ts | 2 +- .../drizzle/test/resolver-postgres.spec.gql | 4 +- .../drizzle/test/resolver-postgres.spec.ts | 2 +- .../drizzle/test/resolver-sqlite.spec.gql | 64 +++- packages/drizzle/test/resolver-sqlite.spec.ts | 2 +- 13 files changed, 579 insertions(+), 196 deletions(-) diff --git a/packages/drizzle/src/factory/input.ts b/packages/drizzle/src/factory/input.ts index d46a1c23..ae230460 100644 --- a/packages/drizzle/src/factory/input.ts +++ b/packages/drizzle/src/factory/input.ts @@ -3,6 +3,7 @@ import { type Column, type InferInsertModel, type InferSelectModel, + type Many, type Table, getTableColumns, getTableName, @@ -19,8 +20,13 @@ import { isNonNullType, } from "graphql" import { getValue, isColumnVisible } from "../helper" -import { DrizzleWeaver } from "../index" +import { type BaseDatabase, DrizzleWeaver } from "../index" import type { DrizzleResolverFactoryOptions, DrizzleSilkConfig } from "../types" +import type { + InferRelationTable, + InferTableRelationalConfig, + QueryBuilder, +} from "./types" export class DrizzleInputFactory { public constructor( @@ -82,6 +88,39 @@ export class DrizzleInputFactory { ) } + public relationToManyArgs() { + const name = `${pascalCase(getTableName(this.table))}RelationToManyArgs` + const existing = weaverContext.getNamedType(name) as GraphQLObjectType + if (existing != null) return existing + + return weaverContext.memoNamedType( + new GraphQLObjectType>({ + name, + fields: { + where: { type: this.filters() }, + orderBy: { type: this.orderBy() }, + limit: { type: GraphQLInt }, + offset: { type: GraphQLInt }, + }, + }) + ) + } + + public relationToOneArgs() { + const name = `${pascalCase(getTableName(this.table))}RelationToOneArgs` + const existing = weaverContext.getNamedType(name) as GraphQLObjectType + if (existing != null) return existing + + return weaverContext.memoNamedType( + new GraphQLObjectType>({ + name, + fields: { + where: { type: this.filters() }, + }, + }) + ) + } + public insertArrayArgs() { const name = `${pascalCase(getTableName(this.table))}InsertArrayArgs` const existing = weaverContext.getNamedType(name) as GraphQLObjectType @@ -495,6 +534,29 @@ export interface CountArgs { where?: Filters } +export type RelationArgs< + TDatabase extends BaseDatabase, + TTable extends Table, + TRelationName extends keyof InferTableRelationalConfig< + QueryBuilder + >["relations"], +> = InferTableRelationalConfig< + QueryBuilder +>["relations"][TRelationName] extends Many + ? RelationToManyArgs> + : RelationToOneArgs> + +export interface RelationToManyArgs { + where?: Filters + orderBy?: Partial, "asc" | "desc">> + limit?: number + offset?: number +} + +export interface RelationToOneArgs { + where?: Filters +} + export interface InsertArrayArgs { values: InferInsertModel[] } diff --git a/packages/drizzle/src/factory/relation-field-loader.ts b/packages/drizzle/src/factory/relation-field-loader.ts index 176a363a..f319b5ab 100644 --- a/packages/drizzle/src/factory/relation-field-loader.ts +++ b/packages/drizzle/src/factory/relation-field-loader.ts @@ -1,87 +1,277 @@ -import { EasyDataLoader, type ResolverPayload } from "@gqloom/core" -import { Many, type Relation, type Table, inArray } from "drizzle-orm" -import { getSelectedColumns, inArrayMultiple } from "../helper" -import type { AnyQueryBuilder, BaseDatabase } from "./types" +import { EasyDataLoader, type ResolverPayload, mapValue } from "@gqloom/core" +import { + type Column, + Many, + type Relation, + type Table, + and, + getTableColumns, + getTableName, + inArray, +} from "drizzle-orm" +import { getSelectedColumns, inArrayMultiple, paramsAsKey } from "../helper" +import type { + AnyQueryBuilder, + BaseDatabase, + QueryToManyFieldOptions, + QueryToOneFieldOptions, +} from "./types" export class RelationFieldLoader extends EasyDataLoader< - [any, payload: ResolverPayload | undefined], + [parent: any, arg: any, payload: ResolverPayload | undefined], any > { - protected isList: boolean + protected isMany: boolean protected fieldsLength: number + protected queryBuilder: AnyQueryBuilder public constructor( protected db: BaseDatabase, + protected relationName: string | number | symbol, protected relation: Relation, - protected queryBuilder: AnyQueryBuilder, + protected sourceTable: Table, protected targetTable: Table ) { - super((...args) => this.batchLoad(...args)) - this.isList = relation instanceof Many + const isMany = relation instanceof Many + super((...args) => + isMany ? this.batchLoadMany(...args) : this.batchLoadOne(...args) + ) + this.isMany = isMany this.fieldsLength = relation.sourceColumns.length + const queryBuilder = matchQueryBuilder(this.db.query, this.sourceTable) + if (!queryBuilder) { + throw new Error( + `Query builder not found for source table ${getTableName(this.sourceTable)}` + ) + } + this.queryBuilder = queryBuilder } - protected async batchLoad( - inputs: [any, payload: ResolverPayload | undefined][] - ) { - const where = (() => { - if (this.fieldsLength === 1) { - const values = inputs.map( - ([parent]) => parent[this.relation.sourceColumns[0].name] + protected async batchLoadOne( + inputs: [ + parent: any, + args: QueryToOneFieldOptions, + payload: ResolverPayload | undefined, + ][] + ): Promise { + const inputGroups = this.keyByArgs(inputs) + + const resultsGroups = new Map>() + await Promise.all( + Array.from(inputGroups.values()).map(async (inputs) => { + const args = inputs[0][1] + const columns = this.columns(inputs) + const results = await this.loadOneByParent( + inputs.map((input) => input[0]), + args, + columns + ) + const groups = new Map( + results.map((item: any) => [this.getKeyByReference(item), item]) + ) + resultsGroups.set(paramsAsKey(args), groups) + }) + ) + + return inputs.map(([parent, args]) => { + const paramsKey = paramsAsKey(args) + const groups = resultsGroups.get(paramsKey) + if (!groups) return null + const key = this.getKeyByField(parent) + return groups.get(key) ?? null + }) + } + + protected async loadOneByParent( + parents: any[], + args: QueryToOneFieldOptions, + columns: Partial> + ): Promise { + return await (this.db as any) + .select(columns) + .from(this.targetTable) + .where(and(this.whereByParent(parents), args.where)) + } + + protected async batchLoadMany( + inputs: [ + parent: any, + args: QueryToManyFieldOptions, + payload: ResolverPayload | undefined, + ][] + ): Promise { + const inputGroups = this.keyByArgs(inputs) + const resultsGroups = new Map>() + await Promise.all( + Array.from(inputGroups.values()).map(async (inputs) => { + const args = inputs[0][1] + const columns = this.columns(inputs) + const parentResults = await this.loadManyByParent( + inputs.map((input) => input[0]), + args, + columns ) - return inArray(this.relation.targetColumns[0], values) - } - const values = inputs.map((input) => - this.relation.sourceColumns.map((field) => input[0][field.name]) + const groups = new Map( + parentResults.map((parent: any) => [ + this.getKeyForParent(parent), + parent, + ]) + ) + resultsGroups.set(paramsAsKey(args), groups) + }) + ) + + return inputs.map(([parent, args]) => { + const paramsKey = paramsAsKey(args) + const groups = resultsGroups.get(paramsKey) + if (!groups) return [] + const key = this.getKeyForParent(parent) + const parentResult = groups.get(key) + if (!parentResult) return [] + return parentResult?.[this.relationName] ?? [] + }) + } + + protected async loadManyByParent( + parents: any[], + args: QueryToManyFieldOptions, + columns: Partial> + ): Promise { + return await this.queryBuilder.findMany({ + where: { RAW: (table) => this.whereForParent(table, parents) }, + with: { + [this.relationName]: { + where: { RAW: args.where }, + orderBy: args.orderBy, + limit: args.limit, + offset: args.offset, + columns: mapValue(columns, () => true), + }, + } as never, + }) + } + + protected keyByArgs( + inputs: [parent: any, args: TArgs, payload: ResolverPayload | undefined][] + ): Map< + string, + [parent: any, args: TArgs, payload: ResolverPayload | undefined][] + > { + const inputGroups = new Map< + string, + [parent: any, args: TArgs, payload: ResolverPayload | undefined][] + >() + for (const input of inputs) { + const key = paramsAsKey(input[1]) + const array = inputGroups.get(key) ?? [] + array.push(input) + inputGroups.set(key, array) + } + return inputGroups + } + + protected whereByParent(parents: any[]) { + if (this.fieldsLength === 1) { + const values = parents.map( + (parent) => + parent[ + this.fieldKey(this.sourceTable, this.relation.sourceColumns[0]) + ] + ) + return inArray(this.relation.targetColumns[0], values) + } + const values = parents.map((parent) => + this.relation.sourceColumns.map( + (field) => parent[this.fieldKey(this.sourceTable, field)] ) - return inArrayMultiple( - this.relation.targetColumns, - values, - this.targetTable + ) + return inArrayMultiple( + this.relation.targetColumns, + values, + this.targetTable + ) + } + + protected whereForParent(table: Table, parents: any[]) { + const primaryColumns = Object.entries(getTableColumns(table)).filter( + ([_, col]) => col.primary + ) + if (primaryColumns.length === 1) { + const [key, column] = primaryColumns[0] + return inArray( + column, + parents.map((parent) => parent[key]) + ) + } + return inArrayMultiple( + primaryColumns.map((it) => it[1]), + parents.map((parent) => primaryColumns.map((it) => parent[it[0]])), + this.sourceTable + ) + } + + protected tablesColumnToFieldKey: Map> = new Map() + + protected fieldKey(table: Table, column: Column): string { + const columnNameToFieldKeys = + this.tablesColumnToFieldKey.get(table) ?? + new Map( + Object.entries(getTableColumns(table)).map(([key, sourceColumn]) => [ + sourceColumn.name, + key, + ]) + ) + this.tablesColumnToFieldKey.set(table, columnNameToFieldKeys) + const key = columnNameToFieldKeys.get(column.name) + if (!key) { + throw new Error( + `Column ${column.name} not found in source table ${getTableName(this.sourceTable)}` ) - })() + } + return key + } + + protected columns( + inputs: [parent: any, args: any, payload: ResolverPayload | undefined][] + ) { const selectedColumns = getSelectedColumns( this.targetTable, - inputs.map((input) => input[1]) + inputs.map((input) => input[2]) ) const referenceColumns = Object.fromEntries( this.relation.targetColumns.map((col) => [col.name, col]) ) - - const list = await (this.db as any) - .select({ ...selectedColumns, ...referenceColumns }) - .from(this.targetTable) - .where(where) - - const groups = new Map() - for (const item of list) { - const key = this.getKeyByReference(item) - this.isList - ? groups.set(key, [...(groups.get(key) ?? []), item]) - : groups.set(key, item) - } - return inputs.map(([parent]) => { - const key = this.getKeyByField(parent) - return groups.get(key) ?? (this.isList ? [] : null) - }) + return { ...selectedColumns, ...referenceColumns } } - protected getKeyByField(parent: any) { - if (this.fieldsLength === 1) { - return parent[this.relation.sourceColumns[0].name] - } + protected getKeyByField(parent: any): string { return this.relation.sourceColumns - .map((field) => parent[field.name]) - .join("-") + .map((col) => parent[this.fieldKey(this.sourceTable, col)]) + .join() } - protected getKeyByReference(item: any) { - if (this.fieldsLength === 1) { - return item[this.relation.targetColumns[0].name] - } + protected getKeyByReference(item: any): string { return this.relation.targetColumns - .map((reference) => item[reference.name]) - .join("-") + .map((col) => item[this.fieldKey(this.targetTable, col)]) + .join() + } + + protected getKeyForParent(parent: any): string { + return Object.entries(getTableColumns(this.sourceTable)) + .filter(([_, col]) => col.primary) + .map(([key]) => parent[key]) + .join() + } +} + +function matchQueryBuilder( + queries: Record, + table: any +): AnyQueryBuilder | undefined { + for (const qb of Object.values(queries)) { + if (qb.table != null && qb.table === table) { + return qb + } } } diff --git a/packages/drizzle/src/factory/resolver.ts b/packages/drizzle/src/factory/resolver.ts index 1237ae16..85806772 100644 --- a/packages/drizzle/src/factory/resolver.ts +++ b/packages/drizzle/src/factory/resolver.ts @@ -58,28 +58,33 @@ import { type CountArgs, type DeleteArgs, DrizzleInputFactory, + type Filters, type FiltersCore, type InsertArrayArgs, type InsertSingleArgs, + type RelationArgs, + type RelationToManyArgs, + type RelationToOneArgs, type SelectArrayArgs, type SelectSingleArgs, type UpdateArgs, } from "./input" import { RelationFieldLoader } from "./relation-field-loader" import type { - AnyQueryBuilder, BaseDatabase, + CountOptions, CountQuery, DeleteMutation, DrizzleQueriesResolver, - InferRelationTable, InferTableName, InferTableRelationalConfig, InsertArrayMutation, InsertSingleMutation, QueryBuilder, - RelationManyField, - RelationOneField, + QueryFieldOptions, + QueryToManyFieldOptions, + QueryToOneFieldOptions, + RelationField, SelectArrayOptions, SelectArrayQuery, SelectSingleOptions, @@ -181,18 +186,21 @@ export abstract class DrizzleResolverFactory< input, ...options }: GraphQLFieldOptions & { - input?: GraphQLSilk, TInputI> + input?: GraphQLSilk middlewares?: Middleware>[] } = {}): CountQuery { - input ??= silk, CountArgs>(() => - this.inputFactory.countArgs() - ) as GraphQLSilk, TInputI> + input ??= silk>( + () => this.inputFactory.countArgs(), + (args) => ({ + value: { where: this.extractFilters(args.where, this.table) }, + }) + ) as GraphQLSilk return new QueryFactoryWithResolve(silk(new GraphQLNonNull(GraphQLInt)), { input, ...options, - resolve: (args) => { - return this.db.$count(this.table, this.extractFilters(args.where)) + resolve: (args: CountOptions) => { + return this.db.$count(this.table, args.where) }, } as QueryOptions) } @@ -215,10 +223,11 @@ export abstract class DrizzleResolverFactory< } protected extractFilters( - filters: SelectArrayArgs["where"], + filters: Filters | undefined, table?: any ): SQL | undefined { if (filters == null) return + table ??= this.table const entries = Object.entries(filters as FiltersCore) const variants: (SQL | undefined)[] = [] @@ -258,7 +267,7 @@ export abstract class DrizzleResolverFactory< continue } - const column = getTableColumns(this.table)[columnName]! + const column = getTableColumns(table)[columnName]! const extractedColumn = this.extractFiltersColumn( column, columnName, @@ -372,51 +381,35 @@ export abstract class DrizzleResolverFactory< TRelationName extends keyof InferTableRelationalConfig< QueryBuilder >["relations"], + TInputI = RelationArgs, >( relationName: TRelationName, - options?: GraphQLFieldOptions & { + { + input, + ...options + }: GraphQLFieldOptions & { + input?: GraphQLSilk< + QueryFieldOptions, + TInputI + > middlewares?: Middleware< - InferTableRelationalConfig< - QueryBuilder - >["relations"][TRelationName] extends Many - ? RelationManyField< - TTable, - InferRelationTable - > - : RelationOneField< - TTable, - InferRelationTable - > + RelationField >[] - } - ): InferTableRelationalConfig< - QueryBuilder - >["relations"][TRelationName] extends Many - ? RelationManyField< - TTable, - InferRelationTable - > - : RelationOneField< - TTable, - InferRelationTable - > { - const [relation, queryBuilder, targetTable] = (() => { + } = {} + ): RelationField { + const [relation, targetTable] = (() => { const tableKey = matchTableByTablesConfig( this.db._.relations.tablesConfig, this.table )?.tsName - if (!tableKey) return [undefined, undefined, undefined] + if (!tableKey) return [undefined, undefined] const relation = this.db._.relations["config"]?.[tableKey]?.[ relationName ] as Relation const targetTable = relation?.targetTable - return [ - relation, - matchQueryBuilder(this.db.query, targetTable), - targetTable, - ] + return [relation, targetTable] })() - if (!relation || !queryBuilder || !(targetTable instanceof Table)) { + if (!relation || !(targetTable instanceof Table)) { throw new Error( `GQLoom-Drizzle Error: Relation ${this.tableName}.${String( relationName @@ -424,29 +417,62 @@ export abstract class DrizzleResolverFactory< ) } const output = DrizzleWeaver.unravel(targetTable) - - const isList = relation instanceof Many + const toMany = relation instanceof Many + const targetInputFactory = new DrizzleInputFactory(targetTable) + + input ??= ( + toMany + ? silk>( + () => targetInputFactory.relationToManyArgs(), + (args) => ({ + value: { + where: this.extractFilters(args.where, targetTable), + orderBy: args.orderBy, + limit: args.limit, + offset: args.offset, + }, + }) + ) + : silk>( + () => targetInputFactory.relationToOneArgs(), + (args) => ({ + value: { + where: this.extractFilters(args.where, targetTable), + }, + }) + ) + ) as GraphQLSilk< + QueryFieldOptions, + TInputI + > const columns = Object.entries(getTableColumns(targetTable)) const dependencies = relation.sourceColumns.map( (col) => columns.find(([_, value]) => value === col)?.[0] ?? col.name ) const initLoader = () => - new RelationFieldLoader(this.db, relation, queryBuilder, targetTable) + new RelationFieldLoader( + this.db, + relationName, + relation, + this.table, + targetTable + ) return new FieldFactoryWithResolve( - isList ? output.$list() : output.$nullable(), + toMany ? output.$list() : output.$nullable(), { + input, ...options, dependencies, - resolve: (parent, _input, payload) => { + resolve: (parent, input, payload) => { const loader = (() => { if (!payload) return initLoader() const memoMap = getMemoizationMap(payload) if (!memoMap.has(initLoader)) memoMap.set(initLoader, initLoader()) return memoMap.get(initLoader) as ReturnType })() - return loader.load([parent, payload]) + return loader.load([parent, input, payload]) }, } as FieldOptions ) @@ -549,14 +575,3 @@ function matchTableByTablesConfig( } } } - -function matchQueryBuilder( - queries: Record, - targetTable: any -): AnyQueryBuilder | undefined { - for (const qb of Object.values(queries)) { - if (qb.table != null && qb.table === targetTable) { - return qb - } - } -} diff --git a/packages/drizzle/src/factory/types.ts b/packages/drizzle/src/factory/types.ts index ee3e1add..6450c2a4 100644 --- a/packages/drizzle/src/factory/types.ts +++ b/packages/drizzle/src/factory/types.ts @@ -27,6 +27,8 @@ import type { InsertSingleArgs, InsertSingleWithOnConflictArgs, MutationResult, + RelationToManyArgs, + RelationToOneArgs, SelectArrayArgs, SelectSingleArgs, UpdateArgs, @@ -107,22 +109,24 @@ export interface SelectArrayQuery< GraphQLSilk > {} -export type SelectArrayOptions = { +export interface SelectArrayOptions { where?: SQL orderBy?: (Column | SQL | SQL.Aliased)[] limit?: number offset?: number } -export interface CountQuery< - TTable extends Table, - TInputI = SelectArrayArgs, -> extends QueryFactoryWithResolve< - CountArgs, +export interface CountQuery> + extends QueryFactoryWithResolve< + CountOptions, GraphQLSilk, - GraphQLSilk, TInputI> + GraphQLSilk > {} +export interface CountOptions { + where?: SQL +} + export interface SelectSingleQuery< TTable extends Table, TInputI = SelectSingleArgs, @@ -135,34 +139,81 @@ export interface SelectSingleQuery< GraphQLSilk > {} -export type SelectSingleOptions = { +export interface SelectSingleOptions { where?: SQL orderBy?: (Column | SQL | SQL.Aliased)[] offset?: number } +export type RelationField< + TDatabase extends BaseDatabase, + TTable extends Table, + TRelationName extends keyof InferTableRelationalConfig< + QueryBuilder + >["relations"], +> = InferTableRelationalConfig< + QueryBuilder +>["relations"][TRelationName] extends Many + ? RelationManyField< + TTable, + InferRelationTable + > + : RelationOneField< + TTable, + InferRelationTable + > + export interface RelationManyField< TTable extends Table, TRelationTable extends Table, + TInputI = RelationToManyArgs, > extends FieldFactoryWithResolve< GraphQLSilk, SelectiveTable>, GraphQLSilk< InferSelectModel[], InferSelectModel[] - > + >, + QueryToManyFieldOptions, + GraphQLSilk > {} +export interface QueryToManyFieldOptions { + where?: SQL + orderBy?: Partial> + limit?: number + offset?: number +} + export interface RelationOneField< TTable extends Table, TRelationTable extends Table, + TInputI = RelationToOneArgs, > extends FieldFactoryWithResolve< GraphQLSilk, SelectiveTable>, GraphQLSilk< InferSelectModel | null | undefined, InferSelectModel | null | undefined - > + >, + QueryToOneFieldOptions, + GraphQLSilk > {} +export interface QueryToOneFieldOptions { + where?: SQL +} + +export type QueryFieldOptions< + TDatabase extends BaseDatabase, + TTable extends Table, + TRelationName extends keyof InferTableRelationalConfig< + QueryBuilder + >["relations"], +> = InferTableRelationalConfig< + QueryBuilder +>["relations"][TRelationName] extends Many + ? QueryToManyFieldOptions + : QueryToOneFieldOptions + export type InsertArrayMutation< TTable extends Table, TInputI = InsertArrayArgs, diff --git a/packages/drizzle/src/helper.ts b/packages/drizzle/src/helper.ts index e1936eb5..9c02ceac 100644 --- a/packages/drizzle/src/helper.ts +++ b/packages/drizzle/src/helper.ts @@ -138,19 +138,6 @@ export function getSelectedColumns( }) as SelectedTableColumns } -/** - * Get the queried columns from the resolver payload - * @param table - The table to get the queried columns from - * @param payload - The resolver payload - * @returns The queried columns - */ -export function getQueriedColumns( - table: Table, - payload: ResolverPayload | (ResolverPayload | undefined)[] | undefined -): Partial> { - return mapValue(getSelectedColumns(table, payload), () => true) -} - export function paramsAsKey(params: any): string { if (typeof params !== "object" || params === null) return String(params) diff --git a/packages/drizzle/test/helper.spec.ts b/packages/drizzle/test/helper.spec.ts index cd0368f3..6256ede3 100644 --- a/packages/drizzle/test/helper.spec.ts +++ b/packages/drizzle/test/helper.spec.ts @@ -8,7 +8,6 @@ import { GraphQLString, execute, parse } from "graphql" import { beforeEach, describe, expect, it } from "vitest" import { getEnumNameByColumn, - getQueriedColumns, getSelectedColumns, getValue, inArrayMultiple, @@ -96,7 +95,6 @@ describe("helper", () => { describe("getSelectedColumns", () => { const selectedColumns = new Set() - const queriedColumns = new Set() const r = resolver.of(sqliteTables.users, { users: query(sqliteTables.users.$list()).resolve((_input, payload) => { for (const column of Object.keys( @@ -107,17 +105,6 @@ describe("getSelectedColumns", () => { return [] }), - usersByQuery: query(sqliteTables.users.$list()).resolve( - (_input, payload) => { - for (const column of Object.keys( - getQueriedColumns(sqliteTables.users, payload) - )) { - queriedColumns.add(column) - } - return [] - } - ), - greeting: field(silk(GraphQLString)) .derivedFrom("name") .resolve((user) => `Hello ${user.name}`), @@ -139,18 +126,6 @@ describe("getSelectedColumns", () => { expect(selectedColumns).toEqual(new Set(["id", "name"])) }) - it("should access queried columns", async () => { - const query = parse(/* GraphQL */ ` - query { - usersByQuery { - id - } - } - `) - await execute({ schema, document: query }) - expect(queriedColumns).toEqual(new Set(["id"])) - }) - it("should handle derived fields", async () => { const query = parse(/* GraphQL */ ` query { diff --git a/packages/drizzle/test/resolver-factory.spec.ts b/packages/drizzle/test/resolver-factory.spec.ts index 6aedcfab..b421d4de 100644 --- a/packages/drizzle/test/resolver-factory.spec.ts +++ b/packages/drizzle/test/resolver-factory.spec.ts @@ -1,5 +1,5 @@ import { resolver } from "@gqloom/core" -import { defineRelations, eq, inArray, sql } from "drizzle-orm" +import { defineRelations, eq, gte, inArray, like, lt, sql } from "drizzle-orm" import { type LibSQLDatabase, drizzle as sqliteDrizzle, @@ -45,7 +45,7 @@ import { relations as sqliteRelations } from "./schema/sqlite-relations" const pathToDB = new URL("./schema/sqlite.db", import.meta.url) -describe.concurrent("DrizzleResolverFactory", () => { +describe("DrizzleResolverFactory", () => { let db: LibSQLDatabase let userFactory: DrizzleSQLiteResolverFactory< typeof db, @@ -137,6 +137,8 @@ describe.concurrent("DrizzleResolverFactory", () => { expect(["", ...log, ""].join("\n")).toMatchInlineSnapshot(` " + select "id", "name", "age", "email" from "users" order by "users"."name" asc, "users"."age" asc + select "id", "name", "age", "email" from "users" order by "users"."age" desc, "users"."name" asc " `) }) @@ -393,7 +395,7 @@ describe.concurrent("DrizzleResolverFactory", () => { }) }) - describe.concurrent("selectSingleQuery", () => { + describe("selectSingleQuery", () => { it("should be used without error", () => { const userResolver = resolver.of(sqliteSchemas.users, { user: userFactory.selectSingleQuery(), @@ -483,22 +485,22 @@ describe.concurrent("DrizzleResolverFactory", () => { expect(answer).toBe(5) answer = await query["~meta"].resolve({ - where: { age: { gte: 12 } }, + where: gte(sqliteSchemas.users.age, 12), }) expect(answer).toBe(3) answer = await query["~meta"].resolve({ - where: { age: { lt: 12 } }, + where: lt(sqliteSchemas.users.age, 12), }) expect(answer).toBe(2) answer = await query["~meta"].resolve({ - where: { age: { in: [10, 11] } }, + where: inArray(sqliteSchemas.users.age, [10, 11]), }) expect(answer).toBe(2) answer = await query["~meta"].resolve({ - where: { name: { like: "J%" } }, + where: like(sqliteSchemas.users.name, "J%"), }) expect(answer).toBe(5) }) @@ -510,7 +512,7 @@ describe.concurrent("DrizzleResolverFactory", () => { age: v.nullish(v.number()), }), v.transform(({ age }) => ({ - where: age != null ? { age: { eq: age } } : undefined, + where: age != null ? eq(sqliteSchemas.users.age, age) : undefined, })) ), }) @@ -680,10 +682,9 @@ describe.concurrent("DrizzleResolverFactory", () => { })) ) - let answer - answer = await Promise.all( + const answer = await Promise.all( studentCourses.map((sc) => { - return gradeField["~meta"].resolve(sc, undefined) + return gradeField["~meta"].resolve(sc, {}) }) ) @@ -701,13 +702,56 @@ describe.concurrent("DrizzleResolverFactory", () => { }, ]) ) + }) + + it("should resolve correctly for to-many relation", async () => { + const John = await db.query.users.findFirst({ + where: { name: "John" }, + }) + if (!John) throw new Error("John not found") + const Joe = await db.query.users.findFirst({ + where: { name: "Joe" }, + }) + if (!Joe) throw new Error("Joe not found") + + const [math, english] = await db + .insert(sqliteSchemas.courses) + .values([{ name: "Math" }, { name: "English" }]) + .returning() + + const studentCourses = await db + .insert(sqliteSchemas.studentToCourses) + .values([ + { studentId: John.id, courseId: math.id }, + { studentId: John.id, courseId: english.id }, + { studentId: Joe.id, courseId: math.id }, + { studentId: Joe.id, courseId: english.id }, + ]) + .returning() + + await db.insert(sqliteSchemas.studentCourseGrades).values( + studentCourses.map((it) => ({ + ...it, + grade: Math.floor(Math.random() * 51) + 50, + })) + ) await db.insert(sqliteSchemas.posts).values([ { authorId: John.id, title: "Hello" }, { authorId: John.id, title: "World" }, ]) const postsField = userFactory.relationField("posts") - answer = await postsField["~meta"].resolve(John, undefined) + const answer = await postsField["~meta"].resolve(John, {}) + // const answer = ( + // await db.query.users.findFirst({ + // where: { RAW: (users) => inArray(users.id, [John.id]) }, + // with: { + // posts: { + // columns: { id: true, title: true, authorId: true }, + // }, + // }, + // }) + // )?.posts expect(answer).toMatchObject([ { authorId: John.id }, { authorId: John.id }, @@ -722,9 +766,10 @@ describe.concurrent("DrizzleResolverFactory", () => { ) }) - it.todo("should handle limit and offset") - it.todo("should handle orderBy") - it.todo("should handle where") + it.todo("should handle limit and offset for to-many relation") + it.todo("should handle orderBy for to-many relation") + it.todo("should handle where for to-many relation") + it.todo("should handle where for to-one relation") }) describe("relationField with multiple field relations", () => { @@ -764,7 +809,7 @@ describe.concurrent("DrizzleResolverFactory", () => { // Test loading multiple relations at once const courseField = studentCourseFactory.relationField("course") const results = await Promise.all( - studentCourses.map((sc) => courseField["~meta"].resolve(sc, undefined)) + studentCourses.map((sc) => courseField["~meta"].resolve(sc, {})) ) expect(results).toMatchObject([ @@ -775,7 +820,7 @@ describe.concurrent("DrizzleResolverFactory", () => { // Test with batch loading multiple parents const studentField = studentCourseFactory.relationField("student") const studentResults = await Promise.all( - studentCourses.map((sc) => studentField["~meta"].resolve(sc, undefined)) + studentCourses.map((sc) => studentField["~meta"].resolve(sc, {})) ) expect(studentResults).toMatchObject([ @@ -822,7 +867,7 @@ describe.concurrent("DrizzleResolverFactory", () => { // Load all courses for all student-course relationships at once const allResults = await Promise.all( - studentCourses.map((sc) => executor.course(sc)) + studentCourses.map((sc) => executor.course(sc, {})) ) // Verify results include both Math and English courses @@ -836,7 +881,7 @@ describe.concurrent("DrizzleResolverFactory", () => { // Test the student relationship in same batch const studentField = studentCourseFactory.relationField("student") const studentResults = await Promise.all( - studentCourses.map((sc) => studentField["~meta"].resolve(sc, undefined)) + studentCourses.map((sc) => studentField["~meta"].resolve(sc, {})) ) // Verify results show both John and Joe @@ -943,7 +988,7 @@ describe.concurrent("DrizzleResolverFactory", () => { expect(studentCourses.length).toBe(3) const grades = await Promise.all( - studentCourses.map((sc) => gradeField["~meta"].resolve(sc, undefined)) + studentCourses.map((sc) => gradeField["~meta"].resolve(sc, {})) ) // Verify we got all the grades back @@ -980,7 +1025,7 @@ describe.concurrent("DrizzleResolverFactory", () => { }) }) - describe.concurrent("queriesResolver", () => { + describe("queriesResolver", () => { it("should be created without error", async () => { const resolver = userFactory.queriesResolver() expect(resolver).toBeDefined() diff --git a/packages/drizzle/test/resolver-mysql.spec.gql b/packages/drizzle/test/resolver-mysql.spec.gql index 52f89d25..00d71f85 100644 --- a/packages/drizzle/test/resolver-mysql.spec.gql +++ b/packages/drizzle/test/resolver-mysql.spec.gql @@ -86,7 +86,7 @@ enum OrderDirection { """A post""" type Post { - author: User + author(where: UserFilters): User authorId: Int content: String id: Int! @@ -163,7 +163,7 @@ type User { """The name of the user""" name: String! - posts: [Post!]! + posts(limit: Int, offset: Int, orderBy: PostOrderBy, where: PostFilters): [Post!]! } """A user""" diff --git a/packages/drizzle/test/resolver-mysql.spec.ts b/packages/drizzle/test/resolver-mysql.spec.ts index 2994b6d7..1c7a99c5 100644 --- a/packages/drizzle/test/resolver-mysql.spec.ts +++ b/packages/drizzle/test/resolver-mysql.spec.ts @@ -212,7 +212,7 @@ describe("resolver by mysql", () => { expect(["", ...logs, ""].join("\n")).toMatchInlineSnapshot(` " select \`id\`, \`name\` from \`users\` where \`users\`.\`name\` like ? order by \`users\`.\`name\` asc - select \`id\`, \`title\`, \`authorId\` from \`posts\` where \`posts\`.\`authorId\` in (?, ?, ?) + select \`d0\`.\`id\` as \`id\`, \`d0\`.\`name\` as \`name\`, \`d0\`.\`age\` as \`age\`, \`d0\`.\`email\` as \`email\`, \`posts\`.\`r\` as \`posts\` from \`users\` as \`d0\` left join lateral(select coalesce(json_arrayagg(json_object('id', \`id\`, 'title', \`title\`, 'authorId', \`authorId\`)), json_array()) as \`r\` from (select \`d1\`.\`id\` as \`id\`, \`d1\`.\`title\` as \`title\`, \`d1\`.\`authorId\` as \`authorId\` from \`posts\` as \`d1\` where \`d0\`.\`id\` = \`d1\`.\`authorId\`) as \`t\`) as \`posts\` on true where \`d0\`.\`id\` in (?, ?, ?) " `) }) diff --git a/packages/drizzle/test/resolver-postgres.spec.gql b/packages/drizzle/test/resolver-postgres.spec.gql index 8b641fb3..159fce5e 100644 --- a/packages/drizzle/test/resolver-postgres.spec.gql +++ b/packages/drizzle/test/resolver-postgres.spec.gql @@ -111,7 +111,7 @@ input PgTextFiltersNested { """A post""" type Post { - author: User + author(where: UserFilters): User authorId: Int content: String id: Int! @@ -209,7 +209,7 @@ type User { """The name of the user""" name: String! - posts: [Post!]! + posts(limit: Int, offset: Int, orderBy: PostOrderBy, where: PostFilters): [Post!]! } """A user""" diff --git a/packages/drizzle/test/resolver-postgres.spec.ts b/packages/drizzle/test/resolver-postgres.spec.ts index 311f8ef9..95ffc572 100644 --- a/packages/drizzle/test/resolver-postgres.spec.ts +++ b/packages/drizzle/test/resolver-postgres.spec.ts @@ -203,7 +203,7 @@ describe("resolver by postgres", () => { expect(["", ...logs, ""].join("\n")).toMatchInlineSnapshot(` " select "id", "name" from "users" where "users"."name" like $1 order by "users"."name" asc - select "id", "title", "authorId" from "posts" where "posts"."authorId" in ($1, $2, $3) + select "d0"."id" as "id", "d0"."name" as "name", "d0"."age" as "age", "d0"."email" as "email", "posts"."r" as "posts" from "users" as "d0" left join lateral(select coalesce(json_agg(row_to_json("t".*)), '[]') as "r" from (select "d1"."id" as "id", "d1"."title" as "title", "d1"."authorId" as "authorId" from "posts" as "d1" where "d0"."id" = "d1"."authorId") as "t") as "posts" on true where "d0"."id" in ($1, $2, $3) " `) }) diff --git a/packages/drizzle/test/resolver-sqlite.spec.gql b/packages/drizzle/test/resolver-sqlite.spec.gql index d1d6a86d..2674c67a 100644 --- a/packages/drizzle/test/resolver-sqlite.spec.gql +++ b/packages/drizzle/test/resolver-sqlite.spec.gql @@ -15,7 +15,7 @@ enum OrderDirection { } type Post { - author: User + author(where: UserFilters): User authorId: Int content: String id: Int! @@ -154,17 +154,75 @@ input SQLiteTextFiltersNested { notLike: String } +input SQLiteTimestampFilters { + AND: [SQLiteTimestampFiltersNested!] + NOT: SQLiteTimestampFiltersNested + OR: [SQLiteTimestampFiltersNested!] + eq: String + gt: String + gte: String + ilike: String + in: [String!] + isNotNull: Boolean + isNull: Boolean + like: String + lt: String + lte: String + ne: String + notIlike: String + notIn: [String!] + notLike: String +} + +input SQLiteTimestampFiltersNested { + eq: String + gt: String + gte: String + ilike: String + in: [String!] + isNotNull: Boolean + isNull: Boolean + like: String + lt: String + lte: String + ne: String + notIlike: String + notIn: [String!] + notLike: String +} + +input StudentToCoursesFilters { + AND: [StudentToCoursesFiltersNested!] + NOT: StudentToCoursesFiltersNested + OR: [StudentToCoursesFiltersNested!] + courseId: SQLiteIntegerFilters + createdAt: SQLiteTimestampFilters + studentId: SQLiteIntegerFilters +} + +input StudentToCoursesFiltersNested { + courseId: SQLiteIntegerFilters + createdAt: SQLiteTimestampFilters + studentId: SQLiteIntegerFilters +} + type StudentToCoursesItem { courseId: Int createdAt: String studentId: Int } +input StudentToCoursesOrderBy { + courseId: OrderDirection + createdAt: OrderDirection + studentId: OrderDirection +} + """A user""" type User { """The age of the user""" age: Int - courses: [StudentToCoursesItem!]! + courses(limit: Int, offset: Int, orderBy: StudentToCoursesOrderBy, where: StudentToCoursesFilters): [StudentToCoursesItem!]! """The email of the user""" email: String @@ -172,7 +230,7 @@ type User { """The name of the user""" name: String! - posts: [Post!]! + posts(limit: Int, offset: Int, orderBy: PostOrderBy, where: PostFilters): [Post!]! } """A user""" diff --git a/packages/drizzle/test/resolver-sqlite.spec.ts b/packages/drizzle/test/resolver-sqlite.spec.ts index fc32c967..dc0eb6ef 100644 --- a/packages/drizzle/test/resolver-sqlite.spec.ts +++ b/packages/drizzle/test/resolver-sqlite.spec.ts @@ -198,7 +198,7 @@ describe("resolver by sqlite", () => { expect(["", ...logs, ""].join("\n")).toMatchInlineSnapshot(` " select "id", "name" from "users" where "users"."name" like ? order by "users"."name" asc - select "id", "title", "authorId" from "posts" where "posts"."authorId" in (?, ?, ?) + select "d0"."id" as "id", "d0"."name" as "name", "d0"."age" as "age", "d0"."email" as "email", coalesce((select json_group_array(json_object('id', "id", 'title', "title", 'authorId', "authorId")) as "r" from (select "d1"."id" as "id", "d1"."title" as "title", "d1"."authorId" as "authorId" from "posts" as "d1" where "d0"."id" = "d1"."authorId") as "t"), jsonb_array()) as "posts" from "users" as "d0" where "d0"."id" in (?, ?, ?) " `) }) From 246c46d337a638432497a4e7af19040b4f9a405e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E2=9B=B5?= <809067559@qq.com> Date: Tue, 10 Jun 2025 23:06:24 +0800 Subject: [PATCH 39/54] feat(relations): add reviewerId and reviewedPosts to Post schema and update resolver tests - Added reviewerId field to Post schema and updated related GraphQL types. - Implemented reviewedPosts relation in the user schema for tracking posts reviewed by users. - Enhanced resolver tests to validate functionality with aliased relations and new fields. - Updated SQL queries in tests to include reviewerId for accurate data handling. --- packages/drizzle/src/factory/types.ts | 7 +- .../drizzle/test/resolver-factory.spec.ts | 69 ++++++++++++++++--- .../drizzle/test/resolver-sqlite.spec.gql | 9 +++ packages/drizzle/test/resolver-sqlite.spec.ts | 12 ++-- .../drizzle/test/schema/sqlite-relations.ts | 9 ++- packages/drizzle/test/schema/sqlite.ts | 1 + 6 files changed, 89 insertions(+), 18 deletions(-) diff --git a/packages/drizzle/src/factory/types.ts b/packages/drizzle/src/factory/types.ts index 6450c2a4..9294c080 100644 --- a/packages/drizzle/src/factory/types.ts +++ b/packages/drizzle/src/factory/types.ts @@ -388,6 +388,11 @@ export type InferRelationTable< TTable extends Table, TTargetTableName extends keyof TDatabase["_"]["relations"]["config"][TTable["_"]["name"]], -> = TDatabase["_"]["relations"]["config"][TTable["_"]["name"]]["relations"][TTargetTableName]["targetTable"] +> = Extract< + ValueOf>, + { + dbName: TDatabase["_"]["relations"]["config"][TTable["_"]["name"]][TTargetTableName]["targetTable"]["_"]["name"] + } +>["table"] type ValueOf = T[keyof T] diff --git a/packages/drizzle/test/resolver-factory.spec.ts b/packages/drizzle/test/resolver-factory.spec.ts index b421d4de..76add311 100644 --- a/packages/drizzle/test/resolver-factory.spec.ts +++ b/packages/drizzle/test/resolver-factory.spec.ts @@ -742,16 +742,6 @@ describe("DrizzleResolverFactory", () => { ]) const postsField = userFactory.relationField("posts") const answer = await postsField["~meta"].resolve(John, {}) - // const answer = ( - // await db.query.users.findFirst({ - // where: { RAW: (users) => inArray(users.id, [John.id]) }, - // with: { - // posts: { - // columns: { id: true, title: true, authorId: true }, - // }, - // }, - // }) - // )?.posts expect(answer).toMatchObject([ { authorId: John.id }, { authorId: John.id }, @@ -766,6 +756,65 @@ describe("DrizzleResolverFactory", () => { ) }) + it("should work with aliased relation", async () => { + const [Jane] = await db + .select() + .from(sqliteSchemas.users) + .where(eq(sqliteSchemas.users.name, "Jane")) + .limit(1) + const [Jill] = await db + .select() + .from(sqliteSchemas.users) + .where(eq(sqliteSchemas.users.name, "Jill")) + .limit(1) + const [Jim] = await db + .select() + .from(sqliteSchemas.users) + .where(eq(sqliteSchemas.users.name, "Jim")) + .limit(1) + + await db.insert(sqliteSchemas.posts).values([ + { authorId: Jane.id, title: "Jane's post", reviewerId: Jim.id }, + { authorId: Jane.id, title: "Jane's post 2", reviewerId: Jim.id }, + { authorId: Jill.id, title: "Jill's post", reviewerId: Jim.id }, + { authorId: Jill.id, title: "Jill's post 2", reviewerId: Jim.id }, + { authorId: Jim.id, title: "Jim's post", reviewerId: Jill.id }, + { authorId: Jim.id, title: "Jim's post 2", reviewerId: Jim.id }, + ]) + + const userFactory = drizzleResolverFactory(db, sqliteSchemas.users) + const postsField = userFactory.relationField("posts") + const reviewedPostsField = userFactory.relationField("reviewedPosts") + let answer + answer = await postsField["~meta"].resolve(Jane, {}) + expect(answer).toMatchObject([ + { title: "Jane's post" }, + { title: "Jane's post 2" }, + ]) + answer = await postsField["~meta"].resolve(Jill, {}) + expect(answer).toMatchObject([ + { title: "Jill's post" }, + { title: "Jill's post 2" }, + ]) + answer = await postsField["~meta"].resolve(Jim, {}) + expect(answer).toMatchObject([ + { title: "Jim's post" }, + { title: "Jim's post 2" }, + ]) + answer = await reviewedPostsField["~meta"].resolve(Jane, {}) + expect(answer).toMatchObject([]) + answer = await reviewedPostsField["~meta"].resolve(Jill, {}) + expect(answer).toMatchObject([{ title: "Jim's post" }]) + answer = await reviewedPostsField["~meta"].resolve(Jim, {}) + expect(answer).toMatchObject([ + { title: "Jane's post" }, + { title: "Jane's post 2" }, + { title: "Jill's post" }, + { title: "Jill's post 2" }, + { title: "Jim's post 2" }, + ]) + }) + it.todo("should handle limit and offset for to-many relation") it.todo("should handle orderBy for to-many relation") it.todo("should handle where for to-many relation") diff --git a/packages/drizzle/test/resolver-sqlite.spec.gql b/packages/drizzle/test/resolver-sqlite.spec.gql index 2674c67a..dc106cc6 100644 --- a/packages/drizzle/test/resolver-sqlite.spec.gql +++ b/packages/drizzle/test/resolver-sqlite.spec.gql @@ -19,6 +19,8 @@ type Post { authorId: Int content: String id: Int! + reviewer(where: UserFilters): User + reviewerId: Int title: String! } @@ -29,6 +31,7 @@ input PostFilters { authorId: SQLiteIntegerFilters content: SQLiteTextFilters id: SQLiteIntegerFilters + reviewerId: SQLiteIntegerFilters title: SQLiteTextFilters } @@ -36,6 +39,7 @@ input PostFiltersNested { authorId: SQLiteIntegerFilters content: SQLiteTextFilters id: SQLiteIntegerFilters + reviewerId: SQLiteIntegerFilters title: SQLiteTextFilters } @@ -43,6 +47,7 @@ input PostInsertInput { authorId: Int content: String id: Int + reviewerId: Int title: String! } @@ -62,6 +67,7 @@ input PostOrderBy { authorId: OrderDirection content: OrderDirection id: OrderDirection + reviewerId: OrderDirection title: OrderDirection } @@ -69,6 +75,7 @@ enum PostTableColumn { authorId content id + reviewerId title } @@ -76,6 +83,7 @@ input PostUpdateInput { authorId: Int content: String id: Int + reviewerId: Int title: String } @@ -231,6 +239,7 @@ type User { """The name of the user""" name: String! posts(limit: Int, offset: Int, orderBy: PostOrderBy, where: PostFilters): [Post!]! + reviewedPosts(limit: Int, offset: Int, orderBy: PostOrderBy, where: PostFilters): [Post!]! } """A user""" diff --git a/packages/drizzle/test/resolver-sqlite.spec.ts b/packages/drizzle/test/resolver-sqlite.spec.ts index dc0eb6ef..136f0f77 100644 --- a/packages/drizzle/test/resolver-sqlite.spec.ts +++ b/packages/drizzle/test/resolver-sqlite.spec.ts @@ -455,8 +455,8 @@ describe("resolver by sqlite", () => { expect(["", ...logs, ""].join("\n")).toMatchInlineSnapshot(` " select "d0"."id" as "id", "d0"."name" as "name", "d0"."age" as "age", "d0"."email" as "email" from "users" as "d0" where "d0"."name" = ? limit ? - insert into "posts" ("id", "title", "content", "authorId") values (null, ?, null, ?) returning "id", "title", "authorId" - select "d0"."id" as "id", "d0"."title" as "title", "d0"."content" as "content", "d0"."authorId" as "authorId" from "posts" as "d0" where "d0"."title" = ? limit ? + insert into "posts" ("id", "title", "content", "authorId", "reviewerId") values (null, ?, null, ?, null) returning "id", "title", "authorId" + select "d0"."id" as "id", "d0"."title" as "title", "d0"."content" as "content", "d0"."authorId" as "authorId", "d0"."reviewerId" as "reviewerId" from "posts" as "d0" where "d0"."title" = ? limit ? " `) }) @@ -493,9 +493,9 @@ describe("resolver by sqlite", () => { expect(updatedPost).toBeDefined() expect(["", ...logs, ""].join("\n")).toMatchInlineSnapshot(` " - insert into "posts" ("id", "title", "content", "authorId") values (null, ?, null, null) returning "id", "title", "content", "authorId" + insert into "posts" ("id", "title", "content", "authorId", "reviewerId") values (null, ?, null, null, null) returning "id", "title", "content", "authorId", "reviewerId" update "posts" set "title" = ? where "posts"."id" = ? returning "id", "title" - select "d0"."id" as "id", "d0"."title" as "title", "d0"."content" as "content", "d0"."authorId" as "authorId" from "posts" as "d0" where "d0"."title" = ? limit ? + select "d0"."id" as "id", "d0"."title" as "title", "d0"."content" as "content", "d0"."authorId" as "authorId", "d0"."reviewerId" as "reviewerId" from "posts" as "d0" where "d0"."title" = ? limit ? " `) }) @@ -531,9 +531,9 @@ describe("resolver by sqlite", () => { expect(deletedPost).toBeUndefined() expect(["", ...logs, ""].join("\n")).toMatchInlineSnapshot(` " - insert into "posts" ("id", "title", "content", "authorId") values (null, ?, null, null) returning "id", "title", "content", "authorId" + insert into "posts" ("id", "title", "content", "authorId", "reviewerId") values (null, ?, null, null, null) returning "id", "title", "content", "authorId", "reviewerId" delete from "posts" where "posts"."id" = ? returning "id", "title" - select "d0"."id" as "id", "d0"."title" as "title", "d0"."content" as "content", "d0"."authorId" as "authorId" from "posts" as "d0" where "d0"."id" = ? limit ? + select "d0"."id" as "id", "d0"."title" as "title", "d0"."content" as "content", "d0"."authorId" as "authorId", "d0"."reviewerId" as "reviewerId" from "posts" as "d0" where "d0"."id" = ? limit ? " `) }) diff --git a/packages/drizzle/test/schema/sqlite-relations.ts b/packages/drizzle/test/schema/sqlite-relations.ts index 651c40b9..dd8f5fd5 100644 --- a/packages/drizzle/test/schema/sqlite-relations.ts +++ b/packages/drizzle/test/schema/sqlite-relations.ts @@ -3,13 +3,20 @@ import * as schema from "./sqlite" export const relations = defineRelations(schema, (r) => ({ users: { - posts: r.many.posts(), + posts: r.many.posts({ alias: "author" }), + reviewedPosts: r.many.posts({ alias: "reviewer" }), courses: r.many.studentToCourses(), }, posts: { author: r.one.users({ from: r.posts.authorId, to: r.users.id, + alias: "author", + }), + reviewer: r.one.users({ + from: r.posts.reviewerId, + to: r.users.id, + alias: "reviewer", }), }, courses: { diff --git a/packages/drizzle/test/schema/sqlite.ts b/packages/drizzle/test/schema/sqlite.ts index 88b655f6..bc0023de 100644 --- a/packages/drizzle/test/schema/sqlite.ts +++ b/packages/drizzle/test/schema/sqlite.ts @@ -26,6 +26,7 @@ export const posts = drizzleSilk( title: t.text().notNull(), content: t.text(), authorId: t.int().references(() => users.id, { onDelete: "cascade" }), + reviewerId: t.int().references(() => users.id, { onDelete: "cascade" }), }), { name: "Post" } ) From 835a451173922776c2ee3adbf7a5a96daef0e543 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E2=9B=B5?= <809067559@qq.com> Date: Tue, 10 Jun 2025 23:42:43 +0800 Subject: [PATCH 40/54] feat(tests): implement tests for limit, offset, and orderBy in to-many relations - Added tests to validate limit and offset functionality for to-many relations in the resolver. - Implemented tests for orderBy functionality, ensuring correct sorting of related posts. - Enhanced existing test cases to ensure comprehensive coverage of relation handling. --- .../drizzle/test/resolver-factory.spec.ts | 79 ++++++++++++++++++- 1 file changed, 77 insertions(+), 2 deletions(-) diff --git a/packages/drizzle/test/resolver-factory.spec.ts b/packages/drizzle/test/resolver-factory.spec.ts index 76add311..63aaf1d0 100644 --- a/packages/drizzle/test/resolver-factory.spec.ts +++ b/packages/drizzle/test/resolver-factory.spec.ts @@ -815,8 +815,83 @@ describe("DrizzleResolverFactory", () => { ]) }) - it.todo("should handle limit and offset for to-many relation") - it.todo("should handle orderBy for to-many relation") + it("should handle limit and offset for to-many relation", async () => { + const John = await db.query.users.findFirst({ + where: { name: "John" }, + }) + if (!John) throw new Error("John not found") + + await db + .delete(sqliteSchemas.posts) + .where(eq(sqliteSchemas.posts.authorId, John.id)) + + await db.insert(sqliteSchemas.posts).values([ + { authorId: John.id, title: "Post 1" }, + { authorId: John.id, title: "Post 2" }, + { authorId: John.id, title: "Post 3" }, + { authorId: John.id, title: "Post 4" }, + { authorId: John.id, title: "Post 5" }, + ]) + + const postsField = userFactory.relationField("posts") + + let answer = await postsField["~meta"].resolve(John, { + limit: 2, + orderBy: { title: "asc" }, + }) + expect(answer).toHaveLength(2) + expect(answer.map((p) => p.title)).toEqual(["Post 1", "Post 2"]) + + answer = await postsField["~meta"].resolve(John, { + limit: 100, + offset: 2, + orderBy: { title: "asc" }, + }) + expect(answer).toHaveLength(3) + expect(answer.map((p) => p.title)).toEqual(["Post 3", "Post 4", "Post 5"]) + + answer = await postsField["~meta"].resolve(John, { + limit: 2, + offset: 1, + orderBy: { title: "asc" }, + }) + expect(answer).toHaveLength(2) + expect(answer.map((p) => p.title)).toEqual(["Post 2", "Post 3"]) + }) + + it("should handle orderBy for to-many relation", async () => { + const John = await db.query.users.findFirst({ + where: { name: "John" }, + }) + if (!John) throw new Error("John not found") + + // 先删除已有的 posts + await db + .delete(sqliteSchemas.posts) + .where(eq(sqliteSchemas.posts.authorId, John.id)) + + // 插入新的测试数据 + await db.insert(sqliteSchemas.posts).values([ + { authorId: John.id, title: "Post C" }, + { authorId: John.id, title: "Post A" }, + { authorId: John.id, title: "Post B" }, + ]) + + const postsField = userFactory.relationField("posts") + + // Test ascending order + let answer = await postsField["~meta"].resolve(John, { + orderBy: { title: "asc" }, + }) + expect(answer.map((p) => p.title)).toEqual(["Post A", "Post B", "Post C"]) + + // Test descending order + answer = await postsField["~meta"].resolve(John, { + orderBy: { title: "desc" }, + }) + expect(answer.map((p) => p.title)).toEqual(["Post C", "Post B", "Post A"]) + }) + it.todo("should handle where for to-many relation") it.todo("should handle where for to-one relation") }) From 3d95459262bb690cd0877a2619196bba43d0d6c5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E2=9B=B5?= <809067559@qq.com> Date: Tue, 10 Jun 2025 23:47:08 +0800 Subject: [PATCH 41/54] refactor(tests): clean up post deletion and insertion comments in resolver tests - Removed redundant comments regarding the deletion of existing posts and insertion of new test data. - Streamlined the test cases for verifying the order of posts, focusing on ascending and descending order checks. --- packages/drizzle/test/resolver-factory.spec.ts | 4 ---- 1 file changed, 4 deletions(-) diff --git a/packages/drizzle/test/resolver-factory.spec.ts b/packages/drizzle/test/resolver-factory.spec.ts index 63aaf1d0..8949fce1 100644 --- a/packages/drizzle/test/resolver-factory.spec.ts +++ b/packages/drizzle/test/resolver-factory.spec.ts @@ -865,12 +865,10 @@ describe("DrizzleResolverFactory", () => { }) if (!John) throw new Error("John not found") - // 先删除已有的 posts await db .delete(sqliteSchemas.posts) .where(eq(sqliteSchemas.posts.authorId, John.id)) - // 插入新的测试数据 await db.insert(sqliteSchemas.posts).values([ { authorId: John.id, title: "Post C" }, { authorId: John.id, title: "Post A" }, @@ -879,13 +877,11 @@ describe("DrizzleResolverFactory", () => { const postsField = userFactory.relationField("posts") - // Test ascending order let answer = await postsField["~meta"].resolve(John, { orderBy: { title: "asc" }, }) expect(answer.map((p) => p.title)).toEqual(["Post A", "Post B", "Post C"]) - // Test descending order answer = await postsField["~meta"].resolve(John, { orderBy: { title: "desc" }, }) From 7b407666bb700cb74bc65d6fdd276cb2295a1fd1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E2=9B=B5?= <809067559@qq.com> Date: Wed, 11 Jun 2025 01:15:51 +0800 Subject: [PATCH 42/54] feat(helper): enhance paramsAsKey to handle functions and circular references - Updated paramsAsKey function to correctly serialize functions as "[Function]". - Implemented handling for circular references in both arrays and objects, ensuring proper representation in the query string. - Added tests to validate the new functionality, including cases for functions and recursive objects. --- packages/drizzle/src/helper.ts | 34 ++++++++++++++++-- packages/drizzle/test/helper.spec.ts | 54 +++++++++++++++++++++++++++- 2 files changed, 85 insertions(+), 3 deletions(-) diff --git a/packages/drizzle/src/helper.ts b/packages/drizzle/src/helper.ts index 9c02ceac..831f7434 100644 --- a/packages/drizzle/src/helper.ts +++ b/packages/drizzle/src/helper.ts @@ -142,17 +142,36 @@ export function paramsAsKey(params: any): string { if (typeof params !== "object" || params === null) return String(params) const searchParams = new URLSearchParams() + const visited = new WeakMap() function addToParams(obj: unknown, prefix = "") { + // Handle functions + if (typeof obj === "function") { + searchParams.set(prefix, "[Function]") + return + } + if (Array.isArray(obj)) { + // Check for circular reference + if (visited.has(obj)) { + searchParams.set(prefix, `[Circular](${visited.get(obj)})`) + return + } + visited.set(obj, prefix) + obj.forEach((value, index) => { const key = prefix ? `${prefix}.${index}` : String(index) - if (value != null && typeof value === "object") { + if ( + value != null && + (typeof value === "object" || typeof value === "function") + ) { addToParams(value, key) } else { searchParams.set(key, String(value)) } }) + + visited.delete(obj) return } @@ -161,11 +180,20 @@ export function paramsAsKey(params: any): string { return } + // Check for circular reference + if (visited.has(obj)) { + searchParams.set(prefix, `[Circular](${visited.get(obj)})`) + return + } + visited.set(obj, prefix) + for (const [key, value] of Object.entries(obj)) { const newPrefix = prefix ? `${prefix}.${key}` : key if (value == null) { searchParams.set(newPrefix, "") + } else if (typeof value === "function") { + searchParams.set(newPrefix, "[Function]") } else if (Array.isArray(value)) { addToParams(value, newPrefix) } else if (typeof value === "object") { @@ -174,9 +202,11 @@ export function paramsAsKey(params: any): string { searchParams.set(newPrefix, String(value)) } } + + visited.delete(obj) } addToParams(params) searchParams.sort() - return searchParams.toString() + return decodeURI(searchParams.toString()) } diff --git a/packages/drizzle/test/helper.spec.ts b/packages/drizzle/test/helper.spec.ts index 6256ede3..8a330506 100644 --- a/packages/drizzle/test/helper.spec.ts +++ b/packages/drizzle/test/helper.spec.ts @@ -1,7 +1,7 @@ import { field, query, resolver, silk, weave } from "@gqloom/core" import type { Table } from "drizzle-orm" import type { Column } from "drizzle-orm" -import { sql } from "drizzle-orm" +import { and, eq, inArray, sql } from "drizzle-orm" import * as mysql from "drizzle-orm/mysql-core" import * as pg from "drizzle-orm/pg-core" import { GraphQLString, execute, parse } from "graphql" @@ -211,4 +211,56 @@ describe("paramsAsKey", () => { "group.0.id=1&group.0.name=foo&group.1.id=2&group.1.name=bar" ) }) + + it("should handle functions", () => { + const obj = { + foo: () => void 0, + bar: function test() { + return 1 + }, + baz: "normal", + } + expect(paramsAsKey(obj)).toBe("bar=[Function]&baz=normal&foo=[Function]") + }) + + it("should handle recursive objects", () => { + const foo: any = { + bar: {}, + normal: "value", + } + foo.bar = foo + expect(paramsAsKey(foo)).toBe("bar=[Circular]()&normal=value") + + const o1 = { + a: 1, + b: 2, + foo, + } + const o2 = { + a: 1, + b: 2, + foo, + } + expect(paramsAsKey(o1)).toBe(paramsAsKey(o2)) + expect(paramsAsKey(o1)).toBe( + "a=1&b=2&foo.bar=[Circular](foo)&foo.normal=value" + ) + }) + + it("should drizzle sql", () => { + expect(paramsAsKey(sql`1`)).toEqual(paramsAsKey(sql`1`)) + expect(paramsAsKey(sql`1`)).not.toEqual(paramsAsKey(sql`2`)) + expect(paramsAsKey(and(eq(sqliteTables.users.id, 2)))).toEqual( + paramsAsKey(and(eq(sqliteTables.users.id, 2))) + ) + expect(paramsAsKey(and(eq(sqliteTables.users.id, 2)))).not.toEqual( + paramsAsKey(and(eq(sqliteTables.users.id, 3))) + ) + expect(paramsAsKey(inArray(sqliteTables.users.id, [2]))).toEqual( + paramsAsKey(inArray(sqliteTables.users.id, [2])) + ) + expect(paramsAsKey(inArray(sqliteTables.users.id, [2]))).not.toEqual( + paramsAsKey(inArray(sqliteTables.users.id, [3])) + ) + }) }) From 7ece35a2c3e4d70dbe06ad2d28fa985cd8939a22 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E2=9B=B5?= <809067559@qq.com> Date: Wed, 11 Jun 2025 01:53:29 +0800 Subject: [PATCH 43/54] feat(relations): enhance QueryToOneFieldOptions and QueryToManyFieldOptions to support generic table types - Updated QueryToOneFieldOptions and QueryToManyFieldOptions to accept generic table types, allowing for more flexible query definitions. - Modified RelationFieldLoader and DrizzleResolverFactory to utilize the new generic options, improving type safety and functionality. - Added tests to validate the handling of where clauses for both to-many and to-one relations, ensuring correct filtering behavior. --- .../src/factory/relation-field-loader.ts | 15 +- packages/drizzle/src/factory/resolver.ts | 8 +- packages/drizzle/src/factory/types.ts | 22 ++- .../drizzle/test/resolver-factory.spec.ts | 185 +++++++++++++++++- 4 files changed, 207 insertions(+), 23 deletions(-) diff --git a/packages/drizzle/src/factory/relation-field-loader.ts b/packages/drizzle/src/factory/relation-field-loader.ts index f319b5ab..bccb835a 100644 --- a/packages/drizzle/src/factory/relation-field-loader.ts +++ b/packages/drizzle/src/factory/relation-field-loader.ts @@ -50,7 +50,7 @@ export class RelationFieldLoader extends EasyDataLoader< protected async batchLoadOne( inputs: [ parent: any, - args: QueryToOneFieldOptions, + args: QueryToOneFieldOptions, payload: ResolverPayload | undefined, ][] ): Promise { @@ -84,19 +84,23 @@ export class RelationFieldLoader extends EasyDataLoader< protected async loadOneByParent( parents: any[], - args: QueryToOneFieldOptions, + args: QueryToOneFieldOptions, columns: Partial> ): Promise { + const where = + typeof args.where === "function" + ? args.where(this.targetTable) + : args.where return await (this.db as any) .select(columns) .from(this.targetTable) - .where(and(this.whereByParent(parents), args.where)) + .where(and(this.whereByParent(parents), where)) } protected async batchLoadMany( inputs: [ parent: any, - args: QueryToManyFieldOptions, + args: QueryToManyFieldOptions, payload: ResolverPayload | undefined, ][] ): Promise { @@ -127,14 +131,13 @@ export class RelationFieldLoader extends EasyDataLoader< if (!groups) return [] const key = this.getKeyForParent(parent) const parentResult = groups.get(key) - if (!parentResult) return [] return parentResult?.[this.relationName] ?? [] }) } protected async loadManyByParent( parents: any[], - args: QueryToManyFieldOptions, + args: QueryToManyFieldOptions, columns: Partial> ): Promise { return await this.queryBuilder.findMany({ diff --git a/packages/drizzle/src/factory/resolver.ts b/packages/drizzle/src/factory/resolver.ts index 85806772..29600e97 100644 --- a/packages/drizzle/src/factory/resolver.ts +++ b/packages/drizzle/src/factory/resolver.ts @@ -422,22 +422,22 @@ export abstract class DrizzleResolverFactory< input ??= ( toMany - ? silk>( + ? silk, RelationToManyArgs>( () => targetInputFactory.relationToManyArgs(), (args) => ({ value: { - where: this.extractFilters(args.where, targetTable), + where: (t) => this.extractFilters(args.where, t), orderBy: args.orderBy, limit: args.limit, offset: args.offset, }, }) ) - : silk>( + : silk, RelationToOneArgs>( () => targetInputFactory.relationToOneArgs(), (args) => ({ value: { - where: this.extractFilters(args.where, targetTable), + where: (t) => this.extractFilters(args.where, t), }, }) ) diff --git a/packages/drizzle/src/factory/types.ts b/packages/drizzle/src/factory/types.ts index 9294c080..a610fc16 100644 --- a/packages/drizzle/src/factory/types.ts +++ b/packages/drizzle/src/factory/types.ts @@ -173,12 +173,12 @@ export interface RelationManyField< InferSelectModel[], InferSelectModel[] >, - QueryToManyFieldOptions, - GraphQLSilk + QueryToManyFieldOptions, + GraphQLSilk, TInputI> > {} -export interface QueryToManyFieldOptions { - where?: SQL +export interface QueryToManyFieldOptions { + where?: SQL | ((table: TTable) => SQL | undefined) orderBy?: Partial> limit?: number offset?: number @@ -194,12 +194,12 @@ export interface RelationOneField< InferSelectModel | null | undefined, InferSelectModel | null | undefined >, - QueryToOneFieldOptions, - GraphQLSilk + QueryToOneFieldOptions, + GraphQLSilk, TInputI> > {} -export interface QueryToOneFieldOptions { - where?: SQL +export interface QueryToOneFieldOptions { + where?: SQL | ((table: TTable) => SQL | undefined) } export type QueryFieldOptions< @@ -211,8 +211,10 @@ export type QueryFieldOptions< > = InferTableRelationalConfig< QueryBuilder >["relations"][TRelationName] extends Many - ? QueryToManyFieldOptions - : QueryToOneFieldOptions + ? QueryToManyFieldOptions< + InferRelationTable + > + : QueryToOneFieldOptions> export type InsertArrayMutation< TTable extends Table, diff --git a/packages/drizzle/test/resolver-factory.spec.ts b/packages/drizzle/test/resolver-factory.spec.ts index 8949fce1..8973e889 100644 --- a/packages/drizzle/test/resolver-factory.spec.ts +++ b/packages/drizzle/test/resolver-factory.spec.ts @@ -1,5 +1,14 @@ import { resolver } from "@gqloom/core" -import { defineRelations, eq, gte, inArray, like, lt, sql } from "drizzle-orm" +import { + defineRelations, + eq, + gte, + inArray, + isNotNull, + like, + lt, + sql, +} from "drizzle-orm" import { type LibSQLDatabase, drizzle as sqliteDrizzle, @@ -888,8 +897,178 @@ describe("DrizzleResolverFactory", () => { expect(answer.map((p) => p.title)).toEqual(["Post C", "Post B", "Post A"]) }) - it.todo("should handle where for to-many relation") - it.todo("should handle where for to-one relation") + it("should handle where for to-many relation", async () => { + const John = await db.query.users.findFirst({ + where: { name: "John" }, + }) + if (!John) throw new Error("John not found") + + await db + .delete(sqliteSchemas.posts) + .where(eq(sqliteSchemas.posts.authorId, John.id)) + + // Insert test posts with different titles and content + await db.insert(sqliteSchemas.posts).values([ + { authorId: John.id, title: "JavaScript Guide", content: "JS content" }, + { + authorId: John.id, + title: "TypeScript Tutorial", + content: "TS content", + }, + { + authorId: John.id, + title: "Python Basics", + content: "Python content", + }, + { + authorId: John.id, + title: "Java Programming", + content: "Java content", + }, + ]) + + const postsField = userFactory.relationField("posts") + + // First test: verify basic functionality without where clause works + let answer = await postsField["~meta"].resolve(John, { + orderBy: { title: "asc" }, + }) + expect(answer).toHaveLength(4) + + // Test filtering by title containing "Script" + answer = await postsField["~meta"].resolve(John, { + where: (p) => like(p.title, "%Script%"), + orderBy: { title: "asc" }, + }) + expect(answer).toHaveLength(2) + expect(answer.map((p) => p.title)).toEqual([ + "JavaScript Guide", + "TypeScript Tutorial", + ]) + + // Test filtering by content containing "content" + answer = await postsField["~meta"].resolve(John, { + where: (p) => like(p.content, "%content"), + orderBy: { title: "asc" }, + }) + expect(answer).toHaveLength(4) + + // Test filtering by exact title + answer = await postsField["~meta"].resolve(John, { + where: (p) => eq(p.title, "Python Basics"), + }) + expect(answer).toHaveLength(1) + expect(answer[0].title).toBe("Python Basics") + + // Test combining where with limit + answer = await postsField["~meta"].resolve(John, { + where: (p) => like(p.title, "%a%"), + orderBy: { title: "asc" }, + limit: 2, + }) + expect(answer).toHaveLength(2) + expect(answer.map((p) => p.title)).toEqual([ + "Java Programming", + "JavaScript Guide", + ]) + + // Test where with no matches + answer = await postsField["~meta"].resolve(John, { + where: (p) => like(p.title, "%NonExistent%"), + }) + expect(answer).toHaveLength(0) + }) + + it("should handle where for to-one relation", async () => { + const postFactory = drizzleResolverFactory(db, sqliteSchemas.posts) + const authorField = postFactory.relationField("author") + + // Create users with different names and ages + const [Alice, Bob, Charlie] = await db + .insert(sqliteSchemas.users) + .values([ + { name: "Alice", age: 25, email: "alice@example.com" }, + { name: "Bob", age: 30, email: "bob@example.com" }, + { name: "Charlie", age: 35 }, + ]) + .returning() + + // Create posts authored by these users + const [post1, post2, post3] = await db + .insert(sqliteSchemas.posts) + .values([ + { authorId: Alice.id, title: "Alice's Post" }, + { authorId: Bob.id, title: "Bob's Post" }, + { authorId: Charlie.id, title: "Charlie's Post" }, + ]) + .returning() + + try { + // Test filtering author by age >= 30 + let author = await authorField["~meta"].resolve(post1, { + where: gte(sqliteSchemas.users.age, 30), + }) + expect(author).toBeNull() // Alice is 25, should not match + + author = await authorField["~meta"].resolve(post2, { + where: gte(sqliteSchemas.users.age, 30), + }) + expect(author).toMatchObject({ name: "Bob", age: 30 }) + + author = await authorField["~meta"].resolve(post3, { + where: gte(sqliteSchemas.users.age, 30), + }) + expect(author).toMatchObject({ name: "Charlie", age: 35 }) + + // Test filtering author by email not null + author = await authorField["~meta"].resolve(post1, { + where: isNotNull(sqliteSchemas.users.email), + }) + expect(author).toMatchObject({ + name: "Alice", + email: "alice@example.com", + }) + + author = await authorField["~meta"].resolve(post3, { + where: isNotNull(sqliteSchemas.users.email), + }) + expect(author).toBeNull() // Charlie has no email + + // Test filtering author by name pattern + author = await authorField["~meta"].resolve(post2, { + where: like(sqliteSchemas.users.name, "B%"), + }) + expect(author).toMatchObject({ name: "Bob" }) + + author = await authorField["~meta"].resolve(post1, { + where: like(sqliteSchemas.users.name, "B%"), + }) + expect(author).toBeNull() // Alice doesn't match pattern + + // Test filtering with exact match + author = await authorField["~meta"].resolve(post3, { + where: eq(sqliteSchemas.users.name, "Charlie"), + }) + expect(author).toMatchObject({ name: "Charlie", age: 35 }) + + author = await authorField["~meta"].resolve(post3, { + where: eq(sqliteSchemas.users.name, "NotCharlie"), + }) + expect(author).toBeNull() + } finally { + // Clean up test data + await db + .delete(sqliteSchemas.posts) + .where( + inArray(sqliteSchemas.posts.id, [post1.id, post2.id, post3.id]) + ) + await db + .delete(sqliteSchemas.users) + .where( + inArray(sqliteSchemas.users.id, [Alice.id, Bob.id, Charlie.id]) + ) + } + }) }) describe("relationField with multiple field relations", () => { From 8f812697f8d9dc1f8e4cb3e9abd5cc8c04910a37 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E2=9B=B5?= <809067559@qq.com> Date: Wed, 11 Jun 2025 04:29:28 +0800 Subject: [PATCH 44/54] feat(helper): add getPrimaryColumns function to retrieve primary keys for tables - Introduced getPrimaryColumns function to fetch primary key columns for various table types (MySQL, PostgreSQL, SQLite). - Updated RelationFieldLoader to utilize getPrimaryColumns for improved primary key handling. - Added tests to validate the functionality of getPrimaryColumns across different database schemas. --- .../src/factory/relation-field-loader.ts | 156 +++++------------- packages/drizzle/src/helper.ts | 55 ++++++ packages/drizzle/test/helper.spec.ts | 32 ++++ .../drizzle/test/resolver-factory.spec.ts | 19 +-- packages/drizzle/test/schema/mysql.ts | 31 ++-- packages/drizzle/test/schema/postgres.ts | 31 ++-- packages/drizzle/test/schema/sqlite.ts | 61 ++++--- 7 files changed, 210 insertions(+), 175 deletions(-) diff --git a/packages/drizzle/src/factory/relation-field-loader.ts b/packages/drizzle/src/factory/relation-field-loader.ts index bccb835a..feca8408 100644 --- a/packages/drizzle/src/factory/relation-field-loader.ts +++ b/packages/drizzle/src/factory/relation-field-loader.ts @@ -4,12 +4,16 @@ import { Many, type Relation, type Table, - and, getTableColumns, getTableName, inArray, } from "drizzle-orm" -import { getSelectedColumns, inArrayMultiple, paramsAsKey } from "../helper" +import { + getPrimaryColumns, + getSelectedColumns, + inArrayMultiple, + paramsAsKey, +} from "../helper" import type { AnyQueryBuilder, BaseDatabase, @@ -32,11 +36,8 @@ export class RelationFieldLoader extends EasyDataLoader< protected sourceTable: Table, protected targetTable: Table ) { - const isMany = relation instanceof Many - super((...args) => - isMany ? this.batchLoadMany(...args) : this.batchLoadOne(...args) - ) - this.isMany = isMany + super((...args) => this.batchLoad(...args)) + this.isMany = relation instanceof Many this.fieldsLength = relation.sourceColumns.length const queryBuilder = matchQueryBuilder(this.db.query, this.sourceTable) if (!queryBuilder) { @@ -47,7 +48,7 @@ export class RelationFieldLoader extends EasyDataLoader< this.queryBuilder = queryBuilder } - protected async batchLoadOne( + protected async batchLoad( inputs: [ parent: any, args: QueryToOneFieldOptions, @@ -55,19 +56,27 @@ export class RelationFieldLoader extends EasyDataLoader< ][] ): Promise { const inputGroups = this.keyByArgs(inputs) - const resultsGroups = new Map>() await Promise.all( Array.from(inputGroups.values()).map(async (inputs) => { const args = inputs[0][1] const columns = this.columns(inputs) - const results = await this.loadOneByParent( - inputs.map((input) => input[0]), - args, - columns - ) + const parentResults = this.isMany + ? await this.loadManyByParent( + inputs.map((input) => input[0]), + args, + columns + ) + : await this.loadOneByParent( + inputs.map((input) => input[0]), + args, + columns + ) const groups = new Map( - results.map((item: any) => [this.getKeyByReference(item), item]) + parentResults.map((parent: any) => [ + this.getKeyForParent(parent), + parent, + ]) ) resultsGroups.set(paramsAsKey(args), groups) }) @@ -77,8 +86,9 @@ export class RelationFieldLoader extends EasyDataLoader< const paramsKey = paramsAsKey(args) const groups = resultsGroups.get(paramsKey) if (!groups) return null - const key = this.getKeyByField(parent) - return groups.get(key) ?? null + const key = this.getKeyForParent(parent) + const parentResult = groups.get(key) + return parentResult?.[this.relationName] ?? null }) } @@ -87,51 +97,14 @@ export class RelationFieldLoader extends EasyDataLoader< args: QueryToOneFieldOptions, columns: Partial> ): Promise { - const where = - typeof args.where === "function" - ? args.where(this.targetTable) - : args.where - return await (this.db as any) - .select(columns) - .from(this.targetTable) - .where(and(this.whereByParent(parents), where)) - } - - protected async batchLoadMany( - inputs: [ - parent: any, - args: QueryToManyFieldOptions, - payload: ResolverPayload | undefined, - ][] - ): Promise { - const inputGroups = this.keyByArgs(inputs) - const resultsGroups = new Map>() - await Promise.all( - Array.from(inputGroups.values()).map(async (inputs) => { - const args = inputs[0][1] - const columns = this.columns(inputs) - const parentResults = await this.loadManyByParent( - inputs.map((input) => input[0]), - args, - columns - ) - const groups = new Map( - parentResults.map((parent: any) => [ - this.getKeyForParent(parent), - parent, - ]) - ) - resultsGroups.set(paramsAsKey(args), groups) - }) - ) - - return inputs.map(([parent, args]) => { - const paramsKey = paramsAsKey(args) - const groups = resultsGroups.get(paramsKey) - if (!groups) return [] - const key = this.getKeyForParent(parent) - const parentResult = groups.get(key) - return parentResult?.[this.relationName] ?? [] + return await this.queryBuilder.findMany({ + where: { RAW: (table) => this.whereForParent(table, parents) }, + with: { + [this.relationName]: { + where: { RAW: args.where }, + columns: mapValue(columns, () => true), + }, + } as never, }) } @@ -173,32 +146,8 @@ export class RelationFieldLoader extends EasyDataLoader< return inputGroups } - protected whereByParent(parents: any[]) { - if (this.fieldsLength === 1) { - const values = parents.map( - (parent) => - parent[ - this.fieldKey(this.sourceTable, this.relation.sourceColumns[0]) - ] - ) - return inArray(this.relation.targetColumns[0], values) - } - const values = parents.map((parent) => - this.relation.sourceColumns.map( - (field) => parent[this.fieldKey(this.sourceTable, field)] - ) - ) - return inArrayMultiple( - this.relation.targetColumns, - values, - this.targetTable - ) - } - protected whereForParent(table: Table, parents: any[]) { - const primaryColumns = Object.entries(getTableColumns(table)).filter( - ([_, col]) => col.primary - ) + const primaryColumns = getPrimaryColumns(table) if (primaryColumns.length === 1) { const [key, column] = primaryColumns[0] return inArray( @@ -209,31 +158,12 @@ export class RelationFieldLoader extends EasyDataLoader< return inArrayMultiple( primaryColumns.map((it) => it[1]), parents.map((parent) => primaryColumns.map((it) => parent[it[0]])), - this.sourceTable + table ) } protected tablesColumnToFieldKey: Map> = new Map() - protected fieldKey(table: Table, column: Column): string { - const columnNameToFieldKeys = - this.tablesColumnToFieldKey.get(table) ?? - new Map( - Object.entries(getTableColumns(table)).map(([key, sourceColumn]) => [ - sourceColumn.name, - key, - ]) - ) - this.tablesColumnToFieldKey.set(table, columnNameToFieldKeys) - const key = columnNameToFieldKeys.get(column.name) - if (!key) { - throw new Error( - `Column ${column.name} not found in source table ${getTableName(this.sourceTable)}` - ) - } - return key - } - protected columns( inputs: [parent: any, args: any, payload: ResolverPayload | undefined][] ) { @@ -248,18 +178,6 @@ export class RelationFieldLoader extends EasyDataLoader< return { ...selectedColumns, ...referenceColumns } } - protected getKeyByField(parent: any): string { - return this.relation.sourceColumns - .map((col) => parent[this.fieldKey(this.sourceTable, col)]) - .join() - } - - protected getKeyByReference(item: any): string { - return this.relation.targetColumns - .map((col) => item[this.fieldKey(this.targetTable, col)]) - .join() - } - protected getKeyForParent(parent: any): string { return Object.entries(getTableColumns(this.sourceTable)) .filter(([_, col]) => col.primary) diff --git a/packages/drizzle/src/helper.ts b/packages/drizzle/src/helper.ts index 831f7434..bef08e2a 100644 --- a/packages/drizzle/src/helper.ts +++ b/packages/drizzle/src/helper.ts @@ -12,6 +12,18 @@ import { getTableName, sql, } from "drizzle-orm" +import { + MySqlTable, + getTableConfig as getMySQLTableConfig, +} from "drizzle-orm/mysql-core" +import { + PgTable, + getTableConfig as getPgTableConfig, +} from "drizzle-orm/pg-core" +import { + SQLiteTable, + getTableConfig as getSQLiteTableConfig, +} from "drizzle-orm/sqlite-core" import type { DrizzleFactoryInputVisibilityBehaviors, SelectedTableColumns, @@ -138,6 +150,49 @@ export function getSelectedColumns( }) as SelectedTableColumns } +const tablePrimaryKeys = new WeakMap() + +export function getPrimaryColumns( + table: Table +): [key: string, column: Column][] { + const cached = tablePrimaryKeys.get(table) + if (cached) return cached + let primaryColumns = Object.entries(getTableColumns(table)).filter( + ([_, col]) => col.primary + ) + if (primaryColumns.length === 0) { + let primaryKey + if (table instanceof SQLiteTable) { + primaryKey = getSQLiteTableConfig(table).primaryKeys[0] + } else if (table instanceof MySqlTable) { + primaryKey = getMySQLTableConfig(table).primaryKeys[0] + } else if (table instanceof PgTable) { + primaryKey = getPgTableConfig(table).primaryKeys[0] + } + const colToKey = new Map( + Object.entries(getTableColumns(table)).map(([key, col]) => [ + col.name, + key, + ]) + ) + const cols = new Map( + Object.values(getTableColumns(table)).map((col) => [col.name, col]) + ) + + if (primaryKey) { + primaryColumns = primaryKey.columns.map((col) => [ + colToKey.get(col.name)!, + cols.get(col.name)!, + ]) + } + } + if (primaryColumns.length === 0) { + throw new Error(`No primary key found for table ${getTableName(table)}`) + } + tablePrimaryKeys.set(table, primaryColumns) + return primaryColumns +} + export function paramsAsKey(params: any): string { if (typeof params !== "object" || params === null) return String(params) diff --git a/packages/drizzle/test/helper.spec.ts b/packages/drizzle/test/helper.spec.ts index 8a330506..9bb7666c 100644 --- a/packages/drizzle/test/helper.spec.ts +++ b/packages/drizzle/test/helper.spec.ts @@ -8,6 +8,7 @@ import { GraphQLString, execute, parse } from "graphql" import { beforeEach, describe, expect, it } from "vitest" import { getEnumNameByColumn, + getPrimaryColumns, getSelectedColumns, getValue, inArrayMultiple, @@ -15,6 +16,8 @@ import { paramsAsKey, } from "../src/helper" import type { DrizzleFactoryInputVisibilityBehaviors } from "../src/types" +import * as mysqlTables from "./schema/mysql" +import * as pgTables from "./schema/postgres" import * as sqliteTables from "./schema/sqlite" describe("getEnumNameByColumn", () => { @@ -264,3 +267,32 @@ describe("paramsAsKey", () => { ) }) }) + +describe("getPrimaryColumns", () => { + it("should return the primary columns for a table", () => { + expect(getPrimaryColumns(sqliteTables.users)).toEqual([ + ["id", sqliteTables.users.id], + ]) + }) + + it("should return the primary columns for a sqlite table with a composite primary key", () => { + expect(getPrimaryColumns(sqliteTables.userStarPosts)).toEqual([ + ["userId", sqliteTables.userStarPosts.userId], + ["postId", sqliteTables.userStarPosts.postId], + ]) + }) + + it("should return the primary columns for a mysql table with a composite primary key", () => { + expect(getPrimaryColumns(mysqlTables.userStarPosts)).toEqual([ + ["userId", mysqlTables.userStarPosts.userId], + ["postId", mysqlTables.userStarPosts.postId], + ]) + }) + + it("should return the primary columns for a pg table with a composite primary key", () => { + expect(getPrimaryColumns(pgTables.userStarPosts)).toEqual([ + ["userId", pgTables.userStarPosts.userId], + ["postId", pgTables.userStarPosts.postId], + ]) + }) +}) diff --git a/packages/drizzle/test/resolver-factory.spec.ts b/packages/drizzle/test/resolver-factory.spec.ts index 8973e889..d9b821bd 100644 --- a/packages/drizzle/test/resolver-factory.spec.ts +++ b/packages/drizzle/test/resolver-factory.spec.ts @@ -683,7 +683,6 @@ describe("DrizzleResolverFactory", () => { { studentId: Joe.id, courseId: english.id }, ]) .returning() - await db.insert(sqliteSchemas.studentCourseGrades).values( studentCourses.map((it) => ({ ...it, @@ -1006,23 +1005,23 @@ describe("DrizzleResolverFactory", () => { try { // Test filtering author by age >= 30 let author = await authorField["~meta"].resolve(post1, { - where: gte(sqliteSchemas.users.age, 30), + where: (u) => gte(u.age, 30), }) expect(author).toBeNull() // Alice is 25, should not match author = await authorField["~meta"].resolve(post2, { - where: gte(sqliteSchemas.users.age, 30), + where: (u) => gte(u.age, 30), }) expect(author).toMatchObject({ name: "Bob", age: 30 }) author = await authorField["~meta"].resolve(post3, { - where: gte(sqliteSchemas.users.age, 30), + where: (u) => gte(u.age, 30), }) expect(author).toMatchObject({ name: "Charlie", age: 35 }) // Test filtering author by email not null author = await authorField["~meta"].resolve(post1, { - where: isNotNull(sqliteSchemas.users.email), + where: (u) => isNotNull(u.email), }) expect(author).toMatchObject({ name: "Alice", @@ -1030,29 +1029,29 @@ describe("DrizzleResolverFactory", () => { }) author = await authorField["~meta"].resolve(post3, { - where: isNotNull(sqliteSchemas.users.email), + where: (u) => isNotNull(u.email), }) expect(author).toBeNull() // Charlie has no email // Test filtering author by name pattern author = await authorField["~meta"].resolve(post2, { - where: like(sqliteSchemas.users.name, "B%"), + where: (u) => like(u.name, "B%"), }) expect(author).toMatchObject({ name: "Bob" }) author = await authorField["~meta"].resolve(post1, { - where: like(sqliteSchemas.users.name, "B%"), + where: (u) => like(u.name, "B%"), }) expect(author).toBeNull() // Alice doesn't match pattern // Test filtering with exact match author = await authorField["~meta"].resolve(post3, { - where: eq(sqliteSchemas.users.name, "Charlie"), + where: (u) => eq(u.name, "Charlie"), }) expect(author).toMatchObject({ name: "Charlie", age: 35 }) author = await authorField["~meta"].resolve(post3, { - where: eq(sqliteSchemas.users.name, "NotCharlie"), + where: (u) => eq(u.name, "NotCharlie"), }) expect(author).toBeNull() } finally { diff --git a/packages/drizzle/test/schema/mysql.ts b/packages/drizzle/test/schema/mysql.ts index c3858941..387baadf 100644 --- a/packages/drizzle/test/schema/mysql.ts +++ b/packages/drizzle/test/schema/mysql.ts @@ -1,12 +1,12 @@ -import * as t from "drizzle-orm/mysql-core" +import { int, mysqlTable, primaryKey, text } from "drizzle-orm/mysql-core" import { drizzleSilk } from "../../src" export const users = drizzleSilk( - t.mysqlTable("users", { - id: t.int().primaryKey().autoincrement(), - name: t.text().notNull(), - age: t.int(), - email: t.text(), + mysqlTable("users", { + id: int().primaryKey().autoincrement(), + name: text().notNull(), + age: int(), + email: text(), }), { name: "User", @@ -18,11 +18,11 @@ export const users = drizzleSilk( ) export const posts = drizzleSilk( - t.mysqlTable("posts", { - id: t.int().primaryKey().autoincrement(), - title: t.text().notNull(), - content: t.text(), - authorId: t.int().references(() => users.id, { onDelete: "cascade" }), + mysqlTable("posts", { + id: int().primaryKey().autoincrement(), + title: text().notNull(), + content: text(), + authorId: int().references(() => users.id, { onDelete: "cascade" }), }), { name: "Post", @@ -32,3 +32,12 @@ export const posts = drizzleSilk( }, } ) + +export const userStarPosts = mysqlTable( + "userStarPosts", + { + userId: int("user_id").references(() => users.id), + postId: int("post_id").references(() => posts.id), + }, + (t) => [primaryKey({ columns: [t.userId, t.postId] })] +) diff --git a/packages/drizzle/test/schema/postgres.ts b/packages/drizzle/test/schema/postgres.ts index 662b8495..bb10551a 100644 --- a/packages/drizzle/test/schema/postgres.ts +++ b/packages/drizzle/test/schema/postgres.ts @@ -1,12 +1,12 @@ -import * as t from "drizzle-orm/pg-core" +import { integer, pgTable, primaryKey, serial, text } from "drizzle-orm/pg-core" import { drizzleSilk } from "../../src" export const users = drizzleSilk( - t.pgTable("users", { - id: t.serial().primaryKey(), - name: t.text().notNull(), - age: t.integer(), - email: t.text(), + pgTable("users", { + id: serial().primaryKey(), + name: text().notNull(), + age: integer(), + email: text(), }), { name: "User", @@ -18,11 +18,11 @@ export const users = drizzleSilk( ) export const posts = drizzleSilk( - t.pgTable("posts", { - id: t.serial().primaryKey(), - title: t.text().notNull(), - content: t.text(), - authorId: t.integer().references(() => users.id, { onDelete: "cascade" }), + pgTable("posts", { + id: serial().primaryKey(), + title: text().notNull(), + content: text(), + authorId: integer().references(() => users.id, { onDelete: "cascade" }), }), { name: "Post", @@ -32,3 +32,12 @@ export const posts = drizzleSilk( }, } ) + +export const userStarPosts = pgTable( + "userStarPosts", + { + userId: integer().references(() => users.id), + postId: integer().references(() => posts.id), + }, + (t) => [primaryKey({ columns: [t.userId, t.postId] })] +) diff --git a/packages/drizzle/test/schema/sqlite.ts b/packages/drizzle/test/schema/sqlite.ts index bc0023de..4a2e48ce 100644 --- a/packages/drizzle/test/schema/sqlite.ts +++ b/packages/drizzle/test/schema/sqlite.ts @@ -1,13 +1,13 @@ import { sql } from "drizzle-orm" -import * as t from "drizzle-orm/sqlite-core" +import { int, primaryKey, sqliteTable, text } from "drizzle-orm/sqlite-core" import { drizzleSilk } from "../../src" export const users = drizzleSilk( - t.sqliteTable("users", { - id: t.int().primaryKey({ autoIncrement: true }), - name: t.text().notNull(), - age: t.int(), - email: t.text(), + sqliteTable("users", { + id: int().primaryKey({ autoIncrement: true }), + name: text().notNull(), + age: int(), + email: text(), }), { name: "User", @@ -21,35 +21,48 @@ export const users = drizzleSilk( ) export const posts = drizzleSilk( - t.sqliteTable("posts", { - id: t.int().primaryKey({ autoIncrement: true }), - title: t.text().notNull(), - content: t.text(), - authorId: t.int().references(() => users.id, { onDelete: "cascade" }), - reviewerId: t.int().references(() => users.id, { onDelete: "cascade" }), + sqliteTable("posts", { + id: int().primaryKey({ autoIncrement: true }), + title: text().notNull(), + content: text(), + authorId: int().references(() => users.id, { onDelete: "cascade" }), + reviewerId: int().references(() => users.id, { onDelete: "cascade" }), }), { name: "Post" } ) +export const userStarPosts = sqliteTable( + "userStarPosts", + { + userId: int().references(() => users.id), + postId: int().references(() => posts.id), + }, + (t) => [primaryKey({ columns: [t.userId, t.postId] })] +) + export const courses = drizzleSilk( - t.sqliteTable("courses", { - id: t.int().primaryKey({ autoIncrement: true }), - name: t.text().notNull(), + sqliteTable("courses", { + id: int().primaryKey({ autoIncrement: true }), + name: text().notNull(), }) ) export const studentToCourses = drizzleSilk( - t.sqliteTable("studentToCourses", { - studentId: t.int().references(() => users.id), - courseId: t.int().references(() => courses.id), - createdAt: t.int({ mode: "timestamp" }).default(sql`(CURRENT_TIMESTAMP)`), - }) + sqliteTable( + "studentToCourses", + { + studentId: int().references(() => users.id), + courseId: int().references(() => courses.id), + createdAt: int({ mode: "timestamp" }).default(sql`(CURRENT_TIMESTAMP)`), + }, + (t) => [primaryKey({ columns: [t.studentId, t.courseId] })] + ) ) export const studentCourseGrades = drizzleSilk( - t.sqliteTable("studentCourseGrades", { - studentId: t.int().references(() => users.id), - courseId: t.int().references(() => courses.id), - grade: t.int(), + sqliteTable("studentCourseGrades", { + studentId: int().references(() => users.id), + courseId: int().references(() => courses.id), + grade: int(), }) ) From 4427d913f042bf0dd49f87820e302d8b978d069f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E2=9B=B5?= <809067559@qq.com> Date: Wed, 11 Jun 2025 04:31:09 +0800 Subject: [PATCH 45/54] refactor(RelationFieldLoader): remove unused fields for cleaner code - Removed the fieldsLength property from RelationFieldLoader as it was unnecessary for the current implementation. - Streamlined the constructor by eliminating the assignment of fieldsLength, enhancing code clarity. --- packages/drizzle/src/factory/relation-field-loader.ts | 2 -- 1 file changed, 2 deletions(-) diff --git a/packages/drizzle/src/factory/relation-field-loader.ts b/packages/drizzle/src/factory/relation-field-loader.ts index feca8408..c44ec041 100644 --- a/packages/drizzle/src/factory/relation-field-loader.ts +++ b/packages/drizzle/src/factory/relation-field-loader.ts @@ -26,7 +26,6 @@ export class RelationFieldLoader extends EasyDataLoader< any > { protected isMany: boolean - protected fieldsLength: number protected queryBuilder: AnyQueryBuilder public constructor( @@ -38,7 +37,6 @@ export class RelationFieldLoader extends EasyDataLoader< ) { super((...args) => this.batchLoad(...args)) this.isMany = relation instanceof Many - this.fieldsLength = relation.sourceColumns.length const queryBuilder = matchQueryBuilder(this.db.query, this.sourceTable) if (!queryBuilder) { throw new Error( From ca50849eece71ccde185e9daa46f735b358ca6a0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E2=9B=B5?= <809067559@qq.com> Date: Wed, 11 Jun 2025 13:44:47 +0800 Subject: [PATCH 46/54] feat(helper, relation-field-loader, tests): enhance relation field loading and add new GraphQL features - Introduced getFullPath function in helper to retrieve the full path of GraphQL resolve info. - Updated RelationFieldLoader to utilize getFullPath for improved input grouping based on GraphQL context. - Enhanced resolver tests to validate the aggregation of relation fields, including starred posts and their associated filters. - Added new GraphQL types and filters for user-starred posts in the schema, improving the flexibility of relation queries. --- .../src/factory/relation-field-loader.ts | 50 ++--- packages/drizzle/src/helper.ts | 13 ++ .../drizzle/test/resolver-factory.spec.ts | 188 +++++++++++++++++- .../drizzle/test/resolver-sqlite.spec.gql | 25 +++ .../drizzle/test/schema/sqlite-relations.ts | 16 ++ 5 files changed, 260 insertions(+), 32 deletions(-) diff --git a/packages/drizzle/src/factory/relation-field-loader.ts b/packages/drizzle/src/factory/relation-field-loader.ts index c44ec041..841dc69c 100644 --- a/packages/drizzle/src/factory/relation-field-loader.ts +++ b/packages/drizzle/src/factory/relation-field-loader.ts @@ -4,15 +4,14 @@ import { Many, type Relation, type Table, - getTableColumns, getTableName, inArray, } from "drizzle-orm" import { + getFullPath, getPrimaryColumns, getSelectedColumns, inArrayMultiple, - paramsAsKey, } from "../helper" import type { AnyQueryBuilder, @@ -49,12 +48,22 @@ export class RelationFieldLoader extends EasyDataLoader< protected async batchLoad( inputs: [ parent: any, - args: QueryToOneFieldOptions, + args: QueryToOneFieldOptions | QueryToManyFieldOptions, payload: ResolverPayload | undefined, ][] ): Promise { - const inputGroups = this.keyByArgs(inputs) - const resultsGroups = new Map>() + const inputGroups = new Map() + const groupKey = (input: (typeof inputs)[number]) => + input[2]?.info ? getFullPath(input[2]?.info) : inputs.indexOf(input) + for (let i = 0; i < inputs.length; i++) { + const input = inputs[i] + const key = groupKey(input) + const array = inputGroups.get(key) ?? [] + array.push(input) + inputGroups.set(key, array) + } + + const resultsGroups = new Map>() await Promise.all( Array.from(inputGroups.values()).map(async (inputs) => { const args = inputs[0][1] @@ -76,15 +85,14 @@ export class RelationFieldLoader extends EasyDataLoader< parent, ]) ) - resultsGroups.set(paramsAsKey(args), groups) + resultsGroups.set(groupKey(inputs[0]), groups) }) ) - return inputs.map(([parent, args]) => { - const paramsKey = paramsAsKey(args) - const groups = resultsGroups.get(paramsKey) + return inputs.map((input) => { + const groups = resultsGroups.get(groupKey(input)) if (!groups) return null - const key = this.getKeyForParent(parent) + const key = this.getKeyForParent(input[0]) const parentResult = groups.get(key) return parentResult?.[this.relationName] ?? null }) @@ -125,25 +133,6 @@ export class RelationFieldLoader extends EasyDataLoader< }) } - protected keyByArgs( - inputs: [parent: any, args: TArgs, payload: ResolverPayload | undefined][] - ): Map< - string, - [parent: any, args: TArgs, payload: ResolverPayload | undefined][] - > { - const inputGroups = new Map< - string, - [parent: any, args: TArgs, payload: ResolverPayload | undefined][] - >() - for (const input of inputs) { - const key = paramsAsKey(input[1]) - const array = inputGroups.get(key) ?? [] - array.push(input) - inputGroups.set(key, array) - } - return inputGroups - } - protected whereForParent(table: Table, parents: any[]) { const primaryColumns = getPrimaryColumns(table) if (primaryColumns.length === 1) { @@ -177,8 +166,7 @@ export class RelationFieldLoader extends EasyDataLoader< } protected getKeyForParent(parent: any): string { - return Object.entries(getTableColumns(this.sourceTable)) - .filter(([_, col]) => col.primary) + return getPrimaryColumns(this.sourceTable) .map(([key]) => parent[key]) .join() } diff --git a/packages/drizzle/src/helper.ts b/packages/drizzle/src/helper.ts index bef08e2a..ffb13257 100644 --- a/packages/drizzle/src/helper.ts +++ b/packages/drizzle/src/helper.ts @@ -24,6 +24,7 @@ import { SQLiteTable, getTableConfig as getSQLiteTableConfig, } from "drizzle-orm/sqlite-core" +import type { GraphQLResolveInfo } from "graphql" import type { DrizzleFactoryInputVisibilityBehaviors, SelectedTableColumns, @@ -193,6 +194,18 @@ export function getPrimaryColumns( return primaryColumns } +export function getFullPath(info: GraphQLResolveInfo): string { + const asKey = (key: string | number) => + typeof key === "number" ? `[n]` : key + let path = asKey(info.path.key) + let parent = info.path.prev + while (parent) { + path = `${asKey(parent.key)}.${path}` + parent = parent.prev + } + return path +} + export function paramsAsKey(params: any): string { if (typeof params !== "object" || params === null) return String(params) diff --git a/packages/drizzle/test/resolver-factory.spec.ts b/packages/drizzle/test/resolver-factory.spec.ts index d9b821bd..a998bad8 100644 --- a/packages/drizzle/test/resolver-factory.spec.ts +++ b/packages/drizzle/test/resolver-factory.spec.ts @@ -1,4 +1,4 @@ -import { resolver } from "@gqloom/core" +import { resolver, weave } from "@gqloom/core" import { defineRelations, eq, @@ -22,6 +22,7 @@ import { drizzle as pgDrizzle, } from "drizzle-orm/node-postgres" import * as sqlite from "drizzle-orm/sqlite-core" +import { execute, parse } from "graphql" import * as v from "valibot" import { afterAll, @@ -1070,6 +1071,191 @@ describe("DrizzleResolverFactory", () => { }) }) + describe("relationField with multiple relation field", () => { + let John: typeof sqliteSchemas.users.$inferSelect + let Jane: typeof sqliteSchemas.users.$inferSelect + let Jill: typeof sqliteSchemas.users.$inferSelect + beforeAll(async () => { + John = (await db.query.users.findFirst({ + where: { name: "John" }, + }))! + Jane = (await db.query.users.findFirst({ + where: { name: "Jane" }, + }))! + Jill = (await db.query.users.findFirst({ + where: { name: "Jill" }, + }))! + const posts = await db + .insert(sqliteSchemas.posts) + .values([ + { authorId: John.id, title: "John's post", reviewerId: Jane.id }, + { authorId: John.id, title: "John's post 2", reviewerId: Jane.id }, + { authorId: Jane.id, title: "Jane's post", reviewerId: John.id }, + { authorId: Jane.id, title: "Jane's post 2", reviewerId: John.id }, + { authorId: Jill.id, title: "Jill's post", reviewerId: John.id }, + { authorId: Jill.id, title: "Jill's post 2", reviewerId: John.id }, + ]) + .returning() + await db.insert(sqliteSchemas.userStarPosts).values([ + { userId: John.id, postId: posts[0].id }, + { userId: Jane.id, postId: posts[1].id }, + { userId: Jill.id, postId: posts[2].id }, + { userId: John.id, postId: posts[3].id }, + { userId: Jane.id, postId: posts[4].id }, + { userId: Jill.id, postId: posts[5].id }, + ]) + }) + + afterAll(async () => { + await db.delete(sqliteSchemas.userStarPosts) + await db.delete(sqliteSchemas.posts) + }) + + it("should aggregate relation fields", async () => { + const userFactory = drizzleResolverFactory(db, sqliteSchemas.users) + const userStarPostsFactory = drizzleResolverFactory( + db, + sqliteSchemas.userStarPosts + ) + const schema = weave( + userFactory.resolver(), + userStarPostsFactory.resolver() + ) + + const { data } = await execute({ + schema, + contextValue: {}, + document: parse(/* GraphQL */ ` + query { + users(where: { name: { in: ["John", "Jane", "Jill"] } }) { + name + posts { + title + } + reviewedPosts { + title + } + starredPosts { + post { + title + } + } + } + } + `), + }) + + expect(data).toMatchInlineSnapshot(` + { + "users": [ + { + "name": "John", + "posts": [ + { + "title": "John's post", + }, + { + "title": "John's post 2", + }, + ], + "reviewedPosts": [ + { + "title": "Jane's post", + }, + { + "title": "Jane's post 2", + }, + { + "title": "Jill's post", + }, + { + "title": "Jill's post 2", + }, + ], + "starredPosts": [ + { + "post": { + "title": "John's post", + }, + }, + { + "post": { + "title": "Jane's post 2", + }, + }, + ], + }, + { + "name": "Jane", + "posts": [ + { + "title": "Jane's post", + }, + { + "title": "Jane's post 2", + }, + ], + "reviewedPosts": [ + { + "title": "John's post", + }, + { + "title": "John's post 2", + }, + ], + "starredPosts": [ + { + "post": { + "title": "John's post 2", + }, + }, + { + "post": { + "title": "Jill's post", + }, + }, + ], + }, + { + "name": "Jill", + "posts": [ + { + "title": "Jill's post", + }, + { + "title": "Jill's post 2", + }, + ], + "reviewedPosts": [], + "starredPosts": [ + { + "post": { + "title": "Jane's post", + }, + }, + { + "post": { + "title": "Jill's post 2", + }, + }, + ], + }, + ], + } + `) + + expect(log).toMatchInlineSnapshot(` + [ + "select "id", "name" from "users" where "users"."name" in (?, ?, ?)", + "select "d0"."id" as "id", "d0"."name" as "name", "d0"."age" as "age", "d0"."email" as "email", coalesce((select json_group_array(json_object('title', "title", 'authorId', "authorId")) as "r" from (select "d1"."title" as "title", "d1"."authorId" as "authorId" from "posts" as "d1" where "d0"."id" = "d1"."authorId") as "t"), jsonb_array()) as "posts" from "users" as "d0" where "d0"."id" in (?, ?, ?)", + "select "d0"."id" as "id", "d0"."name" as "name", "d0"."age" as "age", "d0"."email" as "email", coalesce((select json_group_array(json_object('title', "title", 'reviewerId', "reviewerId")) as "r" from (select "d1"."title" as "title", "d1"."reviewerId" as "reviewerId" from "posts" as "d1" where "d0"."id" = "d1"."reviewerId") as "t"), jsonb_array()) as "reviewedPosts" from "users" as "d0" where "d0"."id" in (?, ?, ?)", + "select "d0"."id" as "id", "d0"."name" as "name", "d0"."age" as "age", "d0"."email" as "email", coalesce((select json_group_array(json_object('postId', "postId", 'userId', "userId")) as "r" from (select "d1"."postId" as "postId", "d1"."userId" as "userId" from "userStarPosts" as "d1" where "d0"."id" = "d1"."userId") as "t"), jsonb_array()) as "starredPosts" from "users" as "d0" where "d0"."id" in (?, ?, ?)", + "select "d0"."userId" as "userId", "d0"."postId" as "postId", (select json_object('title', "title", 'id', "id") as "r" from (select "d1"."title" as "title", "d1"."id" as "id" from "posts" as "d1" where "d0"."postId" = "d1"."id" limit ?) as "t") as "post" from "userStarPosts" as "d0" where ("d0"."userId", "d0"."postId") IN ((?, ?), (?, ?), (?, ?), (?, ?), (?, ?), (?, ?))", + ] + `) + }) + }) + describe("relationField with multiple field relations", () => { afterAll(async () => { await db.delete(sqliteSchemas.studentCourseGrades) diff --git a/packages/drizzle/test/resolver-sqlite.spec.gql b/packages/drizzle/test/resolver-sqlite.spec.gql index dc106cc6..9d85bda4 100644 --- a/packages/drizzle/test/resolver-sqlite.spec.gql +++ b/packages/drizzle/test/resolver-sqlite.spec.gql @@ -21,6 +21,7 @@ type Post { id: Int! reviewer(where: UserFilters): User reviewerId: Int + starredBy(limit: Int, offset: Int, orderBy: UserStarPostsOrderBy, where: UserStarPostsFilters): [UserStarPostsItem!]! title: String! } @@ -240,6 +241,7 @@ type User { name: String! posts(limit: Int, offset: Int, orderBy: PostOrderBy, where: PostFilters): [Post!]! reviewedPosts(limit: Int, offset: Int, orderBy: PostOrderBy, where: PostFilters): [Post!]! + starredPosts(limit: Int, offset: Int, orderBy: UserStarPostsOrderBy, where: UserStarPostsFilters): [UserStarPostsItem!]! } """A user""" @@ -308,6 +310,29 @@ input UserOrderBy { name: OrderDirection } +input UserStarPostsFilters { + AND: [UserStarPostsFiltersNested!] + NOT: UserStarPostsFiltersNested + OR: [UserStarPostsFiltersNested!] + postId: SQLiteIntegerFilters + userId: SQLiteIntegerFilters +} + +input UserStarPostsFiltersNested { + postId: SQLiteIntegerFilters + userId: SQLiteIntegerFilters +} + +type UserStarPostsItem { + postId: Int + userId: Int +} + +input UserStarPostsOrderBy { + postId: OrderDirection + userId: OrderDirection +} + enum UserTableColumn { """The age of the user""" age diff --git a/packages/drizzle/test/schema/sqlite-relations.ts b/packages/drizzle/test/schema/sqlite-relations.ts index dd8f5fd5..d24e56c3 100644 --- a/packages/drizzle/test/schema/sqlite-relations.ts +++ b/packages/drizzle/test/schema/sqlite-relations.ts @@ -6,6 +6,7 @@ export const relations = defineRelations(schema, (r) => ({ posts: r.many.posts({ alias: "author" }), reviewedPosts: r.many.posts({ alias: "reviewer" }), courses: r.many.studentToCourses(), + starredPosts: r.many.userStarPosts(), }, posts: { author: r.one.users({ @@ -18,6 +19,11 @@ export const relations = defineRelations(schema, (r) => ({ to: r.users.id, alias: "reviewer", }), + starredBy: r.many.userStarPosts({ + from: r.posts.id, + to: r.userStarPosts.postId, + alias: "starredBy", + }), }, courses: { students: r.many.studentToCourses(), @@ -36,4 +42,14 @@ export const relations = defineRelations(schema, (r) => ({ to: [r.studentCourseGrades.studentId, r.studentCourseGrades.courseId], }), }, + userStarPosts: { + user: r.one.users({ + from: r.userStarPosts.userId, + to: r.users.id, + }), + post: r.one.posts({ + from: r.userStarPosts.postId, + to: r.posts.id, + }), + }, })) From c5b6b914ac7e0266c17467f31a1bf37cfd304a15 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E2=9B=B5?= <809067559@qq.com> Date: Wed, 11 Jun 2025 19:49:21 +0800 Subject: [PATCH 47/54] refactor(helper, relation-field-loader, resolver): streamline functions and improve relation loading - Replaced getFullPath with getParentPath in helper for better clarity in path retrieval. - Introduced pathKey function to simplify key handling in GraphQLResolveInfo. - Updated RelationFieldLoader to utilize new path handling methods, enhancing loader efficiency. - Removed unused paramsAsKey function and related tests to clean up the codebase. - Adjusted resolver factory to integrate RelationFieldsLoader for improved relation handling. --- .../src/factory/relation-field-loader.ts | 310 +++++++++++------- packages/drizzle/src/factory/resolver.ts | 39 +-- packages/drizzle/src/helper.ts | 87 +---- packages/drizzle/test/helper.spec.ts | 128 +------- .../drizzle/test/resolver-factory.spec.ts | 4 +- packages/drizzle/test/resolver-mysql.spec.ts | 2 +- .../drizzle/test/resolver-postgres.spec.ts | 2 +- packages/drizzle/test/resolver-sqlite.spec.ts | 2 +- packages/drizzle/test/schema/sqlite.ts | 14 +- 9 files changed, 229 insertions(+), 359 deletions(-) diff --git a/packages/drizzle/src/factory/relation-field-loader.ts b/packages/drizzle/src/factory/relation-field-loader.ts index 841dc69c..2a803d4e 100644 --- a/packages/drizzle/src/factory/relation-field-loader.ts +++ b/packages/drizzle/src/factory/relation-field-loader.ts @@ -1,4 +1,10 @@ -import { EasyDataLoader, type ResolverPayload, mapValue } from "@gqloom/core" +import { + type BaseField, + LoomDataLoader, + type ResolverPayload, + getMemoizationMap, + mapValue, +} from "@gqloom/core" import { type Column, Many, @@ -8,167 +14,203 @@ import { inArray, } from "drizzle-orm" import { - getFullPath, + getParentPath, getPrimaryColumns, getSelectedColumns, inArrayMultiple, } from "../helper" -import type { - AnyQueryBuilder, - BaseDatabase, - QueryToManyFieldOptions, - QueryToOneFieldOptions, -} from "./types" - -export class RelationFieldLoader extends EasyDataLoader< - [parent: any, arg: any, payload: ResolverPayload | undefined], +import type { AnyQueryBuilder, BaseDatabase } from "./types" + +interface RelationFieldsLoaderInput { + id: number + selector: RelationFieldSelector + parent: any + args: any + payload: ResolverPayload | undefined +} + +export class RelationFieldsLoader extends LoomDataLoader< + [ + selector: (loader: RelationFieldsLoader) => RelationFieldSelector, + parent: any, + args: any, + payload: ResolverPayload | undefined, + ], any > { - protected isMany: boolean + public static getLoaderByPath( + payload: ResolverPayload | undefined, + ...args: ConstructorParameters + ): RelationFieldsLoader { + if (!payload) return new RelationFieldsLoader(...args) + const memoMap = getMemoizationMap(payload) + const parentPath = getParentPath(payload.info) + /** parentPath -> loader */ + const loaderMap: Map = + memoMap.get(RelationFieldsLoader) ?? new Map() + memoMap.set(RelationFieldsLoader, loaderMap) + const loader = + loaderMap.get(parentPath) ?? new RelationFieldsLoader(...args) + loaderMap.set(parentPath, loader) + + return loader + } + protected queryBuilder: AnyQueryBuilder + protected selectors: Map public constructor( protected db: BaseDatabase, - protected relationName: string | number | symbol, - protected relation: Relation, - protected sourceTable: Table, - protected targetTable: Table + protected table: Table ) { - super((...args) => this.batchLoad(...args)) - this.isMany = relation instanceof Many - const queryBuilder = matchQueryBuilder(this.db.query, this.sourceTable) + const queryBuilder = matchQueryBuilder(db.query, table) if (!queryBuilder) { throw new Error( - `Query builder not found for source table ${getTableName(this.sourceTable)}` + `Query builder not found for source table ${getTableName(table)}` ) } + super() + this.queryBuilder = queryBuilder + this.selectors = new Map() } protected async batchLoad( - inputs: [ + inputBatch: [ + selector: (loader: RelationFieldsLoader) => RelationFieldSelector, parent: any, - args: QueryToOneFieldOptions | QueryToManyFieldOptions, - payload: ResolverPayload | undefined, + args: any, + payload: ResolverPayload | undefined, ][] ): Promise { - const inputGroups = new Map() - const groupKey = (input: (typeof inputs)[number]) => - input[2]?.info ? getFullPath(input[2]?.info) : inputs.indexOf(input) - for (let i = 0; i < inputs.length; i++) { - const input = inputs[i] - const key = groupKey(input) - const array = inputGroups.get(key) ?? [] - array.push(input) - inputGroups.set(key, array) - } - - const resultsGroups = new Map>() - await Promise.all( - Array.from(inputGroups.values()).map(async (inputs) => { - const args = inputs[0][1] - const columns = this.columns(inputs) - const parentResults = this.isMany - ? await this.loadManyByParent( - inputs.map((input) => input[0]), - args, - columns - ) - : await this.loadOneByParent( - inputs.map((input) => input[0]), - args, - columns - ) - const groups = new Map( - parentResults.map((parent: any) => [ - this.getKeyForParent(parent), - parent, - ]) - ) - resultsGroups.set(groupKey(inputs[0]), groups) + const inputs = inputBatch.map( + ([selector, ...rest], index) => ({ + id: index, + parentPath: rest[2]?.info + ? getParentPath(rest[2]?.info) + : `isolated:${index}`, + selector: this.getSelector(selector), + parent: rest[0], + args: rest[1], + payload: rest[2], }) ) + /** field -> inputs */ + const inputGroups = new Map< + RelationFieldSelector, + RelationFieldsLoaderInput[] + >() + for (const input of inputs) { + const groupByField = inputGroups.get(input.selector) ?? [] + groupByField.push(input) + inputGroups.set(input.selector, groupByField) + } - return inputs.map((input) => { - const groups = resultsGroups.get(groupKey(input)) - if (!groups) return null - const key = this.getKeyForParent(input[0]) - const parentResult = groups.get(key) - return parentResult?.[this.relationName] ?? null - }) - } + const results = await this.loadBatchParents(inputGroups) - protected async loadOneByParent( - parents: any[], - args: QueryToOneFieldOptions, - columns: Partial> - ): Promise { - return await this.queryBuilder.findMany({ - where: { RAW: (table) => this.whereForParent(table, parents) }, - with: { - [this.relationName]: { - where: { RAW: args.where }, - columns: mapValue(columns, () => true), - }, - } as never, - }) + return inputs.map(({ id }) => results.get(id)) } - protected async loadManyByParent( - parents: any[], - args: QueryToManyFieldOptions, - columns: Partial> - ): Promise { - return await this.queryBuilder.findMany({ - where: { RAW: (table) => this.whereForParent(table, parents) }, - with: { - [this.relationName]: { - where: { RAW: args.where }, - orderBy: args.orderBy, - limit: args.limit, - offset: args.offset, - columns: mapValue(columns, () => true), - }, - } as never, + protected async loadBatchParents( + inputGroups: Map + ): Promise> { + const { parents, relations } = this.getParentRelation(inputGroups) + + const parentsWithRelationsList = await this.queryBuilder.findMany({ + where: { + RAW: (table) => whereByParent(table, Array.from(parents.values())), + }, + with: relations as never, + columns: Object.fromEntries( + getPrimaryColumns(this.table).map(([key]) => [key, true]) + ), }) + + const parentsWithRelations = new Map( + parentsWithRelationsList.map((parent) => [ + keyForParent(this.table, parent), + parent, + ]) + ) + + /** input.id -> result */ + const results = new Map() + + for (const [selector, inputs] of inputGroups) { + for (const input of inputs) { + const parent = parentsWithRelations.get( + keyForParent(this.table, input.parent) + ) + const result = + parent?.[selector.relationName] ?? (selector.isMany ? [] : null) + results.set(input.id, result) + } + } + + return results } - protected whereForParent(table: Table, parents: any[]) { - const primaryColumns = getPrimaryColumns(table) - if (primaryColumns.length === 1) { - const [key, column] = primaryColumns[0] - return inArray( - column, - parents.map((parent) => parent[key]) + protected getParentRelation( + inputGroups: Map + ) { + const parents = new Map() + const relations: any = {} + for (const [selector, inputs] of inputGroups) { + const selectedColumns = getSelectedColumns( + selector.targetTable, + inputs.map((input) => input.payload) ) + const primaryColumns = Object.fromEntries( + getPrimaryColumns(selector.targetTable) + ) + const columns = { ...selectedColumns, ...primaryColumns } + relations[selector.relationName] = selector.selectField( + inputs[0].args, + columns + ) + for (const input of inputs) { + parents.set(keyForParent(this.table, input.parent), input.parent) + } } - return inArrayMultiple( - primaryColumns.map((it) => it[1]), - parents.map((parent) => primaryColumns.map((it) => parent[it[0]])), - table - ) + return { parents, relations } } - protected tablesColumnToFieldKey: Map> = new Map() - - protected columns( - inputs: [parent: any, args: any, payload: ResolverPayload | undefined][] + protected getSelector( + selector: (loader: RelationFieldsLoader) => RelationFieldSelector ) { - const selectedColumns = getSelectedColumns( - this.targetTable, - inputs.map((input) => input[2]) - ) + const existing = this.selectors.get(selector) + if (existing) return existing + const selectorInstance = selector(this) + this.selectors.set(selector, selectorInstance) + return selectorInstance + } +} - const referenceColumns = Object.fromEntries( - this.relation.targetColumns.map((col) => [col.name, col]) - ) - return { ...selectedColumns, ...referenceColumns } +export class RelationFieldSelector { + public isMany: boolean + + public constructor( + public readonly relationName: string | number | symbol, + relation: Relation, + public readonly targetTable: Table + ) { + this.isMany = relation instanceof Many } - protected getKeyForParent(parent: any): string { - return getPrimaryColumns(this.sourceTable) - .map(([key]) => parent[key]) - .join() + public selectField(args: any, columns: Partial>) { + if (this.isMany) { + return { + where: { RAW: args.where }, + orderBy: args.orderBy, + limit: args.limit, + offset: args.offset, + columns: mapValue(columns, () => true), + } + } + return { + where: { RAW: args.where }, + columns: mapValue(columns, () => true), + } } } @@ -182,3 +224,25 @@ function matchQueryBuilder( } } } + +function keyForParent(table: Table, parent: any) { + return getPrimaryColumns(table) + .map(([key]) => parent[key]) + .join() +} + +function whereByParent(table: Table, parents: any[]) { + const primaryColumns = getPrimaryColumns(table) + if (primaryColumns.length === 1) { + const [key, column] = primaryColumns[0] + return inArray( + column, + parents.map((parent) => parent[key]) + ) + } + return inArrayMultiple( + primaryColumns.map((it) => it[1]), + parents.map((parent) => primaryColumns.map(([key]) => parent[key])), + table + ) +} diff --git a/packages/drizzle/src/factory/resolver.ts b/packages/drizzle/src/factory/resolver.ts index 29600e97..56a784fc 100644 --- a/packages/drizzle/src/factory/resolver.ts +++ b/packages/drizzle/src/factory/resolver.ts @@ -9,7 +9,6 @@ import { QueryFactoryWithResolve, type QueryOptions, capitalize, - getMemoizationMap, loom, mapValue, silk, @@ -69,7 +68,10 @@ import { type SelectSingleArgs, type UpdateArgs, } from "./input" -import { RelationFieldLoader } from "./relation-field-loader" +import { + RelationFieldSelector, + RelationFieldsLoader, +} from "./relation-field-loader" import type { BaseDatabase, CountOptions, @@ -446,18 +448,18 @@ export abstract class DrizzleResolverFactory< TInputI > - const columns = Object.entries(getTableColumns(targetTable)) + /** columnTableName -> columnTsKey */ + const columnKeys = new Map( + Object.entries(getTableColumns(targetTable)).map(([name, col]) => [ + col.name, + name, + ]) + ) const dependencies = relation.sourceColumns.map( - (col) => columns.find(([_, value]) => value === col)?.[0] ?? col.name + (col) => columnKeys.get(col.name) ?? col.name ) - const initLoader = () => - new RelationFieldLoader( - this.db, - relationName, - relation, - this.table, - targetTable - ) + const initSelector = () => + new RelationFieldSelector(relationName, relation, targetTable) return new FieldFactoryWithResolve( toMany ? output.$list() : output.$nullable(), @@ -466,13 +468,12 @@ export abstract class DrizzleResolverFactory< ...options, dependencies, resolve: (parent, input, payload) => { - const loader = (() => { - if (!payload) return initLoader() - const memoMap = getMemoizationMap(payload) - if (!memoMap.has(initLoader)) memoMap.set(initLoader, initLoader()) - return memoMap.get(initLoader) as ReturnType - })() - return loader.load([parent, input, payload]) + const loader = RelationFieldsLoader.getLoaderByPath( + payload, + this.db, + this.table + ) + return loader.load([initSelector, parent, input, payload]) }, } as FieldOptions ) diff --git a/packages/drizzle/src/helper.ts b/packages/drizzle/src/helper.ts index ffb13257..6ebc29cf 100644 --- a/packages/drizzle/src/helper.ts +++ b/packages/drizzle/src/helper.ts @@ -194,87 +194,16 @@ export function getPrimaryColumns( return primaryColumns } -export function getFullPath(info: GraphQLResolveInfo): string { - const asKey = (key: string | number) => - typeof key === "number" ? `[n]` : key - let path = asKey(info.path.key) - let parent = info.path.prev - while (parent) { - path = `${asKey(parent.key)}.${path}` - parent = parent.prev +export function getParentPath(info: GraphQLResolveInfo): string { + let path = "" + let prev = info.path.prev + while (prev) { + path = path ? `${pathKey(prev)}.${path}` : pathKey(prev) + prev = prev.prev } return path } -export function paramsAsKey(params: any): string { - if (typeof params !== "object" || params === null) return String(params) - - const searchParams = new URLSearchParams() - const visited = new WeakMap() - - function addToParams(obj: unknown, prefix = "") { - // Handle functions - if (typeof obj === "function") { - searchParams.set(prefix, "[Function]") - return - } - - if (Array.isArray(obj)) { - // Check for circular reference - if (visited.has(obj)) { - searchParams.set(prefix, `[Circular](${visited.get(obj)})`) - return - } - visited.set(obj, prefix) - - obj.forEach((value, index) => { - const key = prefix ? `${prefix}.${index}` : String(index) - if ( - value != null && - (typeof value === "object" || typeof value === "function") - ) { - addToParams(value, key) - } else { - searchParams.set(key, String(value)) - } - }) - - visited.delete(obj) - return - } - - if (typeof obj !== "object" || obj === null) { - searchParams.set(prefix, String(obj)) - return - } - - // Check for circular reference - if (visited.has(obj)) { - searchParams.set(prefix, `[Circular](${visited.get(obj)})`) - return - } - visited.set(obj, prefix) - - for (const [key, value] of Object.entries(obj)) { - const newPrefix = prefix ? `${prefix}.${key}` : key - - if (value == null) { - searchParams.set(newPrefix, "") - } else if (typeof value === "function") { - searchParams.set(newPrefix, "[Function]") - } else if (Array.isArray(value)) { - addToParams(value, newPrefix) - } else if (typeof value === "object") { - addToParams(value, newPrefix) - } else { - searchParams.set(newPrefix, String(value)) - } - } - - visited.delete(obj) - } - - addToParams(params) - searchParams.sort() - return decodeURI(searchParams.toString()) +export function pathKey(path: GraphQLResolveInfo["path"]): string { + return typeof path.key === "number" ? `[n]` : path.key } diff --git a/packages/drizzle/test/helper.spec.ts b/packages/drizzle/test/helper.spec.ts index 9bb7666c..d9dc6cac 100644 --- a/packages/drizzle/test/helper.spec.ts +++ b/packages/drizzle/test/helper.spec.ts @@ -1,7 +1,7 @@ import { field, query, resolver, silk, weave } from "@gqloom/core" import type { Table } from "drizzle-orm" import type { Column } from "drizzle-orm" -import { and, eq, inArray, sql } from "drizzle-orm" +import { sql } from "drizzle-orm" import * as mysql from "drizzle-orm/mysql-core" import * as pg from "drizzle-orm/pg-core" import { GraphQLString, execute, parse } from "graphql" @@ -13,7 +13,6 @@ import { getValue, inArrayMultiple, isColumnVisible, - paramsAsKey, } from "../src/helper" import type { DrizzleFactoryInputVisibilityBehaviors } from "../src/types" import * as mysqlTables from "./schema/mysql" @@ -143,131 +142,6 @@ describe("getSelectedColumns", () => { }) }) -describe("paramsAsKey", () => { - it("should handle null and undefined", () => { - expect(paramsAsKey(null)).toBe("null") - expect(paramsAsKey(undefined)).toBe("undefined") - }) - - it("should handle primitive values", () => { - expect(paramsAsKey(42)).toBe("42") - expect(paramsAsKey("hello")).toBe("hello") - expect(paramsAsKey(true)).toBe("true") - }) - - it("should handle flat objects", () => { - const obj = { a: 1, b: "test", c: true } - expect(paramsAsKey(obj)).toBe("a=1&b=test&c=true") - }) - - it("should handle nested objects", () => { - const obj = { - a: 1, - b: { - c: "test", - d: { - e: true, - }, - }, - } - expect(paramsAsKey(obj)).toBe("a=1&b.c=test&b.d.e=true") - }) - - it("should handle arrays", () => { - const obj = { - a: [1, 2, 3], - b: { - c: ["x", "y"], - }, - } - expect(paramsAsKey(obj)).toBe("a.0=1&a.1=2&a.2=3&b.c.0=x&b.c.1=y") - }) - - it("should generate same key for objects with different key order", () => { - const obj1 = { a: 1, b: 2, c: 3 } - const obj2 = { c: 3, a: 1, b: 2 } - expect(paramsAsKey(obj1)).toBe(paramsAsKey(obj2)) - }) - - it("should handle null values in objects", () => { - const obj = { a: null, b: undefined, c: 1 } - expect(paramsAsKey(obj)).toBe("a=&b=&c=1") - }) - - it("should handle array of objects", () => { - const arr = [ - { a: 1, b: 2 }, - { a: 3, b: 4 }, - ] - expect(paramsAsKey(arr)).toBe("0.a=1&0.b=2&1.a=3&1.b=4") - }) - - it("should handle object with array of objects", () => { - const obj = { - group: [ - { id: 1, name: "foo" }, - { id: 2, name: "bar" }, - ], - } - - expect(paramsAsKey(obj)).toBe( - "group.0.id=1&group.0.name=foo&group.1.id=2&group.1.name=bar" - ) - }) - - it("should handle functions", () => { - const obj = { - foo: () => void 0, - bar: function test() { - return 1 - }, - baz: "normal", - } - expect(paramsAsKey(obj)).toBe("bar=[Function]&baz=normal&foo=[Function]") - }) - - it("should handle recursive objects", () => { - const foo: any = { - bar: {}, - normal: "value", - } - foo.bar = foo - expect(paramsAsKey(foo)).toBe("bar=[Circular]()&normal=value") - - const o1 = { - a: 1, - b: 2, - foo, - } - const o2 = { - a: 1, - b: 2, - foo, - } - expect(paramsAsKey(o1)).toBe(paramsAsKey(o2)) - expect(paramsAsKey(o1)).toBe( - "a=1&b=2&foo.bar=[Circular](foo)&foo.normal=value" - ) - }) - - it("should drizzle sql", () => { - expect(paramsAsKey(sql`1`)).toEqual(paramsAsKey(sql`1`)) - expect(paramsAsKey(sql`1`)).not.toEqual(paramsAsKey(sql`2`)) - expect(paramsAsKey(and(eq(sqliteTables.users.id, 2)))).toEqual( - paramsAsKey(and(eq(sqliteTables.users.id, 2))) - ) - expect(paramsAsKey(and(eq(sqliteTables.users.id, 2)))).not.toEqual( - paramsAsKey(and(eq(sqliteTables.users.id, 3))) - ) - expect(paramsAsKey(inArray(sqliteTables.users.id, [2]))).toEqual( - paramsAsKey(inArray(sqliteTables.users.id, [2])) - ) - expect(paramsAsKey(inArray(sqliteTables.users.id, [2]))).not.toEqual( - paramsAsKey(inArray(sqliteTables.users.id, [3])) - ) - }) -}) - describe("getPrimaryColumns", () => { it("should return the primary columns for a table", () => { expect(getPrimaryColumns(sqliteTables.users)).toEqual([ diff --git a/packages/drizzle/test/resolver-factory.spec.ts b/packages/drizzle/test/resolver-factory.spec.ts index a998bad8..9e2c9378 100644 --- a/packages/drizzle/test/resolver-factory.spec.ts +++ b/packages/drizzle/test/resolver-factory.spec.ts @@ -1247,9 +1247,7 @@ describe("DrizzleResolverFactory", () => { expect(log).toMatchInlineSnapshot(` [ "select "id", "name" from "users" where "users"."name" in (?, ?, ?)", - "select "d0"."id" as "id", "d0"."name" as "name", "d0"."age" as "age", "d0"."email" as "email", coalesce((select json_group_array(json_object('title', "title", 'authorId', "authorId")) as "r" from (select "d1"."title" as "title", "d1"."authorId" as "authorId" from "posts" as "d1" where "d0"."id" = "d1"."authorId") as "t"), jsonb_array()) as "posts" from "users" as "d0" where "d0"."id" in (?, ?, ?)", - "select "d0"."id" as "id", "d0"."name" as "name", "d0"."age" as "age", "d0"."email" as "email", coalesce((select json_group_array(json_object('title', "title", 'reviewerId', "reviewerId")) as "r" from (select "d1"."title" as "title", "d1"."reviewerId" as "reviewerId" from "posts" as "d1" where "d0"."id" = "d1"."reviewerId") as "t"), jsonb_array()) as "reviewedPosts" from "users" as "d0" where "d0"."id" in (?, ?, ?)", - "select "d0"."id" as "id", "d0"."name" as "name", "d0"."age" as "age", "d0"."email" as "email", coalesce((select json_group_array(json_object('postId', "postId", 'userId', "userId")) as "r" from (select "d1"."postId" as "postId", "d1"."userId" as "userId" from "userStarPosts" as "d1" where "d0"."id" = "d1"."userId") as "t"), jsonb_array()) as "starredPosts" from "users" as "d0" where "d0"."id" in (?, ?, ?)", + "select "d0"."id" as "id", coalesce((select json_group_array(json_object('title', "title", 'id', "id")) as "r" from (select "d1"."title" as "title", "d1"."id" as "id" from "posts" as "d1" where "d0"."id" = "d1"."authorId") as "t"), jsonb_array()) as "posts", coalesce((select json_group_array(json_object('title', "title", 'id', "id")) as "r" from (select "d1"."title" as "title", "d1"."id" as "id" from "posts" as "d1" where "d0"."id" = "d1"."reviewerId") as "t"), jsonb_array()) as "reviewedPosts", coalesce((select json_group_array(json_object('postId', "postId", 'userId', "userId")) as "r" from (select "d1"."postId" as "postId", "d1"."userId" as "userId" from "userStarPosts" as "d1" where "d0"."id" = "d1"."userId") as "t"), jsonb_array()) as "starredPosts" from "users" as "d0" where "d0"."id" in (?, ?, ?)", "select "d0"."userId" as "userId", "d0"."postId" as "postId", (select json_object('title', "title", 'id', "id") as "r" from (select "d1"."title" as "title", "d1"."id" as "id" from "posts" as "d1" where "d0"."postId" = "d1"."id" limit ?) as "t") as "post" from "userStarPosts" as "d0" where ("d0"."userId", "d0"."postId") IN ((?, ?), (?, ?), (?, ?), (?, ?), (?, ?), (?, ?))", ] `) diff --git a/packages/drizzle/test/resolver-mysql.spec.ts b/packages/drizzle/test/resolver-mysql.spec.ts index 1c7a99c5..ed76d0f4 100644 --- a/packages/drizzle/test/resolver-mysql.spec.ts +++ b/packages/drizzle/test/resolver-mysql.spec.ts @@ -212,7 +212,7 @@ describe("resolver by mysql", () => { expect(["", ...logs, ""].join("\n")).toMatchInlineSnapshot(` " select \`id\`, \`name\` from \`users\` where \`users\`.\`name\` like ? order by \`users\`.\`name\` asc - select \`d0\`.\`id\` as \`id\`, \`d0\`.\`name\` as \`name\`, \`d0\`.\`age\` as \`age\`, \`d0\`.\`email\` as \`email\`, \`posts\`.\`r\` as \`posts\` from \`users\` as \`d0\` left join lateral(select coalesce(json_arrayagg(json_object('id', \`id\`, 'title', \`title\`, 'authorId', \`authorId\`)), json_array()) as \`r\` from (select \`d1\`.\`id\` as \`id\`, \`d1\`.\`title\` as \`title\`, \`d1\`.\`authorId\` as \`authorId\` from \`posts\` as \`d1\` where \`d0\`.\`id\` = \`d1\`.\`authorId\`) as \`t\`) as \`posts\` on true where \`d0\`.\`id\` in (?, ?, ?) + select \`d0\`.\`id\` as \`id\`, \`posts\`.\`r\` as \`posts\` from \`users\` as \`d0\` left join lateral(select coalesce(json_arrayagg(json_object('id', \`id\`, 'title', \`title\`)), json_array()) as \`r\` from (select \`d1\`.\`id\` as \`id\`, \`d1\`.\`title\` as \`title\` from \`posts\` as \`d1\` where \`d0\`.\`id\` = \`d1\`.\`authorId\`) as \`t\`) as \`posts\` on true where \`d0\`.\`id\` in (?, ?, ?) " `) }) diff --git a/packages/drizzle/test/resolver-postgres.spec.ts b/packages/drizzle/test/resolver-postgres.spec.ts index 95ffc572..291e448e 100644 --- a/packages/drizzle/test/resolver-postgres.spec.ts +++ b/packages/drizzle/test/resolver-postgres.spec.ts @@ -203,7 +203,7 @@ describe("resolver by postgres", () => { expect(["", ...logs, ""].join("\n")).toMatchInlineSnapshot(` " select "id", "name" from "users" where "users"."name" like $1 order by "users"."name" asc - select "d0"."id" as "id", "d0"."name" as "name", "d0"."age" as "age", "d0"."email" as "email", "posts"."r" as "posts" from "users" as "d0" left join lateral(select coalesce(json_agg(row_to_json("t".*)), '[]') as "r" from (select "d1"."id" as "id", "d1"."title" as "title", "d1"."authorId" as "authorId" from "posts" as "d1" where "d0"."id" = "d1"."authorId") as "t") as "posts" on true where "d0"."id" in ($1, $2, $3) + select "d0"."id" as "id", "posts"."r" as "posts" from "users" as "d0" left join lateral(select coalesce(json_agg(row_to_json("t".*)), '[]') as "r" from (select "d1"."id" as "id", "d1"."title" as "title" from "posts" as "d1" where "d0"."id" = "d1"."authorId") as "t") as "posts" on true where "d0"."id" in ($1, $2, $3) " `) }) diff --git a/packages/drizzle/test/resolver-sqlite.spec.ts b/packages/drizzle/test/resolver-sqlite.spec.ts index 136f0f77..c6a70fcb 100644 --- a/packages/drizzle/test/resolver-sqlite.spec.ts +++ b/packages/drizzle/test/resolver-sqlite.spec.ts @@ -198,7 +198,7 @@ describe("resolver by sqlite", () => { expect(["", ...logs, ""].join("\n")).toMatchInlineSnapshot(` " select "id", "name" from "users" where "users"."name" like ? order by "users"."name" asc - select "d0"."id" as "id", "d0"."name" as "name", "d0"."age" as "age", "d0"."email" as "email", coalesce((select json_group_array(json_object('id', "id", 'title', "title", 'authorId', "authorId")) as "r" from (select "d1"."id" as "id", "d1"."title" as "title", "d1"."authorId" as "authorId" from "posts" as "d1" where "d0"."id" = "d1"."authorId") as "t"), jsonb_array()) as "posts" from "users" as "d0" where "d0"."id" in (?, ?, ?) + select "d0"."id" as "id", coalesce((select json_group_array(json_object('id', "id", 'title', "title")) as "r" from (select "d1"."id" as "id", "d1"."title" as "title" from "posts" as "d1" where "d0"."id" = "d1"."authorId") as "t"), jsonb_array()) as "posts" from "users" as "d0" where "d0"."id" in (?, ?, ?) " `) }) diff --git a/packages/drizzle/test/schema/sqlite.ts b/packages/drizzle/test/schema/sqlite.ts index 4a2e48ce..45c2c0cb 100644 --- a/packages/drizzle/test/schema/sqlite.ts +++ b/packages/drizzle/test/schema/sqlite.ts @@ -60,9 +60,13 @@ export const studentToCourses = drizzleSilk( ) export const studentCourseGrades = drizzleSilk( - sqliteTable("studentCourseGrades", { - studentId: int().references(() => users.id), - courseId: int().references(() => courses.id), - grade: int(), - }) + sqliteTable( + "studentCourseGrades", + { + studentId: int().references(() => users.id), + courseId: int().references(() => courses.id), + grade: int(), + }, + (t) => [primaryKey({ columns: [t.studentId, t.courseId] })] + ) ) From 10aee8c927c0d6afa5e98ea62bb9df9ecde562f2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E2=9B=B5?= <809067559@qq.com> Date: Thu, 12 Jun 2025 01:04:02 +0800 Subject: [PATCH 48/54] refactor(resolver): unify input argument types for mutations and enhance argument transformation - Updated mutation input types across MySQL, PostgreSQL, and SQLite resolvers to use unified options interfaces. - Introduced DrizzleArgsTransformer to streamline argument transformation for various mutation types. - Refactored input handling in resolver factories to improve clarity and maintainability. - Added new interfaces for InsertArrayOptions, InsertSingleOptions, UpdateOptions, and DeleteOptions to standardize input structures. --- packages/drizzle/src/factory/input.ts | 20 +- .../drizzle/src/factory/resolver-mysql.ts | 50 ++- .../drizzle/src/factory/resolver-postgres.ts | 83 ++-- .../drizzle/src/factory/resolver-sqlite.ts | 84 ++-- packages/drizzle/src/factory/resolver.ts | 247 +----------- packages/drizzle/src/factory/transform.ts | 380 ++++++++++++++++++ packages/drizzle/src/factory/types.ts | 71 +++- .../drizzle/test/resolver-factory.spec.ts | 12 +- 8 files changed, 570 insertions(+), 377 deletions(-) create mode 100644 packages/drizzle/src/factory/transform.ts diff --git a/packages/drizzle/src/factory/input.ts b/packages/drizzle/src/factory/input.ts index ae230460..284b143b 100644 --- a/packages/drizzle/src/factory/input.ts +++ b/packages/drizzle/src/factory/input.ts @@ -562,25 +562,18 @@ export interface InsertArrayArgs { } export interface InsertArrayWithOnConflictArgs - extends InsertArrayArgs { - onConflictDoUpdate?: { - target: string[] - set?: Partial> - targetWhere?: Filters - setWhere?: Filters - } - onConflictDoNothing?: { - target?: string[] - where?: Filters - } -} + extends InsertArrayArgs, + InsertOnConflictInputArgs {} export interface InsertSingleArgs { value: InferInsertModel } export interface InsertSingleWithOnConflictArgs - extends InsertSingleArgs { + extends InsertSingleArgs, + InsertOnConflictInputArgs {} + +export interface InsertOnConflictInputArgs { onConflictDoUpdate?: { target: string[] set?: Partial> @@ -592,6 +585,7 @@ export interface InsertSingleWithOnConflictArgs where?: Filters } } + export interface UpdateArgs { where?: Filters set: Partial> diff --git a/packages/drizzle/src/factory/resolver-mysql.ts b/packages/drizzle/src/factory/resolver-mysql.ts index 9322a9fd..a4e56f73 100644 --- a/packages/drizzle/src/factory/resolver-mysql.ts +++ b/packages/drizzle/src/factory/resolver-mysql.ts @@ -21,10 +21,14 @@ import { import { DrizzleResolverFactory } from "./resolver" import type { DeleteMutationReturningSuccess, + DeleteOptions, DrizzleResolverReturningSuccess, InsertArrayMutationReturningSuccess, + InsertArrayOptions, InsertSingleMutationReturningSuccess, + InsertSingleOptions, UpdateMutationReturningSuccess, + UpdateOptions, } from "./types" export class DrizzleMySQLResolverFactory< @@ -41,22 +45,23 @@ export class DrizzleMySQLResolverFactory< input, ...options }: GraphQLFieldOptions & { - input?: GraphQLSilk, TInputI> + input?: GraphQLSilk, TInputI> middlewares?: Middleware< InsertArrayMutationReturningSuccess >[] } = {}): InsertArrayMutationReturningSuccess { input ??= silk( - () => this.inputFactory.insertArrayArgs() as GraphQLOutputType - ) + () => this.inputFactory.insertArrayArgs() as GraphQLOutputType, + this.argsTransformer.toInsertArrayOptions + ) as any return new MutationFactoryWithResolve( DrizzleMySQLResolverFactory.mutationResult, { ...options, input, - resolve: async (input) => { - await this.db.insert(this.table).values(input.values) + resolve: async (args: InsertArrayOptions) => { + await this.db.insert(this.table).values(args.values) return { isSuccess: true } }, } as MutationOptions @@ -67,21 +72,22 @@ export class DrizzleMySQLResolverFactory< input, ...options }: GraphQLFieldOptions & { - input?: GraphQLSilk, TInputI> + input?: GraphQLSilk, TInputI> middlewares?: Middleware< InsertSingleMutationReturningSuccess >[] } = {}): InsertSingleMutationReturningSuccess { input ??= silk( - () => this.inputFactory.insertSingleArgs() as GraphQLOutputType - ) + () => this.inputFactory.insertSingleArgs() as GraphQLOutputType, + this.argsTransformer.toInsertSingleOptions + ) as any return new MutationFactoryWithResolve( DrizzleMySQLResolverFactory.mutationResult, { ...options, input, - resolve: async (args) => { + resolve: async (args: InsertSingleOptions) => { await this.db.insert(this.table).values(args.value) return { isSuccess: true } }, @@ -93,20 +99,23 @@ export class DrizzleMySQLResolverFactory< input, ...options }: GraphQLFieldOptions & { - input?: GraphQLSilk, TInputI> + input?: GraphQLSilk, TInputI> middlewares?: Middleware>[] } = {}): UpdateMutationReturningSuccess { - input ??= silk(() => this.inputFactory.updateArgs() as GraphQLOutputType) + input ??= silk( + () => this.inputFactory.updateArgs() as GraphQLOutputType, + this.argsTransformer.toUpdateOptions + ) as any return new MutationFactoryWithResolve( DrizzleMySQLResolverFactory.mutationResult, { ...options, input, - resolve: async (args) => { - let query = this.db.update(this.table).set(args.set) + resolve: async (args: UpdateOptions) => { + let query: any = this.db.update(this.table).set(args.set) if (args.where) { - query = query.where(this.extractFilters(args.where)) as any + query = query.where(args.where) } await query return { isSuccess: true } @@ -119,20 +128,23 @@ export class DrizzleMySQLResolverFactory< input, ...options }: GraphQLFieldOptions & { - input?: GraphQLSilk, TInputI> + input?: GraphQLSilk middlewares?: Middleware>[] } = {}): DeleteMutationReturningSuccess { - input ??= silk(() => this.inputFactory.deleteArgs() as GraphQLOutputType) + input ??= silk( + () => this.inputFactory.deleteArgs() as GraphQLOutputType, + this.argsTransformer.toDeleteOptions + ) as any return new MutationFactoryWithResolve( DrizzleMySQLResolverFactory.mutationResult, { ...options, input, - resolve: async (args) => { - let query = this.db.delete(this.table) + resolve: async (args: DeleteOptions) => { + let query: any = this.db.delete(this.table) if (args.where) { - query = query.where(this.extractFilters(args.where)) as any + query = query.where(args.where) } await query return { isSuccess: true } diff --git a/packages/drizzle/src/factory/resolver-postgres.ts b/packages/drizzle/src/factory/resolver-postgres.ts index 52572754..a211fb84 100644 --- a/packages/drizzle/src/factory/resolver-postgres.ts +++ b/packages/drizzle/src/factory/resolver-postgres.ts @@ -20,10 +20,14 @@ import type { import { DrizzleResolverFactory } from "./resolver" import type { DeleteMutationReturningItems, + DeleteOptions, DrizzleResolverReturningItems, InsertArrayMutationReturningItems, + InsertArrayWithOnConflictOptions, InsertSingleMutationReturningItem, + InsertSingleWithOnConflictOptions, UpdateMutationReturningItems, + UpdateOptions, } from "./types" export class DrizzlePostgresResolverFactory< @@ -34,38 +38,30 @@ export class DrizzlePostgresResolverFactory< input, ...options }: GraphQLFieldOptions & { - input?: GraphQLSilk, TInputI> + input?: GraphQLSilk, TInputI> middlewares?: Middleware< InsertArrayMutationReturningItems >[] } = {}): InsertArrayMutationReturningItems { input ??= silk( () => - this.inputFactory.insertArrayWithOnConflictArgs() as GraphQLOutputType - ) + this.inputFactory.insertArrayWithOnConflictArgs() as GraphQLOutputType, + this.argsTransformer.toInsertArrayWithOnConflictOptions + ) as any return new MutationFactoryWithResolve(this.output.$list(), { ...options, input, - resolve: async (args: InsertArrayWithOnConflictArgs, payload) => { + resolve: async ( + args: InsertArrayWithOnConflictOptions, + payload + ) => { let query: any = this.db.insert(this.table).values(args.values) if (args.onConflictDoUpdate) { - query = query.onConflictDoUpdate({ - target: args.onConflictDoUpdate.target.map((t) => this.toColumn(t)), - set: args.onConflictDoUpdate.set, - targetWhere: this.extractFilters( - args.onConflictDoUpdate.targetWhere - ), - setWhere: this.extractFilters(args.onConflictDoUpdate.setWhere), - }) + query = query.onConflictDoUpdate(args.onConflictDoUpdate) } if (args.onConflictDoNothing) { - query = query.onConflictDoNothing({ - target: args.onConflictDoNothing.target?.map((t) => - this.toColumn(t) - ), - where: this.extractFilters(args.onConflictDoNothing.where), - }) + query = query.onConflictDoNothing(args.onConflictDoNothing) } return await query.returning(getSelectedColumns(this.table, payload)) }, @@ -78,41 +74,30 @@ export class DrizzlePostgresResolverFactory< input, ...options }: GraphQLFieldOptions & { - input?: GraphQLSilk, TInputI> + input?: GraphQLSilk, TInputI> middlewares?: Middleware< InsertSingleMutationReturningItem >[] } = {}): InsertSingleMutationReturningItem { input ??= silk( () => - this.inputFactory.insertSingleWithOnConflictArgs() as GraphQLOutputType - ) + this.inputFactory.insertSingleWithOnConflictArgs() as GraphQLOutputType, + this.argsTransformer.toInsertSingleWithOnConflictOptions + ) as any return new MutationFactoryWithResolve(this.output.$nullable(), { ...options, input, resolve: async ( - args: InsertSingleWithOnConflictArgs, + args: InsertSingleWithOnConflictOptions, payload ) => { let query: any = this.db.insert(this.table).values(args.value) if (args.onConflictDoUpdate) { - query = query.onConflictDoUpdate({ - target: args.onConflictDoUpdate.target.map((t) => this.toColumn(t)), - set: args.onConflictDoUpdate.set, - targetWhere: this.extractFilters( - args.onConflictDoUpdate.targetWhere - ), - setWhere: this.extractFilters(args.onConflictDoUpdate.setWhere), - }) + query = query.onConflictDoUpdate(args.onConflictDoUpdate) } if (args.onConflictDoNothing) { - query = query.onConflictDoNothing({ - target: args.onConflictDoNothing.target?.map((t) => - this.toColumn(t) - ), - where: this.extractFilters(args.onConflictDoNothing.where), - }) + query = query.onConflictDoNothing(args.onConflictDoNothing) } const result = await query.returning( getSelectedColumns(this.table, payload) @@ -126,18 +111,21 @@ export class DrizzlePostgresResolverFactory< input, ...options }: GraphQLFieldOptions & { - input?: GraphQLSilk, TInputI> + input?: GraphQLSilk, TInputI> middlewares?: Middleware>[] } = {}): UpdateMutationReturningItems { - input ??= silk(() => this.inputFactory.updateArgs() as GraphQLOutputType) + input ??= silk( + () => this.inputFactory.updateArgs() as GraphQLOutputType, + this.argsTransformer.toUpdateOptions + ) as any return new MutationFactoryWithResolve(this.output.$list(), { ...options, input, - resolve: async (args, payload) => { - const query = this.db.update(this.table).set(args.set) + resolve: async (args: UpdateOptions, payload) => { + let query: any = this.db.update(this.table).set(args.set) if (args.where) { - query.where(this.extractFilters(args.where)) + query = query.where(args.where) } return await query.returning(getSelectedColumns(this.table, payload)) }, @@ -148,18 +136,21 @@ export class DrizzlePostgresResolverFactory< input, ...options }: GraphQLFieldOptions & { - input?: GraphQLSilk, TInputI> + input?: GraphQLSilk middlewares?: Middleware>[] } = {}): DeleteMutationReturningItems { - input ??= silk(() => this.inputFactory.deleteArgs() as GraphQLOutputType) + input ??= silk( + () => this.inputFactory.deleteArgs() as GraphQLOutputType, + this.argsTransformer.toDeleteOptions + ) as any return new MutationFactoryWithResolve(this.output.$list(), { ...options, input, - resolve: async (args, payload) => { - const query = this.db.delete(this.table) + resolve: async (args: DeleteOptions, payload) => { + let query: any = this.db.delete(this.table) if (args.where) { - query.where(this.extractFilters(args.where)) + query = query.where(args.where) } return await query.returning(getSelectedColumns(this.table, payload)) }, diff --git a/packages/drizzle/src/factory/resolver-sqlite.ts b/packages/drizzle/src/factory/resolver-sqlite.ts index b8d91b0b..fb8dcb40 100644 --- a/packages/drizzle/src/factory/resolver-sqlite.ts +++ b/packages/drizzle/src/factory/resolver-sqlite.ts @@ -20,10 +20,14 @@ import type { import { DrizzleResolverFactory } from "./resolver" import type { DeleteMutationReturningItems, + DeleteOptions, DrizzleResolverReturningItems, InsertArrayMutationReturningItems, + InsertArrayWithOnConflictOptions, InsertSingleMutationReturningItem, + InsertSingleWithOnConflictOptions, UpdateMutationReturningItems, + UpdateOptions, } from "./types" export class DrizzleSQLiteResolverFactory< @@ -34,38 +38,30 @@ export class DrizzleSQLiteResolverFactory< input, ...options }: GraphQLFieldOptions & { - input?: GraphQLSilk, TInputI> + input?: GraphQLSilk, TInputI> middlewares?: Middleware< InsertArrayMutationReturningItems >[] } = {}): InsertArrayMutationReturningItems { input ??= silk( () => - this.inputFactory.insertArrayWithOnConflictArgs() as GraphQLOutputType - ) + this.inputFactory.insertArrayWithOnConflictArgs() as GraphQLOutputType, + this.argsTransformer.toInsertArrayWithOnConflictOptions + ) as any return new MutationFactoryWithResolve(this.output.$list(), { ...options, input, - resolve: async (args: InsertArrayWithOnConflictArgs, payload) => { + resolve: async ( + args: InsertArrayWithOnConflictOptions, + payload + ) => { let query: any = this.db.insert(this.table).values(args.values) if (args.onConflictDoUpdate) { - query = query.onConflictDoUpdate({ - target: args.onConflictDoUpdate.target.map((t) => this.toColumn(t)), - set: args.onConflictDoUpdate.set, - targetWhere: this.extractFilters( - args.onConflictDoUpdate.targetWhere - ), - setWhere: this.extractFilters(args.onConflictDoUpdate.setWhere), - }) + query = query.onConflictDoUpdate(args.onConflictDoUpdate) } if (args.onConflictDoNothing) { - query = query.onConflictDoNothing({ - target: args.onConflictDoNothing.target?.map((t) => - this.toColumn(t) - ), - where: this.extractFilters(args.onConflictDoNothing.where), - }) + query = query.onConflictDoNothing(args.onConflictDoNothing) } return await query.returning(getSelectedColumns(this.table, payload)) }, @@ -78,40 +74,30 @@ export class DrizzleSQLiteResolverFactory< input, ...options }: GraphQLFieldOptions & { - input?: GraphQLSilk, TInputI> + input?: GraphQLSilk, TInputI> middlewares?: Middleware< InsertSingleMutationReturningItem >[] } = {}): InsertSingleMutationReturningItem { input ??= silk( () => - this.inputFactory.insertSingleWithOnConflictArgs() as GraphQLOutputType - ) + this.inputFactory.insertSingleWithOnConflictArgs() as GraphQLOutputType, + this.argsTransformer.toInsertSingleWithOnConflictOptions + ) as any + return new MutationFactoryWithResolve(this.output.$nullable(), { ...options, input, resolve: async ( - args: InsertSingleWithOnConflictArgs, + args: InsertSingleWithOnConflictOptions, payload ) => { let query: any = this.db.insert(this.table).values(args.value) if (args.onConflictDoUpdate) { - query = query.onConflictDoUpdate({ - target: args.onConflictDoUpdate.target.map((t) => this.toColumn(t)), - set: args.onConflictDoUpdate.set, - targetWhere: this.extractFilters( - args.onConflictDoUpdate.targetWhere - ), - setWhere: this.extractFilters(args.onConflictDoUpdate.setWhere), - }) + query = query.onConflictDoUpdate(args.onConflictDoUpdate) } if (args.onConflictDoNothing) { - query = query.onConflictDoNothing({ - target: args.onConflictDoNothing.target?.map((t) => - this.toColumn(t) - ), - where: this.extractFilters(args.onConflictDoNothing.where), - }) + query = query.onConflictDoNothing(args.onConflictDoNothing) } return ( await query.returning(getSelectedColumns(this.table, payload)) @@ -124,18 +110,21 @@ export class DrizzleSQLiteResolverFactory< input, ...options }: GraphQLFieldOptions & { - input?: GraphQLSilk, TInputI> + input?: GraphQLSilk, TInputI> middlewares?: Middleware>[] } = {}): UpdateMutationReturningItems { - input ??= silk(() => this.inputFactory.updateArgs() as GraphQLOutputType) + input ??= silk( + () => this.inputFactory.updateArgs() as GraphQLOutputType, + this.argsTransformer.toUpdateOptions + ) as any return new MutationFactoryWithResolve(this.output.$list(), { ...options, input, - resolve: async (args, payload) => { - const query = this.db.update(this.table).set(args.set) + resolve: async (args: UpdateOptions, payload) => { + let query: any = this.db.update(this.table).set(args.set) if (args.where) { - query.where(this.extractFilters(args.where)) + query = query.where(args.where) } return await query.returning(getSelectedColumns(this.table, payload)) }, @@ -146,18 +135,21 @@ export class DrizzleSQLiteResolverFactory< input, ...options }: GraphQLFieldOptions & { - input?: GraphQLSilk, TInputI> + input?: GraphQLSilk middlewares?: Middleware>[] } = {}): DeleteMutationReturningItems { - input ??= silk(() => this.inputFactory.deleteArgs() as GraphQLOutputType) + input ??= silk( + () => this.inputFactory.deleteArgs() as GraphQLOutputType, + this.argsTransformer.toDeleteOptions + ) as any return new MutationFactoryWithResolve(this.output.$list(), { ...options, input, - resolve: async (args, payload) => { - const query = this.db.delete(this.table) + resolve: async (args: DeleteOptions, payload) => { + let query: any = this.db.delete(this.table) if (args.where) { - query.where(this.extractFilters(args.where)) + query = query.where(args.where) } return await query.returning(getSelectedColumns(this.table, payload)) }, diff --git a/packages/drizzle/src/factory/resolver.ts b/packages/drizzle/src/factory/resolver.ts index 56a784fc..8be2db2b 100644 --- a/packages/drizzle/src/factory/resolver.ts +++ b/packages/drizzle/src/factory/resolver.ts @@ -14,35 +14,14 @@ import { silk, } from "@gqloom/core" import { - type Column, type InferSelectModel, Many, type Relation, - type SQL, Table, type TableRelationalConfig, type TablesRelationalConfig, - and, - asc, - desc, - eq, getTableColumns, getTableName, - gt, - gte, - ilike, - inArray, - isNotNull, - isNull, - like, - lt, - lte, - ne, - not, - notIlike, - notInArray, - notLike, - or, } from "drizzle-orm" import { GraphQLInt, GraphQLNonNull } from "graphql" import { @@ -53,12 +32,9 @@ import { } from ".." import { getSelectedColumns } from "../helper" import { - type ColumnFilters, type CountArgs, type DeleteArgs, DrizzleInputFactory, - type Filters, - type FiltersCore, type InsertArrayArgs, type InsertSingleArgs, type RelationArgs, @@ -72,16 +48,20 @@ import { RelationFieldSelector, RelationFieldsLoader, } from "./relation-field-loader" +import { DrizzleArgsTransformer } from "./transform" import type { BaseDatabase, CountOptions, CountQuery, DeleteMutation, + DeleteOptions, DrizzleQueriesResolver, InferTableName, InferTableRelationalConfig, InsertArrayMutation, + InsertArrayOptions, InsertSingleMutation, + InsertSingleOptions, QueryBuilder, QueryFieldOptions, QueryToManyFieldOptions, @@ -92,6 +72,7 @@ import type { SelectSingleOptions, SelectSingleQuery, UpdateMutation, + UpdateOptions, } from "./types" export abstract class DrizzleResolverFactory< @@ -100,6 +81,7 @@ export abstract class DrizzleResolverFactory< > { protected readonly inputFactory: DrizzleInputFactory protected readonly tableName: InferTableName + protected readonly argsTransformer: DrizzleArgsTransformer public constructor( protected readonly db: TDatabase, protected readonly table: TTable, @@ -107,6 +89,7 @@ export abstract class DrizzleResolverFactory< ) { this.inputFactory = new DrizzleInputFactory(table, options) this.tableName = getTableName(table) + this.argsTransformer = new DrizzleArgsTransformer(table) } private _output?: TableSilk @@ -124,14 +107,7 @@ export abstract class DrizzleResolverFactory< } = {}): SelectArrayQuery { input ??= silk>( () => this.inputFactory.selectArrayArgs(), - (args) => ({ - value: { - where: this.extractFilters(args.where, this.table), - orderBy: this.extractOrderBy(args.orderBy), - limit: args.limit, - offset: args.offset, - }, - }) + this.argsTransformer.toSelectArrayOptions ) as GraphQLSilk return new QueryFactoryWithResolve(this.output.$list(), { @@ -159,13 +135,7 @@ export abstract class DrizzleResolverFactory< } = {}): SelectSingleQuery { input ??= silk>( () => this.inputFactory.selectSingleArgs(), - (args) => ({ - value: { - where: this.extractFilters(args.where, this.table), - orderBy: this.extractOrderBy(args.orderBy), - offset: args.offset, - }, - }) + this.argsTransformer.toSelectSingleOptions ) as GraphQLSilk return new QueryFactoryWithResolve(this.output.$nullable(), { @@ -193,9 +163,7 @@ export abstract class DrizzleResolverFactory< } = {}): CountQuery { input ??= silk>( () => this.inputFactory.countArgs(), - (args) => ({ - value: { where: this.extractFilters(args.where, this.table) }, - }) + this.argsTransformer.toCountOptions ) as GraphQLSilk return new QueryFactoryWithResolve(silk(new GraphQLNonNull(GraphQLInt)), { @@ -207,178 +175,6 @@ export abstract class DrizzleResolverFactory< } as QueryOptions) } - protected extractOrderBy( - orders?: SelectArrayArgs["orderBy"] - ): SQL[] | undefined { - if (orders == null) return - const answer: SQL[] = [] - const columns = getTableColumns(this.table) - for (const [column, direction] of Object.entries(orders)) { - if (!direction) continue - if (column in columns) { - answer.push( - direction === "asc" ? asc(columns[column]) : desc(columns[column]) - ) - } - } - return answer - } - - protected extractFilters( - filters: Filters | undefined, - table?: any - ): SQL | undefined { - if (filters == null) return - table ??= this.table - - const entries = Object.entries(filters as FiltersCore) - const variants: (SQL | undefined)[] = [] - - for (const [columnName, operators] of entries) { - if (operators == null) continue - - if (columnName === "OR" && Array.isArray(operators)) { - const orConditions: SQL[] = [] - for (const variant of operators) { - const extracted = this.extractFilters(variant, table) - if (extracted) orConditions.push(extracted) - } - if (orConditions.length > 0) { - variants.push(or(...orConditions)) - } - continue - } - - if (columnName === "AND" && Array.isArray(operators)) { - const andConditions: SQL[] = [] - for (const variant of operators) { - const extracted = this.extractFilters(variant, table) - if (extracted) andConditions.push(extracted) - } - if (andConditions.length > 0) { - variants.push(and(...andConditions)) - } - continue - } - - if (columnName === "NOT" && operators) { - const extracted = this.extractFilters(operators, table) - if (extracted) { - variants.push(not(extracted)) - } - continue - } - - const column = getTableColumns(table)[columnName]! - const extractedColumn = this.extractFiltersColumn( - column, - columnName, - operators, - table - ) - if (extractedColumn) variants.push(extractedColumn) - } - - return and(...variants) - } - - protected extractFiltersColumn( - column: TColumn, - columnName: string, - operators: ColumnFilters, - table?: any - ): SQL | undefined { - const entries = Object.entries(operators) - - const variants: (SQL | undefined)[] = [] - const binaryOperators = { eq, ne, gt, gte, lt, lte } - const textOperators = { like, notLike, ilike, notIlike } - const arrayOperators = { in: inArray, notIn: notInArray } - const nullOperators = { isNull, isNotNull } - - const tableColumn = table ? table[columnName] : column - - if (operators.OR) { - const orVariants = [] as SQL[] - - for (const variant of operators.OR) { - const extracted = this.extractFiltersColumn( - column, - columnName, - variant, - table - ) - - if (extracted) orVariants.push(extracted) - } - - variants.push(or(...orVariants)) - } - - if (operators.AND) { - const andVariants = [] as SQL[] - - for (const variant of operators.AND) { - const extracted = this.extractFiltersColumn( - column, - columnName, - variant, - table - ) - - if (extracted) andVariants.push(extracted) - } - - variants.push(and(...andVariants)) - } - - if (operators.NOT) { - const extracted = this.extractFiltersColumn( - column, - columnName, - operators.NOT, - table - ) - if (extracted) { - variants.push(not(extracted)) - } - } - - for (const [operatorName, operatorValue] of entries) { - if (operatorValue === null || operatorValue === false) continue - - if (operatorName in binaryOperators) { - const operator = - binaryOperators[operatorName as keyof typeof binaryOperators] - variants.push(operator(tableColumn, operatorValue)) - } else if (operatorName in textOperators) { - const operator = - textOperators[operatorName as keyof typeof textOperators] - variants.push(operator(tableColumn, operatorValue)) - } else if (operatorName in arrayOperators) { - const operator = - arrayOperators[operatorName as keyof typeof arrayOperators] - variants.push(operator(tableColumn, operatorValue)) - } else if (operatorName in nullOperators) { - const operator = - nullOperators[operatorName as keyof typeof nullOperators] - if (operatorValue === true) variants.push(operator(tableColumn)) - } - } - - return and(...variants) - } - - protected toColumn(columnName: string) { - const column = getTableColumns(this.table)[columnName] - if (!column) { - throw new Error( - `Column ${columnName} not found in table ${this.tableName}` - ) - } - return column - } - public relationField< TRelationName extends keyof InferTableRelationalConfig< QueryBuilder @@ -426,22 +222,11 @@ export abstract class DrizzleResolverFactory< toMany ? silk, RelationToManyArgs>( () => targetInputFactory.relationToManyArgs(), - (args) => ({ - value: { - where: (t) => this.extractFilters(args.where, t), - orderBy: args.orderBy, - limit: args.limit, - offset: args.offset, - }, - }) + this.argsTransformer.toQueryToManyFieldOptions ) : silk, RelationToOneArgs>( () => targetInputFactory.relationToOneArgs(), - (args) => ({ - value: { - where: (t) => this.extractFilters(args.where, t), - }, - }) + this.argsTransformer.toQueryToOneFieldOptions ) ) as GraphQLSilk< QueryFieldOptions, @@ -539,28 +324,28 @@ export abstract class DrizzleResolverFactory< public abstract insertArrayMutation>( options?: GraphQLFieldOptions & { - input?: GraphQLSilk, TInputI> + input?: GraphQLSilk, TInputI> middlewares?: Middleware[] } ): InsertArrayMutation public abstract insertSingleMutation>( options?: GraphQLFieldOptions & { - input?: GraphQLSilk, TInputI> + input?: GraphQLSilk, TInputI> middlewares?: Middleware[] } ): InsertSingleMutation public abstract updateMutation>( options?: GraphQLFieldOptions & { - input?: GraphQLSilk, TInputI> + input?: GraphQLSilk, TInputI> middlewares?: Middleware[] } ): UpdateMutation public abstract deleteMutation>( options?: GraphQLFieldOptions & { - input?: GraphQLSilk, TInputI> + input?: GraphQLSilk middlewares?: Middleware[] } ): DeleteMutation diff --git a/packages/drizzle/src/factory/transform.ts b/packages/drizzle/src/factory/transform.ts new file mode 100644 index 00000000..c1600ad1 --- /dev/null +++ b/packages/drizzle/src/factory/transform.ts @@ -0,0 +1,380 @@ +import type { StandardSchemaV1 } from "@gqloom/core" +import { + type Column, + type SQL, + type Table, + and, + asc, + desc, + eq, + getTableColumns, + getTableName, + gt, + gte, + ilike, + inArray, + isNotNull, + isNull, + like, + lt, + lte, + ne, + not, + notIlike, + notInArray, + notLike, + or, +} from "drizzle-orm" +import type { + ColumnFilters, + CountArgs, + DeleteArgs, + Filters, + FiltersCore, + InsertArrayArgs, + InsertArrayWithOnConflictArgs, + InsertSingleArgs, + InsertSingleWithOnConflictArgs, + RelationToManyArgs, + RelationToOneArgs, + SelectArrayArgs, + SelectSingleArgs, + UpdateArgs, +} from "./input" +import type { + CountOptions, + DeleteOptions, + InsertArrayOptions, + InsertArrayWithOnConflictOptions, + InsertSingleOptions, + InsertSingleWithOnConflictOptions, + QueryToManyFieldOptions, + QueryToOneFieldOptions, + SelectArrayOptions, + SelectSingleOptions, + UpdateOptions, +} from "./types" + +export class DrizzleArgsTransformer { + public toSelectArrayOptions: ( + args: SelectArrayArgs + ) => StandardSchemaV1.Result + + public toSelectSingleOptions: ( + args: SelectSingleArgs + ) => StandardSchemaV1.Result + + public toCountOptions: ( + args: CountArgs + ) => StandardSchemaV1.Result + + public toQueryToManyFieldOptions: ( + args: RelationToManyArgs + ) => StandardSchemaV1.Result> + + public toQueryToOneFieldOptions: ( + args: RelationToOneArgs + ) => StandardSchemaV1.Result> + + public toInsertArrayOptions: ( + args: InsertArrayArgs + ) => StandardSchemaV1.Result> + + public toInsertArrayWithOnConflictOptions: ( + args: InsertArrayWithOnConflictArgs + ) => StandardSchemaV1.Result> + + public toInsertSingleOptions: ( + args: InsertSingleArgs + ) => StandardSchemaV1.Result> + + public toInsertSingleWithOnConflictOptions: ( + args: InsertSingleWithOnConflictArgs + ) => StandardSchemaV1.Result> + + public toUpdateOptions: ( + args: UpdateArgs + ) => StandardSchemaV1.Result> + + public toDeleteOptions: ( + args: DeleteArgs + ) => StandardSchemaV1.Result + + public constructor(protected readonly table: TTable) { + this.toSelectArrayOptions = (args) => ({ + value: { + where: this.extractFilters(args.where), + orderBy: this.extractOrderBy(args.orderBy), + limit: args.limit, + offset: args.offset, + }, + }) + + this.toSelectSingleOptions = (args) => ({ + value: { + where: this.extractFilters(args.where), + orderBy: this.extractOrderBy(args.orderBy), + offset: args.offset, + }, + }) + + this.toCountOptions = (args) => ({ + value: { where: this.extractFilters(args.where) }, + }) + + this.toQueryToManyFieldOptions = (args) => ({ + value: { + where: this.extractFilters(args.where), + orderBy: args.orderBy, + limit: args.limit, + offset: args.offset, + }, + }) + + this.toQueryToOneFieldOptions = (args) => ({ + value: { + where: this.extractFilters(args.where), + }, + }) + this.toInsertArrayOptions = (args) => ({ value: { values: args.values } }) + + this.toInsertArrayWithOnConflictOptions = (args) => ({ + value: { + values: args.values, + onConflictDoNothing: args.onConflictDoNothing + ? { + target: args.onConflictDoNothing.target?.map((t) => + this.toColumn(t) + ), + where: this.extractFilters(args.onConflictDoNothing?.where), + } + : undefined, + onConflictDoUpdate: args.onConflictDoUpdate + ? { + target: args.onConflictDoUpdate.target.map((t) => + this.toColumn(t) + ), + set: args.onConflictDoUpdate.set, + targetWhere: this.extractFilters( + args.onConflictDoUpdate?.targetWhere + ), + setWhere: this.extractFilters(args.onConflictDoUpdate?.setWhere), + } + : undefined, + }, + }) + + this.toInsertSingleOptions = (args) => ({ + value: { value: args.value }, + }) + + this.toInsertSingleWithOnConflictOptions = (args) => ({ + value: { + value: args.value, + onConflictDoNothing: args.onConflictDoNothing + ? { + target: args.onConflictDoNothing.target?.map((t) => + this.toColumn(t) + ), + where: this.extractFilters(args.onConflictDoNothing?.where), + } + : undefined, + onConflictDoUpdate: args.onConflictDoUpdate + ? { + target: args.onConflictDoUpdate.target.map((t) => + this.toColumn(t) + ), + set: args.onConflictDoUpdate.set, + targetWhere: this.extractFilters( + args.onConflictDoUpdate?.targetWhere + ), + setWhere: this.extractFilters(args.onConflictDoUpdate?.setWhere), + } + : undefined, + }, + }) + + this.toUpdateOptions = (args) => ({ + value: { + where: this.extractFilters(args.where), + set: args.set, + }, + }) + + this.toDeleteOptions = (args) => ({ + value: { where: this.extractFilters(args.where) }, + }) + } + + protected toColumn(columnName: string) { + const column = getTableColumns(this.table)[columnName] + if (!column) { + throw new Error( + `Column ${columnName} not found in table ${getTableName(this.table)}` + ) + } + return column + } + + public extractFilters( + filters: Filters | undefined, + table?: any + ): SQL | undefined { + if (filters == null) return + table ??= this.table + + const entries = Object.entries(filters as FiltersCore) + const variants: (SQL | undefined)[] = [] + + for (const [columnName, operators] of entries) { + if (operators == null) continue + + if (columnName === "OR" && Array.isArray(operators)) { + const orConditions: SQL[] = [] + for (const variant of operators) { + const extracted = this.extractFilters(variant, table) + if (extracted) orConditions.push(extracted) + } + if (orConditions.length > 0) { + variants.push(or(...orConditions)) + } + continue + } + + if (columnName === "AND" && Array.isArray(operators)) { + const andConditions: SQL[] = [] + for (const variant of operators) { + const extracted = this.extractFilters(variant, table) + if (extracted) andConditions.push(extracted) + } + if (andConditions.length > 0) { + variants.push(and(...andConditions)) + } + continue + } + + if (columnName === "NOT" && operators) { + const extracted = this.extractFilters(operators, table) + if (extracted) { + variants.push(not(extracted)) + } + continue + } + + const column = getTableColumns(table)[columnName]! + const extractedColumn = this.extractFiltersColumn( + column, + columnName, + operators, + table + ) + if (extractedColumn) variants.push(extractedColumn) + } + + return and(...variants) + } + + public extractFiltersColumn( + column: TColumn, + columnName: string, + operators: ColumnFilters, + table?: any + ): SQL | undefined { + const entries = Object.entries(operators) + + const variants: (SQL | undefined)[] = [] + const binaryOperators = { eq, ne, gt, gte, lt, lte } + const textOperators = { like, notLike, ilike, notIlike } + const arrayOperators = { in: inArray, notIn: notInArray } + const nullOperators = { isNull, isNotNull } + + const tableColumn = table ? table[columnName] : column + + if (operators.OR) { + const orVariants = [] as SQL[] + + for (const variant of operators.OR) { + const extracted = this.extractFiltersColumn( + column, + columnName, + variant, + table + ) + + if (extracted) orVariants.push(extracted) + } + + variants.push(or(...orVariants)) + } + + if (operators.AND) { + const andVariants = [] as SQL[] + + for (const variant of operators.AND) { + const extracted = this.extractFiltersColumn( + column, + columnName, + variant, + table + ) + + if (extracted) andVariants.push(extracted) + } + + variants.push(and(...andVariants)) + } + + if (operators.NOT) { + const extracted = this.extractFiltersColumn( + column, + columnName, + operators.NOT, + table + ) + if (extracted) { + variants.push(not(extracted)) + } + } + + for (const [operatorName, operatorValue] of entries) { + if (operatorValue === null || operatorValue === false) continue + + if (operatorName in binaryOperators) { + const operator = + binaryOperators[operatorName as keyof typeof binaryOperators] + variants.push(operator(tableColumn, operatorValue)) + } else if (operatorName in textOperators) { + const operator = + textOperators[operatorName as keyof typeof textOperators] + variants.push(operator(tableColumn, operatorValue)) + } else if (operatorName in arrayOperators) { + const operator = + arrayOperators[operatorName as keyof typeof arrayOperators] + variants.push(operator(tableColumn, operatorValue)) + } else if (operatorName in nullOperators) { + const operator = + nullOperators[operatorName as keyof typeof nullOperators] + if (operatorValue === true) variants.push(operator(tableColumn)) + } + } + + return and(...variants) + } + + protected extractOrderBy( + orders?: SelectArrayArgs["orderBy"] + ): SQL[] | undefined { + if (orders == null) return + const answer: SQL[] = [] + const columns = getTableColumns(this.table) + for (const [column, direction] of Object.entries(orders)) { + if (!direction) continue + if (column in columns) { + answer.push( + direction === "asc" ? asc(columns[column]) : desc(columns[column]) + ) + } + } + return answer + } +} diff --git a/packages/drizzle/src/factory/types.ts b/packages/drizzle/src/factory/types.ts index a610fc16..f7a483c5 100644 --- a/packages/drizzle/src/factory/types.ts +++ b/packages/drizzle/src/factory/types.ts @@ -7,6 +7,7 @@ import type { import type { AnyRelations, Column, + InferInsertModel, InferSelectModel, Many, SQL, @@ -227,18 +228,18 @@ export interface InsertArrayMutationReturningItems< TTable extends Table, TInputI = InsertArrayWithOnConflictArgs, > extends MutationFactoryWithResolve< - InsertArrayWithOnConflictArgs, + InsertArrayWithOnConflictOptions, GraphQLSilk[], InferSelectModel[]>, - GraphQLSilk, TInputI> + GraphQLSilk, TInputI> > {} export interface InsertArrayMutationReturningSuccess< TTable extends Table, TInputI = InsertArrayArgs, > extends MutationFactoryWithResolve< - InsertArrayArgs, + InsertArrayOptions, GraphQLSilk, - GraphQLSilk, TInputI> + GraphQLSilk, TInputI> > {} export type InsertSingleMutation< @@ -252,23 +253,52 @@ export interface InsertSingleMutationReturningItem< TTable extends Table, TInputI = InsertSingleWithOnConflictArgs, > extends MutationFactoryWithResolve< - InsertSingleWithOnConflictArgs, + InsertSingleWithOnConflictOptions, GraphQLSilk< InferSelectModel | null | undefined, InferSelectModel | null | undefined >, - GraphQLSilk, TInputI> + GraphQLSilk, TInputI> > {} export interface InsertSingleMutationReturningSuccess< TTable extends Table, TInputI = InsertSingleArgs, > extends MutationFactoryWithResolve< - InsertSingleArgs, + InsertSingleOptions, GraphQLSilk, - GraphQLSilk, TInputI> + GraphQLSilk, TInputI> > {} +export interface InsertArrayOptions { + values: InferInsertModel[] +} + +export interface InsertArrayWithOnConflictOptions + extends InsertArrayOptions, + InsertOnConflictInputOptions {} + +export interface InsertSingleOptions { + value: InferInsertModel +} + +export interface InsertSingleWithOnConflictOptions + extends InsertSingleOptions, + InsertOnConflictInputOptions {} + +export interface InsertOnConflictInputOptions { + onConflictDoUpdate?: { + target: Column[] + set?: Partial> + targetWhere?: SQL + setWhere?: SQL + } + onConflictDoNothing?: { + target?: Column[] + where?: SQL + } +} + export type UpdateMutation> = | UpdateMutationReturningItems | UpdateMutationReturningSuccess @@ -277,20 +307,25 @@ export interface UpdateMutationReturningItems< TTable extends Table, TInputI = UpdateArgs, > extends MutationFactoryWithResolve< - UpdateArgs, + UpdateOptions, GraphQLSilk[], InferSelectModel[]>, - GraphQLSilk, TInputI> + GraphQLSilk, TInputI> > {} export interface UpdateMutationReturningSuccess< TTable extends Table, TInputI = UpdateArgs, > extends MutationFactoryWithResolve< - UpdateArgs, + UpdateOptions, GraphQLSilk, - GraphQLSilk, TInputI> + GraphQLSilk, TInputI> > {} +export interface UpdateOptions { + where?: SQL + set: Partial> +} + export type DeleteMutation> = | DeleteMutationReturningItems | DeleteMutationReturningSuccess @@ -299,20 +334,24 @@ export interface DeleteMutationReturningItems< TTable extends Table, TInputI = DeleteArgs, > extends MutationFactoryWithResolve< - DeleteArgs, + DeleteOptions, GraphQLSilk[], InferSelectModel[]>, - GraphQLSilk, TInputI> + GraphQLSilk > {} export interface DeleteMutationReturningSuccess< TTable extends Table, TInputI = DeleteArgs, > extends MutationFactoryWithResolve< - DeleteArgs, + DeleteOptions, GraphQLSilk, - GraphQLSilk, TInputI> + GraphQLSilk > {} +export interface DeleteOptions { + where?: SQL +} + export type QueryBuilder< TDatabase extends BaseDatabase, TTable extends Table, diff --git a/packages/drizzle/test/resolver-factory.spec.ts b/packages/drizzle/test/resolver-factory.spec.ts index 9e2c9378..13df2b34 100644 --- a/packages/drizzle/test/resolver-factory.spec.ts +++ b/packages/drizzle/test/resolver-factory.spec.ts @@ -1693,7 +1693,7 @@ describe.concurrent("DrizzleMySQLResolverFactory", () => { const mutation = userFactory.updateMutation() expect( await mutation["~meta"].resolve({ - where: { name: { eq: "Bob" } }, + where: eq(mysqlSchemas.users.name, "Bob"), set: { age: 19 }, }) ).toMatchObject({ isSuccess: true }) @@ -1721,7 +1721,7 @@ describe.concurrent("DrizzleMySQLResolverFactory", () => { try { const mutation = userFactory.deleteMutation() const answer = await mutation["~meta"].resolve({ - where: { name: { eq: "Alice" } }, + where: eq(mysqlSchemas.users.name, "Alice"), }) expect(answer).toMatchObject({ isSuccess: true }) } finally { @@ -1854,7 +1854,7 @@ describe.concurrent("DrizzlePostgresResolverFactory", () => { try { const mutation = userFactory.updateMutation() const answer = await mutation["~meta"].resolve({ - where: { name: { eq: "Bob" } }, + where: eq(pgSchemas.users.name, "Bob"), set: { age: 19 }, }) expect(answer).toMatchObject([{ name: "Bob", age: 19 }]) @@ -1883,7 +1883,7 @@ describe.concurrent("DrizzlePostgresResolverFactory", () => { try { const mutation = userFactory.deleteMutation() const answer = await mutation["~meta"].resolve({ - where: { name: { eq: "Alice" } }, + where: eq(pgSchemas.users.name, "Alice"), }) expect(answer).toMatchObject([{ name: "Alice", age: 18 }]) } finally { @@ -2020,7 +2020,7 @@ describe.concurrent("DrizzleSQLiteResolverFactory", () => { try { const mutation = userFactory.updateMutation() const answer = await mutation["~meta"].resolve({ - where: { name: { eq: "Bob" } }, + where: eq(sqliteSchemas.users.name, "Bob"), set: { age: 19 }, }) expect(answer).toMatchObject([{ name: "Bob", age: 19 }]) @@ -2051,7 +2051,7 @@ describe.concurrent("DrizzleSQLiteResolverFactory", () => { try { const mutation = userFactory.deleteMutation() const answer = await mutation["~meta"].resolve({ - where: { name: { eq: "Alice" } }, + where: eq(sqliteSchemas.users.name, "Alice"), }) expect(answer).toMatchObject([{ name: "Alice", age: 18 }]) From e398c15c35f2e3e557b263e68b8672c89609d3e8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E2=9B=B5?= <809067559@qq.com> Date: Thu, 12 Jun 2025 16:41:08 +0800 Subject: [PATCH 49/54] refactor(types, helper, factory): update column behavior configuration and streamline input handling - Renamed and refactored visibility behavior types to column behavior types for clarity. - Updated the `isColumnVisible` function to handle new column behavior configurations. - Adjusted the constructor of `DrizzleArgsTransformer` to accept options for better input management. - Modified tests to reflect changes in the column behavior interface and ensure compatibility. --- packages/drizzle/src/factory/resolver.ts | 2 +- packages/drizzle/src/factory/transform.ts | 8 +++++++- packages/drizzle/src/helper.ts | 20 ++++++++++--------- packages/drizzle/src/types.ts | 22 ++++++++++++--------- packages/drizzle/test/helper.spec.ts | 4 ++-- packages/drizzle/test/input-factory.spec.ts | 9 ++++++--- 6 files changed, 40 insertions(+), 25 deletions(-) diff --git a/packages/drizzle/src/factory/resolver.ts b/packages/drizzle/src/factory/resolver.ts index 8be2db2b..987753b6 100644 --- a/packages/drizzle/src/factory/resolver.ts +++ b/packages/drizzle/src/factory/resolver.ts @@ -89,7 +89,7 @@ export abstract class DrizzleResolverFactory< ) { this.inputFactory = new DrizzleInputFactory(table, options) this.tableName = getTableName(table) - this.argsTransformer = new DrizzleArgsTransformer(table) + this.argsTransformer = new DrizzleArgsTransformer(table, options) } private _output?: TableSilk diff --git a/packages/drizzle/src/factory/transform.ts b/packages/drizzle/src/factory/transform.ts index c1600ad1..9ec563af 100644 --- a/packages/drizzle/src/factory/transform.ts +++ b/packages/drizzle/src/factory/transform.ts @@ -25,6 +25,7 @@ import { notLike, or, } from "drizzle-orm" +import type { DrizzleResolverFactoryOptions } from "../types" import type { ColumnFilters, CountArgs, @@ -100,7 +101,12 @@ export class DrizzleArgsTransformer { args: DeleteArgs ) => StandardSchemaV1.Result - public constructor(protected readonly table: TTable) { + public constructor( + protected readonly table: TTable, + protected readonly options: + | DrizzleResolverFactoryOptions + | undefined + ) { this.toSelectArrayOptions = (args) => ({ value: { where: this.extractFilters(args.where), diff --git a/packages/drizzle/src/helper.ts b/packages/drizzle/src/helper.ts index 6ebc29cf..81fe070d 100644 --- a/packages/drizzle/src/helper.ts +++ b/packages/drizzle/src/helper.ts @@ -26,10 +26,10 @@ import { } from "drizzle-orm/sqlite-core" import type { GraphQLResolveInfo } from "graphql" import type { - DrizzleFactoryInputVisibilityBehaviors, + ColumnBehavior, + DrizzleFactoryInputBehaviors, SelectedTableColumns, ValueOrGetter, - VisibilityBehavior, } from "./types" /** @@ -79,20 +79,22 @@ export function getEnumNameByColumn(column: Column): string | undefined { export function isColumnVisible( columnName: string, - options: DrizzleFactoryInputVisibilityBehaviors, - behavior: keyof VisibilityBehavior + options: DrizzleFactoryInputBehaviors
, + behavior: keyof ColumnBehavior ): boolean { // Get specific column configuration const columnConfig = options?.[columnName as keyof typeof options] - // Get global default configuration + // Get table default configuration const defaultConfig = options?.["*"] if (columnConfig != null) { if (typeof columnConfig === "boolean") { return columnConfig } - const specificBehavior = columnConfig[behavior] - if (specificBehavior != null) { - return specificBehavior + if (!("~standard" in columnConfig)) { + const specificBehavior = columnConfig[behavior] + if (specificBehavior != null) { + return specificBehavior !== false + } } } @@ -102,7 +104,7 @@ export function isColumnVisible( } const defaultBehavior = defaultConfig[behavior] if (defaultBehavior != null) { - return defaultBehavior + return defaultBehavior !== false } } diff --git a/packages/drizzle/src/types.ts b/packages/drizzle/src/types.ts index 4a95a8d1..67aacc47 100644 --- a/packages/drizzle/src/types.ts +++ b/packages/drizzle/src/types.ts @@ -1,4 +1,4 @@ -import { SYMBOLS, type WeaverConfig } from "@gqloom/core" +import { type GraphQLSilk, SYMBOLS, type WeaverConfig } from "@gqloom/core" import type { Column, InferSelectModel, SQL, Table } from "drizzle-orm" import type { GraphQLFieldConfig, @@ -27,12 +27,12 @@ export interface DrizzleWeaverConfig */ export interface DrizzleResolverFactoryOptions { /** - * Config the visibility behavior of the columns + * Config the behavior of the columns */ - input: DrizzleFactoryInputVisibilityBehaviors + input: DrizzleFactoryInputBehaviors } -export interface VisibilityBehavior { +export interface ColumnBehavior { /** * Is this column visible in the filters? */ @@ -41,20 +41,24 @@ export interface VisibilityBehavior { /** * Is this column visible in the insert mutation input? */ - insert?: boolean + insert?: boolean | GraphQLSilk /** * Is this column visible in the update mutation input? */ - update?: boolean + update?: boolean | GraphQLSilk } -export type DrizzleFactoryInputVisibilityBehaviors = { - [K in keyof TTable["_"]["columns"]]?: VisibilityBehavior | boolean | undefined +export type DrizzleFactoryInputBehaviors = { + [K in keyof TTable["_"]["columns"]]?: + | ColumnBehavior + | GraphQLSilk + | boolean + | undefined } & { /** * Config the default visibility behavior of all columns */ - "*"?: VisibilityBehavior | boolean | undefined + "*"?: ColumnBehavior | boolean | undefined } export interface DrizzleSilkConfig diff --git a/packages/drizzle/test/helper.spec.ts b/packages/drizzle/test/helper.spec.ts index d9dc6cac..bebe1a4a 100644 --- a/packages/drizzle/test/helper.spec.ts +++ b/packages/drizzle/test/helper.spec.ts @@ -14,7 +14,7 @@ import { inArrayMultiple, isColumnVisible, } from "../src/helper" -import type { DrizzleFactoryInputVisibilityBehaviors } from "../src/types" +import type { DrizzleFactoryInputBehaviors } from "../src/types" import * as mysqlTables from "./schema/mysql" import * as pgTables from "./schema/postgres" import * as sqliteTables from "./schema/sqlite" @@ -55,7 +55,7 @@ describe("helper", () => { describe("isColumnVisible", () => { it("should handle boolean configuration", () => { - const options: DrizzleFactoryInputVisibilityBehaviors
= { + const options: DrizzleFactoryInputBehaviors
= { "*": true, column1: false, column2: { diff --git a/packages/drizzle/test/input-factory.spec.ts b/packages/drizzle/test/input-factory.spec.ts index ad1a641b..1b011988 100644 --- a/packages/drizzle/test/input-factory.spec.ts +++ b/packages/drizzle/test/input-factory.spec.ts @@ -1,8 +1,10 @@ +import { field } from "@gqloom/core" import * as pg from "drizzle-orm/pg-core" import { GraphQLScalarType, printType } from "graphql" +import * as v from "valibot" import { describe, expect, it } from "vitest" import { DrizzleInputFactory, drizzleSilk } from "../src" -import type { DrizzleFactoryInputVisibilityBehaviors } from "../src/types" +import type { DrizzleFactoryInputBehaviors } from "../src/types" describe("DrizzleInputFactory", () => { const userTable = pg.pgTable("users", { @@ -111,14 +113,15 @@ describe("DrizzleInputFactory", () => { }) describe("with column visibility options", () => { - const options: DrizzleFactoryInputVisibilityBehaviors = { + const options: DrizzleFactoryInputBehaviors = { + email: v.pipe(v.string(), v.email()), "*": { filters: true, insert: true, update: true, }, password: { - filters: false, + filters: field.hidden, insert: true, update: true, }, From 443c1a63aecdc11c271fc5b8bcf08d9025146270 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E2=9B=B5?= <809067559@qq.com> Date: Thu, 12 Jun 2025 17:17:03 +0800 Subject: [PATCH 50/54] feat(transform): add validation for insert values and enhance column silk retrieval - Implemented `validateInsertValue` method to validate insert values against column schemas, returning structured results and issues. - Added `getColumnSilk` method to retrieve column behavior configurations for insert and update mutations, improving input handling in `DrizzleArgsTransformer`. --- packages/drizzle/src/factory/transform.ts | 43 +++++++++++++++++++++++ 1 file changed, 43 insertions(+) diff --git a/packages/drizzle/src/factory/transform.ts b/packages/drizzle/src/factory/transform.ts index 9ec563af..ca5cf382 100644 --- a/packages/drizzle/src/factory/transform.ts +++ b/packages/drizzle/src/factory/transform.ts @@ -1,4 +1,5 @@ import type { StandardSchemaV1 } from "@gqloom/core" +import type { GraphQLSilk } from "@gqloom/core" import { type Column, type SQL, @@ -142,6 +143,7 @@ export class DrizzleArgsTransformer { where: this.extractFilters(args.where), }, }) + this.toInsertArrayOptions = (args) => ({ value: { values: args.values } }) this.toInsertArrayWithOnConflictOptions = (args) => ({ @@ -212,6 +214,34 @@ export class DrizzleArgsTransformer { }) } + protected async validateInsertValue( + value: TTable["$inferInsert"], + path: ReadonlyArray + ): Promise> { + const result: Record = {} + const issues: StandardSchemaV1.Issue[] = [] + for (const key of Object.keys(getTableColumns(this.table))) { + const columnSilk = this.getColumnSilk(key, "insert") + if (columnSilk == null) { + result[key] = value[key] + continue + } + const res = await columnSilk["~standard"].validate(value[key]) + if ("value" in res) { + result[key] = res.value + } + if (res.issues) { + issues.push( + ...res.issues.map((issue) => ({ + ...issue, + path: [...path, key, ...(issue.path ?? [])], + })) + ) + } + } + return { value: result, ...(issues.length > 0 ? { issues } : null) } + } + protected toColumn(columnName: string) { const column = getTableColumns(this.table)[columnName] if (!column) { @@ -222,6 +252,19 @@ export class DrizzleArgsTransformer { return column } + protected getColumnSilk( + columnName: string, + mutation: "insert" | "update" + ): GraphQLSilk | undefined { + const behavior = this.options?.input?.[columnName] + if (behavior == null || typeof behavior === "boolean") return undefined + if ("~standard" in behavior) return behavior + const mutationBehavior = behavior[mutation] + if (mutationBehavior == null || typeof mutationBehavior === "boolean") + return undefined + return mutationBehavior + } + public extractFilters( filters: Filters | undefined, table?: any From e82c7edc7045da734f93864928fddd5f5da96062 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E2=9B=B5?= <809067559@qq.com> Date: Thu, 12 Jun 2025 18:21:55 +0800 Subject: [PATCH 51/54] feat(transform): enhance DrizzleArgsTransformer with async validation for insert and update options - Updated methods in DrizzleArgsTransformer to return MayPromise types for select, insert, and update options, enabling asynchronous validation. - Implemented async validation for insert values and update values, improving error handling and input integrity. - Refactored to ensure consistent handling of validation issues across various transformation methods. --- packages/drizzle/src/factory/transform.ts | 225 +++++++++++++++------- 1 file changed, 152 insertions(+), 73 deletions(-) diff --git a/packages/drizzle/src/factory/transform.ts b/packages/drizzle/src/factory/transform.ts index ca5cf382..0818c10e 100644 --- a/packages/drizzle/src/factory/transform.ts +++ b/packages/drizzle/src/factory/transform.ts @@ -1,4 +1,4 @@ -import type { StandardSchemaV1 } from "@gqloom/core" +import type { MayPromise, StandardSchemaV1 } from "@gqloom/core" import type { GraphQLSilk } from "@gqloom/core" import { type Column, @@ -60,47 +60,51 @@ import type { export class DrizzleArgsTransformer { public toSelectArrayOptions: ( args: SelectArrayArgs - ) => StandardSchemaV1.Result + ) => MayPromise> public toSelectSingleOptions: ( args: SelectSingleArgs - ) => StandardSchemaV1.Result + ) => MayPromise> public toCountOptions: ( args: CountArgs - ) => StandardSchemaV1.Result + ) => MayPromise> public toQueryToManyFieldOptions: ( args: RelationToManyArgs - ) => StandardSchemaV1.Result> + ) => MayPromise>> public toQueryToOneFieldOptions: ( args: RelationToOneArgs - ) => StandardSchemaV1.Result> + ) => MayPromise>> public toInsertArrayOptions: ( args: InsertArrayArgs - ) => StandardSchemaV1.Result> + ) => MayPromise>> public toInsertArrayWithOnConflictOptions: ( args: InsertArrayWithOnConflictArgs - ) => StandardSchemaV1.Result> + ) => MayPromise< + StandardSchemaV1.Result> + > public toInsertSingleOptions: ( args: InsertSingleArgs - ) => StandardSchemaV1.Result> + ) => MayPromise>> public toInsertSingleWithOnConflictOptions: ( args: InsertSingleWithOnConflictArgs - ) => StandardSchemaV1.Result> + ) => MayPromise< + StandardSchemaV1.Result> + > public toUpdateOptions: ( args: UpdateArgs - ) => StandardSchemaV1.Result> + ) => MayPromise>> public toDeleteOptions: ( args: DeleteArgs - ) => StandardSchemaV1.Result + ) => MayPromise> public constructor( protected readonly table: TTable, @@ -144,76 +148,123 @@ export class DrizzleArgsTransformer { }, }) - this.toInsertArrayOptions = (args) => ({ value: { values: args.values } }) + this.toInsertArrayOptions = async (args) => { + const valuesResult = await this.validateInsertValues(args.values, [ + "values", + ]) - this.toInsertArrayWithOnConflictOptions = (args) => ({ - value: { - values: args.values, - onConflictDoNothing: args.onConflictDoNothing - ? { - target: args.onConflictDoNothing.target?.map((t) => - this.toColumn(t) - ), - where: this.extractFilters(args.onConflictDoNothing?.where), - } - : undefined, - onConflictDoUpdate: args.onConflictDoUpdate - ? { - target: args.onConflictDoUpdate.target.map((t) => - this.toColumn(t) - ), - set: args.onConflictDoUpdate.set, - targetWhere: this.extractFilters( - args.onConflictDoUpdate?.targetWhere - ), - setWhere: this.extractFilters(args.onConflictDoUpdate?.setWhere), - } - : undefined, - }, - }) + if (valuesResult.issues) return { issues: valuesResult.issues } + return { value: { values: valuesResult.value } } + } - this.toInsertSingleOptions = (args) => ({ - value: { value: args.value }, - }) + this.toInsertArrayWithOnConflictOptions = async (args) => { + const valuesResult = await this.validateInsertValues(args.values, [ + "values", + ]) + if (valuesResult.issues) return { issues: valuesResult.issues } + return { + value: { + values: valuesResult.value, + onConflictDoNothing: args.onConflictDoNothing + ? { + target: args.onConflictDoNothing.target?.map((t) => + this.toColumn(t) + ), + where: this.extractFilters(args.onConflictDoNothing?.where), + } + : undefined, + onConflictDoUpdate: args.onConflictDoUpdate + ? { + target: args.onConflictDoUpdate.target.map((t) => + this.toColumn(t) + ), + set: args.onConflictDoUpdate.set, + targetWhere: this.extractFilters( + args.onConflictDoUpdate?.targetWhere + ), + setWhere: this.extractFilters( + args.onConflictDoUpdate?.setWhere + ), + } + : undefined, + }, + } + } - this.toInsertSingleWithOnConflictOptions = (args) => ({ - value: { - value: args.value, - onConflictDoNothing: args.onConflictDoNothing - ? { - target: args.onConflictDoNothing.target?.map((t) => - this.toColumn(t) - ), - where: this.extractFilters(args.onConflictDoNothing?.where), - } - : undefined, - onConflictDoUpdate: args.onConflictDoUpdate - ? { - target: args.onConflictDoUpdate.target.map((t) => - this.toColumn(t) - ), - set: args.onConflictDoUpdate.set, - targetWhere: this.extractFilters( - args.onConflictDoUpdate?.targetWhere - ), - setWhere: this.extractFilters(args.onConflictDoUpdate?.setWhere), - } - : undefined, - }, - }) + this.toInsertSingleOptions = async (args) => { + const valueResult = await this.validateInsertValue(args.value, ["value"]) + if (valueResult.issues) return { issues: valueResult.issues } + return { value: { value: valueResult.value } } + } - this.toUpdateOptions = (args) => ({ - value: { - where: this.extractFilters(args.where), - set: args.set, - }, - }) + this.toInsertSingleWithOnConflictOptions = async (args) => { + const valueResult = await this.validateInsertValue(args.value, ["value"]) + if (valueResult.issues) return { issues: valueResult.issues } + return { + value: { + value: valueResult.value, + onConflictDoNothing: args.onConflictDoNothing + ? { + target: args.onConflictDoNothing.target?.map((t) => + this.toColumn(t) + ), + where: this.extractFilters(args.onConflictDoNothing?.where), + } + : undefined, + onConflictDoUpdate: args.onConflictDoUpdate + ? { + target: args.onConflictDoUpdate.target.map((t) => + this.toColumn(t) + ), + set: args.onConflictDoUpdate.set, + targetWhere: this.extractFilters( + args.onConflictDoUpdate?.targetWhere + ), + setWhere: this.extractFilters( + args.onConflictDoUpdate?.setWhere + ), + } + : undefined, + }, + } + } + + this.toUpdateOptions = async (args) => { + const setResult = await this.validateUpdateValue(args.set, ["set"]) + if (setResult.issues) return { issues: setResult.issues } + return { + value: { + where: this.extractFilters(args.where), + set: setResult.value, + }, + } + } this.toDeleteOptions = (args) => ({ value: { where: this.extractFilters(args.where) }, }) } + protected async validateInsertValues( + values: TTable["$inferInsert"][], + path: ReadonlyArray + ): Promise> { + const results: TTable["$inferInsert"][] = [] + const issues: StandardSchemaV1.Issue[] = [] + + await Promise.all( + values.map(async (value, index) => { + const res = await this.validateInsertValue(value, [...path, index]) + if (res.issues) { + issues.push(...res.issues) + return + } + results[index] = res.value + }) + ) + return { value: results, ...(issues.length > 0 && { issues }) } + } + protected async validateInsertValue( value: TTable["$inferInsert"], path: ReadonlyArray @@ -239,7 +290,35 @@ export class DrizzleArgsTransformer { ) } } - return { value: result, ...(issues.length > 0 ? { issues } : null) } + return { value: result, ...(issues.length > 0 && { issues }) } + } + + protected async validateUpdateValue( + value: Partial, + path: ReadonlyArray + ): Promise>> { + const result: Record = {} + const issues: StandardSchemaV1.Issue[] = [] + for (const key of Object.keys(value)) { + const columnSilk = this.getColumnSilk(key, "update") + if (columnSilk == null) { + result[key] = value[key] + continue + } + const res = await columnSilk["~standard"].validate(value[key]) + if ("value" in res) { + result[key] = res.value + } + if (res.issues) { + issues.push( + ...res.issues.map((issue) => ({ + ...issue, + path: [...path, key, ...(issue.path ?? [])], + })) + ) + } + } + return { value: result, ...(issues.length > 0 && { issues }) } } protected toColumn(columnName: string) { From b36e2d96064db0ad5f44d3e2e7a9f4cb8e13454e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E2=9B=B5?= <809067559@qq.com> Date: Thu, 12 Jun 2025 20:36:19 +0800 Subject: [PATCH 52/54] feat(factory): implement dynamic column input type retrieval for insert and update mutations - Added `getColumnInputType` method to dynamically determine GraphQL types based on column configurations for insert and update operations. - Refactored input field generation in `insertInput` and `updateInput` methods to utilize the new dynamic type retrieval, improving flexibility and maintainability. - Updated tests to ensure proper context handling and visibility checks for input fields. --- packages/drizzle/src/factory/input.ts | 66 ++++++++++++++------- packages/drizzle/test/input-factory.spec.ts | 19 +++++- 2 files changed, 62 insertions(+), 23 deletions(-) diff --git a/packages/drizzle/src/factory/input.ts b/packages/drizzle/src/factory/input.ts index 284b143b..687cf395 100644 --- a/packages/drizzle/src/factory/input.ts +++ b/packages/drizzle/src/factory/input.ts @@ -1,4 +1,5 @@ import { SYMBOLS, mapValue, pascalCase, weaverContext } from "@gqloom/core" +import { getGraphQLType } from "@gqloom/core" import { type Column, type InferInsertModel, @@ -246,6 +247,30 @@ export class DrizzleInputFactory { ) } + protected getColumnInputType( + key: string, + mutation: "insert" | "update", + column: Column, + columnConfig: ReturnType + ) { + const colSilk = (() => { + const behavior = this.options?.input[key] + if (typeof behavior != "object" || behavior == null) return undefined + if ("~standard" in behavior) { + return behavior + } + const mutationConfigBehavior = behavior[mutation] + if ( + typeof mutationConfigBehavior != "object" || + mutationConfigBehavior == null + ) + return undefined + return mutationConfigBehavior + })() + if (colSilk != null) return getGraphQLType(colSilk) + return getValue(columnConfig?.type) || DrizzleWeaver.getColumnType(column) + } + public insertInput() { const tableConfig = DrizzleWeaver.silkConfigs.get(this.table) const tableName = tableConfig?.name ?? getTableName(this.table) @@ -259,25 +284,21 @@ export class DrizzleInputFactory { new GraphQLObjectType({ name, description: tableConfig?.description, - fields: mapValue(columns, (column, columnName) => { - if ( - !isColumnVisible(columnName, this.options?.input ?? {}, "insert") - ) { + fields: mapValue(columns, (column, key) => { + if (!isColumnVisible(key, this.options?.input ?? {}, "insert")) { return mapValue.SKIP } const fieldConfig = DrizzleInputFactory.getColumnConfig( tableConfig, - columnName + key ) - const type = (() => { - const t = - getValue(fieldConfig?.type) || DrizzleWeaver.getColumnType(column) - if (column.hasDefault) return t - if (column.notNull && !isNonNullType(t)) - return new GraphQLNonNull(t) - return t - })() + let type = this.getColumnInputType(key, "insert", column, fieldConfig) + const isNotNull = + !column.hasDefault && column.notNull && !isNonNullType(type) + if (isNotNull) { + type = new GraphQLNonNull(type) + } return { type, description: fieldConfig?.description } }), }) @@ -343,18 +364,23 @@ export class DrizzleInputFactory { new GraphQLObjectType({ name, description: tableConfig?.description, - fields: mapValue(columns, (column, columnName) => { - if ( - !isColumnVisible(columnName, this.options?.input ?? {}, "update") - ) { + fields: mapValue(columns, (column, key) => { + if (!isColumnVisible(key, this.options?.input ?? {}, "update")) { return mapValue.SKIP } const columnConfig = DrizzleInputFactory.getColumnConfig( tableConfig, - columnName + key ) - const type = - getValue(columnConfig?.type) || DrizzleWeaver.getColumnType(column) + let type = this.getColumnInputType( + key, + "update", + column, + columnConfig + ) + if (type instanceof GraphQLNonNull) { + type = type.ofType + } return { type, description: columnConfig?.description } }), }) diff --git a/packages/drizzle/test/input-factory.spec.ts b/packages/drizzle/test/input-factory.spec.ts index 1b011988..b68e734c 100644 --- a/packages/drizzle/test/input-factory.spec.ts +++ b/packages/drizzle/test/input-factory.spec.ts @@ -1,4 +1,5 @@ -import { field } from "@gqloom/core" +import { field, initWeaverContext, provideWeaverContext } from "@gqloom/core" +import { ValibotWeaver } from "@gqloom/valibot" import * as pg from "drizzle-orm/pg-core" import { GraphQLScalarType, printType } from "graphql" import * as v from "valibot" @@ -140,10 +141,17 @@ describe("DrizzleInputFactory", () => { const inputFactoryWithOptions = new DrizzleInputFactory(userTable, { input: options, }) + const weaverContext = initWeaverContext() + weaverContext.vendorWeavers.set(ValibotWeaver.vendor, ValibotWeaver) it("should respect column visibility in InsertInput", () => { expect( - printType(inputFactoryWithOptions.insertInput()) + printType( + provideWeaverContext( + () => inputFactoryWithOptions.insertInput(), + weaverContext + ) + ) ).toMatchInlineSnapshot(` "type UsersInsertInput { id: Int @@ -156,7 +164,12 @@ describe("DrizzleInputFactory", () => { it("should respect column visibility in UpdateInput", () => { expect( - printType(inputFactoryWithOptions.updateInput()) + printType( + provideWeaverContext( + () => inputFactoryWithOptions.updateInput(), + weaverContext + ) + ) ).toMatchInlineSnapshot(` "type UsersUpdateInput { id: Int From 4b3cbc0b3141328a920d846f8c3f5c65329b1527 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E2=9B=B5?= <809067559@qq.com> Date: Thu, 12 Jun 2025 21:04:57 +0800 Subject: [PATCH 53/54] feat(tests): add email validation for user mutations in MySQL, PostgreSQL, and SQLite resolvers - Implemented tests to ensure that inserting and updating users with invalid email formats throws appropriate errors. - Enhanced user input handling by integrating Valibot for email validation in resolver factories. - Updated existing tests to improve error handling and maintain consistency across different database resolvers. --- .../src/factory/relation-field-loader.ts | 6 +- packages/drizzle/test/resolver-mysql.spec.ts | 59 ++++++++++++++++- .../drizzle/test/resolver-postgres.spec.ts | 59 ++++++++++++++++- packages/drizzle/test/resolver-sqlite.spec.ts | 64 ++++++++++++++++++- 4 files changed, 176 insertions(+), 12 deletions(-) diff --git a/packages/drizzle/src/factory/relation-field-loader.ts b/packages/drizzle/src/factory/relation-field-loader.ts index 2a803d4e..76ec7500 100644 --- a/packages/drizzle/src/factory/relation-field-loader.ts +++ b/packages/drizzle/src/factory/relation-field-loader.ts @@ -218,11 +218,7 @@ function matchQueryBuilder( queries: Record, table: any ): AnyQueryBuilder | undefined { - for (const qb of Object.values(queries)) { - if (qb.table != null && qb.table === table) { - return qb - } - } + return Object.values(queries).find((qb) => qb.table === table) } function keyForParent(table: Table, parent: any) { diff --git a/packages/drizzle/test/resolver-mysql.spec.ts b/packages/drizzle/test/resolver-mysql.spec.ts index ed76d0f4..94464ee3 100644 --- a/packages/drizzle/test/resolver-mysql.spec.ts +++ b/packages/drizzle/test/resolver-mysql.spec.ts @@ -1,4 +1,5 @@ import { weave } from "@gqloom/core" +import { ValibotWeaver } from "@gqloom/valibot" import { drizzle } from "drizzle-orm/mysql2" import type { MySql2Database } from "drizzle-orm/mysql2" import { @@ -7,6 +8,7 @@ import { printSchema, } from "graphql" import { type YogaServerInstance, createYoga } from "graphql-yoga" +import * as v from "valibot" import { afterAll, beforeAll, beforeEach, describe, expect, it } from "vitest" import { config } from "../env.config" import { drizzleResolverFactory } from "../src" @@ -39,7 +41,7 @@ describe("resolver by mysql", () => { const { data, errors } = await response.json() if (response.status !== 200 || errors != null) { - console.info(errors) + // console.info(errors) throw new Error(JSON.stringify(errors)) } return data @@ -52,9 +54,14 @@ describe("resolver by mysql", () => { mode: "default", logger: { logQuery: (query) => logs.push(query) }, }) - const userFactory = drizzleResolverFactory(db, users) + const userFactory = drizzleResolverFactory(db, users, { + input: { + email: v.nullish(v.pipe(v.string(), v.email())), + }, + }) const postFactory = drizzleResolverFactory(db, posts) gqlSchema = weave( + ValibotWeaver, userFactory.resolver({ name: "users" }), postFactory.resolver({ name: "posts" }) ) @@ -249,6 +256,34 @@ describe("resolver by mysql", () => { `) }) + it("should throw error when insert a user with invalid email", async () => { + const q1 = /* GraphQL */ ` + mutation insertIntoUsers($values: [UserInsertInput!]!) { + insertIntoUsers(values: $values) { + isSuccess + } + } + ` + await expect( + execute(q1, { + values: [{ name: "Tina", email: "modevol.com" }], + }) + ).rejects.toThrow("Invalid email") + + const q2 = /* GraphQL */ ` + mutation insertIntoUsersSingle($value: UserInsertInput!) { + insertIntoUsersSingle(value: $value) { + isSuccess + } + } + ` + await expect( + execute(q2, { + value: { name: "Tina", email: "modevol.com" }, + }) + ).rejects.toThrow("Invalid email") + }) + it("should update user information correctly", async () => { const q = /* GraphQL */ ` mutation updateUser($set: UserUpdateInput!, $where: UserFilters!) { @@ -291,6 +326,26 @@ describe("resolver by mysql", () => { `) }) + it("should throw error when update a user with invalid email", async () => { + const q = /* GraphQL */ ` + mutation updateUsers($set: UserUpdateInput!, $where: UserFilters!) { + updateUsers(set: $set, where: $where) { + isSuccess + } + } + ` + const [Danny] = await db + .insert(users) + .values({ name: "Danny" }) + .$returningId() + await expect( + execute(q, { + set: { email: "modevol.com" }, + where: { id: { eq: Danny.id } }, + }) + ).rejects.toThrow("Invalid email") + }) + it("should delete a user correctly", async () => { const q = /* GraphQL */ ` mutation deleteFromUsers($where: UserFilters!) { diff --git a/packages/drizzle/test/resolver-postgres.spec.ts b/packages/drizzle/test/resolver-postgres.spec.ts index 291e448e..fcf464fe 100644 --- a/packages/drizzle/test/resolver-postgres.spec.ts +++ b/packages/drizzle/test/resolver-postgres.spec.ts @@ -1,4 +1,5 @@ import { weave } from "@gqloom/core" +import { ValibotWeaver } from "@gqloom/valibot" import { type NodePgDatabase, drizzle } from "drizzle-orm/node-postgres" import { type GraphQLSchema, @@ -6,6 +7,7 @@ import { printSchema, } from "graphql" import { type YogaServerInstance, createYoga } from "graphql-yoga" +import * as v from "valibot" import { afterAll, beforeAll, beforeEach, describe, expect, it } from "vitest" import { config } from "../env.config" import { drizzleResolverFactory } from "../src" @@ -35,7 +37,7 @@ describe("resolver by postgres", () => { const { data, errors } = await response.json() if (response.status !== 200 || errors != null) { - console.info(errors) + // console.info(errors) throw new Error(JSON.stringify(errors)) } return data @@ -47,9 +49,14 @@ describe("resolver by postgres", () => { relations, logger: { logQuery: (query) => logs.push(query) }, }) - const userFactory = drizzleResolverFactory(db, users) + const userFactory = drizzleResolverFactory(db, users, { + input: { + email: v.nullish(v.pipe(v.string(), v.email())), + }, + }) const postFactory = drizzleResolverFactory(db, posts) gqlSchema = weave( + ValibotWeaver, userFactory.resolver({ name: "users" }), postFactory.resolver({ name: "posts" }) ) @@ -241,6 +248,32 @@ describe("resolver by postgres", () => { `) }) + it("should throw error when insert a user with invalid email", async () => { + const q1 = /* GraphQL */ ` + mutation insertIntoUsers($values: [UserInsertInput!]!) { + insertIntoUsers(values: $values) { + id + name + } + } + ` + await expect( + execute(q1, { values: [{ name: "Tina", email: "modevol.com" }] }) + ).rejects.toThrow("Invalid email") + + const q2 = /* GraphQL */ ` + mutation insertIntoUsersSingle($value: UserInsertInput!) { + insertIntoUsersSingle(value: $value) { + id + name + } + } + ` + await expect( + execute(q2, { value: { name: "Tina", email: "modevol.com" } }) + ).rejects.toThrow("Invalid email") + }) + it("should insert a user with on conflict correctly", async () => { const q = /* GraphQL */ ` mutation insertIntoUsers($values: [UserInsertInput!]!, $doNothing: UserInsertOnConflictDoNothingInput, $doUpdate: UserInsertOnConflictDoUpdateInput) { @@ -398,6 +431,28 @@ describe("resolver by postgres", () => { `) }) + it("should throw error when update a user with invalid email", async () => { + const q = /* GraphQL */ ` + mutation updateUsers($set: UserUpdateInput!, $where: UserFilters!) { + updateUsers(set: $set, where: $where) { + id + name + } + } + ` + const [Danny] = await db + .insert(users) + .values({ name: "Danny" }) + .returning() + + await expect( + execute(q, { + set: { email: "modevol.com" }, + where: { id: { eq: Danny.id } }, + }) + ).rejects.toThrow("Invalid email") + }) + it("should delete a user correctly", async () => { const q = /* GraphQL */ ` mutation deleteFromUsers($where: UserFilters!) { diff --git a/packages/drizzle/test/resolver-sqlite.spec.ts b/packages/drizzle/test/resolver-sqlite.spec.ts index c6a70fcb..f24f86e8 100644 --- a/packages/drizzle/test/resolver-sqlite.spec.ts +++ b/packages/drizzle/test/resolver-sqlite.spec.ts @@ -1,4 +1,5 @@ import { weave } from "@gqloom/core" +import { ValibotWeaver } from "@gqloom/valibot" import { type LibSQLDatabase, drizzle } from "drizzle-orm/libsql" import { type GraphQLSchema, @@ -6,6 +7,7 @@ import { printSchema, } from "graphql" import { type YogaServerInstance, createYoga } from "graphql-yoga" +import * as v from "valibot" import { afterAll, beforeAll, beforeEach, describe, expect, it } from "vitest" import { drizzleResolverFactory } from "../src" import type * as schema from "./schema/sqlite" @@ -35,7 +37,7 @@ describe("resolver by sqlite", () => { const { data, errors } = await response.json() if (response.status !== 200 || errors != null) { - console.info(errors) + // console.info(errors) throw new Error(JSON.stringify(errors)) } return data @@ -47,9 +49,17 @@ describe("resolver by sqlite", () => { connection: { url: `file:${pathToDB.pathname}` }, logger: { logQuery: (query) => logs.push(query) }, }) - const userFactory = drizzleResolverFactory(db, users) + const userFactory = drizzleResolverFactory(db, users, { + input: { + email: v.nullish(v.pipe(v.string(), v.email())), + }, + }) const postFactory = drizzleResolverFactory(db, posts) - gqlSchema = weave(userFactory.resolver(), postFactory.resolver()) + gqlSchema = weave( + ValibotWeaver, + userFactory.resolver(), + postFactory.resolver() + ) yoga = createYoga({ schema: gqlSchema }) await db @@ -236,6 +246,32 @@ describe("resolver by sqlite", () => { `) }) + it("should throw error when insert a user with invalid email", async () => { + const q1 = /* GraphQL */ ` + mutation insertIntoUsers($values: [UserInsertInput!]!) { + insertIntoUsers(values: $values) { + id + name + } + } + ` + await expect( + execute(q1, { values: [{ name: "Tina", email: "modevol.com" }] }) + ).rejects.toThrow("Invalid email") + + const q2 = /* GraphQL */ ` + mutation insertIntoUsersSingle($value: UserInsertInput!) { + insertIntoUsersSingle(value: $value) { + id + name + } + } + ` + await expect( + execute(q2, { value: { name: "Tina", email: "modevol.com" } }) + ).rejects.toThrow("Invalid email") + }) + it("should insert a user with on conflict correctly", async () => { const q = /* GraphQL */ ` mutation insertIntoUsers($values: [UserInsertInput!]!, $doNothing: UserInsertOnConflictDoNothingInput, $doUpdate: UserInsertOnConflictDoUpdateInput) { @@ -386,6 +422,28 @@ describe("resolver by sqlite", () => { `) }) + it("should throw error when update a user with invalid email", async () => { + const q = /* GraphQL */ ` + mutation updateUsers($set: UserUpdateInput!, $where: UserFilters!) { + updateUsers(set: $set, where: $where) { + id + name + } + } + ` + const [Danny] = await db + .insert(users) + .values({ name: "Danny" }) + .returning() + + await expect( + execute(q, { + set: { email: "modevol.com" }, + where: { id: { eq: Danny.id } }, + }) + ).rejects.toThrow("Invalid email") + }) + it("should delete a user correctly", async () => { const q = /* GraphQL */ ` mutation deleteFromUsers($where: UserFilters!) { From 094f457fd59b1c21881dee3acfcfbae4bd7e72ea Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E2=9B=B5?= <809067559@qq.com> Date: Thu, 12 Jun 2025 22:19:02 +0800 Subject: [PATCH 54/54] chore: update peer dependency for @gqloom/core to version 0.10.0 --- packages/drizzle/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/drizzle/package.json b/packages/drizzle/package.json index 568e6754..16b64625 100644 --- a/packages/drizzle/package.json +++ b/packages/drizzle/package.json @@ -44,7 +44,7 @@ "author": "xcfox", "license": "MIT", "peerDependencies": { - "@gqloom/core": ">= 0.19.0", + "@gqloom/core": ">= 0.10.0", "drizzle-orm": ">= 1.0.0-beta.1", "graphql": ">= 16.8.0" },