diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 78d32dcb..742a8092 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -49,6 +49,9 @@ jobs: steps: - name: Checkout uses: actions/checkout@v4 + - name: Init docker + run: | + docker compose -f docker-compose.yml up -d - name: Setup pnpm uses: ./.github/actions/pnpm - name: Build diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 00000000..4cf595f9 --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,28 @@ +version: '3.8' + +services: + mysql: + image: mysql:9.1 + container_name: gqloom_mysql + restart: unless-stopped + environment: + MYSQL_ALLOW_EMPTY_PASSWORD: 1 + ports: + - "3306:3306" + volumes: + - mysql_data:/var/lib/mysql + + postgres: + image: postgres:17.2 + container_name: gqloom_postgres + restart: unless-stopped + environment: + POSTGRES_HOST_AUTH_METHOD: trust + ports: + - "5432:5432" + volumes: + - postgres_data:/var/lib/postgresql/data + +volumes: + mysql_data: + postgres_data: \ No newline at end of file diff --git a/package.json b/package.json index 0de2eb22..fe224f33 100644 --- a/package.json +++ b/package.json @@ -12,7 +12,8 @@ "check": "biome check --write . && pnpm run check:type", "check:type": "tsc --noEmit", "build": "pnpm -F '@gqloom/*' run build", - "prepare": "husky" + "prepare": "husky", + "compose-up": "docker compose up -d" }, "lint-staged": { "*.{ts,tsx,js,jsx,json}": ["biome check --no-errors-on-unmatched --write"], diff --git a/packages/core/src/utils/loader.ts b/packages/core/src/utils/loader.ts index eac21694..41aa6aae 100644 --- a/packages/core/src/utils/loader.ts +++ b/packages/core/src/utils/loader.ts @@ -3,6 +3,15 @@ export type BatchLoadFn = ( ) => Promise<(TData | Error)[]> export class EasyDataLoader { + protected queue: TKey[] + protected cache: Map> + protected resolvers: Map< + TKey, + [ + resolve: (value: TData | PromiseLike) => void, + reject: (reason?: any) => void, + ] + > constructor(protected readonly batchLoadFn: BatchLoadFn) { this.queue = [] this.cache = new Map() @@ -34,16 +43,6 @@ export class EasyDataLoader { this.resolvers.delete(key) } - protected queue: TKey[] - protected cache: Map> - protected resolvers: Map< - TKey, - [ - resolve: (value: TData | PromiseLike) => void, - reject: (reason?: any) => void, - ] - > - protected async executeBatchLoad(): Promise { if (this.queue.length === 0) return diff --git a/packages/core/src/utils/string.spec.ts b/packages/core/src/utils/string.spec.ts index c606a692..6d12309b 100644 --- a/packages/core/src/utils/string.spec.ts +++ b/packages/core/src/utils/string.spec.ts @@ -1,5 +1,5 @@ import { describe, expect, it } from "vitest" -import { pascalCase } from "./string" +import { capitalize, pascalCase } from "./string" describe("toPascalCase", () => { it("should convert kebab-case to PascalCase", () => { @@ -26,3 +26,9 @@ describe("toPascalCase", () => { expect(pascalCase("multiple-words-here")).toBe("MultipleWordsHere") }) }) + +describe("capitalize", () => { + it("should capitalize the first letter of a string", () => { + expect(capitalize("hello")).toBe("Hello") + }) +}) diff --git a/packages/core/src/utils/string.ts b/packages/core/src/utils/string.ts index e0bf229e..e53baf50 100644 --- a/packages/core/src/utils/string.ts +++ b/packages/core/src/utils/string.ts @@ -8,3 +8,7 @@ export function pascalCase(str: string): string { ) .join("") } + +export function capitalize(str: T): Capitalize { + return (str.slice(0, 1).toUpperCase() + str.slice(1)) as Capitalize +} diff --git a/packages/drizzle/.gitignore b/packages/drizzle/.gitignore new file mode 100644 index 00000000..72178f39 --- /dev/null +++ b/packages/drizzle/.gitignore @@ -0,0 +1,2 @@ +test/**/*.db* +.env \ No newline at end of file diff --git a/packages/drizzle/drizzle-mysql.config.ts b/packages/drizzle/drizzle-mysql.config.ts new file mode 100644 index 00000000..ebfc0428 --- /dev/null +++ b/packages/drizzle/drizzle-mysql.config.ts @@ -0,0 +1,11 @@ +import { defineConfig } from "drizzle-kit" +import { config } from "./env.config" + +export default defineConfig({ + dialect: "mysql", + schema: "./test/schema/mysql.ts", + dbCredentials: { + url: config.mysqlUrl, + }, + tablesFilter: ["drizzle_*"], +}) diff --git a/packages/drizzle/drizzle-postgres.config.ts b/packages/drizzle/drizzle-postgres.config.ts new file mode 100644 index 00000000..c0ef3f3e --- /dev/null +++ b/packages/drizzle/drizzle-postgres.config.ts @@ -0,0 +1,12 @@ +import "dotenv/config" +import { defineConfig } from "drizzle-kit" +import { config } from "./env.config" + +export default defineConfig({ + dialect: "postgresql", + schema: "./test/schema/postgres.ts", + dbCredentials: { + url: config.postgresUrl, + }, + tablesFilter: ["drizzle_*"], +}) diff --git a/packages/drizzle/drizzle-sqlite-1.config.ts b/packages/drizzle/drizzle-sqlite-1.config.ts new file mode 100644 index 00000000..c9d5436d --- /dev/null +++ b/packages/drizzle/drizzle-sqlite-1.config.ts @@ -0,0 +1,9 @@ +import { defineConfig } from "drizzle-kit" + +export default defineConfig({ + dialect: "sqlite", + schema: "./test/schema/sqlite.ts", + dbCredentials: { + url: "file:./test/schema/sqlite-1.db", + }, +}) diff --git a/packages/drizzle/drizzle-sqlite.config.ts b/packages/drizzle/drizzle-sqlite.config.ts new file mode 100644 index 00000000..31415b4a --- /dev/null +++ b/packages/drizzle/drizzle-sqlite.config.ts @@ -0,0 +1,9 @@ +import { defineConfig } from "drizzle-kit" + +export default defineConfig({ + dialect: "sqlite", + schema: "./test/schema/sqlite.ts", + dbCredentials: { + url: "file:./test/schema/sqlite.db", + }, +}) diff --git a/packages/drizzle/env.config.ts b/packages/drizzle/env.config.ts new file mode 100644 index 00000000..17e14ec3 --- /dev/null +++ b/packages/drizzle/env.config.ts @@ -0,0 +1,9 @@ +import * as dotenv from "dotenv" + +dotenv.config({ path: new URL("./.env", import.meta.url) }) + +export const config = { + mysqlUrl: process.env.MYSQL_URL ?? "mysql://root@localhost:3306/mysql", + postgresUrl: + process.env.POSTGRESQL_URL ?? "postgres://postgres@localhost:5432/postgres", +} diff --git a/packages/drizzle/package.json b/packages/drizzle/package.json new file mode 100644 index 00000000..68cae04e --- /dev/null +++ b/packages/drizzle/package.json @@ -0,0 +1,64 @@ +{ + "name": "@gqloom/drizzle", + "version": "0.6.0", + "description": "GQLoom integration with Drizzle ORM", + "type": "module", + "main": "./dist/index.js", + "types": "./dist/index.d.ts", + "exports": { + ".": { + "import": { + "types": "./dist/index.d.ts", + "default": "./dist/index.js" + }, + "require": { + "types": "./dist/index.d.cts", + "default": "./dist/index.cjs" + } + } + }, + "scripts": { + "build": "tsup", + "push": "drizzle-kit push --config=drizzle-mysql.config.ts && drizzle-kit push --config=drizzle-postgres.config.ts && drizzle-kit push --config=drizzle-sqlite.config.ts && drizzle-kit push --config=drizzle-sqlite-1.config.ts", + "postinstall": "pnpm run push" + }, + "files": ["dist"], + "keywords": [ + "gqloom", + "graphql", + "schema", + "typescript", + "drizzle", + "drizzle-orm" + ], + "author": "xcfox", + "license": "MIT", + "peerDependencies": { + "@gqloom/core": ">= 0.6.0", + "drizzle-orm": ">= 0.38.0", + "graphql": ">= 16.8.0" + }, + "devDependencies": { + "@gqloom/core": "workspace:*", + "@libsql/client": "^0.14.0", + "@types/pg": "^8.11.10", + "dotenv": "^16.4.7", + "drizzle-kit": "^0.30.1", + "drizzle-orm": "^0.38.3", + "graphql": "^16.8.1", + "graphql-yoga": "^5.6.0", + "mysql2": "^3.12.0", + "pg": "^8.13.1", + "tsx": "^4.7.2", + "valibot": "1.0.0-beta.12" + }, + "homepage": "https://gqloom.dev/", + "repository": { + "type": "git", + "url": "https://github.com/modevol-com/gqloom.git", + "directory": "packages/drizzle" + }, + "publishConfig": { + "access": "public" + } +} diff --git a/packages/drizzle/src/helper.ts b/packages/drizzle/src/helper.ts new file mode 100644 index 00000000..2edda43c --- /dev/null +++ b/packages/drizzle/src/helper.ts @@ -0,0 +1,34 @@ +import type { Column, SQL } from "drizzle-orm" +import { sql } from "drizzle-orm" + +/** + * Creates an IN clause for multiple columns + * Example: (col1, col2) IN ((val1, val2), (val3, val4)) + */ +export function inArrayMultiple( + columns: Column[], + values: readonly unknown[][] +): 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}`), + sql`, ` + )})` + + // Create ((val1, val2), (val3, val4), ...) part + const valueTuples = values.map( + (tuple) => + sql`(${sql.join( + tuple.map((v) => sql`${v}`), + sql`, ` + )})` + ) + const valuesPart = sql.join(valueTuples, sql`, `) + + // Combine into final IN clause + return sql`${columnsPart} IN (${valuesPart})` +} diff --git a/packages/drizzle/src/index.ts b/packages/drizzle/src/index.ts new file mode 100644 index 00000000..b3002530 --- /dev/null +++ b/packages/drizzle/src/index.ts @@ -0,0 +1,196 @@ +import { + type GraphQLSilk, + SYMBOLS, + type StandardSchemaV1, + mapValue, + pascalCase, + silk, + weaverContext, +} from "@gqloom/core" +import { + type Column, + type InferSelectModel, + type Table, + getTableColumns, + getTableName, + is, +} from "drizzle-orm" +import { MySqlInt, MySqlSerial } from "drizzle-orm/mysql-core" +import { type PgArray, PgInteger, PgSerial } from "drizzle-orm/pg-core" +import { SQLiteInteger } from "drizzle-orm/sqlite-core" +import { + GraphQLBoolean, + type GraphQLFieldConfig, + GraphQLFloat, + GraphQLInt, + GraphQLList, + GraphQLNonNull, + GraphQLObjectType, + type GraphQLOutputType, + GraphQLString, + isNonNullType, +} from "graphql" +import type { DrizzleWeaverConfig, DrizzleWeaverConfigOptions } from "./types" + +export class DrizzleWeaver { + static vendor = "gqloom.drizzle" + + /** + * get GraphQL Silk from drizzle table + * @param table drizzle table + * @returns GraphQL Silk Like drizzle table + */ + static unravel(table: TTable): TableSilk { + Object.defineProperty(table, "~standard", { + value: { + version: 1, + vendor: DrizzleWeaver.vendor, + validate: (value: unknown) => ({ + value: value as InferSelectModel, + }), + } satisfies StandardSchemaV1.Props, unknown>, + enumerable: false, + writable: true, + configurable: true, + }) + + Object.defineProperty(table, SYMBOLS.GET_GRAPHQL_TYPE, { + value: DrizzleWeaver.getGraphQLTypeBySelf, + enumerable: false, + writable: true, + configurable: true, + }) + + Object.defineProperty(table, "$nullable", { + value: function () { + return silk.nullable(this as unknown as GraphQLSilk) + }, + enumerable: false, + writable: true, + configurable: true, + }) + + Object.defineProperty(table, "$list", { + value: function () { + return silk.list(this as unknown as GraphQLSilk) as GraphQLSilk< + InferSelectModel[] + > + }, + enumerable: false, + writable: true, + configurable: true, + }) + + return table as TableSilk + } + + static getGraphQLTypeBySelf(this: Table): GraphQLOutputType { + return DrizzleWeaver.getGraphQLType(this) + } + + static getGraphQLType(table: Table): GraphQLNonNull { + const name = `${pascalCase(getTableName(table))}Item` + + const existing = weaverContext.getNamedType(name) + if (existing != null) + return new GraphQLNonNull(existing as GraphQLObjectType) + + const columns = getTableColumns(table) + return new GraphQLNonNull( + weaverContext.memoNamedType( + new GraphQLObjectType({ + name, + fields: mapValue(columns, (value) => { + return DrizzleWeaver.getFieldConfig(value) + }), + }) + ) + ) + } + + static getFieldConfig(column: Column): GraphQLFieldConfig { + let type = DrizzleWeaver.getColumnType(column) + + if (column.notNull && !isNonNullType(type)) { + type = new GraphQLNonNull(type) + } + return { type } + } + + static getColumnType(column: Column): GraphQLOutputType { + const config = + weaverContext.getConfig("gqloom.drizzle") + + const presetType = config?.presetGraphQLType?.(column) + if (presetType) return presetType + + switch (column.dataType) { + case "boolean": { + return GraphQLBoolean + } + case "number": { + return is(column, PgInteger) || + is(column, PgSerial) || + is(column, MySqlInt) || + is(column, MySqlSerial) || + is(column, SQLiteInteger) + ? GraphQLInt + : GraphQLFloat + } + case "json": + case "date": + case "bigint": + case "string": { + return GraphQLString + } + case "buffer": { + return new GraphQLList(new GraphQLNonNull(GraphQLInt)) + } + case "array": { + if ("baseColumn" in column) { + const innerType = DrizzleWeaver.getColumnType( + (column as Column as PgArray).baseColumn + ) + return new GraphQLList(new GraphQLNonNull(innerType)) + } + throw new Error(`Type: ${column.columnType} is not implemented!`) + } + default: { + throw new Error(`Type: ${column.columnType} is not implemented!`) + } + } + } + + static config(config: DrizzleWeaverConfigOptions): DrizzleWeaverConfig { + return { + ...config, + [SYMBOLS.WEAVER_CONFIG]: "gqloom.drizzle", + } + } +} + +/** + * get GraphQL Silk from drizzle table + * @param table drizzle table + * @returns GraphQL Silk Like drizzle table + */ +export function drizzleSilk( + table: TTable +): TableSilk { + return DrizzleWeaver.unravel(table) +} + +export type TableSilk = TTable & + GraphQLSilk, InferSelectModel> & { + $nullable: () => GraphQLSilk< + InferSelectModel | null | undefined, + InferSelectModel | null | undefined + > + $list: () => GraphQLSilk< + InferSelectModel[], + InferSelectModel[] + > + } + +export * from "./input-factory" +export * from "./resolver-factory" diff --git a/packages/drizzle/src/input-factory.ts b/packages/drizzle/src/input-factory.ts new file mode 100644 index 00000000..b39e8d2c --- /dev/null +++ b/packages/drizzle/src/input-factory.ts @@ -0,0 +1,353 @@ +import { mapValue, pascalCase, weaverContext } from "@gqloom/core" +import { + type Column, + type InferInsertModel, + type InferSelectModel, + type Table, + getTableColumns, + getTableName, +} from "drizzle-orm" +import { + GraphQLBoolean, + GraphQLEnumType, + type GraphQLFieldConfig, + GraphQLInt, + GraphQLList, + GraphQLNonNull, + GraphQLObjectType, + GraphQLString, + isNonNullType, +} from "graphql" +import { DrizzleWeaver } from "./index" + +export class DrizzleInputFactory { + constructor(public readonly table: TTable) {} + + public selectArrayArgs() { + const name = `${pascalCase(getTableName(this.table))}SelectArrayArgs` + const existing = weaverContext.getNamedType(name) as GraphQLObjectType + if (existing) return existing + + return weaverContext.memoNamedType( + new GraphQLObjectType>({ + name, + fields: { + offset: { type: GraphQLInt }, + limit: { type: GraphQLInt }, + orderBy: { + type: new GraphQLList(new GraphQLNonNull(this.orderBy())), + }, + where: { type: this.filters() }, + }, + }) + ) + } + + public selectSingleArgs() { + const name = `${pascalCase(getTableName(this.table))}SelectSingleArgs` + const existing = weaverContext.getNamedType(name) as GraphQLObjectType + if (existing != null) return existing + + return weaverContext.memoNamedType( + new GraphQLObjectType>({ + name, + fields: { + offset: { type: GraphQLInt }, + orderBy: { + type: new GraphQLList(new GraphQLNonNull(this.orderBy())), + }, + where: { type: this.filters() }, + }, + }) + ) + } + + public insertArrayArgs() { + const name = `${pascalCase(getTableName(this.table))}InsertArrayArgs` + const existing = weaverContext.getNamedType(name) as GraphQLObjectType + if (existing != null) return existing + + return weaverContext.memoNamedType( + new GraphQLObjectType>({ + name, + fields: { + values: { + type: new GraphQLNonNull( + new GraphQLList(new GraphQLNonNull(this.insertInput())) + ), + }, + }, + }) + ) + } + + public insertSingleArgs() { + const name = `${pascalCase(getTableName(this.table))}InsertSingleArgs` + const existing = weaverContext.getNamedType(name) as GraphQLObjectType + if (existing != null) return existing + + return weaverContext.memoNamedType( + new GraphQLObjectType>({ + name, + fields: { + value: { type: new GraphQLNonNull(this.insertInput()) }, + }, + }) + ) + } + + public updateArgs() { + const name = `${pascalCase(getTableName(this.table))}UpdateArgs` + const existing = weaverContext.getNamedType(name) as GraphQLObjectType + if (existing != null) return existing + + return weaverContext.memoNamedType( + new GraphQLObjectType>({ + name, + fields: { + where: { type: this.filters() }, + set: { type: new GraphQLNonNull(this.updateInput()) }, + }, + }) + ) + } + + public deleteArgs() { + const name = `${pascalCase(getTableName(this.table))}DeleteArgs` + const existing = weaverContext.getNamedType(name) as GraphQLObjectType + if (existing != null) return existing + + return weaverContext.memoNamedType( + new GraphQLObjectType>({ + name, + fields: { + where: { type: this.filters() }, + }, + }) + ) + } + + public insertInput() { + const name = `${pascalCase(getTableName(this.table))}InsertInput` + const existing = weaverContext.getNamedType(name) as GraphQLObjectType + if (existing != null) return existing + + const columns = getTableColumns(this.table) + return weaverContext.memoNamedType( + new GraphQLObjectType({ + name, + fields: mapValue(columns, (column) => { + const type = (() => { + const t = DrizzleWeaver.getColumnType(column) + if (column.hasDefault) return t + if (column.notNull && !isNonNullType(t)) + return new GraphQLNonNull(t) + return t + })() + + return { type } + }), + }) + ) + } + + public updateInput() { + const name = `${pascalCase(getTableName(this.table))}UpdateInput` + const existing = weaverContext.getNamedType(name) as GraphQLObjectType + if (existing != null) return existing + + const columns = getTableColumns(this.table) + return weaverContext.memoNamedType( + new GraphQLObjectType({ + name, + fields: mapValue(columns, (column) => { + const type = DrizzleWeaver.getColumnType(column) + return { type } + }), + }) + ) + } + + public filters() { + const name = `${pascalCase(getTableName(this.table))}Filters` + const existing = weaverContext.getNamedType(name) as GraphQLObjectType + if (existing != null) return existing + + const columns = getTableColumns(this.table) + const filterFields: Record< + string, + GraphQLFieldConfig + > = mapValue(columns, (column) => ({ + type: DrizzleInputFactory.columnFilters(column), + })) + + const filtersOr = new GraphQLObjectType({ + name: `${pascalCase(getTableName(this.table))}FiltersOr`, + fields: { ...filterFields }, + }) + return weaverContext.memoNamedType( + new GraphQLObjectType({ + name, + fields: { + ...filterFields, + OR: { type: new GraphQLList(new GraphQLNonNull(filtersOr)) }, + }, + }) + ) + } + + protected static columnFilters(column: Column) { + const name = `${pascalCase(column.columnType)}Filters` + const existing = weaverContext.getNamedType(name) as GraphQLObjectType + if (existing != null) return existing + + const gqlType = DrizzleWeaver.getColumnType(column) + const gqlListType = new GraphQLList(new GraphQLNonNull(gqlType)) + const baseFields: Record> = { + eq: { type: gqlType }, + ne: { type: gqlType }, + lt: { type: gqlType }, + lte: { type: gqlType }, + gt: { type: gqlType }, + gte: { type: gqlType }, + ...(gqlType === GraphQLString && { + like: { type: GraphQLString }, + notLike: { type: GraphQLString }, + ilike: { type: GraphQLString }, + notIlike: { type: GraphQLString }, + }), + inArray: { type: gqlListType }, + notInArray: { type: gqlListType }, + isNull: { type: GraphQLBoolean }, + isNotNull: { type: GraphQLBoolean }, + } + + const filtersOr = new GraphQLObjectType({ + name: `${pascalCase(column.columnType)}FiltersOr`, + fields: { ...baseFields }, + }) + + return weaverContext.memoNamedType( + new GraphQLObjectType({ + name, + fields: { + ...baseFields, + OR: { type: new GraphQLList(new GraphQLNonNull(filtersOr)) }, + }, + }) + ) + } + + public orderBy() { + const name = `${pascalCase(getTableName(this.table))}OrderBy` + const existing = weaverContext.getNamedType(name) as GraphQLObjectType + if (existing != null) return existing + + const columns = getTableColumns(this.table) + return weaverContext.memoNamedType( + new GraphQLObjectType({ + name, + fields: mapValue(columns, () => { + const type = DrizzleInputFactory.orderDirection() + return { type } + }), + }) + ) + } + + protected static orderDirection() { + const name = "OrderDirection" + const existing = weaverContext.getNamedType(name) as GraphQLEnumType + if (existing != null) return existing + + return weaverContext.memoNamedType( + new GraphQLEnumType({ + name, + values: { + asc: { value: "asc" }, + desc: { value: "desc" }, + }, + }) + ) + } + + public static mutationResult() { + const name = "MutationSuccessResult" + const existing = weaverContext.getNamedType(name) as GraphQLObjectType + if (existing != null) return existing + + return weaverContext.memoNamedType( + new GraphQLObjectType({ + name, + fields: { + isSuccess: { type: new GraphQLNonNull(GraphQLBoolean) }, + }, + }) + ) + } +} + +export interface SelectArrayArgs { + offset?: number + limit?: number + orderBy?: Partial, "asc" | "desc">>[] + where?: Filters +} + +export interface SelectSingleArgs { + offset?: number + orderBy?: Partial, "asc" | "desc">>[] + where?: Filters +} + +export interface InsertArrayArgs { + values: InferInsertModel[] +} + +export interface InsertSingleArgs { + value: InferInsertModel +} + +export interface UpdateArgs { + where?: Filters + set: Partial> +} + +export interface DeleteArgs { + where?: Filters +} + +export type FiltersCore = Partial<{ + [Column in keyof TTable["_"]["columns"]]: ColumnFilters< + TTable["_"]["columns"][Column]["_"]["data"] + > +}> + +export type Filters = FiltersCore & { + OR?: FiltersCore[] +} + +export interface ColumnFiltersCore { + eq?: TType + ne?: TType + lt?: TType + lte?: TType + gt?: TType + gte?: TType + like?: TType extends string ? string : never + notLike?: TType extends string ? string : never + ilike?: TType extends string ? string : never + notIlike?: TType extends string ? string : never + inArray?: TType[] + notInArray?: TType[] + isNull?: boolean + isNotNull?: boolean +} + +export interface ColumnFilters extends ColumnFiltersCore { + OR?: ColumnFiltersCore[] +} + +export interface MutationResult { + isSuccess: boolean +} diff --git a/packages/drizzle/src/resolver-factory.ts b/packages/drizzle/src/resolver-factory.ts new file mode 100644 index 00000000..91d42e40 --- /dev/null +++ b/packages/drizzle/src/resolver-factory.ts @@ -0,0 +1,1128 @@ +import { + EasyDataLoader, + type FieldOrOperation, + type GraphQLFieldOptions, + type GraphQLSilk, + type Middleware, + capitalize, + createMemoization, + loom, + mapValue, + silk, +} from "@gqloom/core" +import { + type Column, + type InferSelectModel, + Many, + type Relation, + type SQL, + type Table, + and, + asc, + desc, + eq, + getTableColumns, + getTableName, + gt, + gte, + ilike, + inArray, + isNotNull, + isNull, + like, + lt, + lte, + ne, + normalizeRelation, + notIlike, + notInArray, + notLike, + or, +} from "drizzle-orm" +import { MySqlDatabase, type MySqlTable } from "drizzle-orm/mysql-core" +import type { RelationalQueryBuilder as MySqlRelationalQueryBuilder } from "drizzle-orm/mysql-core/query-builders/query" +import { PgDatabase, type PgTable } from "drizzle-orm/pg-core" +import type { RelationalQueryBuilder as PgRelationalQueryBuilder } from "drizzle-orm/pg-core/query-builders/query" +import type { BaseSQLiteDatabase, SQLiteTable } from "drizzle-orm/sqlite-core" +import type { RelationalQueryBuilder as SQLiteRelationalQueryBuilder } from "drizzle-orm/sqlite-core/query-builders/query" +import { GraphQLError } from "graphql" +import { DrizzleWeaver, type TableSilk } from "." +import { inArrayMultiple } from "./helper" +import { + type ColumnFilters, + type DeleteArgs, + DrizzleInputFactory, + type FiltersCore, + type InsertArrayArgs, + type InsertSingleArgs, + type MutationResult, + type SelectArrayArgs, + type SelectSingleArgs, + type UpdateArgs, +} from "./input-factory" + +export abstract class DrizzleResolverFactory< + TDatabase extends BaseDatabase, + TTable extends Table, +> { + static create< + TDatabase extends BaseSQLiteDatabase, + TTableName extends keyof NonNullable, + >( + db: TDatabase, + tableName: TTableName + ): DrizzleSQLiteResolverFactory< + TDatabase, + NonNullable[TTableName] + > + static create< + TDatabase extends BaseSQLiteDatabase, + TTable extends SQLiteTable, + >( + db: TDatabase, + table: TTable + ): DrizzleSQLiteResolverFactory + + static create< + TDatabase extends PgDatabase, + TTableName extends keyof NonNullable, + >( + db: TDatabase, + tableName: TTableName + ): DrizzlePostgresResolverFactory< + TDatabase, + NonNullable[TTableName] + > + static create< + TDatabase extends PgDatabase, + TTable extends PgTable, + >( + db: TDatabase, + table: TTable + ): DrizzlePostgresResolverFactory + + static create< + TDatabase extends MySqlDatabase, + TTableName extends keyof NonNullable, + >( + db: TDatabase, + tableName: TTableName + ): DrizzleMySQLResolverFactory< + TDatabase, + NonNullable[TTableName] + > + static create< + TDatabase extends MySqlDatabase, + TTable extends MySqlTable, + >( + db: TDatabase, + table: TTable + ): DrizzleMySQLResolverFactory + + static create(db: BaseDatabase, tableOrName: Table | string) { + const table = + typeof tableOrName === "string" + ? (db._.fullSchema[tableOrName] as Table) + : tableOrName + if (db instanceof PgDatabase) { + return new DrizzlePostgresResolverFactory(db, table as PgTable) + } + if (db instanceof MySqlDatabase) { + return new DrizzleMySQLResolverFactory(db, table as MySqlTable) + } + return new DrizzleSQLiteResolverFactory(db, table as SQLiteTable) + } + + public readonly inputFactory: DrizzleInputFactory + public readonly tableName: InferTableName + public readonly queryBuilder: QueryBuilder> + constructor( + public readonly db: TDatabase, + public readonly table: TTable + ) { + this.inputFactory = new DrizzleInputFactory(table) + this.tableName = getTableName(table) + const queryBuilder = this.db.query[ + this.tableName as keyof typeof this.db.query + ] as QueryBuilder> + + 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 + } + + private _output?: TableSilk + protected get output() { + this._output ??= DrizzleWeaver.unravel(this.table) + return this._output as TableSilk + } + + public selectArrayQuery>({ + input, + ...options + }: GraphQLFieldOptions & { + input?: GraphQLSilk, TInputI> + middlewares?: Middleware>[] + } = {}): SelectArrayQuery { + const queryBase = this.queryBuilder + input ??= silk< + InferSelectArrayOptions, + SelectArrayArgs + >( + () => this.inputFactory.selectArrayArgs(), + (args) => ({ + value: { + where: this.extractFilters(args.where), + orderBy: this.extractOrderBy(args.orderBy), + limit: args.limit, + offset: args.offset, + }, + }) + ) as GraphQLSilk, TInputI> + + return loom.query(this.output.$list(), { + input, + ...options, + resolve: (opts) => { + return queryBase.findMany(opts) as any + }, + }) + } + + public selectSingleQuery>({ + input, + ...options + }: GraphQLFieldOptions & { + input?: GraphQLSilk, TInputI> + middlewares?: Middleware>[] + } = {}): SelectSingleQuery { + const queryBase = this.queryBuilder + input ??= silk< + InferSelectSingleOptions, + SelectSingleArgs + >( + () => this.inputFactory.selectSingleArgs(), + (args) => ({ + value: { + where: this.extractFilters(args.where), + orderBy: this.extractOrderBy(args.orderBy), + offset: args.offset, + }, + }) + ) as GraphQLSilk, TInputI> + + return loom.query(this.output.$nullable(), { + input, + ...options, + resolve: (opts) => { + return queryBase.findFirst(opts) as any + }, + }) + } + + 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 { + if (filters == null) return + const tableName = getTableName(this.table) + + if (!filters.OR?.length) delete filters.OR + + const entries = Object.entries(filters as FiltersCore) + + 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 variant of filters.OR) { + const extracted = this.extractFilters(variant) + if (extracted) variants.push(extracted) + } + + return or(...variants) + } + + const variants: SQL[] = [] + for (const [columnName, operators] of entries) { + if (operators == null) continue + + const column = getTableColumns(this.table)[columnName]! + variants.push(this.extractFiltersColumn(column, columnName, operators)!) + } + + return and(...variants) + } + + protected extractFiltersColumn( + column: TColumn, + columnName: string, + operators: ColumnFilters + ): SQL | undefined { + if (!operators.OR?.length) delete operators.OR + + 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) + + 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 } + const arrayOperators = { inArray, notInArray } + const nullOperators = { isNull, isNotNull } + + 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)) + } else if (operatorName in textOperators) { + const operator = + textOperators[operatorName as keyof typeof textOperators] + variants.push(operator(column, operatorValue)) + } else if (operatorName in arrayOperators) { + const operator = + arrayOperators[operatorName as keyof typeof arrayOperators] + variants.push(operator(column, operatorValue)) + } else if (operatorName in nullOperators) { + const operator = + nullOperators[operatorName as keyof typeof nullOperators] + if (operatorValue === true) variants.push(operator(column)) + } + } + + return and(...variants) + } + + public relationField< + TRelationName extends keyof InferTableRelationalConfig< + QueryBuilder> + >["relations"], + >( + relationName: TRelationName, + options?: GraphQLFieldOptions & { + middlewares?: Middleware< + InferTableRelationalConfig< + QueryBuilder> + >["relations"][TRelationName] extends Many + ? RelationManyField< + TTable, + InferRelationTable + > + : RelationOneField< + TTable, + InferRelationTable + > + >[] + } + ): InferTableRelationalConfig< + QueryBuilder> + >["relations"][TRelationName] extends Many + ? RelationManyField< + TTable, + InferRelationTable + > + : RelationOneField< + TTable, + InferRelationTable + > { + const relation = this.db._.schema?.[this.tableName]?.relations?.[ + 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?` + ) + } + const output = DrizzleWeaver.unravel(relation.referencedTable) + const tableName = getTableName(relation.referencedTable) + 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 getKeyByField = (parent: any) => { + if (fieldsLength === 1) { + return parent[normalizedRelation.fields[0].name] + } + return normalizedRelation.fields + .map((field) => parent[field.name]) + .join("-") + } + + const getKeyByReference = (item: any) => { + if (fieldsLength === 1) { + return item[normalizedRelation.references[0].name] + } + return normalizedRelation.references + .map((reference) => item[reference.name]) + .join("-") + } + + const useLoader = createMemoization(() => { + return new EasyDataLoader(async (parents: any[]) => { + const where = (() => { + if (fieldsLength === 1) { + const values = parents.map( + (parent) => parent[normalizedRelation.fields[0].name] + ) + return inArray(normalizedRelation.references[0], values) + } + const values = parents.map((parent) => + normalizedRelation.fields.map((field) => parent[field.name]) + ) + return inArrayMultiple(normalizedRelation.references, values) + })() + + const list = await queryBuilder.findMany({ 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 parents.map((parent) => { + const key = getKeyByField(parent) + return groups.get(key) ?? (isList ? [] : null) + }) + }) + }) + + return loom.field(isList ? output.$list() : output.$nullable(), { + ...options, + resolve: (parent) => { + const loader = useLoader() + return loader.load(parent) + }, + }) as any + } + + public resolver(options?: { + name?: TTableName + middlewares?: Middleware[] + }): DrizzleResolver { + const name = options?.name ?? this.tableName + + const fields: Record< + string, + FieldOrOperation + > = mapValue( + this.db._.schema?.[this.tableName]?.relations ?? {}, + (_, key) => this.relationField(key) + ) + + return loom.resolver.of( + this.output, + { + ...fields, + [name]: this.selectArrayQuery(), + [`${name}Single`]: this.selectSingleQuery(), + [`insertInto${capitalize(name)}`]: this.insertArrayMutation(), + [`insertInto${capitalize(name)}Single`]: this.insertSingleMutation(), + [`update${capitalize(name)}`]: this.updateMutation(), + [`deleteFrom${capitalize(name)}`]: this.deleteMutation(), + }, + options + ) as any + } + + public abstract insertArrayMutation>( + options?: GraphQLFieldOptions & { + input?: GraphQLSilk, TInputI> + middlewares?: Middleware>[] + } + ): InsertArrayMutation + + public abstract insertSingleMutation>( + options?: GraphQLFieldOptions & { + input?: GraphQLSilk, TInputI> + middlewares?: Middleware>[] + } + ): InsertSingleMutation + + public abstract updateMutation>( + options?: GraphQLFieldOptions & { + input?: GraphQLSilk, TInputI> + middlewares?: Middleware>[] + } + ): UpdateMutation + + public abstract deleteMutation>( + options?: GraphQLFieldOptions & { + input?: GraphQLSilk, TInputI> + middlewares?: Middleware>[] + } + ): DeleteMutation +} + +export class DrizzleMySQLResolverFactory< + TDatabase extends MySqlDatabase, + TTable extends MySqlTable, +> extends DrizzleResolverFactory { + protected static get mutationResult() { + return silk(() => + DrizzleInputFactory.mutationResult() + ) + } + + public insertArrayMutation>({ + input, + ...options + }: GraphQLFieldOptions & { + input?: GraphQLSilk, TInputI> + middlewares?: Middleware< + InsertArrayMutationReturningSuccess + >[] + } = {}): InsertArrayMutationReturningSuccess { + input ??= silk(() => this.inputFactory.insertArrayArgs()) + + return loom.mutation(DrizzleMySQLResolverFactory.mutationResult, { + ...options, + input, + resolve: async (input) => { + await this.db.insert(this.table).values(input.values) + return { isSuccess: true } + }, + }) + } + + public insertSingleMutation>({ + input, + ...options + }: GraphQLFieldOptions & { + input?: GraphQLSilk, TInputI> + middlewares?: Middleware< + InsertSingleMutationReturningSuccess + >[] + } = {}): InsertSingleMutationReturningSuccess { + input ??= silk(() => this.inputFactory.insertSingleArgs()) + + return loom.mutation(DrizzleMySQLResolverFactory.mutationResult, { + ...options, + input, + resolve: async (args) => { + await this.db.insert(this.table).values(args.value) + return { isSuccess: true } + }, + }) + } + + public updateMutation>({ + input, + ...options + }: GraphQLFieldOptions & { + input?: GraphQLSilk, TInputI> + middlewares?: Middleware>[] + } = {}): UpdateMutationReturningSuccess { + input ??= silk(() => this.inputFactory.updateArgs()) + + return loom.mutation(DrizzleMySQLResolverFactory.mutationResult, { + ...options, + input, + resolve: async (args) => { + let query = this.db.update(this.table).set(args.set) + if (args.where) { + query = query.where(this.extractFilters(args.where)) as any + } + + await query + + return { isSuccess: true } + }, + }) + } + + public deleteMutation>({ + input, + ...options + }: GraphQLFieldOptions & { + input?: GraphQLSilk, TInputI> + middlewares?: Middleware>[] + } = {}): DeleteMutationReturningSuccess { + input ??= silk(() => this.inputFactory.deleteArgs()) + + return loom.mutation(DrizzleMySQLResolverFactory.mutationResult, { + ...options, + input, + resolve: async (args) => { + let query = this.db.delete(this.table) + if (args.where) { + query = query.where(this.extractFilters(args.where)) as any + } + await query + return { isSuccess: true } + }, + }) + } + + public resolver( + options: { + name?: TTableName + middlewares?: Middleware[] + } = {} + ): DrizzleResolverReturningSuccess { + return super.resolver(options) as DrizzleResolverReturningSuccess< + TDatabase, + TTable, + TTableName + > + } +} + +export class DrizzlePostgresResolverFactory< + TDatabase extends PgDatabase, + TTable extends PgTable, +> extends DrizzleResolverFactory { + public insertArrayMutation>({ + input, + ...options + }: GraphQLFieldOptions & { + input?: GraphQLSilk, TInputI> + middlewares?: Middleware< + InsertArrayMutationReturningItems + >[] + } = {}): InsertArrayMutationReturningItems { + input ??= silk(() => this.inputFactory.insertArrayArgs()) + + return loom.mutation(this.output.$list(), { + ...options, + input, + resolve: async (args) => { + const result = await this.db + .insert(this.table) + .values(args.values) + .returning() + .onConflictDoNothing() + + return result + }, + }) + } + + public insertSingleMutation>({ + input, + ...options + }: GraphQLFieldOptions & { + input?: GraphQLSilk, TInputI> + middlewares?: Middleware< + InsertSingleMutationReturningItem + >[] + } = {}): InsertSingleMutationReturningItem { + input ??= silk(() => this.inputFactory.insertSingleArgs()) + + return loom.mutation(this.output.$nullable(), { + ...options, + input, + resolve: async (args) => { + const result = await this.db + .insert(this.table) + .values(args.value) + .returning() + .onConflictDoNothing() + + return result[0] as any + }, + }) + } + + public updateMutation>({ + input, + ...options + }: GraphQLFieldOptions & { + input?: GraphQLSilk, TInputI> + middlewares?: Middleware>[] + } = {}): UpdateMutationReturningItems { + input ??= silk(() => this.inputFactory.updateArgs()) + + return loom.mutation(this.output.$list(), { + ...options, + input, + resolve: async (args) => { + const query = this.db.update(this.table).set(args.set) + if (args.where) { + query.where(this.extractFilters(args.where)) + } + return await query.returning() + }, + }) + } + + public deleteMutation>({ + input, + ...options + }: GraphQLFieldOptions & { + input?: GraphQLSilk, TInputI> + middlewares?: Middleware>[] + } = {}): DeleteMutationReturningItems { + input ??= silk(() => this.inputFactory.deleteArgs()) + + return loom.mutation(this.output.$list(), { + ...options, + input, + resolve: async (args) => { + const query = this.db.delete(this.table) + if (args.where) { + query.where(this.extractFilters(args.where)) + } + return await query.returning() + }, + }) + } + + public resolver( + options: { + name?: TTableName + middlewares?: Middleware[] + } = {} + ): DrizzleResolverReturningItems { + return super.resolver(options) as DrizzleResolverReturningItems< + TDatabase, + TTable, + TTableName + > + } +} + +export class DrizzleSQLiteResolverFactory< + TDatabase extends BaseSQLiteDatabase, + TTable extends SQLiteTable, +> extends DrizzleResolverFactory { + public insertArrayMutation>({ + input, + ...options + }: GraphQLFieldOptions & { + input?: GraphQLSilk, TInputI> + middlewares?: Middleware< + InsertArrayMutationReturningItems + >[] + } = {}): InsertArrayMutationReturningItems { + input ??= silk(() => this.inputFactory.insertArrayArgs()) + + return loom.mutation(this.output.$list(), { + ...options, + input, + resolve: async (args) => { + const result = await this.db + .insert(this.table) + .values(args.values) + .returning() + .onConflictDoNothing() + return result + }, + }) + } + + public insertSingleMutation>({ + input, + ...options + }: GraphQLFieldOptions & { + input?: GraphQLSilk, TInputI> + middlewares?: Middleware< + InsertSingleMutationReturningItem + >[] + } = {}): InsertSingleMutationReturningItem { + input ??= silk(() => this.inputFactory.insertSingleArgs()) + return loom.mutation(this.output.$nullable(), { + ...options, + input, + resolve: async (args) => { + const result = await this.db + .insert(this.table) + .values(args.value) + .returning() + .onConflictDoNothing() + + return result[0] as any + }, + }) + } + + public updateMutation>({ + input, + ...options + }: GraphQLFieldOptions & { + input?: GraphQLSilk, TInputI> + middlewares?: Middleware>[] + } = {}): UpdateMutationReturningItems { + input ??= silk(() => this.inputFactory.updateArgs()) + + return loom.mutation(this.output.$list(), { + ...options, + input, + resolve: async (args) => { + const query = this.db.update(this.table).set(args.set) + if (args.where) { + query.where(this.extractFilters(args.where)) + } + return await query.returning() + }, + }) + } + + public deleteMutation>({ + input, + ...options + }: GraphQLFieldOptions & { + input?: GraphQLSilk, TInputI> + middlewares?: Middleware>[] + } = {}): DeleteMutationReturningItems { + input ??= silk(() => this.inputFactory.deleteArgs()) + + return loom.mutation(this.output.$list(), { + ...options, + input, + resolve: async (args) => { + const query = this.db.delete(this.table) + if (args.where) { + query.where(this.extractFilters(args.where)) + } + return await query.returning() + }, + }) + } + + public resolver( + options: { + name?: TTableName + middlewares?: Middleware[] + } = {} + ): DrizzleResolverReturningItems { + return super.resolver(options) as DrizzleResolverReturningItems< + TDatabase, + TTable, + TTableName + > + } +} + +export type DrizzleResolver< + TDatabase extends BaseDatabase, + TTable extends Table, + TTableName extends string = TTable["_"]["name"], +> = + | DrizzleResolverReturningItems + | DrizzleResolverReturningSuccess + +export type DrizzleResolverReturningItems< + TDatabase extends BaseDatabase, + TTable extends Table, + TTableName extends string = TTable["_"]["name"], +> = { + [key in TTableName]: SelectArrayQuery +} & { + [key in `${TTableName}Single`]: SelectArrayQuery +} & { + [key in `insertInto${Capitalize}`]: InsertArrayMutationReturningItems +} & { + [key in `insertInto${Capitalize}Single`]: InsertSingleMutationReturningItem +} & { + [key in `update${Capitalize}`]: UpdateMutationReturningItems +} & { + [key in `deleteFrom${Capitalize}`]: DeleteMutationReturningItems +} & DrizzleResolverRelations + +export type DrizzleResolverReturningSuccess< + TDatabase extends BaseDatabase, + TTable extends Table, + TTableName extends string = TTable["_"]["name"], +> = { + [key in TTableName]: SelectArrayQuery +} & { + [key in `${TTableName}Single`]: SelectArrayQuery +} & { + [key in `insertInto${Capitalize}`]: InsertArrayMutationReturningSuccess +} & { + [key in `insertInto${Capitalize}Single`]: InsertSingleMutationReturningSuccess +} & { + [key in `update${Capitalize}`]: UpdateMutationReturningSuccess +} & { + [key in `deleteFrom${Capitalize}`]: DeleteMutationReturningSuccess +} & DrizzleResolverRelations + +export type DrizzleResolverRelations< + TDatabase extends BaseDatabase, + TTable extends Table, +> = { + [TRelationName in keyof InferTableRelationalConfig< + QueryBuilder> + >["relations"]]: InferTableRelationalConfig< + QueryBuilder> + >["relations"][TRelationName] extends Many + ? RelationManyField< + TTable, + InferRelationTable + > + : RelationOneField< + TTable, + InferRelationTable + > +} + +export interface SelectArrayQuery< + TDatabase extends BaseDatabase, + TTable extends Table, + TInputI = SelectArrayArgs, +> extends FieldOrOperation< + undefined, + GraphQLSilk[], InferSelectModel[]>, + GraphQLSilk, TInputI>, + "query" + > {} + +export type InferSelectArrayOptions< + TDatabase extends BaseDatabase, + TTable extends Table, +> = Parameters["findMany"]>[0] + +export interface SelectSingleQuery< + TDatabase extends BaseDatabase, + TTable extends Table, + TInputI = SelectSingleArgs, +> extends FieldOrOperation< + undefined, + GraphQLSilk< + InferSelectModel | null | undefined, + InferSelectModel | null | undefined + >, + GraphQLSilk, TInputI>, + "query" + > {} + +export type InferSelectSingleOptions< + TDatabase extends BaseDatabase, + TTable extends Table, +> = Parameters["findFirst"]>[0] + +export interface RelationManyField< + TTable extends Table, + TRelationTable extends Table, +> extends FieldOrOperation< + GraphQLSilk, InferSelectModel>, + GraphQLSilk< + InferSelectModel[], + InferSelectModel[] + >, + undefined, + "field" + > {} + +export interface RelationOneField< + TTable extends Table, + TRelationTable extends Table, +> extends FieldOrOperation< + GraphQLSilk, InferSelectModel>, + GraphQLSilk< + InferSelectModel | null | undefined, + InferSelectModel | null | undefined + >, + undefined, + "field" + > {} + +export type InsertArrayMutation< + TTable extends Table, + TInputI = InsertArrayArgs, +> = + | InsertArrayMutationReturningItems + | InsertArrayMutationReturningSuccess + +export interface InsertArrayMutationReturningItems< + TTable extends Table, + TInputI = InsertArrayArgs, +> extends FieldOrOperation< + undefined, + GraphQLSilk[], InferSelectModel[]>, + GraphQLSilk, TInputI>, + "mutation" + > {} + +export interface InsertArrayMutationReturningSuccess< + TTable extends Table, + TInputI = InsertArrayArgs, +> extends FieldOrOperation< + undefined, + GraphQLSilk, + GraphQLSilk, TInputI>, + "mutation" + > {} + +export type InsertSingleMutation< + TTable extends Table, + TInputI = InsertSingleArgs, +> = + | InsertSingleMutationReturningItem + | InsertSingleMutationReturningSuccess + +export interface InsertSingleMutationReturningItem< + TTable extends Table, + TInputI = InsertSingleArgs, +> extends FieldOrOperation< + undefined, + GraphQLSilk< + InferSelectModel | null | undefined, + InferSelectModel | null | undefined + >, + GraphQLSilk, TInputI>, + "mutation" + > {} + +export interface InsertSingleMutationReturningSuccess< + TTable extends Table, + TInputI = InsertSingleArgs, +> extends FieldOrOperation< + undefined, + GraphQLSilk, + GraphQLSilk, TInputI>, + "mutation" + > {} + +export type UpdateMutation> = + | UpdateMutationReturningItems + | UpdateMutationReturningSuccess + +export interface UpdateMutationReturningItems< + TTable extends Table, + TInputI = UpdateArgs, +> extends FieldOrOperation< + undefined, + GraphQLSilk[], InferSelectModel[]>, + GraphQLSilk, TInputI>, + "mutation" + > {} + +export interface UpdateMutationReturningSuccess< + TTable extends Table, + TInputI = UpdateArgs, +> extends FieldOrOperation< + undefined, + GraphQLSilk, + GraphQLSilk, TInputI>, + "mutation" + > {} + +export type DeleteMutation> = + | DeleteMutationReturningItems + | DeleteMutationReturningSuccess + +export interface DeleteMutationReturningItems< + TTable extends Table, + TInputI = DeleteArgs, +> extends FieldOrOperation< + undefined, + GraphQLSilk[], InferSelectModel[]>, + GraphQLSilk, TInputI>, + "mutation" + > {} + +export interface DeleteMutationReturningSuccess< + TTable extends Table, + TInputI = DeleteArgs, +> extends FieldOrOperation< + undefined, + GraphQLSilk, + GraphQLSilk, TInputI>, + "mutation" + > {} + +type QueryBuilder< + TDatabase extends BaseDatabase, + TTableName extends keyof TDatabase["_"]["schema"], +> = TDatabase["query"] extends { [key in TTableName]: any } + ? TDatabase["query"][TTableName] + : never + +type AnyQueryBuilder = + | MySqlRelationalQueryBuilder + | PgRelationalQueryBuilder + | SQLiteRelationalQueryBuilder + +type InferTableRelationalConfig = + TQueryBuilder extends MySqlRelationalQueryBuilder< + any, + any, + infer TTableRelationalConfig + > + ? TTableRelationalConfig + : TQueryBuilder extends PgRelationalQueryBuilder< + any, + infer TTableRelationalConfig + > + ? TTableRelationalConfig + : TQueryBuilder extends SQLiteRelationalQueryBuilder< + any, + any, + any, + infer TTableRelationalConfig + > + ? TTableRelationalConfig + : never + +type BaseDatabase = + | BaseSQLiteDatabase + | PgDatabase + | MySqlDatabase + +type InferTableName = TTable["_"]["name"] + +type InferRelationTable< + TDatabase extends BaseDatabase, + TTable extends Table, + TRelationName extends keyof InferTableRelationalConfig< + QueryBuilder> + >["relations"], +> = TDatabase["_"]["fullSchema"][InferTableRelationalConfig< + QueryBuilder> +>["relations"][TRelationName]["referencedTableName"]] diff --git a/packages/drizzle/src/types.ts b/packages/drizzle/src/types.ts new file mode 100644 index 00000000..82e5df58 --- /dev/null +++ b/packages/drizzle/src/types.ts @@ -0,0 +1,13 @@ +import { SYMBOLS, type WeaverConfig } from "@gqloom/core" +import type { Column } from "drizzle-orm" +import type { GraphQLOutputType } from "graphql" + +export interface DrizzleWeaverConfigOptions { + presetGraphQLType?: (column: Column) => GraphQLOutputType | undefined +} + +export interface DrizzleWeaverConfig + extends WeaverConfig, + DrizzleWeaverConfigOptions { + [SYMBOLS.WEAVER_CONFIG]: "gqloom.drizzle" +} diff --git a/packages/drizzle/test/input-factory.spec.ts b/packages/drizzle/test/input-factory.spec.ts new file mode 100644 index 00000000..fb4a4e0c --- /dev/null +++ b/packages/drizzle/test/input-factory.spec.ts @@ -0,0 +1,57 @@ +import * as pg from "drizzle-orm/pg-core" +import { printType } from "graphql" +import { describe, expect, it } from "vitest" +import { DrizzleInputFactory } from "../src" + +describe("DrizzleInputFactory", () => { + const userTable = pg.pgTable("users", { + id: pg.serial("id").primaryKey(), + name: pg + .text("name") + .notNull() + .$defaultFn(() => "John Doe"), + email: pg.text("email").notNull(), + }) + + const inputFactory = new DrizzleInputFactory(userTable) + it("should generate InsertInput type for a table", () => { + expect(printType(inputFactory.insertInput())).toMatchInlineSnapshot(` + "type UsersInsertInput { + id: Int + name: String + email: String! + }" + `) + }) + + it("should generate UpdateInput type for a table", () => { + expect(printType(inputFactory.updateInput())).toMatchInlineSnapshot(` + "type UsersUpdateInput { + id: Int + name: String + email: String + }" + `) + }) + + it("should generate Filters type for a table", () => { + expect(printType(inputFactory.filters())).toMatchInlineSnapshot(` + "type UsersFilters { + id: PgSerialFilters + name: PgTextFilters + email: PgTextFilters + OR: [UsersFiltersOr!] + }" + `) + }) + + it("should generate OrderBy type for a table", () => { + expect(printType(inputFactory.orderBy())).toMatchInlineSnapshot(` + "type UsersOrderBy { + id: OrderDirection + name: OrderDirection + email: OrderDirection + }" + `) + }) +}) diff --git a/packages/drizzle/test/resolver-factory.spec.ts b/packages/drizzle/test/resolver-factory.spec.ts new file mode 100644 index 00000000..cb3e08a3 --- /dev/null +++ b/packages/drizzle/test/resolver-factory.spec.ts @@ -0,0 +1,713 @@ +import { eq, inArray, sql } from "drizzle-orm" +import { + type LibSQLDatabase, + drizzle as sqliteDrizzle, +} from "drizzle-orm/libsql" +import { + type MySql2Database, + drizzle as mysqlDrizzle, +} from "drizzle-orm/mysql2" +import { + type NodePgDatabase, + drizzle as pgDrizzle, +} 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 { config } from "../env.config" +import { + type DrizzleMySQLResolverFactory, + type DrizzlePostgresResolverFactory, + DrizzleResolverFactory, + type DrizzleSQLiteResolverFactory, + type InferSelectArrayOptions, + type InferSelectSingleOptions, +} from "../src" +import * as mysqlSchemas from "./schema/mysql" +import * as pgSchemas from "./schema/postgres" +import * as sqliteSchemas from "./schema/sqlite" + +const pathToDB = new URL("./schema/sqlite.db", import.meta.url) + +describe.concurrent("DrizzleResolverFactory", () => { + let db: LibSQLDatabase + let userFactory: DrizzleSQLiteResolverFactory< + typeof db, + typeof sqliteSchemas.user + > + + beforeAll(async () => { + db = sqliteDrizzle({ + schema: sqliteSchemas, + connection: { url: `file:${pathToDB.pathname}` }, + }) + + userFactory = DrizzleResolverFactory.create(db, sqliteSchemas.user) + + await db.insert(sqliteSchemas.user).values([ + { + name: "John", + age: 10, + email: "john@example.com", + }, + { + name: "Jane", + age: 11, + }, + { + name: "Jim", + age: 12, + }, + { + name: "Joe", + age: 13, + }, + { + name: "Jill", + age: 14, + }, + ] satisfies (typeof sqliteSchemas.user.$inferInsert)[]) + }) + + afterAll(async () => { + await db.delete(sqliteSchemas.user) + }) + + it("should create a resolver factory", () => { + expect(userFactory).toBeInstanceOf(DrizzleResolverFactory) + }) + + it("should throw an error if the table is not found", () => { + const unknownTable = sqlite.sqliteTable("unknown", { + id: sqlite.integer("id").primaryKey(), + }) + expect(() => DrizzleResolverFactory.create(db, unknownTable)).toThrow( + "GQLoom-Drizzle Error: Table unknown not found in drizzle instance. Did you forget to pass schema to drizzle constructor?" + ) + }) + + describe.concurrent("selectArrayQuery", () => { + it("should be created without error", async () => { + const query = userFactory.selectArrayQuery() + expect(query).toBeDefined() + }) + + it("should resolve correctly with orderBy", async () => { + const query = userFactory.selectArrayQuery() + + let answer + answer = await query.resolve({ orderBy: [{ age: "asc" }] }) + expect(answer).toMatchObject([ + { age: 10 }, + { age: 11 }, + { age: 12 }, + { age: 13 }, + { age: 14 }, + ]) + + answer = await query.resolve({ orderBy: [{ age: "desc" }] }) + expect(answer).toMatchObject([ + { age: 14 }, + { age: 13 }, + { age: 12 }, + { age: 11 }, + { age: 10 }, + ]) + }) + + it("should resolve correctly with filters", async () => { + const query = userFactory.selectArrayQuery() + let answer + answer = await query.resolve({}) + expect(answer).toHaveLength(5) + + answer = await query.resolve({ + where: { age: { gte: 12 } }, + }) + expect(answer).toMatchObject([{ age: 12 }, { age: 13 }, { age: 14 }]) + + answer = await query.resolve({ + where: { age: { lt: 12 } }, + }) + expect(answer).toMatchObject([{ age: 10 }, { age: 11 }]) + answer = await query.resolve({ + where: { age: { gte: 12, lt: 13 } }, + }) + expect(answer).toMatchObject([{ age: 12 }]) + + answer = await query.resolve({ + where: { age: { inArray: [10, 11] } }, + }) + expect(new Set(answer)).toMatchObject(new Set([{ age: 10 }, { age: 11 }])) + + answer = await query.resolve({ + where: { age: { notInArray: [10, 11] } }, + }) + expect(new Set(answer)).toMatchObject( + new Set([{ age: 12 }, { age: 13 }, { age: 14 }]) + ) + + answer = await query.resolve({ + where: { age: { OR: [{ eq: 10 }, { eq: 11 }] } }, + }) + expect(new Set(answer)).toMatchObject(new Set([{ age: 10 }, { age: 11 }])) + + answer = await query.resolve({ + where: { OR: [{ age: { eq: 10 } }, { age: { eq: 11 } }] }, + }) + expect(new Set(answer)).toMatchObject(new Set([{ age: 10 }, { age: 11 }])) + + answer = await query.resolve({ + where: { name: { like: "J%" } }, + }) + expect(answer).toHaveLength(5) + + await expect(() => + query.resolve({ + where: { age: { eq: 10 }, OR: [{ age: { eq: 11 } }] }, + }) + ).rejects.toThrow("Cannot specify both fields and 'OR' in table filters!") + await expect(() => + query.resolve({ + where: { age: { eq: 10, OR: [{ eq: 11 }] } }, + }) + ).rejects.toThrow( + "WHERE age: Cannot specify both fields and 'OR' in column operators!" + ) + + answer = await query.resolve({ + where: { age: { isNull: true } }, + }) + expect(answer).toHaveLength(0) + }) + + it("should be created with custom input", async () => { + const query = userFactory.selectArrayQuery({ + input: v.pipe( + v.object({ + age: v.nullish(v.number()), + }), + v.transform(({ age }) => ({ + where: age != null ? eq(sqliteSchemas.user.age, age) : undefined, + })) + ), + }) + + expect(query).toBeDefined() + const answer = await query.resolve({ age: 10 }) + expect(answer).toMatchObject([{ age: 10 }]) + }) + + it("should be created with middlewares", async () => { + type SelectArrayOptions = InferSelectArrayOptions< + typeof db, + typeof sqliteSchemas.user + > + + let count = 0 + + const query = userFactory.selectArrayQuery({ + middlewares: [ + async ({ parseInput, next }) => { + const opts = await parseInput() + if (opts.issues) throw new Error("Invalid input") + expectTypeOf(opts.value).toEqualTypeOf< + NonNullable + >() + count++ + const answer = await next() + expectTypeOf(answer).toEqualTypeOf< + (typeof sqliteSchemas.user.$inferSelect)[] + >() + return answer + }, + ], + }) + + await query.resolve({}) + expect(count).toBe(1) + }) + }) + + describe.concurrent("selectSingleQuery", () => { + it("should be created without error", () => { + const query = userFactory.selectSingleQuery() + expect(query).toBeDefined() + }) + + it("should resolve correctly with orderBy", async () => { + const query = userFactory.selectSingleQuery() + expect( + await query.resolve({ + orderBy: [{ age: "asc" }], + }) + ).toMatchObject({ age: 10 }) + }) + + it("should resolve correctly with filters", async () => { + const query = userFactory.selectSingleQuery() + expect( + await query.resolve({ + where: { age: { eq: 12 } }, + }) + ).toMatchObject({ age: 12 }) + }) + + it("should be created with custom input", async () => { + const query = userFactory.selectSingleQuery({ + input: v.pipe( + v.object({ + age: v.nullish(v.number()), + }), + v.transform(({ age }) => ({ + where: age != null ? eq(sqliteSchemas.user.age, age) : undefined, + })) + ), + }) + + expect(query).toBeDefined() + expect(await query.resolve({ age: 10 })).toMatchObject({ age: 10 }) + }) + + it("should be created with middlewares", async () => { + type SelectSingleOptions = InferSelectSingleOptions< + typeof db, + typeof sqliteSchemas.user + > + let count = 0 + const query = userFactory.selectSingleQuery({ + middlewares: [ + async ({ parseInput, next }) => { + const opts = await parseInput() + if (opts.issues) throw new Error("Invalid input") + expectTypeOf(opts.value).toEqualTypeOf< + NonNullable + >() + count++ + const answer = await next() + expectTypeOf(answer).toEqualTypeOf< + typeof sqliteSchemas.user.$inferSelect | undefined | null + >() + return answer + }, + ], + }) + + await query.resolve({}) + expect(count).toBe(1) + }) + }) + + describe("relationField", () => { + afterAll(async () => { + await db.delete(sqliteSchemas.studentCourseGrade) + await db.delete(sqliteSchemas.studentToCourse) + await db.delete(sqliteSchemas.course) + await db.delete(sqliteSchemas.post) + }) + + it("should be created without error", () => { + const postsField = userFactory.relationField("posts") + expect(postsField).toBeDefined() + + const postFactory = DrizzleResolverFactory.create(db, "post") + const authorField = postFactory.relationField("author") + expect(authorField).toBeDefined() + }) + + it("should resolve correctly", async () => { + const studentCourseFactory = DrizzleResolverFactory.create( + db, + "studentToCourse" + ) + const gradeField = studentCourseFactory.relationField("grade") + const John = await db.query.user.findFirst({ + where: eq(sqliteSchemas.user.name, "John"), + }) + if (!John) throw new Error("John not found") + const Joe = await db.query.user.findFirst({ + where: eq(sqliteSchemas.user.name, "Joe"), + }) + if (!Joe) throw new Error("Joe not found") + + const [math, english] = await db + .insert(sqliteSchemas.course) + .values([{ name: "Math" }, { name: "English" }]) + .returning() + + const studentCourses = await db + .insert(sqliteSchemas.studentToCourse) + .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.studentCourseGrade).values( + studentCourses.map((it) => ({ + ...it, + grade: Math.floor(Math.random() * 51) + 50, + })) + ) + + let answer + answer = await Promise.all( + studentCourses.map((sc) => { + return gradeField.resolve(sc, undefined) + }) + ) + expect(new Set(answer)).toMatchObject( + new Set([ + { studentId: John.id, courseId: math.id, grade: expect.any(Number) }, + { + studentId: John.id, + courseId: english.id, + grade: expect.any(Number), + }, + { studentId: Joe.id, courseId: math.id, grade: expect.any(Number) }, + { + studentId: Joe.id, + courseId: english.id, + grade: expect.any(Number), + }, + ]) + ) + + await db.insert(sqliteSchemas.post).values([ + { authorId: John.id, title: "Hello" }, + { authorId: John.id, title: "World" }, + ]) + const postsField = userFactory.relationField("posts") + answer = await postsField.resolve(John, undefined) + expect(answer).toMatchObject([ + { authorId: John.id, title: "Hello" }, + { authorId: John.id, title: "World" }, + ]) + }) + }) + + describe("resolver", () => { + it("should be created without error", () => { + const userResolver = userFactory.resolver() + expect(userResolver).toBeDefined() + expect(userResolver.user).toBeDefined() + expect(userResolver.userSingle).toBeDefined() + expect(userResolver.insertIntoUser).toBeDefined() + expect(userResolver.insertIntoUserSingle).toBeDefined() + expect(userResolver.updateUser).toBeDefined() + expect(userResolver.deleteFromUser).toBeDefined() + expect(userResolver.courses).toBeDefined() + expect(userResolver.posts).toBeDefined() + }) + }) +}) + +describe.concurrent("DrizzleMySQLResolverFactory", () => { + const schema = { + drizzle_user: mysqlSchemas.user, + } + let db: MySql2Database + let userFactory: DrizzleMySQLResolverFactory< + typeof db, + typeof mysqlSchemas.user + > + + beforeAll(async () => { + db = mysqlDrizzle(config.mysqlUrl, { schema, mode: "default" }) + userFactory = DrizzleResolverFactory.create(db, "drizzle_user") + await db.execute(sql`select 1`) + }) + + describe("insertArrayMutation", () => { + it("should be created without error", async () => { + const mutation = userFactory.insertArrayMutation() + expect(mutation).toBeDefined() + }) + + it("should resolve correctly", async () => { + const mutation = userFactory.insertArrayMutation() + expect( + await mutation.resolve({ + values: [ + { name: "John", age: 5 }, + { name: "Jane", age: 6 }, + ], + }) + ).toMatchObject({ isSuccess: true }) + + await db + .delete(mysqlSchemas.user) + .where(inArray(mysqlSchemas.user.age, [5, 6])) + }) + }) + + describe("insertSingleMutation", () => { + it("should be created without error", async () => { + const mutation = userFactory.insertSingleMutation() + expect(mutation).toBeDefined() + }) + + it("should resolve correctly", async () => { + const mutation = userFactory.insertSingleMutation() + const answer = await mutation.resolve({ + value: { name: "John", age: 7 }, + }) + expect(answer).toMatchObject({ isSuccess: true }) + + await db.delete(mysqlSchemas.user).where(eq(mysqlSchemas.user.age, 7)) + }) + }) + + describe("updateMutation", () => { + it("should be created without error", async () => { + const mutation = userFactory.updateMutation() + expect(mutation).toBeDefined() + }) + + it("should resolve correctly", async () => { + await db.insert(mysqlSchemas.user).values({ name: "Bob", age: 18 }) + const mutation = userFactory.updateMutation() + expect( + await mutation.resolve({ + where: { name: { eq: "Bob" } }, + set: { age: 19 }, + }) + ).toMatchObject({ isSuccess: true }) + await db + .delete(mysqlSchemas.user) + .where(eq(mysqlSchemas.user.name, "Bob")) + }) + }) + + describe("deleteMutation", () => { + it("should be created without error", async () => { + const mutation = userFactory.deleteMutation() + expect(mutation).toBeDefined() + }) + + it("should resolve correctly", async () => { + await db.insert(mysqlSchemas.user).values({ name: "Alice", age: 18 }) + try { + const mutation = userFactory.deleteMutation() + const answer = await mutation.resolve({ + where: { name: { eq: "Alice" } }, + }) + expect(answer).toMatchObject({ isSuccess: true }) + } finally { + await db + .delete(mysqlSchemas.user) + .where(eq(mysqlSchemas.user.name, "Alice")) + } + }) + }) +}) + +describe.concurrent("DrizzlePostgresResolverFactory", () => { + const schema = { + drizzle_user: pgSchemas.user, + } + let db: NodePgDatabase + let userFactory: DrizzlePostgresResolverFactory< + typeof db, + typeof pgSchemas.user + > + + beforeAll(async () => { + db = pgDrizzle(config.postgresUrl, { schema }) + userFactory = DrizzleResolverFactory.create(db, "drizzle_user") + await db.execute(sql`select 1`) + }) + + describe("insertArrayMutation", () => { + it("should be created without error", async () => { + const mutation = userFactory.insertArrayMutation() + expect(mutation).toBeDefined() + }) + + it("should resolve correctly", async () => { + const mutation = userFactory.insertArrayMutation() + const answer = await mutation.resolve({ + values: [ + { name: "John", age: 5 }, + { name: "Jane", age: 6 }, + ], + }) + expect(answer).toMatchObject([ + { name: "John", age: 5 }, + { name: "Jane", age: 6 }, + ]) + + await db.delete(pgSchemas.user).where(inArray(pgSchemas.user.age, [5, 6])) + }) + }) + + describe("insertSingleMutation", () => { + it("should be created without error", async () => { + const mutation = userFactory.insertSingleMutation() + expect(mutation).toBeDefined() + }) + + it("should resolve correctly", async () => { + const mutation = userFactory.insertSingleMutation() + const answer = await mutation.resolve({ + value: { name: "John", age: 7 }, + }) + + expect(answer).toMatchObject({ name: "John", age: 7 }) + + await db.delete(pgSchemas.user).where(eq(pgSchemas.user.id, answer!.id)) + }) + }) + + describe("updateMutation", () => { + it("should be created without error", async () => { + const mutation = userFactory.updateMutation() + expect(mutation).toBeDefined() + }) + + it("should resolve correctly", async () => { + await db.insert(pgSchemas.user).values({ name: "Bob", age: 18 }) + try { + const mutation = userFactory.updateMutation() + const answer = await mutation.resolve({ + where: { name: { eq: "Bob" } }, + set: { age: 19 }, + }) + expect(answer).toMatchObject([{ name: "Bob", age: 19 }]) + } finally { + await db.delete(pgSchemas.user).where(eq(pgSchemas.user.name, "Bob")) + } + }) + }) + + describe("deleteMutation", () => { + it("should be created without error", async () => { + const mutation = userFactory.deleteMutation() + expect(mutation).toBeDefined() + }) + + it("should resolve correctly", async () => { + await db.insert(pgSchemas.user).values({ name: "Alice", age: 18 }) + try { + const mutation = userFactory.deleteMutation() + const answer = await mutation.resolve({ + where: { name: { eq: "Alice" } }, + }) + expect(answer).toMatchObject([{ name: "Alice", age: 18 }]) + } finally { + await db.delete(pgSchemas.user).where(eq(pgSchemas.user.name, "Alice")) + } + }) + }) +}) + +describe.concurrent("DrizzleSQLiteResolverFactory", () => { + let db: LibSQLDatabase + let userFactory: DrizzleSQLiteResolverFactory< + typeof db, + typeof sqliteSchemas.user + > + + beforeAll(async () => { + db = sqliteDrizzle({ + schema: sqliteSchemas, + connection: { url: `file:${pathToDB.pathname}` }, + }) + + userFactory = DrizzleResolverFactory.create(db, "user") + }) + + describe("insertArrayMutation", () => { + it("should be created without error", async () => { + const mutation = userFactory.insertArrayMutation() + expect(mutation).toBeDefined() + }) + + it("should resolve correctly", async () => { + const mutation = userFactory.insertArrayMutation() + + const answer = await mutation.resolve({ + values: [ + { name: "John", age: 5 }, + { name: "Jane", age: 6 }, + ], + }) + + expect(answer).toMatchObject([ + { name: "John", age: 5 }, + { name: "Jane", age: 6 }, + ]) + + await db + .delete(sqliteSchemas.user) + .where(inArray(sqliteSchemas.user.age, [5, 6])) + }) + }) + + describe("insertSingleMutation", () => { + it("should be created without error", async () => { + const mutation = userFactory.insertSingleMutation() + expect(mutation).toBeDefined() + }) + + it("should resolve correctly", async () => { + const mutation = userFactory.insertSingleMutation() + const answer = await mutation.resolve({ + value: { name: "John", age: 7 }, + }) + expect(answer).toMatchObject({ name: "John", age: 7 }) + + await db + .delete(sqliteSchemas.user) + .where(eq(sqliteSchemas.user.id, answer!.id)) + }) + }) + + describe("updateMutation", () => { + it("should be created without error", async () => { + const mutation = userFactory.updateMutation() + expect(mutation).toBeDefined() + }) + + it("should resolve correctly", async () => { + await db.insert(sqliteSchemas.user).values({ name: "Bob", age: 18 }) + try { + const mutation = userFactory.updateMutation() + const answer = await mutation.resolve({ + where: { name: { eq: "Bob" } }, + set: { age: 19 }, + }) + expect(answer).toMatchObject([{ name: "Bob", age: 19 }]) + } finally { + await db + .delete(sqliteSchemas.user) + .where(eq(sqliteSchemas.user.name, "Bob")) + } + }) + }) + + describe("deleteMutation", () => { + it("should be created without error", async () => { + const mutation = userFactory.deleteMutation() + expect(mutation).toBeDefined() + }) + + it("should resolve correctly", async () => { + await db.insert(sqliteSchemas.user).values({ name: "Alice", age: 18 }) + try { + const mutation = userFactory.deleteMutation() + const answer = await mutation.resolve({ + where: { name: { eq: "Alice" } }, + }) + + expect(answer).toMatchObject([{ name: "Alice", age: 18 }]) + } finally { + await db + .delete(sqliteSchemas.user) + .where(eq(sqliteSchemas.user.name, "Alice")) + } + }) + }) +}) diff --git a/packages/drizzle/test/resolver-mysql.spec.gql b/packages/drizzle/test/resolver-mysql.spec.gql new file mode 100644 index 00000000..7c9accc9 --- /dev/null +++ b/packages/drizzle/test/resolver-mysql.spec.gql @@ -0,0 +1,176 @@ +input DrizzlePostFilters { + OR: [DrizzlePostFiltersOr!] + authorId: MySqlIntFilters + content: MySqlTextFilters + id: MySqlIntFilters + title: MySqlTextFilters +} + +input DrizzlePostFiltersOr { + authorId: MySqlIntFilters + content: MySqlTextFilters + id: MySqlIntFilters + title: MySqlTextFilters +} + +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: MySqlIntFilters + email: MySqlTextFilters + id: MySqlIntFilters + name: MySqlTextFilters +} + +input DrizzleUserFiltersOr { + age: MySqlIntFilters + email: MySqlTextFilters + id: MySqlIntFilters + name: MySqlTextFilters +} + +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): 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 new file mode 100644 index 00000000..17cf10e6 --- /dev/null +++ b/packages/drizzle/test/resolver-mysql.spec.ts @@ -0,0 +1,385 @@ +import { weave } from "@gqloom/core" +import { eq } from "drizzle-orm" +import { drizzle } from "drizzle-orm/mysql2" +import type { MySql2Database } from "drizzle-orm/mysql2" +import { + type GraphQLSchema, + lexicographicSortSchema, + printSchema, +} from "graphql" +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" + +const schema = { + drizzle_user: user, + drizzle_post: post, + usersRelations, + postsRelations, +} + +describe("resolver by mysql", () => { + let db: MySql2Database + let gqlSchema: GraphQLSchema + let yoga: YogaServerInstance<{}, {}> + + const execute = async (query: string, variables?: Record) => { + const response = await yoga.fetch("http://localhost/graphql", { + method: "POST", + headers: { + "content-type": "application/json", + }, + body: JSON.stringify({ + query, + variables, + }), + }) + + const { data, errors } = await response.json() + + if (response.status !== 200 || errors != null) { + console.info(errors) + throw new Error(JSON.stringify(errors)) + } + return data + } + + beforeAll(async () => { + try { + db = drizzle(config.mysqlUrl, { schema, mode: "default" }) + const userFactory = DrizzleResolverFactory.create(db, "drizzle_user") + const postFactory = DrizzleResolverFactory.create(db, "drizzle_post") + gqlSchema = weave( + userFactory.resolver({ name: "user" }), + postFactory.resolver({ name: "post" }) + ) + yoga = createYoga({ schema: gqlSchema }) + + 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 Tony = await db.query.drizzle_user.findFirst({ + where: eq(user.name, "Tony"), + }) + const Taylor = await db.query.drizzle_user.findFirst({ + where: eq(user.name, "Taylor"), + }) + if (!Tom || !Tony || !Taylor) throw new Error("User not found") + + await db.insert(post).values([ + { title: "Post 1", authorId: Tom.id }, + { title: "Post 2", authorId: Tony.id }, + { title: "Post 3", authorId: Taylor.id }, + { title: "Post 4", authorId: Tom.id }, + ]) + } catch (error) { + console.info(error) + } + }) + + afterAll(async () => { + await db.delete(post) + await db.delete(user) + }) + + it("should weave GraphQL schema correctly", async () => { + await expect( + printSchema(lexicographicSortSchema(gqlSchema)) + ).toMatchFileSnapshot("./resolver-mysql.spec.gql") + }) + + 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 + } + } + ` + await expect( + execute(q, { + orderBy: [{ name: "asc" }], + where: { name: { like: "T%" } }, + }) + ).resolves.toMatchObject({ + user: [{ name: "Taylor" }, { name: "Tom" }, { name: "Tony" }], + }) + + await expect( + execute(q, { + orderBy: [{ name: "asc" }], + where: { name: { like: "T%" } }, + limit: 2, + }) + ).resolves.toMatchObject({ + user: [{ name: "Taylor" }, { name: "Tom" }], + }) + + await expect( + execute(q, { + orderBy: [{ name: "asc" }], + where: { name: { like: "T%" } }, + limit: 1, + offset: 1, + }) + ).resolves.toMatchObject({ + user: [{ name: "Tom" }], + }) + }) + + it("should query user single correctly", async () => { + await expect( + execute( + /* GraphQL */ ` + query user ($orderBy: [DrizzleUserOrderBy!], $where: DrizzleUserFilters!, $offset: Int) { + userSingle(orderBy: $orderBy, where: $where, offset: $offset) { + id + name + } + } + `, + { + where: { name: { eq: "Taylor" } }, + } + ) + ).resolves.toMatchObject({ + userSingle: { name: "Taylor" }, + }) + }) + + 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) { + id + name + posts { + id + title + } + } + } + ` + + await expect( + execute(q, { + orderBy: [{ name: "asc" }], + where: { name: { like: "T%" } }, + }) + ).resolves.toMatchObject({ + user: [ + { + name: "Taylor", + posts: [{ title: "Post 3" }], + }, + { + name: "Tom", + posts: [{ title: "Post 1" }, { title: "Post 4" }], + }, + { + name: "Tony", + posts: [{ title: "Post 2" }], + }, + ], + }) + }) + }) + + describe("mutation", () => { + it("should insert a new user correctly", async () => { + const q = /* GraphQL */ ` + mutation insertIntoUser($values: [DrizzleUserInsertInput!]!) { + insertIntoUser(values: $values) { + isSuccess + } + } + ` + + await expect( + execute(q, { + values: [{ name: "Tina" }], + }) + ).resolves.toMatchObject({ + insertIntoUser: { isSuccess: true }, + }) + + // Verify the user was inserted + const Tina = await db.query.drizzle_user.findFirst({ + where: eq(user.name, "Tina"), + }) + expect(Tina).toBeDefined() + }) + + it("should update user information correctly", async () => { + const q = /* GraphQL */ ` + mutation updateUser($set: DrizzleUserUpdateInput!, $where: DrizzleUserFilters!) { + updateUser(set: $set, where: $where) { + isSuccess + } + } + ` + + const [TroyID] = await db + .insert(user) + .values({ name: "Troy" }) + .$returningId() + const Troy = await db.query.drizzle_user.findFirst({ + where: eq(user.id, TroyID.id), + }) + if (!Troy) throw new Error("User not found") + + await expect( + execute(q, { + set: { name: "Tiffany" }, + where: { id: { eq: Troy.id } }, + }) + ).resolves.toMatchObject({ + updateUser: { isSuccess: true }, + }) + + // Verify the user was updated + const updatedUser = await db.query.drizzle_user.findFirst({ + where: eq(user.name, "Tiffany"), + }) + expect(updatedUser).toBeDefined() + }) + + it("should delete a user correctly", async () => { + const q = /* GraphQL */ ` + mutation deleteFromUser($where: DrizzleUserFilters!) { + deleteFromUser(where: $where) { + isSuccess + } + } + ` + + const Tony = await db.query.drizzle_user.findFirst({ + where: eq(user.name, "Tony"), + }) + if (!Tony) throw new Error("User not found") + + await expect( + execute(q, { + where: { id: { eq: Tony.id } }, + }) + ).resolves.toMatchObject({ + deleteFromUser: { + isSuccess: true, + }, + }) + + // Verify the user was deleted + const deletedUser = await db.query.drizzle_user.findFirst({ + where: eq(user.name, "Tony"), + }) + expect(deletedUser).toBeUndefined() + }) + + it("should insert a new post correctly", async () => { + const q = /* GraphQL */ ` + mutation insertIntoPost($values: [DrizzlePostInsertInput!]!) { + insertIntoPost(values: $values) { + isSuccess + } + } + ` + + const Tom = await db.query.drizzle_user.findFirst({ + where: eq(user.name, "Tom"), + }) + if (!Tom) throw new Error("User not found") + + await expect( + execute(q, { + values: [{ title: "Post 5", authorId: Tom.id }], + }) + ).resolves.toMatchObject({ + insertIntoPost: { + isSuccess: true, + }, + }) + + // Verify the post was inserted + const p = await db.query.drizzle_post.findFirst({ + where: eq(post.title, "Post 5"), + }) + expect(p).toBeDefined() + }) + + it("should update post information correctly", async () => { + const q = /* GraphQL */ ` + mutation updatePost($set: DrizzlePostUpdateInput!, $where: DrizzlePostFilters!) { + updatePost(set: $set, where: $where) { + isSuccess + } + } + ` + + const [PostUID] = await db + .insert(post) + .values({ title: "Post U" }) + .$returningId() + + const PostU = await db.query.drizzle_post.findFirst({ + where: eq(post.id, PostUID.id), + }) + if (!PostU) throw new Error("Post not found") + + await expect( + execute(q, { + set: { title: "Updated Post U" }, + where: { id: { eq: PostU.id } }, + }) + ).resolves.toMatchObject({ + updatePost: { isSuccess: true }, + }) + + // Verify the post was updated + const updatedPost = await db.query.drizzle_post.findFirst({ + where: eq(post.title, "Updated Post U"), + }) + expect(updatedPost).toBeDefined() + }) + + it("should delete a post correctly", async () => { + const q = /* GraphQL */ ` + mutation deleteFromPost($where: DrizzlePostFilters!) { + deleteFromPost(where: $where) { + isSuccess + } + } + ` + + const [PostDID] = await db + .insert(post) + .values({ title: "Post D" }) + .$returningId() + + const PostD = await db.query.drizzle_post.findFirst({ + where: eq(post.id, PostDID.id), + }) + if (!PostD) throw new Error("Post not found") + + await expect( + execute(q, { + where: { id: { eq: PostD.id } }, + }) + ).resolves.toMatchObject({ + deleteFromPost: { isSuccess: true }, + }) + + // Verify the post was deleted + const deletedPost = await db.query.drizzle_post.findFirst({ + where: eq(post.id, PostD.id), + }) + expect(deletedPost).toBeUndefined() + }) + }) +}) diff --git a/packages/drizzle/test/resolver-postgres.spec.gql b/packages/drizzle/test/resolver-postgres.spec.gql new file mode 100644 index 00000000..77ba955a --- /dev/null +++ b/packages/drizzle/test/resolver-postgres.spec.gql @@ -0,0 +1,199 @@ +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!]! +} + +enum OrderDirection { + asc + desc +} + +input PgIntegerFilters { + OR: [PgIntegerFiltersOr!] + eq: Int + gt: Int + gte: Int + inArray: [Int!] + isNotNull: Boolean + isNull: Boolean + lt: Int + lte: Int + ne: Int + notInArray: [Int!] +} + +input PgIntegerFiltersOr { + eq: Int + gt: Int + gte: Int + inArray: [Int!] + isNotNull: Boolean + isNull: Boolean + lt: Int + lte: Int + ne: Int + notInArray: [Int!] +} + +input PgSerialFilters { + OR: [PgSerialFiltersOr!] + eq: Int + gt: Int + gte: Int + inArray: [Int!] + isNotNull: Boolean + isNull: Boolean + lt: Int + lte: Int + ne: Int + notInArray: [Int!] +} + +input PgSerialFiltersOr { + eq: Int + gt: Int + gte: Int + inArray: [Int!] + isNotNull: Boolean + isNull: Boolean + lt: Int + lte: Int + ne: Int + notInArray: [Int!] +} + +input PgTextFilters { + OR: [PgTextFiltersOr!] + 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 PgTextFiltersOr { + 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 +} + +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-postgres.spec.ts b/packages/drizzle/test/resolver-postgres.spec.ts new file mode 100644 index 00000000..7ab794a5 --- /dev/null +++ b/packages/drizzle/test/resolver-postgres.spec.ts @@ -0,0 +1,387 @@ +import { weave } from "@gqloom/core" +import { eq } from "drizzle-orm" +import { type NodePgDatabase, drizzle } from "drizzle-orm/node-postgres" +import { + type GraphQLSchema, + lexicographicSortSchema, + printSchema, +} from "graphql" +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" + +const schema = { + drizzle_user: user, + drizzle_post: post, + usersRelations, + postsRelations, +} + +describe("resolver by postgres", () => { + let db: NodePgDatabase + let gqlSchema: GraphQLSchema + let yoga: YogaServerInstance<{}, {}> + + const execute = async (query: string, variables?: Record) => { + const response = await yoga.fetch("http://localhost/graphql", { + method: "POST", + headers: { + "content-type": "application/json", + }, + body: JSON.stringify({ + query, + variables, + }), + }) + + const { data, errors } = await response.json() + + if (response.status !== 200 || errors != null) { + console.info(errors) + throw new Error(JSON.stringify(errors)) + } + return data + } + + beforeAll(async () => { + try { + db = drizzle(config.postgresUrl, { schema }) + const userFactory = DrizzleResolverFactory.create(db, "drizzle_user") + const postFactory = DrizzleResolverFactory.create(db, "drizzle_post") + gqlSchema = weave( + userFactory.resolver({ name: "user" }), + postFactory.resolver({ name: "post" }) + ) + yoga = createYoga({ schema: gqlSchema }) + + 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 Tony = await db.query.drizzle_user.findFirst({ + where: eq(user.name, "Tony"), + }) + const Taylor = await db.query.drizzle_user.findFirst({ + where: eq(user.name, "Taylor"), + }) + if (!Tom || !Tony || !Taylor) throw new Error("User not found") + + await db.insert(post).values([ + { title: "Post 1", authorId: Tom.id }, + { title: "Post 2", authorId: Tony.id }, + { title: "Post 3", authorId: Taylor.id }, + { title: "Post 4", authorId: Tom.id }, + ]) + } catch (error) { + console.info(error) + } + }) + + afterAll(async () => { + await db.delete(post) + await db.delete(user) + }) + + it("should weave GraphQL schema correctly", async () => { + await expect( + printSchema(lexicographicSortSchema(gqlSchema)) + ).toMatchFileSnapshot("./resolver-postgres.spec.gql") + }) + + 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 + } + } + ` + await expect( + execute(q, { + orderBy: [{ name: "asc" }], + where: { name: { like: "T%" } }, + }) + ).resolves.toMatchObject({ + user: [{ name: "Taylor" }, { name: "Tom" }, { name: "Tony" }], + }) + + await expect( + execute(q, { + orderBy: [{ name: "asc" }], + where: { name: { like: "T%" } }, + limit: 2, + }) + ).resolves.toMatchObject({ + user: [{ name: "Taylor" }, { name: "Tom" }], + }) + + await expect( + execute(q, { + orderBy: [{ name: "asc" }], + where: { name: { like: "T%" } }, + limit: 1, + offset: 1, + }) + ).resolves.toMatchObject({ + user: [{ name: "Tom" }], + }) + }) + + it("should query user single correctly", async () => { + await expect( + execute( + /* GraphQL */ ` + query user ($orderBy: [DrizzleUserOrderBy!], $where: DrizzleUserFilters!, $offset: Int) { + userSingle(orderBy: $orderBy, where: $where, offset: $offset) { + id + name + } + } + `, + { + where: { name: { eq: "Taylor" } }, + } + ) + ).resolves.toMatchObject({ + userSingle: { name: "Taylor" }, + }) + }) + + 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) { + id + name + posts { + id + title + } + } + } + ` + + await expect( + execute(q, { + orderBy: [{ name: "asc" }], + where: { name: { like: "T%" } }, + }) + ).resolves.toMatchObject({ + user: [ + { + name: "Taylor", + posts: [{ title: "Post 3" }], + }, + { + name: "Tom", + posts: [{ title: "Post 1" }, { title: "Post 4" }], + }, + { + name: "Tony", + posts: [{ title: "Post 2" }], + }, + ], + }) + }) + }) + + describe("mutation", () => { + it("should insert a new user correctly", async () => { + const q = /* GraphQL */ ` + mutation insertIntoUser($values: [DrizzleUserInsertInput!]!) { + insertIntoUser(values: $values) { + id + name + } + } + ` + + await expect( + execute(q, { + values: [{ name: "Tina" }], + }) + ).resolves.toMatchObject({ + insertIntoUser: [{ name: "Tina" }], + }) + + // Verify the user was inserted + const Tina = await db.query.drizzle_user.findFirst({ + where: eq(user.name, "Tina"), + }) + expect(Tina).toBeDefined() + }) + + it("should update user information correctly", async () => { + const q = /* GraphQL */ ` + mutation updateUser($set: DrizzleUserUpdateInput!, $where: DrizzleUserFilters!) { + updateUser(set: $set, where: $where) { + id + name + } + } + ` + + const [TroyID] = await db + .insert(user) + .values({ name: "Troy" }) + .returning() + const Troy = await db.query.drizzle_user.findFirst({ + where: eq(user.id, TroyID.id), + }) + if (!Troy) throw new Error("User not found") + + await expect( + execute(q, { + set: { name: "Tiffany" }, + where: { id: { eq: Troy.id } }, + }) + ).resolves.toMatchObject({ + updateUser: [{ id: Troy.id, name: "Tiffany" }], + }) + + // Verify the user was updated + const updatedUser = await db.query.drizzle_user.findFirst({ + where: eq(user.name, "Tiffany"), + }) + expect(updatedUser).toBeDefined() + }) + + it("should delete a user correctly", async () => { + const q = /* GraphQL */ ` + mutation deleteFromUser($where: DrizzleUserFilters!) { + deleteFromUser(where: $where) { + id + name + } + } + ` + + const Tony = await db.query.drizzle_user.findFirst({ + where: eq(user.name, "Tony"), + }) + if (!Tony) throw new Error("User not found") + + await expect( + execute(q, { + where: { id: { eq: Tony.id } }, + }) + ).resolves.toMatchObject({ + deleteFromUser: [{ id: Tony.id, name: "Tony" }], + }) + + // Verify the user was deleted + const deletedUser = await db.query.drizzle_user.findFirst({ + where: eq(user.name, "Tony"), + }) + expect(deletedUser).toBeUndefined() + }) + + it("should insert a new post correctly", async () => { + const q = /* GraphQL */ ` + mutation insertIntoPost($values: [DrizzlePostInsertInput!]!) { + insertIntoPost(values: $values) { + id + title + authorId + } + } + ` + + const Tom = await db.query.drizzle_user.findFirst({ + where: eq(user.name, "Tom"), + }) + if (!Tom) throw new Error("User not found") + + await expect( + execute(q, { + values: [{ title: "Post 5", authorId: Tom.id }], + }) + ).resolves.toMatchObject({ + insertIntoPost: [{ title: "Post 5", authorId: Tom.id }], + }) + + // Verify the post was inserted + const p = await db.query.drizzle_post.findFirst({ + where: eq(post.title, "Post 5"), + }) + expect(p).toBeDefined() + }) + + it("should update post information correctly", async () => { + const q = /* GraphQL */ ` + mutation updatePost($set: DrizzlePostUpdateInput!, $where: DrizzlePostFilters!) { + updatePost(set: $set, where: $where) { + id + title + } + } + ` + + const [PostUID] = await db + .insert(post) + .values({ title: "Post U" }) + .returning() + + const PostU = await db.query.drizzle_post.findFirst({ + where: eq(post.id, PostUID.id), + }) + if (!PostU) throw new Error("Post not found") + + await expect( + execute(q, { + set: { title: "Updated Post U" }, + where: { id: { eq: PostU.id } }, + }) + ).resolves.toMatchObject({ + updatePost: [{ id: PostU.id, title: "Updated Post U" }], + }) + + // Verify the post was updated + const updatedPost = await db.query.drizzle_post.findFirst({ + where: eq(post.title, "Updated Post U"), + }) + expect(updatedPost).toBeDefined() + }) + + it("should delete a post correctly", async () => { + const q = /* GraphQL */ ` + mutation deleteFromPost($where: DrizzlePostFilters!) { + deleteFromPost(where: $where) { + id + title + } + } + ` + + const [PostDID] = await db + .insert(post) + .values({ title: "Post D" }) + .returning() + + const PostD = await db.query.drizzle_post.findFirst({ + where: eq(post.id, PostDID.id), + }) + if (!PostD) throw new Error("Post not found") + + await expect( + execute(q, { + where: { id: { eq: PostD.id } }, + }) + ).resolves.toMatchObject({ + deleteFromPost: [{ id: PostD.id, title: "Post D" }], + }) + + // Verify the post was deleted + const deletedPost = await db.query.drizzle_post.findFirst({ + where: eq(post.id, PostD.id), + }) + expect(deletedPost).toBeUndefined() + }) + }) +}) diff --git a/packages/drizzle/test/resolver-sqlite.spec.gql b/packages/drizzle/test/resolver-sqlite.spec.gql new file mode 100644 index 00000000..223a0e6b --- /dev/null +++ b/packages/drizzle/test/resolver-sqlite.spec.gql @@ -0,0 +1,179 @@ +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!]! +} + +enum OrderDirection { + asc + desc +} + +input PostFilters { + OR: [PostFiltersOr!] + authorId: SQLiteIntegerFilters + content: SQLiteTextFilters + id: SQLiteIntegerFilters + title: SQLiteTextFilters +} + +input PostFiltersOr { + authorId: SQLiteIntegerFilters + content: SQLiteTextFilters + id: SQLiteIntegerFilters + title: SQLiteTextFilters +} + +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: [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 { + OR: [SQLiteIntegerFiltersOr!] + eq: Int + gt: Int + gte: Int + inArray: [Int!] + isNotNull: Boolean + isNull: Boolean + lt: Int + lte: Int + ne: Int + notInArray: [Int!] +} + +input SQLiteIntegerFiltersOr { + eq: Int + gt: Int + gte: Int + inArray: [Int!] + isNotNull: Boolean + isNull: Boolean + lt: Int + lte: Int + ne: Int + notInArray: [Int!] +} + +input SQLiteTextFilters { + OR: [SQLiteTextFiltersOr!] + 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 SQLiteTextFiltersOr { + 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 +} + +type StudentToCourseItem { + courseId: Int + createdAt: String + studentId: Int +} + +input UserFilters { + OR: [UserFiltersOr!] + age: SQLiteIntegerFilters + email: SQLiteTextFilters + id: SQLiteIntegerFilters + name: SQLiteTextFilters +} + +input UserFiltersOr { + age: SQLiteIntegerFilters + email: SQLiteTextFilters + id: SQLiteIntegerFilters + name: SQLiteTextFilters +} + +input UserInsertInput { + age: Int + email: String + id: Int + name: String! +} + +type UserItem { + age: Int + courses: [StudentToCourseItem!]! + 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-sqlite.spec.ts b/packages/drizzle/test/resolver-sqlite.spec.ts new file mode 100644 index 00000000..9b2f3a52 --- /dev/null +++ b/packages/drizzle/test/resolver-sqlite.spec.ts @@ -0,0 +1,363 @@ +import { weave } from "@gqloom/core" +import { eq } from "drizzle-orm" +import { type LibSQLDatabase, drizzle } from "drizzle-orm/libsql" +import { + type GraphQLSchema, + lexicographicSortSchema, + printSchema, +} from "graphql" +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" + +const pathToDB = new URL("./schema/sqlite-1.db", import.meta.url) + +describe("resolver by sqlite", () => { + let db: LibSQLDatabase + let gqlSchema: GraphQLSchema + let yoga: YogaServerInstance<{}, {}> + + const execute = async (query: string, variables?: Record) => { + const response = await yoga.fetch("http://localhost/graphql", { + method: "POST", + headers: { + "content-type": "application/json", + }, + body: JSON.stringify({ + query, + variables, + }), + }) + + const { data, errors } = await response.json() + + if (response.status !== 200) { + throw new Error(JSON.stringify(errors)) + } + return data + } + + beforeAll(async () => { + db = drizzle({ + schema, + connection: { url: `file:${pathToDB.pathname}` }, + }) + const userFactory = DrizzleResolverFactory.create(db, "user") + const postFactory = DrizzleResolverFactory.create(db, "post") + gqlSchema = weave(userFactory.resolver(), postFactory.resolver()) + yoga = createYoga({ schema: gqlSchema }) + + await db + .insert(user) + .values([{ name: "Tom" }, { name: "Tony" }, { name: "Taylor" }]) + const Tom = await db.query.user.findFirst({ + where: eq(user.name, "Tom"), + }) + const Tony = await db.query.user.findFirst({ + where: eq(user.name, "Tony"), + }) + const Taylor = await db.query.user.findFirst({ + where: eq(user.name, "Taylor"), + }) + if (!Tom || !Tony || !Taylor) throw new Error("User not found") + + await db.insert(post).values([ + { title: "Post 1", authorId: Tom.id }, + { title: "Post 2", authorId: Tony.id }, + { title: "Post 3", authorId: Taylor.id }, + { title: "Post 4", authorId: Tom.id }, + ]) + }) + + afterAll(async () => { + await db.delete(post) + await db.delete(user) + }) + + it("should weave GraphQL schema correctly", async () => { + await expect( + printSchema(lexicographicSortSchema(gqlSchema)) + ).toMatchFileSnapshot("./resolver-sqlite.spec.gql") + }) + + 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) { + id + name + } + } + ` + await expect( + execute(q, { + orderBy: [{ name: "asc" }], + where: { name: { like: "T%" } }, + }) + ).resolves.toMatchObject({ + user: [{ name: "Taylor" }, { name: "Tom" }, { name: "Tony" }], + }) + + await expect( + execute(q, { + orderBy: [{ name: "asc" }], + where: { name: { like: "T%" } }, + limit: 2, + }) + ).resolves.toMatchObject({ + user: [{ name: "Taylor" }, { name: "Tom" }], + }) + + await expect( + execute(q, { + orderBy: [{ name: "asc" }], + where: { name: { like: "T%" } }, + limit: 1, + offset: 1, + }) + ).resolves.toMatchObject({ + user: [{ name: "Tom" }], + }) + }) + + it("should query user single correctly", async () => { + await expect( + execute( + /* GraphQL */ ` + query user ($orderBy: [UserOrderBy!], $where: UserFilters!, $offset: Int) { + userSingle(orderBy: $orderBy, where: $where, offset: $offset) { + id + name + } + } + `, + { + where: { name: { eq: "Taylor" } }, + } + ) + ).resolves.toMatchObject({ + userSingle: { 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) { + id + name + posts { + id + title + } + } + } + ` + + await expect( + execute(q, { + orderBy: [{ name: "asc" }], + where: { name: { like: "T%" } }, + }) + ).resolves.toMatchObject({ + user: [ + { + name: "Taylor", + posts: [{ title: "Post 3" }], + }, + { + name: "Tom", + posts: [{ title: "Post 1" }, { title: "Post 4" }], + }, + { + name: "Tony", + posts: [{ title: "Post 2" }], + }, + ], + }) + }) + }) + + describe("mutation", () => { + it("should insert a new user correctly", async () => { + const q = /* GraphQL */ ` + mutation insertIntoUser($values: [UserInsertInput!]!) { + insertIntoUser(values: $values) { + id + name + } + } + ` + + await expect( + execute(q, { + values: [{ name: "Tina" }], + }) + ).resolves.toMatchObject({ + insertIntoUser: [{ name: "Tina" }], + }) + + // Verify the user was inserted + const Tina = await db.query.user.findFirst({ + where: eq(user.name, "Tina"), + }) + expect(Tina).toBeDefined() + }) + + it("should update user information correctly", async () => { + const q = /* GraphQL */ ` + mutation updateUser($set: UserUpdateInput!, $where: UserFilters!) { + updateUser(set: $set, where: $where) { + id + name + } + } + ` + + const [Troy] = await db.insert(user).values({ name: "Troy" }).returning() + if (!Troy) throw new Error("User not found") + + await expect( + execute(q, { + set: { name: "Tiffany" }, + where: { id: { eq: Troy.id } }, + }) + ).resolves.toMatchObject({ + updateUser: [{ id: Troy.id, name: "Tiffany" }], + }) + + // Verify the user was updated + const updatedUser = await db.query.user.findFirst({ + where: eq(user.name, "Tiffany"), + }) + expect(updatedUser).toBeDefined() + }) + + it("should delete a user correctly", async () => { + const q = /* GraphQL */ ` + mutation deleteFromUser($where: UserFilters!) { + deleteFromUser(where: $where) { + id + name + } + } + ` + + const Tony = await db.query.user.findFirst({ + where: eq(user.name, "Tony"), + }) + if (!Tony) throw new Error("User not found") + + await expect( + execute(q, { + where: { id: { eq: Tony.id } }, + }) + ).resolves.toMatchObject({ + deleteFromUser: [{ id: Tony.id, name: "Tony" }], + }) + + // Verify the user was deleted + const deletedUser = await db.query.user.findFirst({ + where: eq(user.name, "Tony"), + }) + expect(deletedUser).toBeUndefined() + }) + + it("should insert a new post correctly", async () => { + const q = /* GraphQL */ ` + mutation insertIntoPost($values: [PostInsertInput!]!) { + insertIntoPost(values: $values) { + id + title + authorId + } + } + ` + + const Tom = await db.query.user.findFirst({ + where: eq(user.name, "Tom"), + }) + if (!Tom) throw new Error("User not found") + + await expect( + execute(q, { + values: [{ title: "Post 5", authorId: Tom.id }], + }) + ).resolves.toMatchObject({ + insertIntoPost: [{ title: "Post 5", authorId: Tom.id }], + }) + + // Verify the post was inserted + const p = await db.query.post.findFirst({ + where: eq(post.title, "Post 5"), + }) + expect(p).toBeDefined() + }) + + it("should update post information correctly", async () => { + const q = /* GraphQL */ ` + mutation updatePost($set: PostUpdateInput!, $where: PostFilters!) { + updatePost(set: $set, where: $where) { + id + title + } + } + ` + + const [PostU] = await db + .insert(post) + .values({ title: "Post U" }) + .returning() + if (!PostU) throw new Error("Post not found") + + await expect( + execute(q, { + set: { title: "Updated Post U" }, + where: { id: { eq: PostU.id } }, + }) + ).resolves.toMatchObject({ + updatePost: [{ id: PostU.id, title: "Updated Post U" }], + }) + + // Verify the post was updated + const updatedPost = await db.query.post.findFirst({ + where: eq(post.title, "Updated Post U"), + }) + expect(updatedPost).toBeDefined() + }) + + it("should delete a post correctly", async () => { + const q = /* GraphQL */ ` + mutation deleteFromPost($where: PostFilters!) { + deleteFromPost(where: $where) { + id + title + } + } + ` + + const [PostD] = await db + .insert(post) + .values({ title: "Post D" }) + .returning() + if (!PostD) throw new Error("Post not found") + + await expect( + execute(q, { + where: { id: { eq: PostD.id } }, + }) + ).resolves.toMatchObject({ + deleteFromPost: [{ id: PostD.id, title: "Post D" }], + }) + + // Verify the post was deleted + const deletedPost = await db.query.post.findFirst({ + where: eq(post.id, PostD.id), + }) + expect(deletedPost).toBeUndefined() + }) + }) +}) diff --git a/packages/drizzle/test/schema.spec.ts b/packages/drizzle/test/schema.spec.ts new file mode 100644 index 00000000..55f3f027 --- /dev/null +++ b/packages/drizzle/test/schema.spec.ts @@ -0,0 +1,164 @@ +import { getGraphQLType, query, resolver, weave } from "@gqloom/core" +import { pgTable } from "drizzle-orm/pg-core" +import * as pg from "drizzle-orm/pg-core" +import { sqliteTable } from "drizzle-orm/sqlite-core" +import * as sqlite from "drizzle-orm/sqlite-core" +import { + GraphQLNonNull, + type GraphQLObjectType, + type GraphQLOutputType, + GraphQLScalarType, + printSchema, + printType, +} from "graphql" +import { describe, expect, it } from "vitest" +import { DrizzleWeaver, drizzleSilk } from "../src" + +describe("drizzleSilk", () => { + it("should handle pg table and column types", () => { + const Foo = drizzleSilk( + pgTable("foo", { + serial: pg.serial().primaryKey(), + integer: pg.integer(), + boolean: pg.boolean(), + text: pg.text(), + textNotNull: pg.text().notNull(), + varchar1: pg.varchar(), + varchar2: pg.varchar({ length: 256 }), + char1: pg.char(), + char2: pg.char({ length: 256 }), + numeric: pg.numeric(), + real: pg.real(), + double: pg.doublePrecision(), + json: pg.json(), + jsonb: pg.jsonb(), + time: pg.time(), + timestamp: pg.timestamp(), + date: pg.date(), + interval: pg.interval(), + array: pg.text().array(), + }) + ) + + const gqlType = getGraphQLType(Foo) + expect(printType(unwrap(gqlType))).toMatchInlineSnapshot(` + "type FooItem { + serial: Int! + integer: Int + boolean: Boolean + text: String + textNotNull: String! + varchar1: String + varchar2: String + char1: String + char2: String + numeric: String + real: Float + double: Float + json: String + jsonb: String + time: String + timestamp: String + date: String + interval: String + array: [String!] + }" + `) + }) + + it("should handle sqlite table and column types", () => { + const Foo = drizzleSilk( + sqliteTable("foo", { + integer: sqlite.integer().primaryKey(), + real: sqlite.real(), + text: sqlite.text(), + blob: sqlite.blob(), + boolean: sqlite.integer({ mode: "boolean" }), + }) + ) + + const gqlType = getGraphQLType(Foo) + expect(printType(unwrap(gqlType))).toMatchInlineSnapshot(` + "type FooItem { + integer: Int! + real: Float + text: String + blob: [Int!] + boolean: Boolean + }" + `) + }) + + it("should handle preset types", () => { + const GraphQLDate = new GraphQLScalarType({ name: "Date" }) + + const config = DrizzleWeaver.config({ + presetGraphQLType: (column) => { + if (column.dataType === "date") { + return GraphQLDate + } + }, + }) + + const Foo = drizzleSilk( + pgTable("foo", { + date: pg.timestamp(), + }) + ) + + const r1 = resolver({ + foo: query(Foo, () => ({ date: new Date() })), + foo2: query(Foo.$nullable(), () => null), + foos: query(Foo.$list(), () => []), + }) + + const schema = weave(DrizzleWeaver, config, r1) + expect(printSchema(schema)).toMatchInlineSnapshot(` + "type Query { + foo: FooItem! + foo2: FooItem + foos: [FooItem!]! + } + + type FooItem { + date: Date + } + + scalar Date" + `) + }) + + it("should throw error when not implemented", () => { + const notImplemented1 = drizzleSilk( + pgTable("not_implemented", { + line: pg.line(), + }) + ) + expect(() => getGraphQLType(notImplemented1)).toThrow( + "Type: PgLine is not implemented!" + ) + const customType = pg.customType<{ + data: number + notNull: true + default: true + }>({ + dataType: () => "CustomType", + }) + const notImplemented2 = drizzleSilk( + pgTable("not_implemented", { + customType: customType(), + }) + ) + + expect(() => getGraphQLType(notImplemented2)).toThrow( + "Type: PgCustomColumn is not implemented!" + ) + }) +}) + +function unwrap(gqlType: GraphQLOutputType) { + if (gqlType instanceof GraphQLNonNull) { + return gqlType.ofType as GraphQLObjectType + } + return gqlType as GraphQLObjectType +} diff --git a/packages/drizzle/test/schema/mysql.ts b/packages/drizzle/test/schema/mysql.ts new file mode 100644 index 00000000..f61662e9 --- /dev/null +++ b/packages/drizzle/test/schema/mysql.ts @@ -0,0 +1,32 @@ +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", { + id: t.int().primaryKey().autoincrement(), + name: t.text().notNull(), + age: t.int(), + email: t.text(), + }) +) + +export const usersRelations = relations(user, ({ many }) => ({ + posts: many(post), +})) + +export const post = drizzleSilk( + t.mysqlTable("drizzle_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.ts b/packages/drizzle/test/schema/postgres.ts new file mode 100644 index 00000000..f8c3f118 --- /dev/null +++ b/packages/drizzle/test/schema/postgres.ts @@ -0,0 +1,32 @@ +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", { + id: t.serial().primaryKey(), + name: t.text().notNull(), + age: t.integer(), + email: t.text(), + }) +) + +export const usersRelations = relations(user, ({ many }) => ({ + posts: many(post), +})) + +export const post = drizzleSilk( + t.pgTable("drizzle_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.ts b/packages/drizzle/test/schema/sqlite.ts new file mode 100644 index 00000000..7ad36922 --- /dev/null +++ b/packages/drizzle/test/schema/sqlite.ts @@ -0,0 +1,78 @@ +import { relations, sql } from "drizzle-orm" +import * as t from "drizzle-orm/sqlite-core" +import { drizzleSilk } from "../../src" + +export const user = drizzleSilk( + t.sqliteTable("user", { + id: t.int().primaryKey({ autoIncrement: true }), + name: t.text().notNull(), + age: t.int(), + email: t.text(), + }) +) + +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 }), + 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], + }), +})) + +export const course = drizzleSilk( + t.sqliteTable("course", { + id: t.int().primaryKey({ autoIncrement: true }), + name: t.text().notNull(), + }) +) + +export const coursesRelations = relations(course, ({ many }) => ({ + students: many(studentToCourse), +})) + +export const studentToCourse = drizzleSilk( + t.sqliteTable("studentToCourse", { + studentId: t.int().references(() => user.id), + courseId: t.int().references(() => course.id), + createdAt: t.int({ mode: "timestamp" }).default(sql`(CURRENT_TIMESTAMP)`), + }) +) + +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), + courseId: t.int().references(() => course.id), + grade: t.int(), + }) +) diff --git a/packages/drizzle/tsup.config.json b/packages/drizzle/tsup.config.json new file mode 100644 index 00000000..af6c7eef --- /dev/null +++ b/packages/drizzle/tsup.config.json @@ -0,0 +1,8 @@ +{ + "entry": ["./src/index.ts"], + "format": ["esm", "cjs"], + "minify": false, + "dts": true, + "outDir": "./dist", + "clean": true +} diff --git a/packages/drizzle/vitest.config.ts b/packages/drizzle/vitest.config.ts new file mode 100644 index 00000000..3ec27601 --- /dev/null +++ b/packages/drizzle/vitest.config.ts @@ -0,0 +1,3 @@ +import { projectConfig } from "../../vitest.config" + +export default projectConfig diff --git a/packages/mikro-orm/package.json b/packages/mikro-orm/package.json index b49ab3da..4e337593 100644 --- a/packages/mikro-orm/package.json +++ b/packages/mikro-orm/package.json @@ -25,14 +25,14 @@ "author": "xcfox", "license": "MIT", "peerDependencies": { - "graphql": ">= 16.8.0", "@gqloom/core": ">= 0.5.0", - "@mikro-orm/core": ">= 6.0.0" + "@mikro-orm/core": ">= 6.0.0", + "graphql": ">= 16.8.0" }, "devDependencies": { "@gqloom/core": "workspace:*", - "@mikro-orm/better-sqlite": "^6.2.8", - "@mikro-orm/core": "^6.2.8" + "@mikro-orm/better-sqlite": "^6.4.3", + "@mikro-orm/core": "^6.4.3" }, "homepage": "https://gqloom.dev/", "repository": { diff --git a/packages/mikro-orm/test/schema.spec.ts b/packages/mikro-orm/test/schema.spec.ts index 653fd567..c55ec0fe 100644 --- a/packages/mikro-orm/test/schema.spec.ts +++ b/packages/mikro-orm/test/schema.spec.ts @@ -11,7 +11,7 @@ import { mikroSilk } from "../src" const nullable = true -describe("MikroSilk", () => { +describe("mikroSilk", () => { interface IBook { ISBN: string sales: number diff --git a/packages/prisma/package.json b/packages/prisma/package.json index 7c19065f..8e63936b 100644 --- a/packages/prisma/package.json +++ b/packages/prisma/package.json @@ -50,7 +50,7 @@ "@gqloom/zod": "workspace:*", "@mikro-orm/better-sqlite": "^6.2.8", "@mikro-orm/core": "^6.2.8", - "@prisma/client": "5.20.0", + "@prisma/client": "^6.1.0", "graphql-yoga": "^5.6.0", "prisma": "^6.1.0", "zod": "^3.24.0" @@ -65,7 +65,7 @@ "access": "public" }, "dependencies": { - "@prisma/generator-helper": "^5.20.0", - "ts-morph": "^23.0.0" + "@prisma/generator-helper": "^6.1.0", + "ts-morph": "^25.0.0" } } diff --git a/packages/prisma/src/bobbin.ts b/packages/prisma/src/bobbin.ts index 016b2f45..dbd162f2 100644 --- a/packages/prisma/src/bobbin.ts +++ b/packages/prisma/src/bobbin.ts @@ -33,31 +33,31 @@ import type { import { capitalize, gqlType as gt } from "./utils" export class PrismaModelBobbin< - TModalSilk extends PrismaModelSilk>, + TModelSilk extends PrismaModelSilk>, TClient extends PrismaClient, > { protected modelData: PrismaModelMeta - protected delegate: InferPrismaDelegate + protected delegate: InferPrismaDelegate protected typeWeaver: PrismaActionArgsWeaver constructor( - protected readonly silk: TModalSilk, + protected readonly silk: TModelSilk, protected readonly client: TClient ) { this.modelData = silk.meta this.delegate = PrismaModelBobbin.getDelegate( silk.model.name, client - ) as InferPrismaDelegate + ) as InferPrismaDelegate this.typeWeaver = new PrismaActionArgsWeaver(silk) } - public relationField>( + public relationField>( key: TKey, options: { - middlewares?: Middleware>[] + middlewares?: Middleware>[] } & GraphQLFieldOptions = {} - ): BobbinRelationField { + ): BobbinRelationField { const field = this.silk.model.fields.find((field) => field.name === key) if (field == null) throw new Error( @@ -91,7 +91,7 @@ export class PrismaModelBobbin< protected idKey?: string protected uniqueWhere( - instance: StandardSchemaV1.InferOutput> + instance: StandardSchemaV1.InferOutput> ): any { if (this.silk.model.primaryKey == null) { this.idKey ??= (() => { @@ -115,7 +115,7 @@ export class PrismaModelBobbin< public resolver( options?: ResolverOptionsWithExtensions - ): BobbinResolver { + ): BobbinResolver { const name = capitalize(this.silk.name) return loom.resolver.of( this.silk, @@ -138,25 +138,25 @@ export class PrismaModelBobbin< ), }, options - ) as BobbinResolver + ) as BobbinResolver } public countQuery< TInputI = InferDelegateCountArgs< - InferPrismaDelegate + InferPrismaDelegate >, >({ input, ...options }: GraphQLFieldOptions & { input?: GraphQLSilk< - InferDelegateCountArgs>, + InferDelegateCountArgs>, TInputI > - middlewares?: Middleware>[] - } = {}): BobbinCountQuery { + middlewares?: Middleware>[] + } = {}): BobbinCountQuery { input ??= silk(() => this.typeWeaver.countArgs()) as GraphQLSilk< - InferDelegateCountArgs>, + InferDelegateCountArgs>, TInputI > @@ -172,7 +172,7 @@ export class PrismaModelBobbin< public findFirstQuery< TInputI = InferDelegateFindFirstArgs< - InferPrismaDelegate + InferPrismaDelegate >, >({ input, @@ -180,14 +180,14 @@ export class PrismaModelBobbin< }: GraphQLFieldOptions & { input?: GraphQLSilk< InferDelegateFindFirstArgs< - InferPrismaDelegate + InferPrismaDelegate >, TInputI > middlewares?: Middleware< - BobbinFindFirstQuery + BobbinFindFirstQuery >[] - } = {}): BobbinFindFirstQuery { + } = {}): BobbinFindFirstQuery { input ??= silk(() => this.typeWeaver.findFirstArgs()) const output = PrismaWeaver.unravel(this.silk.model, this.modelData) @@ -196,12 +196,12 @@ export class PrismaModelBobbin< ...options, input, resolve: (input) => this.delegate.findFirst(input), - }) as BobbinFindFirstQuery + }) as BobbinFindFirstQuery } public findManyQuery< TInputI = InferDelegateFindManyArgs< - InferPrismaDelegate + InferPrismaDelegate >, >({ input, @@ -209,14 +209,14 @@ export class PrismaModelBobbin< }: GraphQLFieldOptions & { input?: GraphQLSilk< InferDelegateFindManyArgs< - InferPrismaDelegate + InferPrismaDelegate >, TInputI > middlewares?: Middleware< - BobbinFindManyQuery + BobbinFindManyQuery >[] - } = {}): BobbinFindManyQuery { + } = {}): BobbinFindManyQuery { input ??= silk(() => this.typeWeaver.findManyArgs()) const output = PrismaWeaver.unravel(this.silk.model, this.modelData) @@ -225,12 +225,12 @@ export class PrismaModelBobbin< ...options, input, resolve: (input) => this.delegate.findMany(input), - }) as BobbinFindManyQuery + }) as BobbinFindManyQuery } public findUniqueQuery< TInputI = InferDelegateFindUniqueArgs< - InferPrismaDelegate + InferPrismaDelegate >, >({ input, @@ -238,14 +238,14 @@ export class PrismaModelBobbin< }: GraphQLFieldOptions & { input?: GraphQLSilk< InferDelegateFindUniqueArgs< - InferPrismaDelegate + InferPrismaDelegate >, TInputI > middlewares?: Middleware< - BobbinFindUniqueQuery + BobbinFindUniqueQuery >[] - } = {}): BobbinFindUniqueQuery { + } = {}): BobbinFindUniqueQuery { input ??= silk(() => this.typeWeaver.findUniqueArgs()) const output = PrismaWeaver.unravel(this.silk.model, this.modelData) @@ -254,25 +254,25 @@ export class PrismaModelBobbin< ...options, input, resolve: (input) => this.delegate.findUnique(input), - }) as BobbinFindUniqueQuery + }) as BobbinFindUniqueQuery } public createMutation< TInputI = InferDelegateCreateArgs< - InferPrismaDelegate + InferPrismaDelegate >, >({ input, ...options }: GraphQLFieldOptions & { input?: GraphQLSilk< - InferDelegateCreateArgs>, + InferDelegateCreateArgs>, TInputI > middlewares?: Middleware< - BobbinCreateMutation + BobbinCreateMutation >[] - } = {}): BobbinCreateMutation { + } = {}): BobbinCreateMutation { input ??= silk(() => gt.nonNull(this.typeWeaver.createArgs())) const output = PrismaWeaver.unravel(this.silk.model, this.modelData) @@ -281,12 +281,12 @@ export class PrismaModelBobbin< ...options, input, resolve: (input) => this.delegate.create(input), - }) as BobbinCreateMutation + }) as BobbinCreateMutation } public createManyMutation< TInputI = InferDelegateCreateManyArgs< - InferPrismaDelegate + InferPrismaDelegate >, >({ input, @@ -294,14 +294,14 @@ export class PrismaModelBobbin< }: GraphQLFieldOptions & { input?: GraphQLSilk< InferDelegateCreateManyArgs< - InferPrismaDelegate + InferPrismaDelegate >, TInputI > middlewares?: Middleware< - BobbinCreateManyMutation + BobbinCreateManyMutation >[] - } = {}): BobbinCreateManyMutation { + } = {}): BobbinCreateManyMutation { input ??= silk(() => gt.nonNull(this.typeWeaver.createManyArgs())) const output = PrismaModelBobbin.batchPayloadSilk() @@ -310,25 +310,25 @@ export class PrismaModelBobbin< ...options, input, resolve: (input) => this.delegate.createMany(input), - }) as BobbinCreateManyMutation + }) as BobbinCreateManyMutation } public deleteMutation< TInputI = InferDelegateDeleteArgs< - InferPrismaDelegate + InferPrismaDelegate >, >({ input, ...options }: GraphQLFieldOptions & { input?: GraphQLSilk< - InferDelegateDeleteArgs>, + InferDelegateDeleteArgs>, TInputI > middlewares?: Middleware< - BobbinDeleteMutation + BobbinDeleteMutation >[] - } = {}): BobbinDeleteMutation { + } = {}): BobbinDeleteMutation { input ??= silk(() => gt.nonNull(this.typeWeaver.deleteArgs())) const output = PrismaWeaver.unravel(this.silk.model, this.modelData) @@ -345,12 +345,12 @@ export class PrismaModelBobbin< return null } }, - }) as BobbinDeleteMutation + }) as BobbinDeleteMutation } public deleteManyMutation< TInputI = InferDelegateDeleteManyArgs< - InferPrismaDelegate + InferPrismaDelegate >, >({ input, @@ -358,14 +358,14 @@ export class PrismaModelBobbin< }: GraphQLFieldOptions & { input?: GraphQLSilk< InferDelegateDeleteManyArgs< - InferPrismaDelegate + InferPrismaDelegate >, TInputI > middlewares?: Middleware< - BobbinDeleteManyMutation + BobbinDeleteManyMutation >[] - } = {}): BobbinDeleteManyMutation { + } = {}): BobbinDeleteManyMutation { input ??= silk(() => gt.nonNull(this.typeWeaver.deleteManyArgs())) const output = PrismaModelBobbin.batchPayloadSilk() return loom.mutation(output, { @@ -379,32 +379,32 @@ export class PrismaModelBobbin< public updateMutation< TInputI = InferDelegateUpdateArgs< - InferPrismaDelegate + InferPrismaDelegate >, >({ input, ...options }: GraphQLFieldOptions & { input?: GraphQLSilk< - InferDelegateUpdateArgs>, + InferDelegateUpdateArgs>, TInputI > middlewares?: Middleware< - BobbinUpdateMutation + BobbinUpdateMutation >[] - } = {}): BobbinUpdateMutation { + } = {}): BobbinUpdateMutation { input ??= silk(() => gt.nonNull(this.typeWeaver.updateArgs())) const output = PrismaWeaver.unravel(this.silk.model, this.modelData) return loom.mutation(output, { ...options, input, resolve: (input) => this.delegate.update(input), - }) as BobbinUpdateMutation + }) as BobbinUpdateMutation } public updateManyMutation< TInputI = InferDelegateUpdateManyArgs< - InferPrismaDelegate + InferPrismaDelegate >, >({ input, @@ -412,14 +412,14 @@ export class PrismaModelBobbin< }: GraphQLFieldOptions & { input?: GraphQLSilk< InferDelegateUpdateManyArgs< - InferPrismaDelegate + InferPrismaDelegate >, TInputI > middlewares?: Middleware< - BobbinUpdateManyMutation + BobbinUpdateManyMutation >[] - } = {}): BobbinUpdateManyMutation { + } = {}): BobbinUpdateManyMutation { input ??= silk(() => gt.nonNull(this.typeWeaver.updateManyArgs())) const output = PrismaModelBobbin.batchPayloadSilk() @@ -432,27 +432,27 @@ export class PrismaModelBobbin< public upsertMutation< TInputI = InferDelegateUpsertArgs< - InferPrismaDelegate + InferPrismaDelegate >, >({ input, ...options }: GraphQLFieldOptions & { input?: GraphQLSilk< - InferDelegateUpsertArgs>, + InferDelegateUpsertArgs>, TInputI > middlewares?: Middleware< - BobbinUpsertMutation + BobbinUpsertMutation >[] - } = {}): BobbinUpsertMutation { + } = {}): BobbinUpsertMutation { input ??= silk(() => gt.nonNull(this.typeWeaver.upsertArgs())) const output = PrismaWeaver.unravel(this.silk.model, this.modelData) return loom.mutation(output, { ...options, input, resolve: (input) => this.delegate.upsert(input), - }) as BobbinUpsertMutation + }) as BobbinUpsertMutation } protected static getDelegate( @@ -482,43 +482,43 @@ export class PrismaModelBobbin< } export interface BobbinRelationField< - TModalSilk extends PrismaModelSilk>, - TKey extends keyof NonNullable, + TModelSilk extends PrismaModelSilk>, + TKey extends keyof NonNullable, > extends FieldOrOperation< - TModalSilk, - GraphQLSilk[TKey]>, + TModelSilk, + GraphQLSilk[TKey]>, undefined, "field" > {} export interface BobbinCountQuery< - TModalSilk extends PrismaModelSilk>, + TModelSilk extends PrismaModelSilk>, TClient extends PrismaClient, TInputI = InferDelegateCountArgs< - InferPrismaDelegate + InferPrismaDelegate >, > extends FieldOrOperation< undefined, GraphQLSilk, GraphQLSilk< - InferDelegateCountArgs>, + InferDelegateCountArgs>, TInputI >, "query" > {} export interface BobbinFindFirstQuery< - TModalSilk extends PrismaModelSilk>, + TModelSilk extends PrismaModelSilk>, TClient extends PrismaClient, TInputI = InferDelegateFindFirstArgs< - InferPrismaDelegate + InferPrismaDelegate >, > extends FieldOrOperation< undefined, - ReturnType, + ReturnType, GraphQLSilk< InferDelegateFindFirstArgs< - InferPrismaDelegate + InferPrismaDelegate >, TInputI >, @@ -526,17 +526,17 @@ export interface BobbinFindFirstQuery< > {} export interface BobbinFindManyQuery< - TModalSilk extends PrismaModelSilk>, + TModelSilk extends PrismaModelSilk>, TClient extends PrismaClient, TInputI = InferDelegateFindManyArgs< - InferPrismaDelegate + InferPrismaDelegate >, > extends FieldOrOperation< undefined, - ReturnType, + ReturnType, GraphQLSilk< InferDelegateFindManyArgs< - InferPrismaDelegate + InferPrismaDelegate >, TInputI >, @@ -544,17 +544,17 @@ export interface BobbinFindManyQuery< > {} export interface BobbinFindUniqueQuery< - TModalSilk extends PrismaModelSilk>, + TModelSilk extends PrismaModelSilk>, TClient extends PrismaClient, TInputI = InferDelegateFindUniqueArgs< - InferPrismaDelegate + InferPrismaDelegate >, > extends FieldOrOperation< undefined, - ReturnType, + ReturnType, GraphQLSilk< InferDelegateFindUniqueArgs< - InferPrismaDelegate + InferPrismaDelegate >, TInputI >, @@ -562,66 +562,66 @@ export interface BobbinFindUniqueQuery< > {} export interface BobbinCreateMutation< - TModalSilk extends PrismaModelSilk>, + TModelSilk extends PrismaModelSilk>, TClient extends PrismaClient, TInputI = InferDelegateCreateArgs< - InferPrismaDelegate + InferPrismaDelegate >, > extends FieldOrOperation< undefined, - TModalSilk, + TModelSilk, GraphQLSilk< - InferDelegateCreateArgs>, + InferDelegateCreateArgs>, TInputI >, "mutation" > {} export interface BobbinCreateManyMutation< - TModalSilk extends PrismaModelSilk>, + TModelSilk extends PrismaModelSilk>, TClient extends PrismaClient, TInputI = InferDelegateCreateManyArgs< - InferPrismaDelegate + InferPrismaDelegate >, > extends FieldOrOperation< undefined, GraphQLSilk, GraphQLSilk< InferDelegateCreateManyArgs< - InferPrismaDelegate + InferPrismaDelegate >, TInputI >, "mutation" > {} export interface BobbinDeleteMutation< - TModalSilk extends PrismaModelSilk>, + TModelSilk extends PrismaModelSilk>, TClient extends PrismaClient, TInputI = InferDelegateDeleteArgs< - InferPrismaDelegate + InferPrismaDelegate >, > extends FieldOrOperation< undefined, - ReturnType, + ReturnType, GraphQLSilk< - InferDelegateDeleteArgs>, + InferDelegateDeleteArgs>, TInputI >, "mutation" > {} export interface BobbinDeleteManyMutation< - TModalSilk extends PrismaModelSilk>, + TModelSilk extends PrismaModelSilk>, TClient extends PrismaClient, TInputI = InferDelegateDeleteManyArgs< - InferPrismaDelegate + InferPrismaDelegate >, > extends FieldOrOperation< undefined, GraphQLSilk, GraphQLSilk< InferDelegateDeleteManyArgs< - InferPrismaDelegate + InferPrismaDelegate >, TInputI >, @@ -629,33 +629,33 @@ export interface BobbinDeleteManyMutation< > {} export interface BobbinUpdateMutation< - TModalSilk extends PrismaModelSilk>, + TModelSilk extends PrismaModelSilk>, TClient extends PrismaClient, TInputI = InferDelegateUpdateArgs< - InferPrismaDelegate + InferPrismaDelegate >, > extends FieldOrOperation< undefined, - TModalSilk, + TModelSilk, GraphQLSilk< - InferDelegateUpdateArgs>, + InferDelegateUpdateArgs>, TInputI >, "mutation" > {} export interface BobbinUpdateManyMutation< - TModalSilk extends PrismaModelSilk>, + TModelSilk extends PrismaModelSilk>, TClient extends PrismaClient, TInputI = InferDelegateUpdateManyArgs< - InferPrismaDelegate + InferPrismaDelegate >, > extends FieldOrOperation< undefined, GraphQLSilk, GraphQLSilk< InferDelegateUpdateManyArgs< - InferPrismaDelegate + InferPrismaDelegate >, TInputI >, @@ -663,82 +663,82 @@ export interface BobbinUpdateManyMutation< > {} export interface BobbinUpsertMutation< - TModalSilk extends PrismaModelSilk>, + TModelSilk extends PrismaModelSilk>, TClient extends PrismaClient, TInputI = InferDelegateUpsertArgs< - InferPrismaDelegate + InferPrismaDelegate >, > extends FieldOrOperation< undefined, GraphQLSilk, GraphQLSilk< - InferDelegateUpsertArgs>, + InferDelegateUpsertArgs>, TInputI >, "mutation" > {} export type BobbinResolver< - TModalSilk extends PrismaModelSilk>, + TModelSilk extends PrismaModelSilk>, TClient extends PrismaClient, > = { - [TKey in keyof NonNullable]-?: BobbinRelationField< - TModalSilk, + [TKey in keyof NonNullable]-?: BobbinRelationField< + TModelSilk, TKey > } & { - [key in `count${Capitalize}`]: BobbinCountQuery< - TModalSilk, + [key in `count${Capitalize}`]: BobbinCountQuery< + TModelSilk, TClient > } & { - [key in `findFirst${Capitalize}`]: BobbinFindFirstQuery< - TModalSilk, + [key in `findFirst${Capitalize}`]: BobbinFindFirstQuery< + TModelSilk, TClient > } & { - [key in `findMany${Capitalize}`]: BobbinFindManyQuery< - TModalSilk, + [key in `findMany${Capitalize}`]: BobbinFindManyQuery< + TModelSilk, TClient > } & { - [key in `findUnique${Capitalize}`]: BobbinFindUniqueQuery< - TModalSilk, + [key in `findUnique${Capitalize}`]: BobbinFindUniqueQuery< + TModelSilk, TClient > } & { - [key in `create${Capitalize}`]: BobbinCreateMutation< - TModalSilk, + [key in `create${Capitalize}`]: BobbinCreateMutation< + TModelSilk, TClient > } & { - [key in `createMany${Capitalize}`]: BobbinCreateManyMutation< - TModalSilk, + [key in `createMany${Capitalize}`]: BobbinCreateManyMutation< + TModelSilk, TClient > } & { - [key in `delete${Capitalize}`]: BobbinDeleteMutation< - TModalSilk, + [key in `delete${Capitalize}`]: BobbinDeleteMutation< + TModelSilk, TClient > } & { - [key in `deleteMany${Capitalize}`]: BobbinDeleteManyMutation< - TModalSilk, + [key in `deleteMany${Capitalize}`]: BobbinDeleteManyMutation< + TModelSilk, TClient > } & { - [key in `update${Capitalize}`]: BobbinUpdateMutation< - TModalSilk, + [key in `update${Capitalize}`]: BobbinUpdateMutation< + TModelSilk, TClient > } & { - [key in `updateMany${Capitalize}`]: BobbinUpdateManyMutation< - TModalSilk, + [key in `updateMany${Capitalize}`]: BobbinUpdateManyMutation< + TModelSilk, TClient > } & { - [key in `upsert${Capitalize}`]: BobbinUpsertMutation< - TModalSilk, + [key in `upsert${Capitalize}`]: BobbinUpsertMutation< + TModelSilk, TClient > } diff --git a/packages/prisma/test/bobbin-resolver.spec.ts b/packages/prisma/test/bobbin-resolver.spec.ts index b54940cd..498b4fed 100644 --- a/packages/prisma/test/bobbin-resolver.spec.ts +++ b/packages/prisma/test/bobbin-resolver.spec.ts @@ -171,8 +171,11 @@ describe("Bobbin Resolver", () => { }) }) - it("should be able to create a post with a author", async () => { - const query = /* GraphQL */ ` + it( + "should be able to create a post with a author", + { retry: 6 }, + async () => { + const query = /* GraphQL */ ` mutation createPost($data: PostCreateInput!) { createPost(data: $data) { id @@ -186,37 +189,38 @@ describe("Bobbin Resolver", () => { } ` - const response = await execute(query, { - data: { - title: "Hello World", - author: { - connectOrCreate: { - where: { - email: "bob@bob.com", - }, - create: { - email: "bob@bob.com", - name: "Bob", + const response = await execute(query, { + data: { + title: "Hello World", + author: { + connectOrCreate: { + where: { + email: "bob@bob.com", + }, + create: { + email: "bob@bob.com", + name: "Bob", + }, }, }, }, - }, - }) + }) - expect(response).toMatchObject({ - createPost: { - id: expect.any(String), - title: "Hello World", - author: { + expect(response).toMatchObject({ + createPost: { id: expect.any(String), - name: "Bob", - email: "bob@bob.com", + title: "Hello World", + author: { + id: expect.any(String), + name: "Bob", + email: "bob@bob.com", + }, }, - }, - }) - }) + }) + } + ) - it("should be able to delete a user", async () => { + it("should be able to delete a user", { retry: 6 }, async () => { await db.user.create({ data: { email: "bob@bob.com" } }) const query = /* GraphQL */ ` diff --git a/packages/prisma/test/schema.spec.ts b/packages/prisma/test/schema.spec.ts index b0295e07..727faadf 100644 --- a/packages/prisma/test/schema.spec.ts +++ b/packages/prisma/test/schema.spec.ts @@ -6,6 +6,7 @@ import { PrismaWeaver } from "../src" const UserModel: DMMF.Model = { name: "User", + schema: null, dbName: null, fields: [ { diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index d73a35b8..ba145697 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -202,6 +202,45 @@ importers: specifier: 1.0.0-beta.4 version: 1.0.0-beta.4 + packages/drizzle: + devDependencies: + '@gqloom/core': + specifier: workspace:* + version: link:../core + '@libsql/client': + specifier: ^0.14.0 + version: 0.14.0 + '@types/pg': + specifier: ^8.11.10 + version: 8.11.10 + dotenv: + specifier: ^16.4.7 + version: 16.4.7 + drizzle-kit: + specifier: ^0.30.1 + version: 0.30.1 + drizzle-orm: + specifier: ^0.38.3 + version: 0.38.3(@libsql/client@0.14.0)(@prisma/client@6.1.0(prisma@6.1.0))(@types/pg@8.11.10)(@types/react@18.3.6)(better-sqlite3@11.7.2)(knex@3.1.0(better-sqlite3@11.7.2)(mysql2@3.12.0)(pg@8.13.1))(mysql2@3.12.0)(pg@8.13.1)(prisma@6.1.0)(react@18.3.1) + graphql: + specifier: ^16.8.1 + version: 16.8.1(patch_hash=2p6wc42resg5at2p4utt5idamq) + graphql-yoga: + specifier: ^5.6.0 + version: 5.6.0(graphql@16.8.1(patch_hash=2p6wc42resg5at2p4utt5idamq)) + mysql2: + specifier: ^3.12.0 + version: 3.12.0 + pg: + specifier: ^8.13.1 + version: 8.13.1 + tsx: + specifier: ^4.7.2 + version: 4.7.2 + valibot: + specifier: 1.0.0-beta.12 + version: 1.0.0-beta.12(typescript@5.4.3) + packages/federation: devDependencies: '@apollo/server': @@ -245,23 +284,23 @@ importers: specifier: workspace:* version: link:../core '@mikro-orm/better-sqlite': - specifier: ^6.2.8 - version: 6.2.8(@mikro-orm/core@6.2.8) + specifier: ^6.4.3 + version: 6.4.3(@mikro-orm/core@6.4.3)(libsql@0.4.7) '@mikro-orm/core': - specifier: ^6.2.8 - version: 6.2.8 + specifier: ^6.4.3 + version: 6.4.3 packages/prisma: dependencies: '@prisma/generator-helper': - specifier: ^5.20.0 - version: 5.20.0 + specifier: ^6.1.0 + version: 6.1.0 graphql: specifier: '>= 16.8.0' version: 16.8.1(patch_hash=2p6wc42resg5at2p4utt5idamq) ts-morph: - specifier: ^23.0.0 - version: 23.0.0 + specifier: ^25.0.0 + version: 25.0.0 devDependencies: '@gqloom/core': specifier: workspace:* @@ -276,8 +315,8 @@ importers: specifier: ^6.2.8 version: 6.2.8 '@prisma/client': - specifier: 5.20.0 - version: 5.20.0(prisma@6.1.0) + specifier: ^6.1.0 + version: 6.1.0(prisma@6.1.0) graphql-yoga: specifier: ^5.6.0 version: 5.6.0(graphql@16.8.1(patch_hash=2p6wc42resg5at2p4utt5idamq)) @@ -529,6 +568,9 @@ packages: '@bufbuild/protobuf@2.2.0': resolution: {integrity: sha512-+imAQkHf7U/Rwvu0wk1XWgsP3WnpCWmK7B48f0XqSNzgk64+grljTKC7pnO/xBiEMUziF7vKRfbBnOQhg126qQ==} + '@drizzle-team/brocli@0.10.2': + resolution: {integrity: sha512-z33Il7l5dKjUgGULTqBsQBQwckHh5AbIuxhdsIxDDiZAzBOrZO6q9ogcWC65kU382AfynTfgNumVcNIjuIua6w==} + '@envelop/core@5.0.1': resolution: {integrity: sha512-wxA8EyE1fPnlbP0nC/SFI7uU8wSNf4YjxZhAPu0P63QbgIvqHtHsH4L3/u+rsTruzhk3OvNRgQyLsMfaR9uzAQ==} engines: {node: '>=18.0.0'} @@ -537,6 +579,14 @@ packages: resolution: {integrity: sha512-IPjmgSc4KpQRlO4qbEDnBEixvtb06WDmjKfi/7fkZaryh5HuOmTtixe1EupQI5XfXO8joc3d27uUZ0QdC++euA==} engines: {node: '>=18.0.0'} + '@esbuild-kit/core-utils@3.3.2': + resolution: {integrity: sha512-sPRAnw9CdSsRmEtnsl2WXWdyquogVpB3yZ3dgwJfe8zrOzTsV7cJvmwrKVa+0ma5BoiGJ+BoqkMvawbayKUsqQ==} + deprecated: 'Merged into tsx: https://tsx.is' + + '@esbuild-kit/esm-loader@2.6.5': + resolution: {integrity: sha512-FxEMIkJKnodyA1OaCUoEvbYRkoZlLZ4d/eXFu9Fh8CbBBgP5EmZxrfTRyN0qpXZ4vOvqnE5YdRdcrmUUXuU+dA==} + deprecated: 'Merged into tsx: https://tsx.is' + '@esbuild/aix-ppc64@0.19.12': resolution: {integrity: sha512-bmoCYyWdEL3wDQIVbcyzRyeKLgk2WtWLTWz1ZIAZF/EGbNOwSA6ew3PftJ1PqMiOOGu0OyFMzG53L0zqIpPeNA==} engines: {node: '>=12'} @@ -549,6 +599,12 @@ packages: cpu: [ppc64] os: [aix] + '@esbuild/android-arm64@0.18.20': + resolution: {integrity: sha512-Nz4rJcchGDtENV0eMKUNa6L12zz2zBDXuhj/Vjh18zGqB44Bi7MBMSXjgunJgjRhCmKOjnPuZp4Mb6OKqtMHLQ==} + engines: {node: '>=12'} + cpu: [arm64] + os: [android] + '@esbuild/android-arm64@0.19.12': resolution: {integrity: sha512-P0UVNGIienjZv3f5zq0DP3Nt2IE/3plFzuaS96vihvD0Hd6H/q4WXUGpCxD/E8YrSXfNyRPbpTq+T8ZQioSuPA==} engines: {node: '>=12'} @@ -561,6 +617,12 @@ packages: cpu: [arm64] os: [android] + '@esbuild/android-arm@0.18.20': + resolution: {integrity: sha512-fyi7TDI/ijKKNZTUJAQqiG5T7YjJXgnzkURqmGj13C6dCqckZBLdl4h7bkhHt/t0WP+zO9/zwroDvANaOqO5Sw==} + engines: {node: '>=12'} + cpu: [arm] + os: [android] + '@esbuild/android-arm@0.19.12': resolution: {integrity: sha512-qg/Lj1mu3CdQlDEEiWrlC4eaPZ1KztwGJ9B6J+/6G+/4ewxJg7gqj8eVYWvao1bXrqGiW2rsBZFSX3q2lcW05w==} engines: {node: '>=12'} @@ -573,6 +635,12 @@ packages: cpu: [arm] os: [android] + '@esbuild/android-x64@0.18.20': + resolution: {integrity: sha512-8GDdlePJA8D6zlZYJV/jnrRAi6rOiNaCC/JclcXpB+KIuvfBN4owLtgzY2bsxnx666XjJx2kDPUmnTtR8qKQUg==} + engines: {node: '>=12'} + cpu: [x64] + os: [android] + '@esbuild/android-x64@0.19.12': resolution: {integrity: sha512-3k7ZoUW6Q6YqhdhIaq/WZ7HwBpnFBlW905Fa4s4qWJyiNOgT1dOqDiVAQFwBH7gBRZr17gLrlFCRzF6jFh7Kew==} engines: {node: '>=12'} @@ -585,6 +653,12 @@ packages: cpu: [x64] os: [android] + '@esbuild/darwin-arm64@0.18.20': + resolution: {integrity: sha512-bxRHW5kHU38zS2lPTPOyuyTm+S+eobPUnTNkdJEfAddYgEcll4xkT8DB9d2008DtTbl7uJag2HuE5NZAZgnNEA==} + engines: {node: '>=12'} + cpu: [arm64] + os: [darwin] + '@esbuild/darwin-arm64@0.19.12': resolution: {integrity: sha512-B6IeSgZgtEzGC42jsI+YYu9Z3HKRxp8ZT3cqhvliEHovq8HSX2YX8lNocDn79gCKJXOSaEot9MVYky7AKjCs8g==} engines: {node: '>=12'} @@ -597,6 +671,12 @@ packages: cpu: [arm64] os: [darwin] + '@esbuild/darwin-x64@0.18.20': + resolution: {integrity: sha512-pc5gxlMDxzm513qPGbCbDukOdsGtKhfxD1zJKXjCCcU7ju50O7MeAZ8c4krSJcOIJGFR+qx21yMMVYwiQvyTyQ==} + engines: {node: '>=12'} + cpu: [x64] + os: [darwin] + '@esbuild/darwin-x64@0.19.12': resolution: {integrity: sha512-hKoVkKzFiToTgn+41qGhsUJXFlIjxI/jSYeZf3ugemDYZldIXIxhvwN6erJGlX4t5h417iFuheZ7l+YVn05N3A==} engines: {node: '>=12'} @@ -609,6 +689,12 @@ packages: cpu: [x64] os: [darwin] + '@esbuild/freebsd-arm64@0.18.20': + resolution: {integrity: sha512-yqDQHy4QHevpMAaxhhIwYPMv1NECwOvIpGCZkECn8w2WFHXjEwrBn3CeNIYsibZ/iZEUemj++M26W3cNR5h+Tw==} + engines: {node: '>=12'} + cpu: [arm64] + os: [freebsd] + '@esbuild/freebsd-arm64@0.19.12': resolution: {integrity: sha512-4aRvFIXmwAcDBw9AueDQ2YnGmz5L6obe5kmPT8Vd+/+x/JMVKCgdcRwH6APrbpNXsPz+K653Qg8HB/oXvXVukA==} engines: {node: '>=12'} @@ -621,6 +707,12 @@ packages: cpu: [arm64] os: [freebsd] + '@esbuild/freebsd-x64@0.18.20': + resolution: {integrity: sha512-tgWRPPuQsd3RmBZwarGVHZQvtzfEBOreNuxEMKFcd5DaDn2PbBxfwLcj4+aenoh7ctXcbXmOQIn8HI6mCSw5MQ==} + engines: {node: '>=12'} + cpu: [x64] + os: [freebsd] + '@esbuild/freebsd-x64@0.19.12': resolution: {integrity: sha512-EYoXZ4d8xtBoVN7CEwWY2IN4ho76xjYXqSXMNccFSx2lgqOG/1TBPW0yPx1bJZk94qu3tX0fycJeeQsKovA8gg==} engines: {node: '>=12'} @@ -633,6 +725,12 @@ packages: cpu: [x64] os: [freebsd] + '@esbuild/linux-arm64@0.18.20': + resolution: {integrity: sha512-2YbscF+UL7SQAVIpnWvYwM+3LskyDmPhe31pE7/aoTMFKKzIc9lLbyGUpmmb8a8AixOL61sQ/mFh3jEjHYFvdA==} + engines: {node: '>=12'} + cpu: [arm64] + os: [linux] + '@esbuild/linux-arm64@0.19.12': resolution: {integrity: sha512-EoTjyYyLuVPfdPLsGVVVC8a0p1BFFvtpQDB/YLEhaXyf/5bczaGeN15QkR+O4S5LeJ92Tqotve7i1jn35qwvdA==} engines: {node: '>=12'} @@ -645,6 +743,12 @@ packages: cpu: [arm64] os: [linux] + '@esbuild/linux-arm@0.18.20': + resolution: {integrity: sha512-/5bHkMWnq1EgKr1V+Ybz3s1hWXok7mDFUMQ4cG10AfW3wL02PSZi5kFpYKrptDsgb2WAJIvRcDm+qIvXf/apvg==} + engines: {node: '>=12'} + cpu: [arm] + os: [linux] + '@esbuild/linux-arm@0.19.12': resolution: {integrity: sha512-J5jPms//KhSNv+LO1S1TX1UWp1ucM6N6XuL6ITdKWElCu8wXP72l9MM0zDTzzeikVyqFE6U8YAV9/tFyj0ti+w==} engines: {node: '>=12'} @@ -657,6 +761,12 @@ packages: cpu: [arm] os: [linux] + '@esbuild/linux-ia32@0.18.20': + resolution: {integrity: sha512-P4etWwq6IsReT0E1KHU40bOnzMHoH73aXp96Fs8TIT6z9Hu8G6+0SHSw9i2isWrD2nbx2qo5yUqACgdfVGx7TA==} + engines: {node: '>=12'} + cpu: [ia32] + os: [linux] + '@esbuild/linux-ia32@0.19.12': resolution: {integrity: sha512-Thsa42rrP1+UIGaWz47uydHSBOgTUnwBwNq59khgIwktK6x60Hivfbux9iNR0eHCHzOLjLMLfUMLCypBkZXMHA==} engines: {node: '>=12'} @@ -669,6 +779,12 @@ packages: cpu: [ia32] os: [linux] + '@esbuild/linux-loong64@0.18.20': + resolution: {integrity: sha512-nXW8nqBTrOpDLPgPY9uV+/1DjxoQ7DoB2N8eocyq8I9XuqJ7BiAMDMf9n1xZM9TgW0J8zrquIb/A7s3BJv7rjg==} + engines: {node: '>=12'} + cpu: [loong64] + os: [linux] + '@esbuild/linux-loong64@0.19.12': resolution: {integrity: sha512-LiXdXA0s3IqRRjm6rV6XaWATScKAXjI4R4LoDlvO7+yQqFdlr1Bax62sRwkVvRIrwXxvtYEHHI4dm50jAXkuAA==} engines: {node: '>=12'} @@ -681,6 +797,12 @@ packages: cpu: [loong64] os: [linux] + '@esbuild/linux-mips64el@0.18.20': + resolution: {integrity: sha512-d5NeaXZcHp8PzYy5VnXV3VSd2D328Zb+9dEq5HE6bw6+N86JVPExrA6O68OPwobntbNJ0pzCpUFZTo3w0GyetQ==} + engines: {node: '>=12'} + cpu: [mips64el] + os: [linux] + '@esbuild/linux-mips64el@0.19.12': resolution: {integrity: sha512-fEnAuj5VGTanfJ07ff0gOA6IPsvrVHLVb6Lyd1g2/ed67oU1eFzL0r9WL7ZzscD+/N6i3dWumGE1Un4f7Amf+w==} engines: {node: '>=12'} @@ -693,6 +815,12 @@ packages: cpu: [mips64el] os: [linux] + '@esbuild/linux-ppc64@0.18.20': + resolution: {integrity: sha512-WHPyeScRNcmANnLQkq6AfyXRFr5D6N2sKgkFo2FqguP44Nw2eyDlbTdZwd9GYk98DZG9QItIiTlFLHJHjxP3FA==} + engines: {node: '>=12'} + cpu: [ppc64] + os: [linux] + '@esbuild/linux-ppc64@0.19.12': resolution: {integrity: sha512-nYJA2/QPimDQOh1rKWedNOe3Gfc8PabU7HT3iXWtNUbRzXS9+vgB0Fjaqr//XNbd82mCxHzik2qotuI89cfixg==} engines: {node: '>=12'} @@ -705,6 +833,12 @@ packages: cpu: [ppc64] os: [linux] + '@esbuild/linux-riscv64@0.18.20': + resolution: {integrity: sha512-WSxo6h5ecI5XH34KC7w5veNnKkju3zBRLEQNY7mv5mtBmrP/MjNBCAlsM2u5hDBlS3NGcTQpoBvRzqBcRtpq1A==} + engines: {node: '>=12'} + cpu: [riscv64] + os: [linux] + '@esbuild/linux-riscv64@0.19.12': resolution: {integrity: sha512-2MueBrlPQCw5dVJJpQdUYgeqIzDQgw3QtiAHUC4RBz9FXPrskyyU3VI1hw7C0BSKB9OduwSJ79FTCqtGMWqJHg==} engines: {node: '>=12'} @@ -717,6 +851,12 @@ packages: cpu: [riscv64] os: [linux] + '@esbuild/linux-s390x@0.18.20': + resolution: {integrity: sha512-+8231GMs3mAEth6Ja1iK0a1sQ3ohfcpzpRLH8uuc5/KVDFneH6jtAJLFGafpzpMRO6DzJ6AvXKze9LfFMrIHVQ==} + engines: {node: '>=12'} + cpu: [s390x] + os: [linux] + '@esbuild/linux-s390x@0.19.12': resolution: {integrity: sha512-+Pil1Nv3Umes4m3AZKqA2anfhJiVmNCYkPchwFJNEJN5QxmTs1uzyy4TvmDrCRNT2ApwSari7ZIgrPeUx4UZDg==} engines: {node: '>=12'} @@ -729,6 +869,12 @@ packages: cpu: [s390x] os: [linux] + '@esbuild/linux-x64@0.18.20': + resolution: {integrity: sha512-UYqiqemphJcNsFEskc73jQ7B9jgwjWrSayxawS6UVFZGWrAAtkzjxSqnoclCXxWtfwLdzU+vTpcNYhpn43uP1w==} + engines: {node: '>=12'} + cpu: [x64] + os: [linux] + '@esbuild/linux-x64@0.19.12': resolution: {integrity: sha512-B71g1QpxfwBvNrfyJdVDexenDIt1CiDN1TIXLbhOw0KhJzE78KIFGX6OJ9MrtC0oOqMWf+0xop4qEU8JrJTwCg==} engines: {node: '>=12'} @@ -741,6 +887,12 @@ packages: cpu: [x64] os: [linux] + '@esbuild/netbsd-x64@0.18.20': + resolution: {integrity: sha512-iO1c++VP6xUBUmltHZoMtCUdPlnPGdBom6IrO4gyKPFFVBKioIImVooR5I83nTew5UOYrk3gIJhbZh8X44y06A==} + engines: {node: '>=12'} + cpu: [x64] + os: [netbsd] + '@esbuild/netbsd-x64@0.19.12': resolution: {integrity: sha512-3ltjQ7n1owJgFbuC61Oj++XhtzmymoCihNFgT84UAmJnxJfm4sYCiSLTXZtE00VWYpPMYc+ZQmB6xbSdVh0JWA==} engines: {node: '>=12'} @@ -753,6 +905,12 @@ packages: cpu: [x64] os: [netbsd] + '@esbuild/openbsd-x64@0.18.20': + resolution: {integrity: sha512-e5e4YSsuQfX4cxcygw/UCPIEP6wbIL+se3sxPdCiMbFLBWu0eiZOJ7WoD+ptCLrmjZBK1Wk7I6D/I3NglUGOxg==} + engines: {node: '>=12'} + cpu: [x64] + os: [openbsd] + '@esbuild/openbsd-x64@0.19.12': resolution: {integrity: sha512-RbrfTB9SWsr0kWmb9srfF+L933uMDdu9BIzdA7os2t0TXhCRjrQyCeOt6wVxr79CKD4c+p+YhCj31HBkYcXebw==} engines: {node: '>=12'} @@ -765,6 +923,12 @@ packages: cpu: [x64] os: [openbsd] + '@esbuild/sunos-x64@0.18.20': + resolution: {integrity: sha512-kDbFRFp0YpTQVVrqUd5FTYmWo45zGaXe0X8E1G/LKFC0v8x0vWrhOWSLITcCn63lmZIxfOMXtCfti/RxN/0wnQ==} + engines: {node: '>=12'} + cpu: [x64] + os: [sunos] + '@esbuild/sunos-x64@0.19.12': resolution: {integrity: sha512-HKjJwRrW8uWtCQnQOz9qcU3mUZhTUQvi56Q8DPTLLB+DawoiQdjsYq+j+D3s9I8VFtDr+F9CjgXKKC4ss89IeA==} engines: {node: '>=12'} @@ -777,6 +941,12 @@ packages: cpu: [x64] os: [sunos] + '@esbuild/win32-arm64@0.18.20': + resolution: {integrity: sha512-ddYFR6ItYgoaq4v4JmQQaAI5s7npztfV4Ag6NrhiaW0RrnOXqBkgwZLofVTlq1daVTQNhtI5oieTvkRPfZrePg==} + engines: {node: '>=12'} + cpu: [arm64] + os: [win32] + '@esbuild/win32-arm64@0.19.12': resolution: {integrity: sha512-URgtR1dJnmGvX864pn1B2YUYNzjmXkuJOIqG2HdU62MVS4EHpU2946OZoTMnRUHklGtJdJZ33QfzdjGACXhn1A==} engines: {node: '>=12'} @@ -789,6 +959,12 @@ packages: cpu: [arm64] os: [win32] + '@esbuild/win32-ia32@0.18.20': + resolution: {integrity: sha512-Wv7QBi3ID/rROT08SABTS7eV4hX26sVduqDOTe1MvGMjNd3EjOz4b7zeexIR62GTIEKrfJXKL9LFxTYgkyeu7g==} + engines: {node: '>=12'} + cpu: [ia32] + os: [win32] + '@esbuild/win32-ia32@0.19.12': resolution: {integrity: sha512-+ZOE6pUkMOJfmxmBZElNOx72NKpIa/HFOMGzu8fqzQJ5kgf6aTGrcJaFsNiVMH4JKpMipyK+7k0n2UXN7a8YKQ==} engines: {node: '>=12'} @@ -801,6 +977,12 @@ packages: cpu: [ia32] os: [win32] + '@esbuild/win32-x64@0.18.20': + resolution: {integrity: sha512-kTdfRcSiDfQca/y9QIkng02avJ+NCaQvrMejlsB3RRv5sE9rRoeBPISaZpKxHELzRxZyLvNts1P27W3wV+8geQ==} + engines: {node: '>=12'} + cpu: [x64] + os: [win32] + '@esbuild/win32-x64@0.19.12': resolution: {integrity: sha512-T1QyPSDCyMXaO3pzBkF96E8xMkiRYbUEZADd29SyPGabqxMViNoii+NcK7eWJAEoU6RZyEm5lVSIjTmcdoB9HA==} engines: {node: '>=12'} @@ -941,6 +1123,57 @@ packages: '@kamilkisiela/fast-url-parser@1.1.4': resolution: {integrity: sha512-gbkePEBupNydxCelHCESvFSFM8XPh1Zs/OAVRW/rKpEqPAl5PbOM90Si8mv9bvnR53uPD2s/FiRxdvSejpRJew==} + '@libsql/client@0.14.0': + resolution: {integrity: sha512-/9HEKfn6fwXB5aTEEoMeFh4CtG0ZzbncBb1e++OCdVpgKZ/xyMsIVYXm0w7Pv4RUel803vE6LwniB3PqD72R0Q==} + + '@libsql/core@0.14.0': + resolution: {integrity: sha512-nhbuXf7GP3PSZgdCY2Ecj8vz187ptHlZQ0VRc751oB2C1W8jQUXKKklvt7t1LJiUTQBVJuadF628eUk+3cRi4Q==} + + '@libsql/darwin-arm64@0.4.7': + resolution: {integrity: sha512-yOL742IfWUlUevnI5PdnIT4fryY3LYTdLm56bnY0wXBw7dhFcnjuA7jrH3oSVz2mjZTHujxoITgAE7V6Z+eAbg==} + cpu: [arm64] + os: [darwin] + + '@libsql/darwin-x64@0.4.7': + resolution: {integrity: sha512-ezc7V75+eoyyH07BO9tIyJdqXXcRfZMbKcLCeF8+qWK5nP8wWuMcfOVywecsXGRbT99zc5eNra4NEx6z5PkSsA==} + cpu: [x64] + os: [darwin] + + '@libsql/hrana-client@0.7.0': + resolution: {integrity: sha512-OF8fFQSkbL7vJY9rfuegK1R7sPgQ6kFMkDamiEccNUvieQ+3urzfDFI616oPl8V7T9zRmnTkSjMOImYCAVRVuw==} + + '@libsql/isomorphic-fetch@0.3.1': + resolution: {integrity: sha512-6kK3SUK5Uu56zPq/Las620n5aS9xJq+jMBcNSOmjhNf/MUvdyji4vrMTqD7ptY7/4/CAVEAYDeotUz60LNQHtw==} + engines: {node: '>=18.0.0'} + + '@libsql/isomorphic-ws@0.1.5': + resolution: {integrity: sha512-DtLWIH29onUYR00i0GlQ3UdcTRC6EP4u9w/h9LxpUZJWRMARk6dQwZ6Jkd+QdwVpuAOrdxt18v0K2uIYR3fwFg==} + + '@libsql/linux-arm64-gnu@0.4.7': + resolution: {integrity: sha512-WlX2VYB5diM4kFfNaYcyhw5y+UJAI3xcMkEUJZPtRDEIu85SsSFrQ+gvoKfcVh76B//ztSeEX2wl9yrjF7BBCA==} + cpu: [arm64] + os: [linux] + + '@libsql/linux-arm64-musl@0.4.7': + resolution: {integrity: sha512-6kK9xAArVRlTCpWeqnNMCoXW1pe7WITI378n4NpvU5EJ0Ok3aNTIC2nRPRjhro90QcnmLL1jPcrVwO4WD1U0xw==} + cpu: [arm64] + os: [linux] + + '@libsql/linux-x64-gnu@0.4.7': + resolution: {integrity: sha512-CMnNRCmlWQqqzlTw6NeaZXzLWI8bydaXDke63JTUCvu8R+fj/ENsLrVBtPDlxQ0wGsYdXGlrUCH8Qi9gJep0yQ==} + cpu: [x64] + os: [linux] + + '@libsql/linux-x64-musl@0.4.7': + resolution: {integrity: sha512-nI6tpS1t6WzGAt1Kx1n1HsvtBbZ+jHn0m7ogNNT6pQHZQj7AFFTIMeDQw/i/Nt5H38np1GVRNsFe99eSIMs9XA==} + cpu: [x64] + os: [linux] + + '@libsql/win32-x64-msvc@0.4.7': + resolution: {integrity: sha512-7pJzOWzPm6oJUxml+PCDRzYQ4A1hTMHAciTAHfFK4fkbDZX33nWPVG7Y3vqdKtslcwAzwmrNDc6sXy2nwWnbiw==} + cpu: [x64] + os: [win32] + '@lukeed/ms@2.0.2': resolution: {integrity: sha512-9I2Zn6+NJLfaGoz9jN3lpwDgAYvfGeNYdbAIjJOqzs4Tpc+VU3Jqq4IofSUBKajiDS8k9fZIg18/z13mpk1bsA==} engines: {node: '>=8'} @@ -967,16 +1200,42 @@ packages: peerDependencies: '@mikro-orm/core': ^6.0.0 + '@mikro-orm/better-sqlite@6.4.3': + resolution: {integrity: sha512-T54U4xRzVU7iY+rQaDtz7NvIYWyL/LVPARjr3Znttp3qZXTAKC41fmSTpKyFH5npkabr3hulc+Lx64FI86NrIg==} + engines: {node: '>= 18.12.0'} + peerDependencies: + '@mikro-orm/core': ^6.0.0 + '@mikro-orm/core@6.2.8': resolution: {integrity: sha512-zXw9THLF3zq/zUmL+nKZqM3kSVuOrSC2rs7ZSlnoselcqkXfYeblYC9E1Fg7ktpNa/4zzovYAWHrifFzpz2FHg==} engines: {node: '>= 18.12.0'} + '@mikro-orm/core@6.4.3': + resolution: {integrity: sha512-UTaqKs1bomYtGmEEZ8sNBOmW2OqT5NcMh+pBV2iJ6WLM5MuiIEuNhDMuvvPE5gNEwUzc1HyRhUV87bRDhDIGRg==} + engines: {node: '>= 18.12.0'} + '@mikro-orm/knex@6.2.8': resolution: {integrity: sha512-lO434glly1YSTYtgxXgdAhBYYb0t81iSbutsD3/L2hiDgcyXspmEIKb3lROnfXzSrfCNCLU65Z/HZPEHKlO27w==} engines: {node: '>= 18.12.0'} peerDependencies: '@mikro-orm/core': ^6.0.0 + '@mikro-orm/knex@6.4.3': + resolution: {integrity: sha512-gVkRD/cIn6qxk/P9nR+IufZxJwuCCdv0AtcGvShxXXvaoIrQPJYDV7HRxBOHCEyNygr6M3Fqpph1oPoT6aezTQ==} + engines: {node: '>= 18.12.0'} + peerDependencies: + '@mikro-orm/core': ^6.0.0 + better-sqlite3: '*' + libsql: '*' + mariadb: '*' + peerDependenciesMeta: + better-sqlite3: + optional: true + libsql: + optional: true + mariadb: + optional: true + '@module-federation/runtime-tools@0.5.1': resolution: {integrity: sha512-nfBedkoZ3/SWyO0hnmaxuz0R0iGPSikHZOAZ0N/dVSQaIzlffUo35B5nlC2wgWIc0JdMZfkwkjZRrnuuDIJbzg==} @@ -989,6 +1248,9 @@ packages: '@module-federation/webpack-bundler-runtime@0.5.1': resolution: {integrity: sha512-mMhRFH0k2VjwHt3Jol9JkUsmI/4XlrAoBG3E0o7HoyoPYv1UFOWyqAflfANcUPgbYpvqmyLzDcO+3IT36LXnrA==} + '@neon-rs/load@0.0.4': + resolution: {integrity: sha512-kTPhdZyTQxB+2wpiRcFWrDcejc4JI6tkPuS7UZCG4l6Zvc5kU/gGQ/ozvHTh1XR5tS+UlfAfGuPajjzQjCiHCw==} + '@nodelib/fs.scandir@2.1.5': resolution: {integrity: sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==} engines: {node: '>= 8'} @@ -1014,6 +1276,15 @@ packages: prisma: optional: true + '@prisma/client@6.1.0': + resolution: {integrity: sha512-AbQYc5+EJKm1Ydfq3KxwcGiy7wIbm4/QbjCKWWoNROtvy7d6a3gmAGkKjK0iUCzh+rHV8xDhD5Cge8ke/kiy5Q==} + engines: {node: '>=18.18'} + peerDependencies: + prisma: '*' + peerDependenciesMeta: + prisma: + optional: true + '@prisma/debug@5.20.0': resolution: {integrity: sha512-oCx79MJ4HSujokA8S1g0xgZUGybD4SyIOydoHMngFYiwEwYDQ5tBQkK5XoEHuwOYDKUOKRn/J0MEymckc4IgsQ==} @@ -1038,8 +1309,8 @@ packages: '@prisma/fetch-engine@6.1.0': resolution: {integrity: sha512-asdFi7TvPlEZ8CzSZ/+Du5wZ27q6OJbRSXh+S8ISZguu+S9KtS/gP7NeXceZyb1Jv1SM1S5YfiCv+STDsG6rrg==} - '@prisma/generator-helper@5.20.0': - resolution: {integrity: sha512-37Aibw0wVRQgQVtCdNAIN71YFnSQfvetok7vd95KKkYkQRbEx94gsvPDpyN9Mw7p3IwA3nFgPfLc3jBRztUkKw==} + '@prisma/generator-helper@6.1.0': + resolution: {integrity: sha512-drHaTKRmRsz6esHk2dpn7aPoxfttoqkYWSaI7zXsL5YQz73jww1YgJpGbPgOUiblriJAtdT4o7mibMqnf8TOsA==} '@prisma/get-platform@5.20.0': resolution: {integrity: sha512-8/+CehTZZNzJlvuryRgc77hZCWrUDYd/PmlZ7p2yNXtmf2Una4BWnTbak3us6WVdqoz5wmptk6IhsXdG2v5fmA==} @@ -1351,8 +1622,8 @@ packages: '@swc/helpers@0.5.15': resolution: {integrity: sha512-JQ5TuMi45Owi4/BIMAJBoSQoOJu12oOk/gADqlcUL9JEdHB8vyjUSsxqeNXnmXHjYKMi2WcYtezGEEhqUI/E2g==} - '@ts-morph/common@0.24.0': - resolution: {integrity: sha512-c1xMmNHWpNselmpIqursHeOHHBTIsJLbB+NuovbTTRCNiTLEr/U9dbJ8qy0jd/O2x5pc3seWuOUN5R2IoOTp8A==} + '@ts-morph/common@0.26.0': + resolution: {integrity: sha512-/RmKAtctStXqM5nECMQ46duT74Hoig/DBzhWXGHcodlDNrgRbsbwwHqSKFNbca6z9Xt/CUWMeXOsC9QEN1+rqw==} '@types/acorn@4.0.6': resolution: {integrity: sha512-veQTnWP+1D/xbxVrPC3zHnCZRjSrKfhbMUlEA43iMZLu7EsnTtkJklIuwrCPbOi8YkvDQAiW05VQQFvvz9oieQ==} @@ -1417,6 +1688,9 @@ packages: '@types/parse5@6.0.3': resolution: {integrity: sha512-SuT16Q1K51EAVPz1K29DJ/sXjhSQ0zjvsypYJ6tlwVsRV9jwW5Adq2ch8Dq8kDBCkYnELS7N7VNCSB5nC56t/g==} + '@types/pg@8.11.10': + resolution: {integrity: sha512-LczQUW4dbOQzsH2RQ5qoeJ6qJPdrcM/DcMLoqWQkMLMsq83J5lAX3LXjdkWdpscFy67JSOWDnh7Ny/sPFykmkg==} + '@types/prop-types@15.7.13': resolution: {integrity: sha512-hCZTSvwbzWGvhqxp/RqVqwU999pBf2vp7hzIjiYOsl8wqOmUxkQ6ddw1cV3l8811+kdUFus/q4d1Y3E3SyEifA==} @@ -1444,6 +1718,9 @@ packages: '@types/uuid@9.0.8': resolution: {integrity: sha512-jg+97EGIcY9AGHJJRaaPVgetKDsrTgbRjQ5Msgjh/DQKEFl0DtyRr/VCOyD1T2R1MNeWPK/u7JoGhlDZnKBAfA==} + '@types/ws@8.5.13': + resolution: {integrity: sha512-osM/gWBTPKgHV8XkTunnegTRIsvF6owmf5w+JtAfOw472dptdm0dlGv4xCt6GwQRcC2XVOvvRE/0bAoQcL2QkA==} + '@ungap/structured-clone@1.2.0': resolution: {integrity: sha512-zuVdFrMJiuCDQUMCzQaD6KL28MjnqqN8XnAqiEq9PNm/hCPTSGfrXCOfwj1ow4LFb/tNymJPwsNbVePc1xFqrQ==} @@ -1677,6 +1954,10 @@ packages: avvio@8.3.2: resolution: {integrity: sha512-st8e519GWHa/azv8S87mcJvZs4WsgTBjOw/Ih1CP6u+8SZvcOeAYNG6JbsIrAUUJJ7JfmrnOkR8ipDS+u9SIRQ==} + aws-ssl-profiles@1.1.2: + resolution: {integrity: sha512-NZKeq9AfyQvEeNlN0zSYAaWrmBffJh3IELMZfRpJVWgrpEbtEpnjvzqBPf+mxoI287JohRDoa+/nsfqqiZmF6g==} + engines: {node: '>= 6.0.0'} + bail@2.0.2: resolution: {integrity: sha512-0xO6mYd7JB2YesxDKplafRpsiOzPt9V02ddPCLbY1xYGPOX24NTyN50qnUxgCPcSoYMhKpAuBTjQoRZCAkUDRw==} @@ -1689,6 +1970,9 @@ packages: better-sqlite3@10.0.0: resolution: {integrity: sha512-rOz0JY8bt9oMgrFssP7GnvA5R3yln73y/NizzWqy3WlFth8Ux8+g4r/N9fjX97nn4X1YX6MTER2doNpTu5pqiA==} + better-sqlite3@11.7.2: + resolution: {integrity: sha512-10a57cHVDmfNQS4jrZ9AH2t+2ekzYh5Rhbcnb4ytpmYweoLdogDmyTt5D+hLiY9b44Mx9foowb/4iXBTO2yP3Q==} + big.js@5.2.2: resolution: {integrity: sha512-vyL2OymJxmarO8gxMr0mhChsO9QGwhynfuu4+MHTAW6czfq9humCB7rKpUjDd9YUiDPU4mzpyupFSvOClAwbmQ==} @@ -1821,8 +2105,8 @@ packages: resolution: {integrity: sha512-nPdaFdQ0h/GEigbPClz11D0v/ZJEwxmeVZGeMo3Z5StPtUTkA9o1lD6QwoirYiSDzbcwn2XcjwmCp68W1IS4TA==} engines: {node: '>=18'} - code-block-writer@13.0.2: - resolution: {integrity: sha512-XfXzAGiStXSmCIwrkdfvc7FS5Dtj8yelCtyOf2p2skCAfvLd6zu0rGzuS9NSCO3bq1JKpFZ7tbKdKlcd5occQA==} + code-block-writer@13.0.3: + resolution: {integrity: sha512-Oofo0pq3IKnsFtuHqSF7TqBfr71aeyZDVJ0HpmqB7FBM2qEigL0iPONSCZSO9pE9dZTAxANe5XHG9Uy0YMv8cg==} color-convert@2.0.1: resolution: {integrity: sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==} @@ -1920,6 +2204,9 @@ packages: dataloader@2.2.2: resolution: {integrity: sha512-8YnDaaf7N3k/q5HnTJVuzSyLETjoZjVmHc4AeKAzOvKHEFQKcn64OKBfzHYtE9zGjctNM7V9I0MfnUVLpi7M5g==} + dataloader@2.2.3: + resolution: {integrity: sha512-y2krtASINtPFS1rSDjacrFgn1dcUuoREVabwlOGOe4SdxenREqwjwjElAdwvbGM7kgZz9a3KVicWR7vcz8rnzA==} + debug@2.6.9: resolution: {integrity: sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==} peerDependencies: @@ -1976,6 +2263,10 @@ packages: resolution: {integrity: sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==} engines: {node: '>=0.4.0'} + denque@2.1.0: + resolution: {integrity: sha512-HVQE3AAb/pxF8fQAoiqpvg9i3evqug3hoiwakOyZAwJm+6vZehbkYXZ0l4JxS+I3QxM97v5aaRNhj8v5oBhekw==} + engines: {node: '>=0.10'} + depd@2.0.0: resolution: {integrity: sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==} engines: {node: '>= 0.8'} @@ -1988,6 +2279,10 @@ packages: resolution: {integrity: sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==} engines: {node: '>= 0.8', npm: 1.2.8000 || >= 1.4.16} + detect-libc@2.0.2: + resolution: {integrity: sha512-UX6sGumvvqSaXgdKGUsgZWqcUyIXZ/vZTrlRT/iobiKhGL0zL4d3osHj3uqllWJK+i+sixDS/3COVEOFbupFyw==} + engines: {node: '>=8'} + detect-libc@2.0.3: resolution: {integrity: sha512-bwy0MGW55bG41VqxxypOsdSdGqLwXPI/focwgTYCFMbdUiBAxLg9CFzG08sz2aqzknwiX7Hkl0bQENjg8iLByw==} engines: {node: '>=8'} @@ -2030,6 +2325,106 @@ packages: resolution: {integrity: sha512-ZmdL2rui+eB2YwhsWzjInR8LldtZHGDoQ1ugH85ppHKwpUHL7j7rN0Ti9NCnGiQbhaZ11FpR+7ao1dNsmduNUg==} engines: {node: '>=12'} + dotenv@16.4.7: + resolution: {integrity: sha512-47qPchRCykZC03FhkYAhrvwU4xDBFIj1QPqaarj6mdM/hgUzfPHcpkHJOn3mJAufFeeAxAzeGsr5X0M4k6fLZQ==} + engines: {node: '>=12'} + + drizzle-kit@0.30.1: + resolution: {integrity: sha512-HmA/NeewvHywhJ2ENXD3KvOuM/+K2dGLJfxVfIHsGwaqKICJnS+Ke2L6UcSrSrtMJLJaT0Im1Qv4TFXfaZShyw==} + hasBin: true + + drizzle-orm@0.38.3: + resolution: {integrity: sha512-w41Y+PquMpSff/QDRGdItG0/aWca+/J3Sda9PPGkTxBtjWQvgU1jxlFBXdjog5tYvTu58uvi3PwR1NuCx0KeZg==} + 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/react': '>=18' + '@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' + postgres: '>=3' + prisma: '*' + react: '>=18' + 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/react': + 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 + react: + optional: true + sql.js: + optional: true + sqlite3: + optional: true + dset@3.1.3: resolution: {integrity: sha512-20TuZZHCEZ2O71q9/+8BwKwZ0QtD9D8ObhrihJPr+vLLYlSuAU3/zL4cSlgbfeoGHTjCSJBa7NGcrF9/Bx/WJQ==} engines: {node: '>=4'} @@ -2091,6 +2486,16 @@ packages: es-module-lexer@1.5.4: resolution: {integrity: sha512-MVNK56NiMrOwitFB7cqDwq0CQutbw+0BvLshJSse0MUNU+y1FC3bUS/AQg7oUng+/wKrrki7JfmwtVHkVfPLlw==} + esbuild-register@3.6.0: + resolution: {integrity: sha512-H2/S7Pm8a9CL1uhp9OvjwrBh5Pvx0H8qVOxNu8Wed9Y7qv56MPtq+GGM8RJpq6glYJn9Wspr8uw7l55uyinNeg==} + peerDependencies: + esbuild: '>=0.12 <1' + + esbuild@0.18.20: + resolution: {integrity: sha512-ceqxoedUrcayh7Y7ZX6NdbbDzGROiyVBgC4PriJThBKSVPWnnFHZAkfI1lJT8QFkOwH4qOS2SJkS4wvpGl8BpA==} + engines: {node: '>=12'} + hasBin: true + esbuild@0.19.12: resolution: {integrity: sha512-aARqgq8roFBj054KvQr5f1sFu0D65G+miZRCuJyJ0G13Zwx7vRar5Zhn2tkQNzIXcBrNVsv/8stehpj+GAjgbg==} engines: {node: '>=12'} @@ -2683,6 +3088,9 @@ packages: resolution: {integrity: sha512-34wB/Y7MW7bzjKRjUKTa46I2Z7eV62Rkhva+KkopW7Qvv/OSWBqvkSY7vusOPrNuZcUG3tApvdVgNB8POj3SPw==} engines: {node: '>=10'} + js-base64@3.7.7: + resolution: {integrity: sha512-7rCnleh0z2CkXhH67J8K1Ytz0b2Y+yxTPL+/KOJoa20hfnVQ/3/T6W/KflYI4bRHRagNeXeU2bkNGI3v1oS/lw==} + js-levenshtein@1.1.6: resolution: {integrity: sha512-X2BB11YZtrRqY4EnQcLX5Rh373zbK4alC1FW7D7MBhL2gtcC17cTnr6DmfHZeS0s2rTHjUTMMHfG7gO8SSdw+g==} engines: {node: '>=0.10.0'} @@ -2765,6 +3173,11 @@ packages: leac@0.6.0: resolution: {integrity: sha512-y+SqErxb8h7nE/fiEX07jsbuhrpO9lL8eca7/Y1nuWV2moNlXhyd59iDGcRf6moVyDMbmTNzL40SUyrFU/yDpg==} + libsql@0.4.7: + resolution: {integrity: sha512-T9eIRCs6b0J1SHKYIvD8+KCJMcWZ900iZyxdnSCdqxN12Z1ijzT+jY5nrk72Jw4B0HGzms2NgpryArlJqvc3Lw==} + cpu: [x64, arm64, wasm32] + os: [darwin, linux, win32] + light-my-request@5.13.0: resolution: {integrity: sha512-9IjUN9ZyCS9pTG+KqTDEQo68Sui2lHsYBrfMyVUTTZ3XhH8PMZq7xO94Kr+eP9dhi/kcKsx4N41p2IXEBil1pQ==} @@ -2828,6 +3241,9 @@ packages: long@4.0.0: resolution: {integrity: sha512-XsP+KhQif4bjX1kbuSiySJFNAehNxgLb6hPRGJ9QsUr8ajHkuXGdrHmFUTUUXhDwVX2R5bY4JNZEwbUiMhV+MA==} + long@5.2.4: + resolution: {integrity: sha512-qtzLbJE8hq7VabR3mISmVGtoXP8KGc2Z/AT8OuqlYD7JTR3oqrgwdjnk07wpj1twXxYmgDXgoKVWUG/fReSzHg==} + longest-streak@3.1.0: resolution: {integrity: sha512-9Ri+o0JYgehTaVBBDoMqIl8GXtbWg711O3srftcHhZ0dqnETqLaoIK0x17fUw9rFSlK/0NlsKe0Ahhyl5pXE2g==} @@ -2853,6 +3269,10 @@ packages: resolution: {integrity: sha512-jumlc0BIUrS3qJGgIkWZsyfAM7NCWiBcCDhnd+3NNM5KbBmLTgHVfWBcg6W+rLUsIpzpERPsvwUP7CckAQSOoA==} engines: {node: '>=12'} + lru.min@1.1.1: + resolution: {integrity: sha512-FbAj6lXil6t8z4z3j0E5mfRlPzxkySotzUHwRXjlpRh10vc6AI6WN62ehZj82VG7M20rqogJ0GLwar2Xa05a8Q==} + engines: {bun: '>=1.0.0', deno: '>=1.30.0', node: '>=8.0.0'} + lunr@2.3.9: resolution: {integrity: sha512-zTU3DaZaF3Rt9rhN3uBMGQD3dD2/vFQqnvZCDv4dl5iOzq2IZQqTxu90r4E5J+nP70J3ilqVCrbho2eWaeW8Ow==} @@ -3104,6 +3524,10 @@ packages: resolution: {integrity: sha512-UMPa7QtzP5Cb98NDHtL7Q4rBCoUB6BQ3wa+vXPlZO79TVfQTd9fAdJuIdB+49qZJRPcEMowg2POYs1axtJoAPQ==} engines: {node: '>= 18.12.0'} + mikro-orm@6.4.3: + resolution: {integrity: sha512-xDNzmLiL4EUTMOu9CbZ2d0sNIaUdH4RzDv4oqw27+u0/FPfvZTIagd+luxx1lWWqe/vg/iNtvqr5OcNQIYYrtQ==} + engines: {node: '>= 18.12.0'} + mime-db@1.52.0: resolution: {integrity: sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==} engines: {node: '>= 0.6'} @@ -3152,11 +3576,6 @@ packages: mkdirp-classic@0.5.3: resolution: {integrity: sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A==} - mkdirp@3.0.1: - resolution: {integrity: sha512-+NsyUUAZDmo6YVHzL/stxSu3t9YS1iljliy3BSDrXJ/dkn1KYdmtZODGGjLcc9XLgVVpH4KshHB8XmZgMhaBXg==} - engines: {node: '>=10'} - hasBin: true - mqemitter@5.0.0: resolution: {integrity: sha512-rqNRQhGgl0W/NV+Zrx0rpAUTZcSlAtivCVUmXBUPcFYt+AeDEpoJgy5eKlFWJP6xnatONL59WIFdV0W6niOMhw==} engines: {node: '>=10'} @@ -3174,9 +3593,17 @@ packages: ms@2.1.3: resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==} + mysql2@3.12.0: + resolution: {integrity: sha512-C8fWhVysZoH63tJbX8d10IAoYCyXy4fdRFz2Ihrt9jtPILYynFEKUUzpp1U7qxzDc3tMbotvaBH+sl6bFnGZiw==} + engines: {node: '>= 8.0'} + mz@2.7.0: resolution: {integrity: sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q==} + named-placeholders@1.1.3: + resolution: {integrity: sha512-eLoBxg6wE/rZkJPhU/xRX1WTpkFEwDJEN96oxFrTsqBdbT5ec295Q+CoHrL9IT0DipqKhmGcaZmwOt8OON5x1w==} + engines: {node: '>=12.0.0'} + nanoid@3.3.7: resolution: {integrity: sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g==} engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} @@ -3245,6 +3672,9 @@ packages: resolution: {integrity: sha512-IRZSRuzJiynemAXPYtPe5BoI/RESNYR7TYm50MC5Mqbd3Jmw5y790sErYw3V6SryFJD64b74qQQs9wn5Bg/k3g==} engines: {node: '>= 0.4'} + obuf@1.1.2: + resolution: {integrity: sha512-PX1wu0AmAdPqOL1mWhqmlOd8kOIZQwGZw6rh7uby9fTc5lhaOWFLX3I6R1hrF9k3zUY40e6igsLGkDXK92LJNg==} + on-exit-leak-free@2.1.2: resolution: {integrity: sha512-0eJJY6hXLGf1udHwfNftBqH+g73EU4B504nZeKpz1sYRKafAghwxEJunB2O7rDZkL4PGfsMVnTXZ2EjibbqcsA==} engines: {node: '>=14.0.0'} @@ -3331,9 +3761,51 @@ packages: periscopic@3.1.0: resolution: {integrity: sha512-vKiQ8RRtkl9P+r/+oefh25C3fhybptkHKCZSPlcXiJux2tJF55GnEj3BVn4A5gKfq9NWWXXrxkHBwVPUfH0opw==} + pg-cloudflare@1.1.1: + resolution: {integrity: sha512-xWPagP/4B6BgFO+EKz3JONXv3YDgvkbVrGw2mTo3D6tVDQRh1e7cqVGvyR3BE+eQgAvx1XhW/iEASj4/jCWl3Q==} + pg-connection-string@2.6.2: resolution: {integrity: sha512-ch6OwaeaPYcova4kKZ15sbJ2hKb/VP48ZD2gE7i1J+L4MspCtBMAx8nMgz7bksc7IojCIIWuEhHibSMFH8m8oA==} + pg-connection-string@2.7.0: + resolution: {integrity: sha512-PI2W9mv53rXJQEOb8xNR8lH7Hr+EKa6oJa38zsK0S/ky2er16ios1wLKhZyxzD7jUReiWokc9WK5nxSnC7W1TA==} + + pg-int8@1.0.1: + resolution: {integrity: sha512-WCtabS6t3c8SkpDBUlb1kjOs7l66xsGdKpIPZsg4wR+B3+u9UAum2odSsF9tnvxg80h4ZxLWMy4pRjOsFIqQpw==} + engines: {node: '>=4.0.0'} + + pg-numeric@1.0.2: + resolution: {integrity: sha512-BM/Thnrw5jm2kKLE5uJkXqqExRUY/toLHda65XgFTBTFYZyopbKjBe29Ii3RbkvlsMoFwD+tHeGaCjjv0gHlyw==} + engines: {node: '>=4'} + + pg-pool@3.7.0: + resolution: {integrity: sha512-ZOBQForurqh4zZWjrgSwwAtzJ7QiRX0ovFkZr2klsen3Nm0aoh33Ls0fzfv3imeH/nw/O27cjdz5kzYJfeGp/g==} + peerDependencies: + pg: '>=8.0' + + pg-protocol@1.7.0: + resolution: {integrity: sha512-hTK/mE36i8fDDhgDFjy6xNOG+LCorxLG3WO17tku+ij6sVHXh1jQUJ8hYAnRhNla4QVD2H8er/FOjc/+EgC6yQ==} + + pg-types@2.2.0: + resolution: {integrity: sha512-qTAAlrEsl8s4OiEQY69wDvcMIdQN6wdz5ojQiOy6YRMuynxenON0O5oCpJI6lshc6scgAY8qvJ2On/p+CXY0GA==} + engines: {node: '>=4'} + + pg-types@4.0.2: + resolution: {integrity: sha512-cRL3JpS3lKMGsKaWndugWQoLOCoP+Cic8oseVcbr0qhPzYD5DWXK+RZ9LY9wxRf7RQia4SCwQlXk0q6FCPrVng==} + engines: {node: '>=10'} + + pg@8.13.1: + resolution: {integrity: sha512-OUir1A0rPNZlX//c7ksiu7crsGZTKSOXJPgtNiHGIlC9H0lO+NC6ZDYksSgBYY/thSWhnSRBv8w1lieNNGATNQ==} + engines: {node: '>= 8.0.0'} + peerDependencies: + pg-native: '>=3.0.1' + peerDependenciesMeta: + pg-native: + optional: true + + pgpass@1.0.5: + resolution: {integrity: sha512-FdW9r/jQZhSeohs1Z3sI1yxFQNFvMcnmfuj4WBMUTxOrAyLMaTcE1aAMBiTlbMNaXvBCQuVi0R7hd8udDSP7ug==} + picocolors@1.1.0: resolution: {integrity: sha512-TQ92mBOW0l3LeMeyLV6mzy/kWr8lkd/hp3mTg7wYK7zJhuBStmGMBG0BdeDZS/dZx1IukaX6Bk11zcln25o1Aw==} @@ -3379,6 +3851,41 @@ packages: resolution: {integrity: sha512-OCVPnIObs4N29kxTjzLfUryOkvZEq+pf8jTF0lg8E7uETuWHA+v7j3c/xJmiqpX450191LlmZfUKkXxkTry7nA==} engines: {node: ^10 || ^12 || >=14} + postgres-array@2.0.0: + resolution: {integrity: sha512-VpZrUqU5A69eQyW2c5CA1jtLecCsN2U/bD6VilrFDWq5+5UIEVO7nazS3TEcHf1zuPYO/sqGvUvW62g86RXZuA==} + engines: {node: '>=4'} + + postgres-array@3.0.2: + resolution: {integrity: sha512-6faShkdFugNQCLwucjPcY5ARoW1SlbnrZjmGl0IrrqewpvxvhSLHimCVzqeuULCbG0fQv7Dtk1yDbG3xv7Veog==} + engines: {node: '>=12'} + + postgres-bytea@1.0.0: + resolution: {integrity: sha512-xy3pmLuQqRBZBXDULy7KbaitYqLcmxigw14Q5sj8QBVLqEwXfeybIKVWiqAXTlcvdvb0+xkOtDbfQMOf4lST1w==} + engines: {node: '>=0.10.0'} + + postgres-bytea@3.0.0: + resolution: {integrity: sha512-CNd4jim9RFPkObHSjVHlVrxoVQXz7quwNFpz7RY1okNNme49+sVyiTvTRobiLV548Hx/hb1BG+iE7h9493WzFw==} + engines: {node: '>= 6'} + + postgres-date@1.0.7: + resolution: {integrity: sha512-suDmjLVQg78nMK2UZ454hAG+OAW+HQPZ6n++TNDUX+L0+uUlLywnoxJKDou51Zm+zTCjrCl0Nq6J9C5hP9vK/Q==} + engines: {node: '>=0.10.0'} + + postgres-date@2.1.0: + resolution: {integrity: sha512-K7Juri8gtgXVcDfZttFKVmhglp7epKb1K4pgrkLxehjqkrgPhfG6OO8LHLkfaqkbpjNRnra018XwAr1yQFWGcA==} + engines: {node: '>=12'} + + postgres-interval@1.2.0: + resolution: {integrity: sha512-9ZhXKM/rw350N1ovuWHbGxnGh/SNJ4cnxHiM0rxE4VN41wsg8P8zWn9hv/buK00RP4WvlOyr/RBDiptyxVbkZQ==} + engines: {node: '>=0.10.0'} + + postgres-interval@3.0.0: + resolution: {integrity: sha512-BSNDnbyZCXSxgA+1f5UU2GmwhoI0aU5yMxRGO8CdFEcY2BQF9xm/7MqKnYoM1nJDk8nONNWDk9WeSmePFhQdlw==} + engines: {node: '>=12'} + + postgres-range@1.1.4: + resolution: {integrity: sha512-i/hbxIE9803Alj/6ytL7UHQxRvZkI9O4Sy+J3HGc4F4oo/2eQAjTSNJ0bfxyse3bH0nuVesCk+3IRLaMtG3H6w==} + prebuild-install@7.1.2: resolution: {integrity: sha512-UnNke3IQb6sgarcZIDU3gbMeTp/9SSU1DAIkil7PrqG1vZlBtY5msYccSKSHDqa3hNg436IXK+SNImReuA1wEQ==} engines: {node: '>=10'} @@ -3409,6 +3916,9 @@ packages: resolution: {integrity: sha512-cdGef/drWFoydD1JsMzuFf8100nZl+GT+yacc2bEced5f9Rjk4z+WtFUTBu9PhOi9j/jfmBPu0mMEY4wIdAF8A==} engines: {node: '>= 0.6.0'} + promise-limit@2.7.0: + resolution: {integrity: sha512-7nJ6v5lnJsXwGprnGXga4wx6d1POjvi5Qmf1ivTRxTjH4Z/9Czja/UCMLVmB9N93GeWOU93XaFaEt6jbuoagNw==} + prop-types@15.8.1: resolution: {integrity: sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==} @@ -3797,6 +4307,9 @@ packages: resolution: {integrity: sha512-qqWzuOjSFOuqPjFe4NOsMLafToQQwBSOEpS+FwEt3A2V3vKubTquT3vmLTQpFgMXp8AlFWFuP1qKaJZOtPpVXg==} engines: {node: '>= 0.8.0'} + seq-queue@0.0.5: + resolution: {integrity: sha512-hr3Wtp/GZIc/6DAGPDcV4/9WoZhjrkXsi5B/07QgX8tsdc6ilr7BFM6PM6rbdAX1kFSDYeZGLipIZZKyQP0O5Q==} + serialize-javascript@6.0.2: resolution: {integrity: sha512-Saa1xPByTTq2gdeFZYLLo+RFE35NHZkAbqZeWNd3BpzppeVisAqpDjcp8dyf6uIvEqJRd46jemmyA4iFIeVk8g==} @@ -4141,8 +4654,8 @@ packages: ts-interface-checker@0.1.13: resolution: {integrity: sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA==} - ts-morph@23.0.0: - resolution: {integrity: sha512-FcvFx7a9E8TUe6T3ShihXJLiJOiqyafzFKUO4aqIHDUCIvADdGNShcbc2W5PMr3LerXRv7mafvFZ9lRENxJmug==} + ts-morph@25.0.0: + resolution: {integrity: sha512-ERPTUVO5qF8cEGJgAejGOsCVlbk8d0SDyiJsucKQT5XgqoZslv0Qml+gnui6Yy6o+uQqw5SestyW2HvlVtT/Sg==} tslib@2.6.2: resolution: {integrity: sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==} @@ -4294,6 +4807,14 @@ packages: engines: {node: '>=8'} hasBin: true + valibot@1.0.0-beta.12: + resolution: {integrity: sha512-j3WIxJ0pmUFMfdfUECn3YnZPYOiG0yHYcFEa/+RVgo0I+MXE3ToLt7gNRLtY5pwGfgNmsmhenGZfU5suu9ijUA==} + peerDependencies: + typescript: '>=5' + peerDependenciesMeta: + typescript: + optional: true + valibot@1.0.0-beta.7: resolution: {integrity: sha512-8CsDu3tqyg7quEHMzCOYdQ/d9NlmVQKtd4AlFje6oJpvqo70EIZjSakKIeWltJyNAiUtdtLe0LAk4625gavoeQ==} peerDependencies: @@ -4723,6 +5244,8 @@ snapshots: '@bufbuild/protobuf@2.2.0': {} + '@drizzle-team/brocli@0.10.2': {} + '@envelop/core@5.0.1': dependencies: '@envelop/types': 5.0.0 @@ -4732,138 +5255,214 @@ snapshots: dependencies: tslib: 2.6.2 + '@esbuild-kit/core-utils@3.3.2': + dependencies: + esbuild: 0.18.20 + source-map-support: 0.5.21 + + '@esbuild-kit/esm-loader@2.6.5': + dependencies: + '@esbuild-kit/core-utils': 3.3.2 + get-tsconfig: 4.7.3 + '@esbuild/aix-ppc64@0.19.12': optional: true '@esbuild/aix-ppc64@0.21.5': optional: true + '@esbuild/android-arm64@0.18.20': + optional: true + '@esbuild/android-arm64@0.19.12': optional: true '@esbuild/android-arm64@0.21.5': optional: true + '@esbuild/android-arm@0.18.20': + optional: true + '@esbuild/android-arm@0.19.12': optional: true '@esbuild/android-arm@0.21.5': optional: true + '@esbuild/android-x64@0.18.20': + optional: true + '@esbuild/android-x64@0.19.12': optional: true '@esbuild/android-x64@0.21.5': optional: true + '@esbuild/darwin-arm64@0.18.20': + optional: true + '@esbuild/darwin-arm64@0.19.12': optional: true '@esbuild/darwin-arm64@0.21.5': optional: true + '@esbuild/darwin-x64@0.18.20': + optional: true + '@esbuild/darwin-x64@0.19.12': optional: true '@esbuild/darwin-x64@0.21.5': optional: true + '@esbuild/freebsd-arm64@0.18.20': + optional: true + '@esbuild/freebsd-arm64@0.19.12': optional: true '@esbuild/freebsd-arm64@0.21.5': optional: true + '@esbuild/freebsd-x64@0.18.20': + optional: true + '@esbuild/freebsd-x64@0.19.12': optional: true '@esbuild/freebsd-x64@0.21.5': optional: true + '@esbuild/linux-arm64@0.18.20': + optional: true + '@esbuild/linux-arm64@0.19.12': optional: true '@esbuild/linux-arm64@0.21.5': optional: true + '@esbuild/linux-arm@0.18.20': + optional: true + '@esbuild/linux-arm@0.19.12': optional: true '@esbuild/linux-arm@0.21.5': optional: true + '@esbuild/linux-ia32@0.18.20': + optional: true + '@esbuild/linux-ia32@0.19.12': optional: true '@esbuild/linux-ia32@0.21.5': optional: true + '@esbuild/linux-loong64@0.18.20': + optional: true + '@esbuild/linux-loong64@0.19.12': optional: true '@esbuild/linux-loong64@0.21.5': optional: true + '@esbuild/linux-mips64el@0.18.20': + optional: true + '@esbuild/linux-mips64el@0.19.12': optional: true '@esbuild/linux-mips64el@0.21.5': optional: true + '@esbuild/linux-ppc64@0.18.20': + optional: true + '@esbuild/linux-ppc64@0.19.12': optional: true '@esbuild/linux-ppc64@0.21.5': optional: true + '@esbuild/linux-riscv64@0.18.20': + optional: true + '@esbuild/linux-riscv64@0.19.12': optional: true '@esbuild/linux-riscv64@0.21.5': optional: true + '@esbuild/linux-s390x@0.18.20': + optional: true + '@esbuild/linux-s390x@0.19.12': optional: true '@esbuild/linux-s390x@0.21.5': optional: true + '@esbuild/linux-x64@0.18.20': + optional: true + '@esbuild/linux-x64@0.19.12': optional: true '@esbuild/linux-x64@0.21.5': optional: true + '@esbuild/netbsd-x64@0.18.20': + optional: true + '@esbuild/netbsd-x64@0.19.12': optional: true '@esbuild/netbsd-x64@0.21.5': optional: true + '@esbuild/openbsd-x64@0.18.20': + optional: true + '@esbuild/openbsd-x64@0.19.12': optional: true '@esbuild/openbsd-x64@0.21.5': optional: true + '@esbuild/sunos-x64@0.18.20': + optional: true + '@esbuild/sunos-x64@0.19.12': optional: true '@esbuild/sunos-x64@0.21.5': optional: true + '@esbuild/win32-arm64@0.18.20': + optional: true + '@esbuild/win32-arm64@0.19.12': optional: true '@esbuild/win32-arm64@0.21.5': optional: true + '@esbuild/win32-ia32@0.18.20': + optional: true + '@esbuild/win32-ia32@0.19.12': optional: true '@esbuild/win32-ia32@0.21.5': optional: true + '@esbuild/win32-x64@0.18.20': + optional: true + '@esbuild/win32-x64@0.19.12': optional: true @@ -5030,6 +5629,62 @@ snapshots: '@kamilkisiela/fast-url-parser@1.1.4': {} + '@libsql/client@0.14.0': + dependencies: + '@libsql/core': 0.14.0 + '@libsql/hrana-client': 0.7.0 + js-base64: 3.7.7 + libsql: 0.4.7 + promise-limit: 2.7.0 + transitivePeerDependencies: + - bufferutil + - utf-8-validate + + '@libsql/core@0.14.0': + dependencies: + js-base64: 3.7.7 + + '@libsql/darwin-arm64@0.4.7': + optional: true + + '@libsql/darwin-x64@0.4.7': + optional: true + + '@libsql/hrana-client@0.7.0': + dependencies: + '@libsql/isomorphic-fetch': 0.3.1 + '@libsql/isomorphic-ws': 0.1.5 + js-base64: 3.7.7 + node-fetch: 3.3.2 + transitivePeerDependencies: + - bufferutil + - utf-8-validate + + '@libsql/isomorphic-fetch@0.3.1': {} + + '@libsql/isomorphic-ws@0.1.5': + dependencies: + '@types/ws': 8.5.13 + ws: 8.18.0 + transitivePeerDependencies: + - bufferutil + - utf-8-validate + + '@libsql/linux-arm64-gnu@0.4.7': + optional: true + + '@libsql/linux-arm64-musl@0.4.7': + optional: true + + '@libsql/linux-x64-gnu@0.4.7': + optional: true + + '@libsql/linux-x64-musl@0.4.7': + optional: true + + '@libsql/win32-x64-msvc@0.4.7': + optional: true + '@lukeed/ms@2.0.2': {} '@mdx-js/loader@2.3.0(webpack@5.94.0(esbuild@0.21.5))': @@ -5093,6 +5748,24 @@ snapshots: - supports-color - tedious + '@mikro-orm/better-sqlite@6.4.3(@mikro-orm/core@6.4.3)(libsql@0.4.7)': + dependencies: + '@mikro-orm/core': 6.4.3 + '@mikro-orm/knex': 6.4.3(@mikro-orm/core@6.4.3)(better-sqlite3@11.7.2)(libsql@0.4.7) + better-sqlite3: 11.7.2 + fs-extra: 11.2.0 + sqlstring-sqlite: 0.1.1 + transitivePeerDependencies: + - libsql + - mariadb + - mysql + - mysql2 + - pg + - pg-native + - sqlite3 + - supports-color + - tedious + '@mikro-orm/core@6.2.8': dependencies: dataloader: 2.2.2 @@ -5103,6 +5776,16 @@ snapshots: mikro-orm: 6.2.8 reflect-metadata: 0.2.2 + '@mikro-orm/core@6.4.3': + dependencies: + dataloader: 2.2.3 + dotenv: 16.4.7 + esprima: 4.0.1 + fs-extra: 11.2.0 + globby: 11.1.0 + mikro-orm: 6.4.3 + reflect-metadata: 0.2.2 + '@mikro-orm/knex@6.2.8(@mikro-orm/core@6.2.8)(better-sqlite3@10.0.0)': dependencies: '@mikro-orm/core': 6.2.8 @@ -5119,6 +5802,24 @@ snapshots: - supports-color - tedious + '@mikro-orm/knex@6.4.3(@mikro-orm/core@6.4.3)(better-sqlite3@11.7.2)(libsql@0.4.7)': + dependencies: + '@mikro-orm/core': 6.4.3 + fs-extra: 11.2.0 + knex: 3.1.0(better-sqlite3@11.7.2) + sqlstring: 2.3.3 + optionalDependencies: + better-sqlite3: 11.7.2 + libsql: 0.4.7 + transitivePeerDependencies: + - mysql + - mysql2 + - pg + - pg-native + - sqlite3 + - supports-color + - tedious + '@module-federation/runtime-tools@0.5.1': dependencies: '@module-federation/runtime': 0.5.1 @@ -5135,6 +5836,8 @@ snapshots: '@module-federation/runtime': 0.5.1 '@module-federation/sdk': 0.5.1 + '@neon-rs/load@0.0.4': {} + '@nodelib/fs.scandir@2.1.5': dependencies: '@nodelib/fs.stat': 2.0.5 @@ -5154,7 +5857,7 @@ snapshots: optionalDependencies: prisma: 5.20.0 - '@prisma/client@5.20.0(prisma@6.1.0)': + '@prisma/client@6.1.0(prisma@6.1.0)': optionalDependencies: prisma: 6.1.0 @@ -5192,9 +5895,9 @@ snapshots: '@prisma/engines-version': 6.1.0-21.11f085a2012c0f4778414c8db2651556ee0ef959 '@prisma/get-platform': 6.1.0 - '@prisma/generator-helper@5.20.0': + '@prisma/generator-helper@6.1.0': dependencies: - '@prisma/debug': 5.20.0 + '@prisma/debug': 6.1.0 '@prisma/get-platform@5.20.0': dependencies: @@ -5532,11 +6235,10 @@ snapshots: dependencies: tslib: 2.8.1 - '@ts-morph/common@0.24.0': + '@ts-morph/common@0.26.0': dependencies: fast-glob: 3.3.2 - minimatch: 9.0.4 - mkdirp: 3.0.1 + minimatch: 9.0.5 path-browserify: 1.0.1 '@types/acorn@4.0.6': @@ -5615,6 +6317,12 @@ snapshots: '@types/parse5@6.0.3': {} + '@types/pg@8.11.10': + dependencies: + '@types/node': 20.11.30 + pg-protocol: 1.7.0 + pg-types: 4.0.2 + '@types/prop-types@15.7.13': {} '@types/qs@6.9.15': {} @@ -5643,6 +6351,10 @@ snapshots: '@types/uuid@9.0.8': {} + '@types/ws@8.5.13': + dependencies: + '@types/node': 20.11.30 + '@ungap/structured-clone@1.2.0': {} '@vitest/coverage-v8@2.1.8(vitest@2.1.8(@types/node@20.11.30)(jsdom@25.0.1)(terser@5.32.0))': @@ -5910,6 +6622,8 @@ snapshots: '@fastify/error': 3.4.1 fastq: 1.17.1 + aws-ssl-profiles@1.1.2: {} + bail@2.0.2: {} balanced-match@1.0.2: {} @@ -5921,6 +6635,11 @@ snapshots: bindings: 1.5.0 prebuild-install: 7.1.2 + better-sqlite3@11.7.2: + dependencies: + bindings: 1.5.0 + prebuild-install: 7.1.2 + big.js@5.2.2: {} binary-extensions@2.3.0: {} @@ -6066,7 +6785,7 @@ snapshots: slice-ansi: 5.0.0 string-width: 7.2.0 - code-block-writer@13.0.2: {} + code-block-writer@13.0.3: {} color-convert@2.0.1: dependencies: @@ -6146,6 +6865,8 @@ snapshots: dataloader@2.2.2: {} + dataloader@2.2.3: {} + debug@2.6.9: dependencies: ms: 2.0.0 @@ -6183,12 +6904,16 @@ snapshots: delayed-stream@1.0.0: {} + denque@2.1.0: {} + depd@2.0.0: {} dequal@2.0.3: {} destroy@1.2.0: {} + detect-libc@2.0.2: {} + detect-libc@2.0.3: {} devlop@1.1.0: @@ -6237,6 +6962,30 @@ snapshots: dotenv@16.4.5: {} + 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-orm@0.38.3(@libsql/client@0.14.0)(@prisma/client@6.1.0(prisma@6.1.0))(@types/pg@8.11.10)(@types/react@18.3.6)(better-sqlite3@11.7.2)(knex@3.1.0(better-sqlite3@11.7.2)(mysql2@3.12.0)(pg@8.13.1))(mysql2@3.12.0)(pg@8.13.1)(prisma@6.1.0)(react@18.3.1): + optionalDependencies: + '@libsql/client': 0.14.0 + '@prisma/client': 6.1.0(prisma@6.1.0) + '@types/pg': 8.11.10 + '@types/react': 18.3.6 + better-sqlite3: 11.7.2 + knex: 3.1.0(better-sqlite3@11.7.2)(mysql2@3.12.0)(pg@8.13.1) + mysql2: 3.12.0 + pg: 8.13.1 + prisma: 6.1.0 + react: 18.3.1 + dset@3.1.3: {} duplexify@4.1.3: @@ -6287,6 +7036,38 @@ snapshots: es-module-lexer@1.5.4: {} + esbuild-register@3.6.0(esbuild@0.19.12): + dependencies: + debug: 4.4.0 + esbuild: 0.19.12 + transitivePeerDependencies: + - supports-color + + esbuild@0.18.20: + optionalDependencies: + '@esbuild/android-arm': 0.18.20 + '@esbuild/android-arm64': 0.18.20 + '@esbuild/android-x64': 0.18.20 + '@esbuild/darwin-arm64': 0.18.20 + '@esbuild/darwin-x64': 0.18.20 + '@esbuild/freebsd-arm64': 0.18.20 + '@esbuild/freebsd-x64': 0.18.20 + '@esbuild/linux-arm': 0.18.20 + '@esbuild/linux-arm64': 0.18.20 + '@esbuild/linux-ia32': 0.18.20 + '@esbuild/linux-loong64': 0.18.20 + '@esbuild/linux-mips64el': 0.18.20 + '@esbuild/linux-ppc64': 0.18.20 + '@esbuild/linux-riscv64': 0.18.20 + '@esbuild/linux-s390x': 0.18.20 + '@esbuild/linux-x64': 0.18.20 + '@esbuild/netbsd-x64': 0.18.20 + '@esbuild/openbsd-x64': 0.18.20 + '@esbuild/sunos-x64': 0.18.20 + '@esbuild/win32-arm64': 0.18.20 + '@esbuild/win32-ia32': 0.18.20 + '@esbuild/win32-x64': 0.18.20 + esbuild@0.19.12: optionalDependencies: '@esbuild/aix-ppc64': 0.19.12 @@ -6947,7 +7728,6 @@ snapshots: iconv-lite@0.6.3: dependencies: safer-buffer: 2.1.2 - optional: true ieee754@1.2.1: {} @@ -7075,6 +7855,8 @@ snapshots: joycon@3.1.1: {} + js-base64@3.7.7: {} + js-levenshtein@1.1.6: {} js-tokens@4.0.0: {} @@ -7158,8 +7940,66 @@ snapshots: transitivePeerDependencies: - supports-color + knex@3.1.0(better-sqlite3@11.7.2): + dependencies: + colorette: 2.0.19 + commander: 10.0.1 + debug: 4.3.4 + escalade: 3.1.2 + esm: 3.2.25 + get-package-type: 0.1.0 + getopts: 2.3.0 + interpret: 2.2.0 + lodash: 4.17.21 + pg-connection-string: 2.6.2 + rechoir: 0.8.0 + resolve-from: 5.0.0 + tarn: 3.0.2 + tildify: 2.0.0 + optionalDependencies: + better-sqlite3: 11.7.2 + transitivePeerDependencies: + - supports-color + + knex@3.1.0(better-sqlite3@11.7.2)(mysql2@3.12.0)(pg@8.13.1): + dependencies: + colorette: 2.0.19 + commander: 10.0.1 + debug: 4.3.4 + escalade: 3.1.2 + esm: 3.2.25 + get-package-type: 0.1.0 + getopts: 2.3.0 + interpret: 2.2.0 + lodash: 4.17.21 + pg-connection-string: 2.6.2 + rechoir: 0.8.0 + resolve-from: 5.0.0 + tarn: 3.0.2 + tildify: 2.0.0 + optionalDependencies: + better-sqlite3: 11.7.2 + mysql2: 3.12.0 + pg: 8.13.1 + transitivePeerDependencies: + - supports-color + optional: true + leac@0.6.0: {} + libsql@0.4.7: + dependencies: + '@neon-rs/load': 0.0.4 + detect-libc: 2.0.2 + optionalDependencies: + '@libsql/darwin-arm64': 0.4.7 + '@libsql/darwin-x64': 0.4.7 + '@libsql/linux-arm64-gnu': 0.4.7 + '@libsql/linux-arm64-musl': 0.4.7 + '@libsql/linux-x64-gnu': 0.4.7 + '@libsql/linux-x64-musl': 0.4.7 + '@libsql/win32-x64-msvc': 0.4.7 + light-my-request@5.13.0: dependencies: cookie: 0.6.0 @@ -7232,6 +8072,8 @@ snapshots: long@4.0.0: {} + long@5.2.4: {} + longest-streak@3.1.0: {} loose-envify@1.4.0: @@ -7253,6 +8095,8 @@ snapshots: lru-cache@7.18.3: {} + lru.min@1.1.1: {} + lunr@2.3.9: {} magic-string@0.30.15: @@ -7783,6 +8627,8 @@ snapshots: mikro-orm@6.2.8: {} + mikro-orm@6.4.3: {} + mime-db@1.52.0: {} mime-types@2.1.35: @@ -7813,8 +8659,6 @@ snapshots: mkdirp-classic@0.5.3: {} - mkdirp@3.0.1: {} - mqemitter@5.0.0: dependencies: fastparallel: 2.4.1 @@ -7828,12 +8672,28 @@ snapshots: ms@2.1.3: {} + mysql2@3.12.0: + dependencies: + aws-ssl-profiles: 1.1.2 + denque: 2.1.0 + generate-function: 2.3.1 + iconv-lite: 0.6.3 + long: 5.2.4 + lru.min: 1.1.1 + named-placeholders: 1.1.3 + seq-queue: 0.0.5 + sqlstring: 2.3.3 + mz@2.7.0: dependencies: any-promise: 1.3.0 object-assign: 4.1.1 thenify-all: 1.6.0 + named-placeholders@1.1.3: + dependencies: + lru-cache: 7.18.3 + nanoid@3.3.7: {} napi-build-utils@1.0.2: {} @@ -7881,6 +8741,8 @@ snapshots: object-inspect@1.13.2: {} + obuf@1.1.2: {} + on-exit-leak-free@2.1.2: {} on-finished@2.4.1: @@ -7971,8 +8833,55 @@ snapshots: estree-walker: 3.0.3 is-reference: 3.0.2 + pg-cloudflare@1.1.1: + optional: true + pg-connection-string@2.6.2: {} + pg-connection-string@2.7.0: {} + + pg-int8@1.0.1: {} + + pg-numeric@1.0.2: {} + + pg-pool@3.7.0(pg@8.13.1): + dependencies: + pg: 8.13.1 + + pg-protocol@1.7.0: {} + + pg-types@2.2.0: + dependencies: + pg-int8: 1.0.1 + postgres-array: 2.0.0 + postgres-bytea: 1.0.0 + postgres-date: 1.0.7 + postgres-interval: 1.2.0 + + pg-types@4.0.2: + dependencies: + pg-int8: 1.0.1 + pg-numeric: 1.0.2 + postgres-array: 3.0.2 + postgres-bytea: 3.0.0 + postgres-date: 2.1.0 + postgres-interval: 3.0.0 + postgres-range: 1.1.4 + + pg@8.13.1: + dependencies: + pg-connection-string: 2.7.0 + pg-pool: 3.7.0(pg@8.13.1) + pg-protocol: 1.7.0 + pg-types: 2.2.0 + pgpass: 1.0.5 + optionalDependencies: + pg-cloudflare: 1.1.1 + + pgpass@1.0.5: + dependencies: + split2: 4.2.0 + picocolors@1.1.0: {} picocolors@1.1.1: {} @@ -8017,6 +8926,28 @@ snapshots: picocolors: 1.1.1 source-map-js: 1.2.1 + postgres-array@2.0.0: {} + + postgres-array@3.0.2: {} + + postgres-bytea@1.0.0: {} + + postgres-bytea@3.0.0: + dependencies: + obuf: 1.1.2 + + postgres-date@1.0.7: {} + + postgres-date@2.1.0: {} + + postgres-interval@1.2.0: + dependencies: + xtend: 4.0.2 + + postgres-interval@3.0.0: {} + + postgres-range@1.1.4: {} + prebuild-install@7.1.2: dependencies: detect-libc: 2.0.3 @@ -8052,6 +8983,8 @@ snapshots: process@0.11.10: {} + promise-limit@2.7.0: {} + prop-types@15.8.1: dependencies: loose-envify: 1.4.0 @@ -8487,6 +9420,8 @@ snapshots: transitivePeerDependencies: - supports-color + seq-queue@0.0.5: {} + serialize-javascript@6.0.2: dependencies: randombytes: 2.1.0 @@ -8815,10 +9750,10 @@ snapshots: ts-interface-checker@0.1.13: {} - ts-morph@23.0.0: + ts-morph@25.0.0: dependencies: - '@ts-morph/common': 0.24.0 - code-block-writer: 13.0.2 + '@ts-morph/common': 0.26.0 + code-block-writer: 13.0.3 tslib@2.6.2: {} @@ -8988,6 +9923,10 @@ snapshots: kleur: 4.1.5 sade: 1.8.1 + valibot@1.0.0-beta.12(typescript@5.4.3): + optionalDependencies: + typescript: 5.4.3 + valibot@1.0.0-beta.7(typescript@5.4.3): optionalDependencies: typescript: 5.4.3 diff --git a/vitest.config.ts b/vitest.config.ts index ab3d7756..894bdb8a 100644 --- a/vitest.config.ts +++ b/vitest.config.ts @@ -31,7 +31,7 @@ export default defineConfig({ "**/examples/**/*", "**/*.spec-d.ts", "**/*.spec.ts", - "**/vitest.config.ts", + "**/*.config.ts", "**/draft/**", "website/**", "vitest.workspace.ts",