diff --git a/packages/plugin-drizzle/.npmignore b/packages/plugin-drizzle/.npmignore new file mode 100644 index 000000000..d02ad4dc5 --- /dev/null +++ b/packages/plugin-drizzle/.npmignore @@ -0,0 +1,7 @@ +test +tests +.turbo +babel.config.js +jest.config.js +*.tsbuildinfo +tsconfig.* diff --git a/packages/plugin-drizzle/LICENSE b/packages/plugin-drizzle/LICENSE new file mode 100644 index 000000000..4d74f2bcf --- /dev/null +++ b/packages/plugin-drizzle/LICENSE @@ -0,0 +1,6 @@ +ISC License (ISC) +Copyright 2021 Michael Hayes + +Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies. + +THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. diff --git a/packages/plugin-drizzle/README.md b/packages/plugin-drizzle/README.md new file mode 100644 index 000000000..a62909ed3 --- /dev/null +++ b/packages/plugin-drizzle/README.md @@ -0,0 +1,18 @@ +# Drizzle Plugin + +## TODO + +- [ ] connections +- [ ] connection helpers +- [ ] query usage checks +- [ ] normalize extras callback functions before merging +- [ ] with input integration +- [ ] errors integration +- [ ] drizzle field methods on builder +- [ ] drizzle refs +- [ ] drizzle interface types +- [ ] tests +- [ ] docs +- [ ] variants +- [ ] default selections for id/data-loading +- [ ] interface check for compatibility of implementors diff --git a/packages/plugin-drizzle/drizzle.config.json b/packages/plugin-drizzle/drizzle.config.json new file mode 100644 index 000000000..85be05578 --- /dev/null +++ b/packages/plugin-drizzle/drizzle.config.json @@ -0,0 +1,5 @@ +{ + "schema": "./tests/example/db/schema.ts", + "out": "./tests/drizzle", + "connectionString": "file:./tests/example/db/dev.db" +} diff --git a/packages/plugin-drizzle/esm/.gitignore b/packages/plugin-drizzle/esm/.gitignore new file mode 100755 index 000000000..e4704508f --- /dev/null +++ b/packages/plugin-drizzle/esm/.gitignore @@ -0,0 +1,4 @@ +* +!.gitignore +!.npmignore +!package.json diff --git a/packages/plugin-drizzle/esm/.npmignore b/packages/plugin-drizzle/esm/.npmignore new file mode 100755 index 000000000..e69de29bb diff --git a/packages/plugin-drizzle/esm/package.json b/packages/plugin-drizzle/esm/package.json new file mode 100755 index 000000000..3dbc1ca59 --- /dev/null +++ b/packages/plugin-drizzle/esm/package.json @@ -0,0 +1,3 @@ +{ + "type": "module" +} diff --git a/packages/plugin-drizzle/jest.config.js b/packages/plugin-drizzle/jest.config.js new file mode 100644 index 000000000..7d6620a47 --- /dev/null +++ b/packages/plugin-drizzle/jest.config.js @@ -0,0 +1,6 @@ +module.exports = { + transformIgnorePatterns: [`node_modules`], + transform: { + '^.+\\.(t|j)sx?$': ['@swc/jest'], + }, +}; diff --git a/packages/plugin-drizzle/package.json b/packages/plugin-drizzle/package.json new file mode 100644 index 000000000..f208e2432 --- /dev/null +++ b/packages/plugin-drizzle/package.json @@ -0,0 +1,62 @@ +{ + "name": "@pothos/plugin-drizzle", + "version": "0.0.0", + "description": "A Pothos plugin for drizzle", + "main": "./lib/index.js", + "types": "./dts/index.d.ts", + "module": "./esm/index.js", + "exports": { + "import": { + "default": "./esm/index.js" + }, + "require": { + "types": "./dts/index.d.ts", + "default": "./lib/index.js" + } + }, + "scripts": { + "type": "tsc --project tsconfig.type.json", + "build": "pnpm build:clean && pnpm build:cjs && pnpm build:dts && pnpm build:esm", + "build:clean": "git clean -dfX esm lib", + "build:cjs": "swc src -d lib --config-file ../../.swcrc -C module.type=commonjs", + "build:esm": "cp -r dts/* esm/ && swc src -d esm --config-file ../../.swcrc -C module.type=es6 && pnpm esm:extensions", + "build:dts": "tsc", + "esm:extensions": "TS_NODE_PROJECT=../../tsconfig.json node -r @swc-node/register ../../scripts/esm-transformer.ts", + "test": "pnpm vitest --run --segfault-retry=3" + }, + "repository": { + "type": "git", + "url": "git+https://github.com/hayes/pothos.git", + "directory": "packages/plugin-relay" + }, + "author": "Michael Hayes", + "license": "ISC", + "keywords": [ + "pothos", + "graphql", + "schema", + "typescript", + "drizzle", + "plugin" + ], + "publishConfig": { + "access": "public", + "provenance": true + }, + "peerDependencies": { + "@pothos/core": "*", + "graphql": ">=16.4.0" + }, + "devDependencies": { + "@pothos/core": "workspace:*", + "@pothos/plugin-scope-auth": "workspace:*", + "@pothos/plugin-relay": "workspace:*", + "@pothos/test-utils": "workspace:*", + "@types/better-sqlite3": "^7.6.4", + "better-sqlite3": "^8.4.0", + "drizzle-kit": "^0.19.13", + "drizzle-orm": "^0.28.5", + "graphql": "16.6.0", + "graphql-tag": "^2.12.6" + } +} diff --git a/packages/plugin-drizzle/src/drizzle-field-builder.ts b/packages/plugin-drizzle/src/drizzle-field-builder.ts new file mode 100644 index 000000000..9e805a002 --- /dev/null +++ b/packages/plugin-drizzle/src/drizzle-field-builder.ts @@ -0,0 +1,471 @@ +import { InferModelFromColumns, Many, TableRelationalConfig } from 'drizzle-orm'; +import { + CompatibleTypes, + ExposeNullability, + FieldKind, + FieldRef, + InputFieldMap, + InterfaceParam, + MaybePromise, + NormalizeArgs, + ObjectRef, + PluginName, + PothosSchemaError, + RootFieldBuilder, + SchemaTypes, + ShapeFromTypeParam, + TypeParam, +} from '@pothos/core'; +import { + DrizzleConnectionShape, + ListRelation, + RelatedConnectionOptions, + RelatedFieldOptions, + ShapeFromConnection, + TypesForRelation, +} from './types'; +import { getRefFromModel } from './utils/refs'; + +// Workaround for FieldKind not being extended on Builder classes +const RootBuilder: { + // eslint-disable-next-line @typescript-eslint/prefer-function-type + new ( + builder: PothosSchemaTypes.SchemaBuilder, + kind: FieldKind, + graphqlKind: PothosSchemaTypes.PothosKindToGraphQLType[FieldKind], + ): PothosSchemaTypes.RootFieldBuilder; +} = RootFieldBuilder as never; + +export class DrizzleObjectFieldBuilder< + Types extends SchemaTypes, + TableConfig extends TableRelationalConfig, + Shape, + ExposableShape = InferModelFromColumns, +> extends RootBuilder { + exposeBoolean = this.createExpose('Boolean'); + + exposeFloat = this.createExpose('Float'); + + exposeInt = this.createExpose('Int'); + + exposeID = this.createExpose('ID'); + + exposeString = this.createExpose('String'); + + exposeBooleanList = this.createExpose(['Boolean']); + + exposeFloatList = this.createExpose(['Float']); + + exposeIntList = this.createExpose(['Int']); + + exposeIDList = this.createExpose(['ID']); + + exposeStringList = this.createExpose(['String']); + + table: string; + + typename: string; + + relatedConnection: 'relay' extends PluginName + ? < + Field extends ListRelation, + Nullable extends boolean, + Args extends InputFieldMap, + ConnectionInterfaces extends InterfaceParam[] = [], + EdgeInterfaces extends InterfaceParam[] = [], + >( + field: Field, + options: RelatedConnectionOptions, + ...args: NormalizeArgs< + [ + connectionOptions: + | ObjectRef< + Types, + ShapeFromConnection< + PothosSchemaTypes.ConnectionShapeHelper< + Types, + TypesForRelation, + false + > + > + > + | PothosSchemaTypes.ConnectionObjectOptions< + Types, + ObjectRef< + Types, + ShapeFromTypeParam< + Types, + [ObjectRef>], + Nullable + > + >, + false, + false, + DrizzleConnectionShape< + Types, + ShapeFromTypeParam< + Types, + [ObjectRef>], + Nullable + >, + Shape, + Args + >, + ConnectionInterfaces + >, + edgeOptions: + | ObjectRef< + Types, + { + cursor: string; + node?: TypesForRelation; + } + > + | PothosSchemaTypes.ConnectionEdgeObjectOptions< + Types, + ObjectRef< + Types, + ShapeFromTypeParam< + Types, + [ObjectRef>], + Nullable + > + >, + false, + DrizzleConnectionShape< + Types, + ShapeFromTypeParam< + Types, + [ObjectRef>], + Nullable + >, + Shape, + Args + >, + EdgeInterfaces + >, + ], + 0 + > + ) => FieldRef< + Types, + ShapeFromConnection> + > + : '@pothos/plugin-relay is required to use this method' = function relatedConnection( + this: DrizzleObjectFieldBuilder, + name: string, + { + maxSize, + defaultSize, + cursor: cursorValue, + query, + resolve, + extensions, + totalCount, + description, + ...options + }: { + type?: ObjectRef; + totalCount?: boolean; + maxSize?: number | ((args: {}, ctx: {}) => number); + defaultSize?: number | ((args: {}, ctx: {}) => number); + cursor: string; + extensions: {}; + description?: string; + query: ((args: {}, ctx: {}) => {}) | {}; + resolve: ( + query: {}, + parent: unknown, + args: {}, + ctx: {}, + info: {}, + ) => MaybePromise; + }, + connectionOptions = {}, + edgeOptions = {}, + ) { + // const relationField = getRelation(this.model, this.builder, name); + // const ref = options.type ?? getRefFromModel(relationField.type, this.builder); + // let typeName: string | undefined; + // const formatCursor = getCursorFormatter(relationField.type, this.builder, cursorValue); + // const parseCursor = getCursorParser(relationField.type, this.builder, cursorValue); + // const getQuery = (args: PothosSchemaTypes.DefaultConnectionArguments, ctx: {}) => { + // const connectionQuery = prismaCursorConnectionQuery({ + // parseCursor, + // ctx, + // maxSize, + // defaultSize, + // args, + // }); + // const { + // take = connectionQuery.take, + // skip = connectionQuery.skip, + // cursor = connectionQuery.cursor, + // ...fieldQuery + // } = ((typeof query === 'function' ? query(args, ctx) : query) ?? + // {}) as typeof connectionQuery; + // return { + // ...fieldQuery, + // ...connectionQuery, + // take, + // skip, + // ...(cursor ? { cursor } : {}), + // }; + // }; + // const cursorSelection = ModelLoader.getCursorSelection( + // ref as never, + // relationField.type, + // cursorValue, + // this.builder, + // ); + // const relationSelect = ( + // args: object, + // context: object, + // nestedQuery: (query: unknown, path?: unknown) => { select?: {} }, + // getSelection: (path: string[]) => FieldNode | null, + // ) => { + // const nested = nestedQuery(getQuery(args, context), { + // getType: () => { + // if (!typeName) { + // typeName = this.builder.configStore.getTypeConfig(ref).name; + // } + // return typeName; + // }, + // paths: [[{ name: 'nodes' }], [{ name: 'edges' }, { name: 'node' }]], + // }) as SelectionMap; + // const hasTotalCount = totalCount && !!getSelection(['totalCount']); + // const countSelect = this.builder.options.prisma.filterConnectionTotalCount + // ? nested.where + // ? { where: nested.where } + // : true + // : true; + // return { + // select: { + // ...(hasTotalCount ? { _count: { select: { [name]: countSelect } } } : {}), + // [name]: nested?.select + // ? { + // ...nested, + // select: { + // ...cursorSelection, + // ...nested.select, + // }, + // } + // : nested, + // }, + // }; + // }; + // const fieldRef = ( + // this as unknown as { + // connection: (...args: unknown[]) => FieldRef; + // } + // ).connection( + // { + // ...options, + // description: getFieldDescription(this.model, this.builder, name, description), + // extensions: { + // ...extensions, + // pothosPrismaSelect: relationSelect, + // pothosPrismaLoaded: (value: Record) => value[name] !== undefined, + // pothosPrismaFallback: + // resolve && + // (( + // q: { take: number }, + // parent: unknown, + // args: PothosSchemaTypes.DefaultConnectionArguments, + // context: {}, + // info: GraphQLResolveInfo, + // ) => + // Promise.resolve( + // resolve( + // { + // ...q, + // ...getQuery(args, context), + // } as never, + // parent, + // args, + // context, + // info, + // ), + // ).then((result) => wrapConnectionResult(parent, result, args, q.take, formatCursor))), + // }, + // type: ref, + // resolve: ( + // parent: unknown, + // args: PothosSchemaTypes.DefaultConnectionArguments, + // context: {}, + // ) => { + // const connectionQuery = getQuery(args, context); + // return wrapConnectionResult( + // parent, + // (parent as Record)[name], + // args, + // connectionQuery.take, + // formatCursor, + // (parent as { _count?: Record })._count?.[name], + // ); + // }, + // }, + // connectionOptions instanceof ObjectRef + // ? connectionOptions + // : { + // ...connectionOptions, + // fields: totalCount + // ? ( + // t: PothosSchemaTypes.ObjectFieldBuilder, + // ) => ({ + // totalCount: t.int({ + // nullable: false, + // resolve: (parent, args, context) => parent.totalCount, + // }), + // ...(connectionOptions as { fields?: (t: unknown) => {} }).fields?.(t), + // }) + // : (connectionOptions as { fields: undefined }).fields, + // }, + // edgeOptions, + // ); + // return fieldRef; + } as never; + + constructor(typename: string, builder: PothosSchemaTypes.SchemaBuilder, table: string) { + super(builder, 'DrizzleObject', 'Object'); + + this.table = table; + this.typename = typename; + } + + relation< + Field extends keyof TableConfig['relations'], + Nullable extends boolean, + Args extends InputFieldMap, + ResolveReturnShape, + >( + name: Field, + ...allArgs: NormalizeArgs< + [ + options: RelatedFieldOptions< + Types, + TableConfig, + Field, + Nullable, + Args, + ResolveReturnShape, + Shape + >, + ] + > + ): FieldRef, 'Object'> { + const [options = {} as never] = allArgs; + const relationField = + this.builder.options.drizzle.client._.schema?.[this.table].relations[name as string]; + + if (!relationField) { + throw new PothosSchemaError( + `Could not find relation ${name as string} on table ${this.table}`, + ); + } + + const ref = options.type ?? getRefFromModel(relationField.referencedTableName, this.builder); + + const { query = {}, extensions, ...rest } = options; + + const relationSelect = ( + args: object, + context: object, + nestedQuery: (query: unknown) => unknown, + ) => ({ with: { [name]: nestedQuery(query) } }); + + return this.field({ + ...(rest as {}), + type: relationField instanceof Many ? [ref] : ref, + extensions: { + ...extensions, + pothosDrizzleSelect: relationSelect as never, + }, + resolve: (parent: Record) => parent[name as string], + } as never) as never; + } + + expose< + Type extends TypeParam, + Nullable extends boolean, + ResolveReturnShape, + Name extends CompatibleTypes, + >( + ...args: NormalizeArgs< + [ + name: Name, + options: ExposeNullability & + Omit< + PothosSchemaTypes.ObjectFieldOptions< + Types, + Shape, + Type, + Nullable, + {}, + ResolveReturnShape + >, + 'nullable' | 'resolve' | 'select' + >, + ] + > + ) { + const [name, options = {} as never] = args; + + const typeConfig = this.builder.configStore.getTypeConfig(this.typename, 'Object'); + const usingSelect = !!typeConfig.extensions?.pothosDrizzleSelect; + + return this.exposeField(name as never, { + ...options, + extensions: { + ...options.extensions, + pothosDrizzleVariant: name, + pothosDrizzleSelect: usingSelect && { + [name as string]: true, + }, + }, + }); + } + + private createExpose>(type: Type) { + return < + Nullable extends boolean, + ResolveReturnShape, + Name extends CompatibleTypes< + Types, + ExposableShape, + Type, + Type extends [unknown] ? { list: true; items: true } : true + >, + >( + ...args: NormalizeArgs< + [ + name: Name, + options: ExposeNullability & + Omit< + PothosSchemaTypes.ObjectFieldOptions< + Types, + ExposableShape, + Type, + Nullable, + {}, + ResolveReturnShape + >, + 'nullable' | 'resolve' | 'select' | 'type' + > & { + description?: string | false; + }, + ] + > + ): FieldRef, 'DrizzleObject'> => { + const [name, { description, ...options } = {} as never] = args; + + return this.expose( + name as never, + { + ...options, + type, + } as never, + ) as never; + }; + } +} diff --git a/packages/plugin-drizzle/src/field-builder.ts b/packages/plugin-drizzle/src/field-builder.ts new file mode 100644 index 000000000..641a94f35 --- /dev/null +++ b/packages/plugin-drizzle/src/field-builder.ts @@ -0,0 +1,35 @@ +import { GraphQLResolveInfo } from 'graphql'; +import { FieldKind, ObjectRef, RootFieldBuilder, SchemaTypes } from '@pothos/core'; +import { queryFromInfo } from './utils/map-query'; +import { getRefFromModel } from './utils/refs'; + +const fieldBuilderProto = RootFieldBuilder.prototype as PothosSchemaTypes.RootFieldBuilder< + SchemaTypes, + unknown, + FieldKind +>; + +fieldBuilderProto.drizzleField = function drizzleField({ type, resolve, ...options }) { + const modelOrRef = Array.isArray(type) ? type[0] : type; + const typeRef = + // typeof modelOrRef === 'string' ? + getRefFromModel(modelOrRef as string, this.builder); + // : (modelOrRef as ObjectRef); + const typeParam = Array.isArray(type) + ? ([typeRef] as [ObjectRef]) + : typeRef; + return this.field({ + ...(options as {}), + type: typeParam, + resolve: (parent: unknown, args: unknown, context: {}, info: GraphQLResolveInfo) => { + const query = queryFromInfo({ + schema: this.builder.options.drizzle.client._.schema!, + context, + info, + // withUsageCheck: !!this.builder.options.drizzle?.onUnusedQuery, + }); + + return resolve(query, parent, args as never, context, info) as never; + }, + }) as never; +}; diff --git a/packages/plugin-drizzle/src/global-types.ts b/packages/plugin-drizzle/src/global-types.ts new file mode 100644 index 000000000..8d18a7354 --- /dev/null +++ b/packages/plugin-drizzle/src/global-types.ts @@ -0,0 +1,177 @@ +/* eslint-disable @typescript-eslint/no-unused-vars */ +/* eslint-disable @typescript-eslint/no-empty-interface */ +import type { + BuildQueryResult, + DBQueryConfig, + ExtractTablesWithRelations, + TableRelationalConfig, +} from 'drizzle-orm'; +import { + FieldKind, + FieldNullability, + FieldRef, + InputFieldMap, + InterfaceParam, + OutputType, + SchemaTypes, + ShapeFromTypeParam, + TypeParam, +} from '@pothos/core'; + +import type { + DrizzleFieldOptions, + DrizzleObjectFieldOptions, + DrizzleObjectOptions, + DrizzlePluginOptions, + PothosDrizzlePlugin, +} from '.'; + +declare global { + export namespace PothosSchemaTypes { + export interface Plugins { + drizzle: PothosDrizzlePlugin; + } + + export interface SchemaBuilderOptions { + drizzle: DrizzlePluginOptions; + } + + export interface UserSchemaTypes { + DrizzleSchema: Record; + DrizzleRelationSchema: Record; + } + + export interface ExtendDefaultTypes> { + DrizzleSchema: PartialTypes['DrizzleSchema'] & {}; + DrizzleRelationSchema: ExtractTablesWithRelations; + } + + export interface SchemaBuilder { + drizzleObject: < + Interfaces extends InterfaceParam[], + Table extends keyof Types['DrizzleRelationSchema'], + Selection extends + | DBQueryConfig< + 'one', + false, + Types['DrizzleRelationSchema'], + Types['DrizzleRelationSchema'][Table] + > + | true, + Shape = BuildQueryResult< + Types['DrizzleRelationSchema'], + Types['DrizzleRelationSchema'][Table], + Selection + >, + >( + table: Table, + options: DrizzleObjectOptions, + ) => ObjectRef; + } + + export interface PothosKindToGraphQLType { + DrizzleObject: 'Object'; + } + + export interface FieldOptionsByKind< + Types extends SchemaTypes, + ParentShape, + Type extends TypeParam, + Nullable extends FieldNullability, + Args extends InputFieldMap, + ResolveShape, + ResolveReturnShape, + > { + DrizzleObject: DrizzleObjectFieldOptions< + Types, + ParentShape, + Type, + Nullable, + Args, + ResolveShape, + ResolveReturnShape + >; + } + + export interface RootFieldBuilder< + Types extends SchemaTypes, + ParentShape, + Kind extends FieldKind = FieldKind, + > { + drizzleField: < + Args extends InputFieldMap, + Param extends keyof Types['DrizzleRelationSchema'] | [keyof Types['DrizzleRelationSchema']], + Nullable extends FieldNullability, + ResolveShape, + ResolveReturnShape, + Type extends TypeParam = Param extends [unknown] + ? [ + ObjectRef< + Types, + BuildQueryResult< + Types['DrizzleRelationSchema'], + Types['DrizzleRelationSchema'][Param[0] & keyof Types['DrizzleRelationSchema']], + true + > + >, + ] + : ObjectRef< + Types, + BuildQueryResult< + Types['DrizzleRelationSchema'], + Types['DrizzleRelationSchema'][Param & keyof Types['DrizzleRelationSchema']], + true + > + >, + >( + options: DrizzleFieldOptions< + Types, + ParentShape, + Type, + Nullable, + Args, + Kind, + ResolveShape, + ResolveReturnShape, + Param + >, + ) => FieldRef>; + } + + export interface ConnectionFieldOptions< + Types extends SchemaTypes, + ParentShape, + Type extends OutputType, + Nullable extends boolean, + EdgeNullability extends FieldNullability<[unknown]>, + NodeNullability extends boolean, + Args extends InputFieldMap, + ResolveReturnShape, + > {} + + export interface ConnectionObjectOptions< + Types extends SchemaTypes, + Type extends OutputType, + EdgeNullability extends FieldNullability<[unknown]>, + NodeNullability extends boolean, + Resolved, + Interfaces extends InterfaceParam[] = [], + > {} + + export interface ConnectionEdgeObjectOptions< + Types extends SchemaTypes, + Type extends OutputType, + NodeNullability extends boolean, + Resolved, + Interfaces extends InterfaceParam[] = [], + > {} + + export interface DefaultConnectionArguments { + first?: number | null | undefined; + last?: number | null | undefined; + before?: string | null | undefined; + after?: string | null | undefined; + } + export interface ConnectionShapeHelper {} + } +} diff --git a/packages/plugin-drizzle/src/index.ts b/packages/plugin-drizzle/src/index.ts new file mode 100644 index 000000000..f461287c0 --- /dev/null +++ b/packages/plugin-drizzle/src/index.ts @@ -0,0 +1,91 @@ +import './global-types'; +import './field-builder'; +import './schema-builder'; +import { GraphQLFieldResolver } from 'graphql'; +import SchemaBuilder, { BasePlugin, PothosOutputFieldConfig, SchemaTypes } from '@pothos/core'; +import { getLoaderMapping, setLoaderMappings } from './utils/loader-map'; + +export * from './types'; + +const pluginName = 'drizzle'; + +export default pluginName; + +export class PothosDrizzlePlugin extends BasePlugin { + override onOutputFieldConfig( + fieldConfig: PothosOutputFieldConfig, + ): PothosOutputFieldConfig | null { + if (fieldConfig.kind === 'DrizzleObject' && fieldConfig.pothosOptions.select) { + const { select } = fieldConfig.pothosOptions; + return { + ...fieldConfig, + extensions: { + ...fieldConfig.extensions, + pothosDrizzleSelect: + typeof select === 'function' + ? ( + args: {}, + ctx: Types['Context'], + nestedQuery: (query: unknown, path?: string[]) => never, + ) => ({ + select: (select as (args: unknown, ctx: unknown, nestedQuery: unknown) => {})( + args, + ctx, + nestedQuery, + ), + }) + : select, + }, + }; + } + + return fieldConfig; + } + + override wrapResolve( + resolver: GraphQLFieldResolver, + fieldConfig: PothosOutputFieldConfig, + ): GraphQLFieldResolver { + if (fieldConfig.kind !== 'DrizzleObject' || !fieldConfig.extensions?.pothosDrizzleSelect) { + return resolver; + } + + const parentConfig = this.buildCache.getTypeConfig(fieldConfig.parentType); + + const parentTypes = new Set([fieldConfig.parentType]); + + if (parentConfig.kind === 'Interface' || parentConfig.kind === 'Object') { + parentConfig.interfaces.forEach((iface) => { + const interfaceConfig = this.buildCache.getTypeConfig(iface, 'Interface'); + if (interfaceConfig.extensions?.pothosDrizzleModel) { + parentTypes.add(interfaceConfig.name); + } + }); + } + + return (parent, args, context, info) => { + let mapping = getLoaderMapping(context, info.path, info.parentType.name); + + if (!mapping) { + for (const parentType of parentTypes) { + mapping = getLoaderMapping(context, info.path, parentType); + if (mapping) { + break; + } + } + } + + if (mapping) { + setLoaderMappings(context, info, mapping); + + return resolver(parent, args, context, info); + } + + throw new Error( + `Field ${fieldConfig.name} not resolved in initial query and dataloader not implemented yet`, + ); + }; + } +} + +SchemaBuilder.registerPlugin(pluginName, PothosDrizzlePlugin, {}); diff --git a/packages/plugin-drizzle/src/schema-builder.ts b/packages/plugin-drizzle/src/schema-builder.ts new file mode 100644 index 000000000..2c14108b7 --- /dev/null +++ b/packages/plugin-drizzle/src/schema-builder.ts @@ -0,0 +1,28 @@ +import SchemaBuilder, { ObjectRef, SchemaTypes } from '@pothos/core'; +import { DrizzleObjectFieldBuilder } from './drizzle-field-builder'; +import { getRefFromModel } from './utils/refs'; + +const schemaBuilderProto = SchemaBuilder.prototype as PothosSchemaTypes.SchemaBuilder; + +schemaBuilderProto.drizzleObject = function drizzleObject( + table, + { name, select, fields, ...options }, +) { + const ref = getRefFromModel(table, this, 'object') as ObjectRef; + + ref.name = name ?? table; + + this.objectType(ref, { + ...(options as {}), + extensions: { + ...options.extensions, + pothosDrizzleModel: table, + pothosDrizzleTable: this.options.drizzle.client._.schema?.[table], + pothosDrizzleSelect: select, + }, + name, + fields: fields ? () => fields(new DrizzleObjectFieldBuilder(ref.name, this, table)) : undefined, + }); + + return ref as never; +}; diff --git a/packages/plugin-drizzle/src/types.ts b/packages/plugin-drizzle/src/types.ts new file mode 100644 index 000000000..9c59b70ec --- /dev/null +++ b/packages/plugin-drizzle/src/types.ts @@ -0,0 +1,328 @@ +import { + BuildQueryResult, + DBQueryConfig, + Many, + Relation, + TableRelationalConfig, + TablesRelationalConfig, +} from 'drizzle-orm'; +import { FieldNode, GraphQLResolveInfo } from 'graphql'; +import { + FieldKind, + FieldMap, + FieldNullability, + FieldOptionsFromKind, + InputFieldMap, + InputFieldsFromShape, + InputShapeFromFields, + InterfaceParam, + MaybePromise, + Normalize, + ObjectRef, + ObjectTypeOptions, + SchemaTypes, + ShapeFromTypeParam, + TypeParam, +} from '@pothos/core'; +import type { DrizzleObjectFieldBuilder } from './drizzle-field-builder'; +import { IndirectInclude } from './utils/map-query'; +import { SelectionMap } from './utils/selections'; + +// eslint-disable-next-line @typescript-eslint/no-unused-vars +export interface DrizzlePluginOptions { + client: { _: { schema?: TablesRelationalConfig } }; +} + +export const drizzleTableName = Symbol.for('Pothos.drizzleTableName'); + +export type DrizzleObjectOptions< + Types extends SchemaTypes, + Table, + Shape, + Selection, + Interfaces extends InterfaceParam[], +> = Omit< + ObjectTypeOptions, Shape, Interfaces> & { + name: string; + select?: Selection; + }, + 'fields' +> & { + fields?: ( + t: DrizzleObjectFieldBuilder< + Types, + Table extends keyof Types['DrizzleRelationSchema'] + ? Types['DrizzleRelationSchema'][Table] + : never, + Shape & { [drizzleTableName]?: Table } + >, + ) => FieldMap; +}; + +export type DrizzleFieldOptions< + Types extends SchemaTypes, + ParentShape, + Type extends TypeParam, + Nullable extends FieldNullability, + Args extends InputFieldMap, + Kind extends FieldKind, + ResolveShape, + ResolveReturnShape, + Param, +> = Omit< + FieldOptionsFromKind< + Types, + ParentShape, + Type, + Nullable, + Args, + Kind, + ResolveShape, + ResolveReturnShape + >, + 'resolve' | 'type' +> & { + type: Param; + resolve: ( + query: DBQueryConfig< + Param extends [unknown] ? 'many' : 'one', + false, + Types['DrizzleRelationSchema'], + Types['DrizzleRelationSchema'][keyof Types['DrizzleRelationSchema'] & + (Param extends [unknown] ? Param[0] : Param)] + >, + parent: ParentShape, + args: InputShapeFromFields, + ctx: Types['Context'], + info: GraphQLResolveInfo, + ) => MaybePromise>; +}; + +export type DrizzleObjectFieldOptions< + Types extends SchemaTypes, + ParentShape, + Type extends TypeParam, + Nullable extends FieldNullability, + Args extends InputFieldMap, + Select, + ResolveReturnShape, +> = PothosSchemaTypes.ObjectFieldOptions< + Types, + Normalize< + Omit< + unknown extends Select + ? ParentShape + : BuildQueryResult< + Types['DrizzleRelationSchema'], + ExtractTable, + Record & (Select extends (...args: any[]) => infer R ? R : Select) + > & + ParentShape, + typeof drizzleTableName + > + >, + Type, + Nullable, + Args, + ResolveReturnShape +> & { + select?: Select & + ( + | DBQueryConfig< + 'one', + false, + Types['DrizzleRelationSchema'], + ExtractTable + > + | (( + args: InputShapeFromFields, + ctx: Types['Context'], + nestedSelection: ( + selection?: Selection, + path?: string[], + ) => Selection, + ) => DBQueryConfig< + 'one', + false, + Types['DrizzleRelationSchema'], + ExtractTable + >) + ); +}; + +export type DrizzleFieldSelection = + | SelectionMap + | (( + args: {}, + ctx: object, + mergeNestedSelection: ( + selection: SelectionMap | boolean | ((args: object, context: object) => SelectionMap), + path?: IndirectInclude | string[], + ) => SelectionMap | boolean, + resolveSelection: (path: string[]) => FieldNode | null, + ) => SelectionMap); + +type ExtractTable = Shape extends { + [drizzleTableName]?: keyof Types['DrizzleRelationSchema']; +} + ? Types['DrizzleRelationSchema'][NonNullable] + : never; + +export type RelatedFieldOptions< + Types extends SchemaTypes, + Table extends TableRelationalConfig, + Field extends keyof Table['relations'], + Nullable extends boolean, + Args extends InputFieldMap, + ResolveReturnShape, + Shape, +> = Omit< + DrizzleObjectFieldOptions< + Types, + Shape, + RefForRelation, + Nullable, + Args, + { + columns: {}; + with: { [K in Field]: true }; + }, + ResolveReturnShape + >, + 'description' | 'resolve' | 'select' | 'type' +> & { + description?: string | false; + type?: ObjectRef>; + query?: QueryForField; +}; + +export type RefForRelation = Rel extends { + $relationBrand: 'One'; +} + ? ObjectRef> + : [ObjectRef>]; + +export type TypesForRelation = BuildQueryResult< + Types['DrizzleRelationSchema'], + Types['DrizzleRelationSchema'][Rel['referencedTable']['_']['name']], + true +>; + +export type QueryForField< + Types extends SchemaTypes, + Args extends InputFieldMap, + Rel extends Relation, +> = ( + Rel extends { + $relationBrand: 'One'; + } + ? Omit< + DBQueryConfig< + 'one', + false, + Types['DrizzleRelationSchema'], + Types['DrizzleRelationSchema'][Rel['referencedTable']['_']['name']] + >, + 'columns' | 'extra' | 'with' + > + : Omit< + DBQueryConfig< + 'many', + false, + Types['DrizzleRelationSchema'], + Types['DrizzleRelationSchema'][Rel['referencedTable']['_']['name']] + >, + 'columns' | 'extra' | 'with' + > +) extends infer QueryConfig + ? QueryConfig | ((args: InputShapeFromFields, context: Types['Context']) => QueryConfig) + : never; + +export type ListRelation = { + [K in keyof T['relations']]: T['relations'][K] extends Many ? K : never; +}[keyof T['relations']]; + +export type RelatedConnectionOptions< + Types extends SchemaTypes, + Shape, + TableConfig extends TableRelationalConfig, + Field extends keyof TableConfig['relations'], + Nullable extends boolean, + Args extends InputFieldMap, + // eslint-disable-next-line @typescript-eslint/sort-type-constituents +> = Omit< + PothosSchemaTypes.ObjectFieldOptions< + Types, + Shape, + ObjectRef, + Nullable, + InputFieldsFromShape & + (InputFieldMap extends Args ? {} : Args), + unknown + >, + 'args' | 'resolve' | 'type' +> & + Omit< + PothosSchemaTypes.ConnectionFieldOptions< + Types, + Shape, + ObjectRef, + false, + false, + Nullable, + Args, + unknown + >, + 'resolve' | 'type' + > & + (InputShapeFromFields & + PothosSchemaTypes.DefaultConnectionArguments extends infer ConnectionArgs + ? { + resolve?: ( + query: { + // include?: Model['Include']; + // cursor?: Model['WhereUnique']; + // take: number; + // skip: number; + }, + parent: Shape, + args: ConnectionArgs, + context: Types['Context'], + info: GraphQLResolveInfo, + ) => MaybePromise< + ShapeFromTypeParam< + Types, + [ObjectRef>], + Nullable + > + >; + // query?: QueryForField; + // type?: PrismaRef>; + // cursor: CursorFromRelation; + defaultSize?: number | ((args: ConnectionArgs, ctx: Types['Context']) => number); + maxSize?: number | ((args: ConnectionArgs, ctx: Types['Context']) => number); + // totalCount?: NeedsResolve extends false ? boolean : false; + } + : never); + +export type ShapeFromConnection = T extends { shape: unknown } ? T['shape'] : never; + +export type DrizzleConnectionShape< + Types extends SchemaTypes, + T, + Parent, + Args extends InputFieldMap, +> = ( + ShapeFromConnection> extends infer Shape + ? Shape & { + parent: Parent; + args: InputShapeFromFields & PothosSchemaTypes.DefaultConnectionArguments; + } + : never +) extends infer C + ? [C] extends [ + { edges: MaybePromise }, + ] + ? Omit & { edges: (Edge & { connection: C })[] } + : C + : never; diff --git a/packages/plugin-drizzle/src/utils/deep-equal.ts b/packages/plugin-drizzle/src/utils/deep-equal.ts new file mode 100644 index 000000000..8fbffbb90 --- /dev/null +++ b/packages/plugin-drizzle/src/utils/deep-equal.ts @@ -0,0 +1,58 @@ +/* eslint-disable no-continue */ +export function deepEqual(left: unknown, right: unknown, ignore?: Set) { + if (left === right) { + return true; + } + + if (left && right && typeof left === 'object' && typeof right === 'object') { + if (Array.isArray(left)) { + if (!Array.isArray(right)) { + return false; + } + + const { length } = left; + + if (right.length !== length) { + return false; + } + + for (let i = 0; i < length; i += 1) { + if (!deepEqual(left[i], right[i])) { + return false; + } + } + + return true; + } + + const lValue = left.valueOf?.(); + const rValue = right.valueOf?.(); + + if ((lValue != null || rValue != null) && typeof lValue !== 'object') { + return lValue === rValue; + } + + const keys = Object.keys(left); + const keyLength = keys.length; + + if (keyLength !== Object.keys(right).length) { + return false; + } + + for (const key of keys) { + if (ignore?.has(key)) { + continue; + } + + if ( + !deepEqual((left as Record)[key], (right as Record)[key]) + ) { + return false; + } + } + + return true; + } + + return false; +} diff --git a/packages/plugin-drizzle/src/utils/loader-map.ts b/packages/plugin-drizzle/src/utils/loader-map.ts new file mode 100644 index 000000000..22736ff57 --- /dev/null +++ b/packages/plugin-drizzle/src/utils/loader-map.ts @@ -0,0 +1,51 @@ +import { GraphQLResolveInfo } from 'graphql'; +import { createContextCache } from '@pothos/core'; + +export type LoaderMappings = Record< + string, + { + field: string; + type: string; + mappings: LoaderMappings; + indirectPath: string[]; + } +>; + +const cache = createContextCache((ctx) => new Map()); + +export function cacheKey(type: string, path: GraphQLResolveInfo['path'], subPath: string[] = []) { + let key = ''; + let current: GraphQLResolveInfo['path'] | undefined = path; + + while (current) { + if (typeof current.key === 'string') { + key = key ? `${current.key}.${key}` : current.key; + } + current = current.prev; + } + + for (const entry of subPath) { + key = `${key}.${entry}`; + } + + return `${type}@${key}`; +} + +export function setLoaderMappings(ctx: object, info: GraphQLResolveInfo, value: LoaderMappings) { + Object.keys(value).forEach((field) => { + const map = cache(ctx); + + const mapping = value[field]; + const subPath = [...mapping.indirectPath, field]; + const key = cacheKey(mapping.type, info.path, subPath); + + map.set(key, mapping.mappings); + }); +} + +export function getLoaderMapping(ctx: object, path: GraphQLResolveInfo['path'], type: string) { + const map = cache(ctx); + const key = cacheKey(type, path, []); + + return map.get(key) ?? null; +} diff --git a/packages/plugin-drizzle/src/utils/map-query.ts b/packages/plugin-drizzle/src/utils/map-query.ts new file mode 100644 index 000000000..02b6995bc --- /dev/null +++ b/packages/plugin-drizzle/src/utils/map-query.ts @@ -0,0 +1,546 @@ +/* eslint-disable no-continue */ +import { TableRelationalConfig, TablesRelationalConfig } from 'drizzle-orm'; +import { + FieldNode, + FragmentDefinitionNode, + getArgumentValues, + getDirectiveValues, + getNamedType, + GraphQLField, + GraphQLIncludeDirective, + GraphQLInterfaceType, + GraphQLNamedType, + GraphQLObjectType, + GraphQLResolveInfo, + GraphQLSkipDirective, + InlineFragmentNode, + isInterfaceType, + isObjectType, + Kind, + SelectionSetNode, +} from 'graphql'; +import { PothosValidationError } from '@pothos/core'; +import { type DrizzleFieldSelection } from '../types'; +import { LoaderMappings, setLoaderMappings } from './loader-map'; +import { + createState, + mergeSelection, + selectionCompatible, + SelectionMap, + SelectionState, + selectionToQuery, +} from './selections'; + +export interface IndirectInclude { + getType: () => string; + path?: { type?: string; name: string }[]; + paths?: { type?: string; name: string }[][]; +} + +function addTypeSelectionsForField( + schema: TablesRelationalConfig, + type: GraphQLNamedType, + context: object, + info: GraphQLResolveInfo, + state: SelectionState, + selection: FieldNode, + indirectPath: string[], +) { + if (selection.name.value.startsWith('__')) { + return; + } + + const { pothosDrizzleSelect, pothosDrizzleIndirectInclude } = (type.extensions ?? {}) as { + pothosDrizzleIndirectInclude?: IndirectInclude; + pothosDrizzleSelect?: SelectionMap; + }; + + if ( + (!!pothosDrizzleIndirectInclude?.path && pothosDrizzleIndirectInclude.path.length > 0) || + (!!pothosDrizzleIndirectInclude?.paths && pothosDrizzleIndirectInclude.paths.length === 0) + ) { + resolveIndirectIncludePaths( + type, + info, + selection, + [], + pothosDrizzleIndirectInclude.paths ?? [pothosDrizzleIndirectInclude.path!], + indirectPath, + (resolvedType, field, path) => { + addTypeSelectionsForField(schema, resolvedType, context, info, state, field, path); + }, + ); + } else if (pothosDrizzleIndirectInclude) { + addTypeSelectionsForField( + schema, + info.schema.getType(pothosDrizzleIndirectInclude.getType())!, + context, + info, + state, + selection, + indirectPath, + ); + return; + } + + if (!(isObjectType(type) || isInterfaceType(type))) { + return; + } + + if (pothosDrizzleSelect) { + mergeSelection(schema, state, { ...pothosDrizzleSelect }); + } + + if (selection.selectionSet) { + addNestedSelections(schema, type, context, info, state, selection.selectionSet, indirectPath); + } +} + +function resolveIndirectIncludePaths( + type: GraphQLNamedType, + info: GraphQLResolveInfo, + selection: FieldNode | FragmentDefinitionNode | InlineFragmentNode, + pathPrefix: { type?: string; name: string }[], + includePaths: { type?: string; name: string }[][], + path: string[], + resolve: (type: GraphQLNamedType, field: FieldNode, path: string[]) => void, +) { + for (const includePath of includePaths) { + if (pathPrefix.length > 0) { + resolveIndirectInclude(type, info, selection, [...pathPrefix, ...includePath], path, resolve); + } else { + resolveIndirectInclude(type, info, selection, includePath, path, resolve); + } + } +} + +function resolveIndirectInclude( + type: GraphQLNamedType, + info: GraphQLResolveInfo, + selection: FieldNode | FragmentDefinitionNode | InlineFragmentNode, + includePath: { type?: string; name: string }[], + path: string[], + resolve: (type: GraphQLNamedType, field: FieldNode, path: string[]) => void, +) { + if (includePath.length === 0) { + resolve(type, selection as FieldNode, path); + return; + } + + const [include, ...rest] = includePath; + if (!selection.selectionSet || !include) { + return; + } + + for (const sel of selection.selectionSet.selections) { + switch (sel.kind) { + case Kind.FIELD: + if ( + !fieldSkipped(info, sel) && + sel.name.value === include.name && + (isObjectType(type) || isInterfaceType(type)) + ) { + const returnType = getNamedType(type.getFields()[sel.name.value].type); + + resolveIndirectInclude( + returnType, + info, + sel, + rest, + [...path, sel.alias?.value ?? sel.name.value], + resolve, + ); + } + continue; + case Kind.FRAGMENT_SPREAD: + if ( + !include.type || + info.fragments[sel.name.value].typeCondition.name.value === include.type + ) { + resolveIndirectInclude( + include.type ? info.schema.getType(include.type)! : type, + info, + info.fragments[sel.name.value], + includePath, + path, + resolve, + ); + } + + continue; + + case Kind.INLINE_FRAGMENT: + if (!sel.typeCondition || !include.type || sel.typeCondition.name.value === include.type) { + resolveIndirectInclude( + sel.typeCondition ? info.schema.getType(sel.typeCondition.name.value)! : type, + info, + sel, + includePath, + path, + resolve, + ); + } + + continue; + + default: + throw new PothosValidationError( + `Unsupported selection kind ${(selection as { kind: string }).kind}`, + ); + } + } +} + +function addNestedSelections( + schema: TablesRelationalConfig, + type: GraphQLInterfaceType | GraphQLObjectType, + context: object, + info: GraphQLResolveInfo, + state: SelectionState, + selections: SelectionSetNode, + indirectPath: string[], +) { + let parentType = type; + for (const selection of selections.selections) { + switch (selection.kind) { + case Kind.FIELD: + addFieldSelection(schema, type, context, info, state, selection, indirectPath); + + continue; + case Kind.FRAGMENT_SPREAD: + parentType = info.schema.getType( + info.fragments[selection.name.value].typeCondition.name.value, + )! as GraphQLObjectType; + if ( + isObjectType(type) + ? parentType.name !== type.name + : parentType.extensions?.pothosDrizzleModel !== type.extensions.pothosDrizzleModel + ) { + continue; + } + + addNestedSelections( + schema, + parentType, + context, + info, + state, + info.fragments[selection.name.value].selectionSet, + indirectPath, + ); + + continue; + + case Kind.INLINE_FRAGMENT: + parentType = selection.typeCondition + ? (info.schema.getType(selection.typeCondition.name.value) as GraphQLObjectType) + : type; + if ( + isObjectType(type) + ? parentType.name !== type.name + : parentType.extensions?.pothosDrizzleModel !== type.extensions.pothosDrizzleModel + ) { + continue; + } + + addNestedSelections( + schema, + parentType, + context, + info, + state, + selection.selectionSet, + indirectPath, + ); + + continue; + + default: + throw new PothosValidationError( + `Unsupported selection kind ${(selection as { kind: string }).kind}`, + ); + } + } +} + +function addFieldSelection( + schema: TablesRelationalConfig, + type: GraphQLInterfaceType | GraphQLObjectType, + context: object, + info: GraphQLResolveInfo, + state: SelectionState, + selection: FieldNode, + indirectPath: string[], +) { + if (selection.name.value.startsWith('__') || fieldSkipped(info, selection)) { + return; + } + const field = type.getFields()[selection.name.value]; + if (!field) { + throw new PothosValidationError(`Unknown field ${selection.name.value} on ${type.name}`); + } + + const fieldSelect = field.extensions?.pothosDrizzleSelect as DrizzleFieldSelection | undefined; + + let fieldSelectionMap: SelectionMap | undefined; + let mappings: LoaderMappings = {}; + if (typeof fieldSelect === 'function') { + const args = getArgumentValues(field, selection, info.variableValues) as Record< + string, + unknown + >; + fieldSelectionMap = fieldSelect( + args, + context, + (rawQuery, indirectInclude) => { + const returnType = getNamedType(field.type); + const query = typeof rawQuery === 'function' ? rawQuery(args, context) : rawQuery; + const normalizedIndirectInclude = Array.isArray(indirectInclude) + ? normalizeInclude(indirectInclude, getIndirectType(returnType, info)) + : indirectInclude; + const fieldState = createStateForSelection(schema, returnType, state); + if (typeof query === 'object' && Object.keys(query).length > 0) { + mergeSelection(schema, fieldState, { columns: {}, ...query }); + } + if ( + (!!normalizedIndirectInclude?.path && normalizedIndirectInclude.path.length > 0) || + (!!normalizedIndirectInclude?.paths && normalizedIndirectInclude.paths.length > 0) + ) { + resolveIndirectIncludePaths( + returnType, + info, + selection, + (returnType.extensions?.pothosDrizzleIndirectInclude as { path: [] })?.path ?? [], + normalizedIndirectInclude.paths ?? [normalizedIndirectInclude.path!], + [], + (resolvedType, resolvedField, path) => { + addTypeSelectionsForField( + schema, + resolvedType, + context, + info, + fieldState, + resolvedField, + path, + ); + }, + ); + } + addTypeSelectionsForField(schema, returnType, context, info, fieldState, selection, []); + // eslint-disable-next-line prefer-destructuring + mappings = fieldState.mappings; + return selectionToQuery(fieldState); + }, + (path) => { + const returnType = getNamedType(field.type); + let node: FieldNode | null = null; + resolveIndirectInclude( + returnType, + info, + selection, + path.map((name) => ({ + name, + })), + [], + (_, resolvedField) => { + node = resolvedField; + }, + ); + return node; + }, + ); + } else { + fieldSelectionMap = fieldSelect!; + } + + if (fieldSelect && selectionCompatible(state, fieldSelectionMap, true)) { + mergeSelection(schema, state, fieldSelectionMap); + // eslint-disable-next-line no-param-reassign + state.mappings[selection.alias?.value ?? selection.name.value] = { + field: selection.name.value, + type: type.name, + mappings, + indirectPath, + }; + } +} + +export function queryFromInfo({ + schema, + context, + info, + typeName, + select, + path = [], + paths = [], + withUsageCheck = false, +}: { + schema: TablesRelationalConfig; + context: object; + info: GraphQLResolveInfo; + typeName?: string; + select?: T; + path?: string[]; + paths?: string[][]; + withUsageCheck?: boolean; +}): { include?: {} } | { select: T } { + const returnType = getNamedType(info.returnType); + const type = typeName ? info.schema.getTypeMap()[typeName] : returnType; + + let state: SelectionState | undefined; + + if (path.length > 0 || paths.length > 0) { + const { pothosDrizzleIndirectInclude } = (returnType.extensions ?? {}) as { + pothosDrizzleIndirectInclude?: IndirectInclude; + }; + + resolveIndirectInclude( + returnType, + info, + info.fieldNodes[0], + pothosDrizzleIndirectInclude?.path ?? [], + [], + (indirectType, indirectField, subPath) => { + resolveIndirectIncludePaths( + indirectType, + info, + indirectField, + [], + paths.length > 0 + ? paths.map((p) => p.map((n) => (typeof n === 'string' ? { name: n } : n))) + : [path.map((n) => (typeof n === 'string' ? { name: n } : n))], + subPath, + (resolvedType, resolvedField, nested) => { + state = createStateForSelection(schema, resolvedType, undefined, select); + + addTypeSelectionsForField( + schema, + typeName ? type : resolvedType, + context, + info, + state, + resolvedField, + nested, + ); + }, + ); + }, + ); + } else { + state = createStateForSelection(schema, type, undefined, select); + + addTypeSelectionsForField(schema, type, context, info, state, info.fieldNodes[0], []); + } + + if (!state) { + state = createStateForSelection(schema, type, undefined, select); + } + + setLoaderMappings(context, info, state.mappings); + + const query = selectionToQuery(state) as { select: T }; + + return query; + // return withUsageCheck ? wrapWithUsageCheck(query) : query; +} + +export function selectionStateFromInfo( + schema: TablesRelationalConfig, + context: object, + info: GraphQLResolveInfo, + typeName?: string, +) { + const type = typeName ? info.schema.getTypeMap()[typeName] : info.parentType; + + const state = createStateForSelection(schema, type); + + if (!(isObjectType(type) || isInterfaceType(type))) { + throw new PothosValidationError( + 'Drizzle plugin can only resolve selections for object and interface types', + ); + } + + addFieldSelection(schema, type, context, info, state, info.fieldNodes[0], []); + + return state; +} + +function createStateForSelection( + schema: TablesRelationalConfig, + type: GraphQLNamedType, + parent?: SelectionState, + initialSelections?: SelectionMap, +) { + const { pothosDrizzleTable } = (type.extensions ?? {}) as { + pothosDrizzleTable?: TableRelationalConfig; + }; + + if (!pothosDrizzleTable) { + throw new PothosValidationError(`Expected ${type.name} to have a table config`); + } + + const state = createState(pothosDrizzleTable, parent); + + if (initialSelections) { + mergeSelection(schema, state, initialSelections); + } + + return state; +} + +export function getIndirectType(type: GraphQLNamedType, info: GraphQLResolveInfo) { + let targetType = type; + + while (targetType.extensions?.pothosDrizzleIndirectInclude) { + targetType = info.schema.getType( + (targetType.extensions?.pothosDrizzleIndirectInclude as IndirectInclude).getType(), + )!; + } + + return targetType; +} + +export function normalizeInclude(path: string[], type: GraphQLNamedType): IndirectInclude { + let currentType = type; + + const normalized: { name: string; type: string }[] = []; + + if (!(isObjectType(currentType) || isInterfaceType(currentType))) { + throw new PothosValidationError(`Expected ${currentType} to be an Object type`); + } + + for (const fieldName of path) { + const field: GraphQLField = currentType.getFields()[fieldName]; + + if (!field) { + throw new PothosValidationError(`Expected ${currentType} to have a field ${fieldName}`); + } + + currentType = getNamedType(field.type); + + if (!(isObjectType(currentType) || isInterfaceType(currentType))) { + throw new PothosValidationError(`Expected ${currentType} to be an Object or Interface type`); + } + + normalized.push({ name: fieldName, type: currentType.name }); + } + + return { + getType: () => (normalized.length > 0 ? normalized[normalized.length - 1].type : type.name), + path: normalized, + }; +} + +function fieldSkipped(info: GraphQLResolveInfo, selection: FieldNode) { + const skip = getDirectiveValues(GraphQLSkipDirective, selection, info.variableValues); + if (skip?.if === true) { + return true; + } + + const include = getDirectiveValues(GraphQLIncludeDirective, selection, info.variableValues); + if (include?.if === false) { + return true; + } + + return false; +} diff --git a/packages/plugin-drizzle/src/utils/refs.ts b/packages/plugin-drizzle/src/utils/refs.ts new file mode 100644 index 000000000..68fb0b65f --- /dev/null +++ b/packages/plugin-drizzle/src/utils/refs.ts @@ -0,0 +1,23 @@ +import { InterfaceRef, ObjectRef, SchemaTypes } from '@pothos/core'; + +export const refMap = new WeakMap< + object, + Map | ObjectRef> +>(); + +export function getRefFromModel( + name: string, + builder: PothosSchemaTypes.SchemaBuilder, + type: 'interface' | 'object' = 'object', +): InterfaceRef | ObjectRef { + if (!refMap.has(builder)) { + refMap.set(builder, new Map()); + } + const cache = refMap.get(builder)!; + + if (!cache.has(name)) { + cache.set(name, type === 'object' ? new ObjectRef(name) : new InterfaceRef(name)); + } + + return cache.get(name)! as never; +} diff --git a/packages/plugin-drizzle/src/utils/selections.ts b/packages/plugin-drizzle/src/utils/selections.ts new file mode 100644 index 000000000..8c76b1ee9 --- /dev/null +++ b/packages/plugin-drizzle/src/utils/selections.ts @@ -0,0 +1,194 @@ +/* eslint-disable no-param-reassign */ +import { DBQueryConfig, SQL, TableRelationalConfig, TablesRelationalConfig } from 'drizzle-orm'; +import { PothosValidationError } from '@pothos/core'; +import { deepEqual } from './deep-equal'; +import { LoaderMappings } from './loader-map'; + +export interface SelectionState { + table: TableRelationalConfig; + query: object; + allColumns: boolean; + columns: Set; + with: Map; + extras: Map; + mappings: LoaderMappings; + parent?: SelectionState; +} + +export type SelectionMap = DBQueryConfig<'one', false> & { + extras?: Record; +}; + +export function selectionCompatible( + state: SelectionState, + selectionMap: SelectionMap | boolean, + ignoreQuery = false, +): boolean { + if (typeof selectionMap === 'boolean') { + return ignoreQuery || !selectionMap || Object.keys(state.query).length === 0; + } + + const { with: withSelection, extras, columns, ...query } = selectionMap; + + if ( + withSelection && + Object.entries(withSelection).some( + ([key, value]) => + value && + state.with.has(key) && + // TODO: make sure nested extras are normalized + !selectionCompatible(state.with.get(key)!, value as SelectionMap), + ) + ) { + return false; + } + + if ( + extras && + Object.entries(extras).some(([key, value]) => { + const sql = state.extras.get(key); + + return sql && (sql.sql !== value.sql || sql.fieldAlias !== value.fieldAlias); + }) + ) { + return false; + } + + return ignoreQuery || deepEqual(state.query, query); +} + +export function stateCompatible( + state: SelectionState, + newState: SelectionState, + ignoreQuery = false, +): boolean { + for (const [name, relationState] of newState.with) { + if (state.with.has(name) && !stateCompatible(state.with.get(name)!, relationState)) { + return false; + } + } + + return ignoreQuery || deepEqual(state.query, newState.query); +} + +export function mergeState(state: SelectionState, newState: SelectionState) { + for (const [name, relationState] of newState.with) { + if (state.with.has(name)) { + mergeState(state.with.get(name)!, relationState); + } + } + + if (!state.allColumns) { + if (newState.allColumns) { + state.allColumns = true; + } else { + for (const name of newState.columns) { + state.columns.add(name); + } + } + } + + for (const [name, value] of newState.extras) { + state.extras.set(name, value); + } +} + +export function createState(table: TableRelationalConfig, parent?: SelectionState): SelectionState { + return { + table, + parent, + query: {}, + columns: new Set(), + with: new Map(), + extras: new Map(), + mappings: {}, + allColumns: false, + }; +} + +export function mergeSelection( + schema: TablesRelationalConfig, + state: SelectionState, + { with: withSelection, extras, columns, ...query }: SelectionMap, +) { + if (withSelection) { + Object.entries(withSelection).forEach(([key, value]) => { + const relation = state.table.relations[key]; + + if (!relation) { + throw new PothosValidationError(`Relation ${key} does not exist on ${state.table.dbName}`); + } + + const table = schema[relation.referencedTableName]; + + // TODO: make sure that nested extras are normalized + merge(table, key, value as SelectionMap | boolean); + }); + } + + if (Object.keys(query).length > 0) { + state.query = query; + } + + if (extras) { + for (const [key, value] of Object.entries(extras)) { + state.extras.set(key, value); + } + } + + if (state.allColumns) { + return; + } + + if (columns) { + for (const key of Object.keys(columns)) { + state.columns.add(key); + } + } else { + state.allColumns = true; + } + + function merge(table: TableRelationalConfig, key: string, value?: SelectionMap | boolean) { + if (!value) { + return; + } + + const selection = value === true ? {} : value; + + if (state.with.has(key)) { + mergeSelection(schema, state.with.get(key)!, selection); + } else { + const relatedState = createState(table, state); + mergeSelection(schema, relatedState, selection); + state.with.set(key, relatedState); + } + } +} + +export function selectionToQuery(state: SelectionState): SelectionMap { + const query: SelectionMap = { + with: {}, + columns: {}, + extras: {}, + }; + + if (state.allColumns) { + for (const key of Object.keys(state.table.columns)) { + query.columns![key] = true; + } + } else { + for (const key of state.columns) { + query.columns![key] = true; + } + } + + for (const [key, value] of state.extras) { + query.extras![key] = value; + } + + state.with.forEach((sel, relation) => { + query.with![relation] = selectionToQuery(sel); + }); + + return query; +} diff --git a/packages/plugin-drizzle/tests/drizzle/0000_eager_pet_avengers.sql b/packages/plugin-drizzle/tests/drizzle/0000_eager_pet_avengers.sql new file mode 100644 index 000000000..96b677871 --- /dev/null +++ b/packages/plugin-drizzle/tests/drizzle/0000_eager_pet_avengers.sql @@ -0,0 +1,38 @@ +CREATE TABLE `comments` ( + `id` integer PRIMARY KEY AUTOINCREMENT NOT NULL, + `text` text, + `author_id` integer, + `post_id` integer +); + +CREATE TABLE `groups` ( + `id` integer PRIMARY KEY AUTOINCREMENT NOT NULL, + `name` text +); + +CREATE TABLE `posts` ( + `id` integer PRIMARY KEY AUTOINCREMENT NOT NULL, + `content` text, + `author_id` integer +); + +CREATE TABLE `profile_info` ( + `id` integer PRIMARY KEY AUTOINCREMENT NOT NULL, + `user_id` integer, + `metadata` text, + FOREIGN KEY (`user_id`) REFERENCES `users`(`id`) +); + +CREATE TABLE `users` ( + `id` integer PRIMARY KEY AUTOINCREMENT NOT NULL, + `name` text, + `invited_by` integer +); + +CREATE TABLE `users_to_groups` ( + `user_id` integer NOT NULL, + `group_id` integer NOT NULL, + PRIMARY KEY(`user_id`, `group_id`), + FOREIGN KEY (`user_id`) REFERENCES `users`(`id`), + FOREIGN KEY (`group_id`) REFERENCES `groups`(`id`) +); diff --git a/packages/plugin-drizzle/tests/drizzle/meta/0000_snapshot.json b/packages/plugin-drizzle/tests/drizzle/meta/0000_snapshot.json new file mode 100644 index 000000000..110f636b9 --- /dev/null +++ b/packages/plugin-drizzle/tests/drizzle/meta/0000_snapshot.json @@ -0,0 +1,218 @@ +{ + "version": "5", + "dialect": "sqlite", + "id": "263d6da2-a681-453b-8287-96ff7782fe46", + "prevId": "00000000-0000-0000-0000-000000000000", + "tables": { + "comments": { + "name": "comments", + "columns": { + "id": { + "name": "id", + "type": "integer", + "primaryKey": true, + "notNull": true, + "autoincrement": true + }, + "text": { + "name": "text", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "author_id": { + "name": "author_id", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "post_id": { + "name": "post_id", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {} + }, + "groups": { + "name": "groups", + "columns": { + "id": { + "name": "id", + "type": "integer", + "primaryKey": true, + "notNull": true, + "autoincrement": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {} + }, + "posts": { + "name": "posts", + "columns": { + "id": { + "name": "id", + "type": "integer", + "primaryKey": true, + "notNull": true, + "autoincrement": true + }, + "content": { + "name": "content", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "author_id": { + "name": "author_id", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {} + }, + "profile_info": { + "name": "profile_info", + "columns": { + "id": { + "name": "id", + "type": "integer", + "primaryKey": true, + "notNull": true, + "autoincrement": true + }, + "user_id": { + "name": "user_id", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "metadata": { + "name": "metadata", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": { + "profile_info_user_id_users_id_fk": { + "name": "profile_info_user_id_users_id_fk", + "tableFrom": "profile_info", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ] + } + }, + "compositePrimaryKeys": {} + }, + "users": { + "name": "users", + "columns": { + "id": { + "name": "id", + "type": "integer", + "primaryKey": true, + "notNull": true, + "autoincrement": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "invited_by": { + "name": "invited_by", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {} + }, + "users_to_groups": { + "name": "users_to_groups", + "columns": { + "user_id": { + "name": "user_id", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "group_id": { + "name": "group_id", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": { + "users_to_groups_user_id_users_id_fk": { + "name": "users_to_groups_user_id_users_id_fk", + "tableFrom": "users_to_groups", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ] + }, + "users_to_groups_group_id_groups_id_fk": { + "name": "users_to_groups_group_id_groups_id_fk", + "tableFrom": "users_to_groups", + "tableTo": "groups", + "columnsFrom": [ + "group_id" + ], + "columnsTo": [ + "id" + ] + } + }, + "compositePrimaryKeys": { + "users_to_groups_user_id_group_id_pk": { + "columns": [ + "user_id", + "group_id" + ] + } + } + } + }, + "enums": {}, + "_meta": { + "schemas": {}, + "tables": {}, + "columns": {} + } +} \ No newline at end of file diff --git a/packages/plugin-drizzle/tests/drizzle/meta/_journal.json b/packages/plugin-drizzle/tests/drizzle/meta/_journal.json new file mode 100644 index 000000000..cda47483b --- /dev/null +++ b/packages/plugin-drizzle/tests/drizzle/meta/_journal.json @@ -0,0 +1,13 @@ +{ + "version": "5", + "dialect": "sqlite", + "entries": [ + { + "idx": 0, + "version": "5", + "when": 1684673876025, + "tag": "0000_eager_pet_avengers", + "breakpoints": false + } + ] +} \ No newline at end of file diff --git a/packages/plugin-drizzle/tests/example/builder.ts b/packages/plugin-drizzle/tests/example/builder.ts new file mode 100644 index 000000000..4b58976af --- /dev/null +++ b/packages/plugin-drizzle/tests/example/builder.ts @@ -0,0 +1,17 @@ +import SchemaBuilder from '@pothos/core'; +import ScopeAuthPlugin from '@pothos/plugin-relay'; +import RelayPlugin from '@pothos/plugin-scope-auth'; +import DrizzlePlugin from '../../src'; +import { db, DrizzleSchema } from './db'; + +export default new SchemaBuilder<{ + DrizzleSchema: DrizzleSchema; +}>({ + plugins: [ScopeAuthPlugin, RelayPlugin, DrizzlePlugin], + drizzle: { + client: db, + }, + scopeAuth: { + authScopes: () => ({}), + }, +}); diff --git a/packages/plugin-drizzle/tests/example/db.ts b/packages/plugin-drizzle/tests/example/db.ts new file mode 100644 index 000000000..6e5d7abd5 --- /dev/null +++ b/packages/plugin-drizzle/tests/example/db.ts @@ -0,0 +1,9 @@ +import { resolve } from 'node:path'; +import Database from 'better-sqlite3'; +import { drizzle } from 'drizzle-orm/better-sqlite3'; +import * as schema from './db/schema'; + +// eslint-disable-next-line unicorn/prefer-module +const sqlite = new Database(resolve(__dirname, './db/dev.db')); +export const db = drizzle(sqlite, { schema }); +export type DrizzleSchema = typeof schema; diff --git a/packages/plugin-drizzle/tests/example/db/dev.db b/packages/plugin-drizzle/tests/example/db/dev.db new file mode 100644 index 000000000..d5e9cb29f Binary files /dev/null and b/packages/plugin-drizzle/tests/example/db/dev.db differ diff --git a/packages/plugin-drizzle/tests/example/db/schema.ts b/packages/plugin-drizzle/tests/example/db/schema.ts new file mode 100644 index 000000000..920e26647 --- /dev/null +++ b/packages/plugin-drizzle/tests/example/db/schema.ts @@ -0,0 +1,80 @@ +import { relations } from 'drizzle-orm'; +import { integer, primaryKey, sqliteTable, text } from 'drizzle-orm/sqlite-core'; + +export const users = sqliteTable('users', { + id: integer('id').primaryKey({ autoIncrement: true }), + name: text('name'), + invitedBy: integer('invited_by'), +}); + +export const usersRelations = relations(users, ({ one, many }) => ({ + invitee: one(users, { + fields: [users.invitedBy], + references: [users.id], + }), + posts: many(posts), +})); + +export const profileInfo = sqliteTable('profile_info', { + id: integer('id').primaryKey({ autoIncrement: true }), + userId: integer('user_id').references(() => users.id), + metadata: text('metadata'), +}); + +export const profileRelations = relations(profileInfo, ({ one, many }) => ({ + profileInfo: one(users, { + fields: [profileInfo.userId], + references: [users.id], + }), +})); + +export const posts = sqliteTable('posts', { + id: integer('id').primaryKey({ autoIncrement: true }), + content: text('content'), + authorId: integer('author_id'), +}); + +export const postsRelations = relations(posts, ({ one }) => ({ + author: one(users, { + fields: [posts.authorId], + references: [users.id], + }), +})); + +export const comments = sqliteTable('comments', { + id: integer('id').primaryKey({ autoIncrement: true }), + text: text('text'), + authorId: integer('author_id'), + postId: integer('post_id'), +}); + +export const commentsRelations = relations(comments, ({ one }) => ({ + post: one(posts, { + fields: [comments.postId], + references: [posts.id], + }), +})); + +export const groups = sqliteTable('groups', { + id: integer('id').primaryKey({ autoIncrement: true }), + name: text('name'), +}); + +export const usersToGroups = sqliteTable( + 'users_to_groups', + { + userId: integer('user_id') + .notNull() + .references(() => users.id), + groupId: integer('group_id') + .notNull() + .references(() => groups.id), + }, + (t) => ({ + pk: primaryKey(t.userId, t.groupId), + }), +); + +export const groupsRelations = relations(groups, ({ many }) => ({ + usersToGroups: many(usersToGroups), +})); diff --git a/packages/plugin-drizzle/tests/example/schema/index.ts b/packages/plugin-drizzle/tests/example/schema/index.ts new file mode 100644 index 000000000..c8eea8f68 --- /dev/null +++ b/packages/plugin-drizzle/tests/example/schema/index.ts @@ -0,0 +1,121 @@ +import { not, sql } from 'drizzle-orm'; +import builder from '../builder'; +import { db } from '../db'; + +builder.drizzleObject('users', { + name: 'User', + // Default selection when query users (optional, defaults to all columns) + select: { + columns: { + id: true, + }, + with: { + invitee: { + columns: { + name: true, + }, + }, + }, + }, + fields: (t) => ({ + email: t.string({ + resolve: (user) => `${user.lowercase}@example.com`, + // field level selects can be merged in (only queried when the field is requested) + // combines with selections from object level + select: { + // with: { + // posts: true, + // }, + columns: { + invitedBy: true, + }, + extras: { + lowercase: sql`lower(name)`.as('lowercase'), + }, + }, + }), + // column values can be exposed even if they are not in the default selection (will be selected automatically) + name: t.exposeString('name'), + posts: t.relation('posts', { + args: { + limit: t.arg.int(), + }, + // use args to modify how a relation is queried + query: (args) => ({ + limit: args.limit ?? 10, + where: (post, { eq }) => not(eq(post.id, 1)), + }), + // relation available to other plugins even when selections are at the field level + // authScopes: (user) => user.posts.length > 0, + // ^? + }), + invitee: t.relation('invitee'), + postsConnection: t.relatedConnection('posts', { + description: "A connection to a user's posts", + }), + }), +}); + +builder.queryField('user', (t) => + t.drizzleField({ + type: 'users', + args: { + id: t.arg.int({ required: true }), + }, + resolve: async (query, root, args, ctx, info) => { + console.dir(query, { depth: 10 }); + const result = await db.query.users.findFirst({ + ...query, + where: (user, { eq }) => eq(user.id, args.id), + }); + + console.dir(result, { depth: 10 }); + + return result; + }, + }), +); + +builder.drizzleObject('posts', { + name: 'Post', + fields: (t) => ({ + content: t.exposeString('content'), + author: t.relation('author'), + }), +}); + +builder.queryType({}); + +// export const query = /* graphql */ `{ +// query getUser($id: Int!) { +// user(id: $id) { +// name, +// email +// posts { +// content +// } +// invitee { +// name +// email +// posts(limit: 2) { +// author { +// name +// } +// } +// } +// } +// } +// }`; + +// void db.query.users.findFirst({ +// with: { +// posts: { +// columns: { +// id: true, +// content: true, +// }, +// }, +// }, +// }); + +export const schema = builder.toSchema(); diff --git a/packages/plugin-drizzle/tests/example/seed.ts b/packages/plugin-drizzle/tests/example/seed.ts new file mode 100644 index 000000000..f29ad1d15 --- /dev/null +++ b/packages/plugin-drizzle/tests/example/seed.ts @@ -0,0 +1,35 @@ +// import { readFileSync } from 'fs'; +// import { resolve } from 'path'; +// import { sql } from 'drizzle-orm'; +import { db } from './db'; +import { users } from './db/schema'; + +// const migration = readFileSync( +// // eslint-disable-next-line unicorn/prefer-module +// resolve(__dirname, '../drizzle/0000_eager_pet_avengers.sql'), +// ) +// .toString() +// .split(';') +// .map((str) => str.trim()) +// .filter(Boolean); + +async function seed() { + // for (const statement of migration) { + // // eslint-disable-next-line no-await-in-loop + // await db.run(sql([statement] as never)); + // } + + // const userRows = await db + // .insert(users) + // .values(Array.from({ length: 10 }).map((_, i) => ({ id: i, name: `user${i}` }))) + // .run(); + // console.log(userRows); + + db.query.users.findMany({ + with: {}, + }); + + console.log(await db.select().from(users).all()); +} + +seed().catch(console.error); diff --git a/packages/plugin-drizzle/tests/example/server.ts b/packages/plugin-drizzle/tests/example/server.ts new file mode 100644 index 000000000..80458e320 --- /dev/null +++ b/packages/plugin-drizzle/tests/example/server.ts @@ -0,0 +1,10 @@ +import { createTestServer } from '@pothos/test-utils'; +import { schema } from './schema'; + +const server = createTestServer({ + schema, +}); + +server.listen(3000, () => { + console.log('🚀 Server started at http://127.0.0.1:3000'); +}); diff --git a/packages/plugin-drizzle/tests/tsconfig.json b/packages/plugin-drizzle/tests/tsconfig.json new file mode 100644 index 000000000..8bb788a80 --- /dev/null +++ b/packages/plugin-drizzle/tests/tsconfig.json @@ -0,0 +1,11 @@ +{ + "include": [ + "**/*" + ], + "extends": "../../../tsconfig.options.json", + "references": [ + { + "path": "../" + } + ] +} diff --git a/packages/plugin-drizzle/tsconfig.json b/packages/plugin-drizzle/tsconfig.json new file mode 100644 index 000000000..b5a6d2490 --- /dev/null +++ b/packages/plugin-drizzle/tsconfig.json @@ -0,0 +1,14 @@ +{ + "compilerOptions": { + "noEmit": false, + "declaration": true, + "declarationMap": true, + "emitDeclarationOnly": true, + "outDir": "dts", + "rootDir": "src" + }, + "include": [ + "src/**/*" + ], + "extends": "../../tsconfig.options.json", +} \ No newline at end of file diff --git a/packages/plugin-drizzle/tsconfig.type.json b/packages/plugin-drizzle/tsconfig.type.json new file mode 100644 index 000000000..31e222cc4 --- /dev/null +++ b/packages/plugin-drizzle/tsconfig.type.json @@ -0,0 +1,10 @@ +{ + "compilerOptions": { + "skipLibCheck": false + }, + "extends": "../../tsconfig.options.json", + "include": [ + "src/**/*", + "tests/**/*" + ] +} diff --git a/packages/plugin-prisma/src/prisma-field-builder.ts b/packages/plugin-prisma/src/prisma-field-builder.ts index e927261ec..5bdfeb991 100644 --- a/packages/plugin-prisma/src/prisma-field-builder.ts +++ b/packages/plugin-prisma/src/prisma-field-builder.ts @@ -136,10 +136,6 @@ export class PrismaObjectFieldBuilder< > > > - | ObjectRef< - Types, - ShapeFromConnection> - > | PothosSchemaTypes.ConnectionObjectOptions< Types, ObjectRef< diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 43576ac39..870b7f982 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -93,7 +93,7 @@ importers: version: 3.2.0(graphql@16.8.0) '@pothos/core': specifier: ^3.33.0 - version: link:../../packages/core + version: 3.35.0(graphql@16.8.0) '@pothos/plugin-dataloader': specifier: workspace:* version: link:../../packages/plugin-dataloader @@ -392,7 +392,7 @@ importers: version: 8.0.2 '@pothos/core': specifier: ^3.33.0 - version: link:../../packages/core + version: 3.35.0(graphql@16.8.0) '@pothos/plugin-prisma': specifier: workspace:* version: link:../../packages/plugin-prisma @@ -693,6 +693,39 @@ importers: specifier: ^2.4.2 version: 2.4.2 + packages/plugin-drizzle: + devDependencies: + '@pothos/core': + specifier: workspace:* + version: link:../core + '@pothos/plugin-relay': + specifier: workspace:* + version: link:../plugin-relay + '@pothos/plugin-scope-auth': + specifier: workspace:* + version: link:../plugin-scope-auth + '@pothos/test-utils': + specifier: workspace:* + version: link:../test-utils + '@types/better-sqlite3': + specifier: ^7.6.4 + version: 7.6.4 + better-sqlite3: + specifier: ^8.4.0 + version: 8.6.0 + drizzle-kit: + specifier: ^0.19.13 + version: 0.19.13 + drizzle-orm: + specifier: ^0.28.5 + version: 0.28.5(@types/better-sqlite3@7.6.4)(better-sqlite3@8.6.0) + graphql: + specifier: 16.8.0 + version: 16.8.0 + graphql-tag: + specifier: ^2.12.6 + version: 2.12.6(graphql@16.8.0) + packages/plugin-errors: devDependencies: '@pothos/core': @@ -3096,6 +3129,10 @@ packages: dependencies: '@jridgewell/trace-mapping': 0.3.9 + /@drizzle-team/studio@0.0.5: + resolution: {integrity: sha512-ps5qF0tMxWRVu+V5gvCRrQNqlY92aTnIKdq27gm9LZMSdaKYZt6AVvSK1dlUMzs6Rt0Jm80b+eWct6xShBKhIw==} + dev: true + /@envelop/core@4.0.1: resolution: {integrity: sha512-uBLI7ql3hZopz7vMi9UDAb9HWzKw4STKiqg4QT+lb+tu5ZNaeuJ4fom2rrmgITz38B85QZOhZrGyVrlJXxfDzw==} engines: {node: '>=16.0.0'} @@ -5381,6 +5418,14 @@ packages: tslib: 2.6.2 dev: true + /@pothos/core@3.35.0(graphql@16.8.0): + resolution: {integrity: sha512-mSMmLkWsC76zHN93cTuwsA7fqLytcr9uNNjtiAEIDxAj46dgTb4bmfExgZIke1GQc8UKJ4GO2g0OM9TGuB3H/A==} + peerDependencies: + graphql: 16.8.0 || ^16.5.0 + dependencies: + graphql: 16.8.0 + dev: false + /@prisma/client@5.2.0(prisma@5.2.0): resolution: {integrity: sha512-AiTjJwR4J5Rh6Z/9ZKrBBLel3/5DzUNntMohOy7yObVnVoTNVFi2kvpLZlFuKO50d7yDspOtW6XBpiAd0BVXbQ==} engines: {node: '>=16.13'} @@ -6218,6 +6263,12 @@ packages: resolution: {integrity: sha512-EDKtLYNMKrig22jEvhXq8TBFyFgVNSPmDF2b9UzJ7+eylPqdZVo17PCUMkn1jP6/1A/0u78VqYC6VrX6b8pDWA==} dev: false + /@types/better-sqlite3@7.6.4: + resolution: {integrity: sha512-dzrRZCYPXIXfSR1/surNbJ/grU3scTaygS0OMzjlGf71i9sc2fGyHPXXiXmEvNIoE0cGwsanEFMVJxPXmco9Eg==} + dependencies: + '@types/node': 20.5.9 + dev: true + /@types/body-parser@1.19.2: resolution: {integrity: sha512-ALYone6pm6QmwZoAgeyNksccT9Q4AWZQ6PvfwR37GT6r6FWUPguq6sUmNGSMV2Wr761oQoBxwGGa6DR5o1DC9g==} dependencies: @@ -7520,6 +7571,14 @@ packages: is-windows: 1.0.2 dev: true + /better-sqlite3@8.6.0: + resolution: {integrity: sha512-jwAudeiTMTSyby+/SfbHDebShbmC2MCH8mU2+DXi0WJfv13ypEJm47cd3kljmy/H130CazEvkf2Li//ewcMJ1g==} + requiresBuild: true + dependencies: + bindings: 1.5.0 + prebuild-install: 7.1.1 + dev: true + /big-integer@1.6.51: resolution: {integrity: sha512-GPEid2Y9QU1Exl1rpO9B2IPJGHPSupF5GnVIP0blYvNOMer2bTvSWs1jGOUg04hTmu67nmLsQ9TBo1puaotBHg==} engines: {node: '>=0.6'} @@ -7562,6 +7621,12 @@ packages: resolution: {integrity: sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==} engines: {node: '>=8'} + /bindings@1.5.0: + resolution: {integrity: sha512-p2q/t/mhvuOj/UeLlV6566GD/guowlr0hHxClI0W9m7MWYkL1F0hLo+0Aexs9HSPCtR1SXQ0TD3MMKrXZajbiQ==} + dependencies: + file-uri-to-path: 1.0.0 + dev: true + /bl@1.2.3: resolution: {integrity: sha512-pvcNpa0UU69UT341rO6AYy4FVAIkUHuZXRIWbq+zHnsVcRzDDjIAhGuuYoi0d//cwIwtt4pkpKycWEfjdV+vww==} dependencies: @@ -7953,6 +8018,11 @@ packages: engines: {node: '>=10'} dev: true + /camelcase@7.0.1: + resolution: {integrity: sha512-xlx1yCK2Oc1APsPXDL2LdlNP6+uu8OCDdhOBSVT279M/S+y75O30C2VuD8T2ogdePBBl7PfPF4504tnLgX3zfw==} + engines: {node: '>=14.16'} + dev: true + /caniuse-lite@1.0.30001527: resolution: {integrity: sha512-YkJi7RwPgWtXVSgK4lG9AHH57nSzvvOp9MesgXmw4Q7n0C3H04L0foHqfxcmSAm5AcWb8dW9AYj2tR7/5GnddQ==} @@ -8008,6 +8078,11 @@ packages: ansi-styles: 4.3.0 supports-color: 7.2.0 + /chalk@5.3.0: + resolution: {integrity: sha512-dLitG79d+GV1Nb/VYcCDFivJeK1hiukt9QjRNVOsUtTy1rR1YJsmpGGTZ3qJos+uw7WmWF4wUwBd9jxjocFC2w==} + engines: {node: ^12.17.0 || ^14.13 || >=16.0.0} + dev: true + /change-case-all@1.0.14: resolution: {integrity: sha512-CWVm2uT7dmSHdO/z1CXT/n47mWonyypzBbuCy5tN7uMg22BsfkhwT6oHmFCAk+gL1LOOxhdbB9SZz3J1KTY3gA==} dependencies: @@ -8118,6 +8193,10 @@ packages: optionalDependencies: fsevents: 2.3.3 + /chownr@1.1.4: + resolution: {integrity: sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==} + dev: true + /chownr@2.0.0: resolution: {integrity: sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ==} engines: {node: '>=10'} @@ -8152,6 +8231,17 @@ packages: resolution: {integrity: sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A==} engines: {node: '>=6'} + /cli-color@2.0.3: + resolution: {integrity: sha512-OkoZnxyC4ERN3zLzZaY9Emb7f/MhBOIpePv0Ycok0fJYT+Ouo00UBEIwsVsr0yoow++n5YWlSUgST9GKhNHiRQ==} + engines: {node: '>=0.10'} + dependencies: + d: 1.0.1 + es5-ext: 0.10.62 + es6-iterator: 2.0.3 + memoizee: 0.4.15 + timers-ext: 0.1.7 + dev: true + /cli-cursor@3.1.0: resolution: {integrity: sha512-I/zHAwsKf9FqGoXM4WWRACob9+SNukZTd94DWF57E4toouRulbCxcUh6RKUEOQlYTHJnzkPMySvPNaaSLNfLZw==} engines: {node: '>=8'} @@ -8298,6 +8388,11 @@ packages: engines: {node: '>= 10'} dev: true + /commander@9.5.0: + resolution: {integrity: sha512-KRs7WVDKg86PWiuAqhDrAQnTXZKraVcCc6vFdL14qrZ/DcWwuRo7VoiYXalXO7S5GKpqYiVEwCbgFDfxNHKJBQ==} + engines: {node: ^12.20.0 || >=14} + dev: true + /common-tags@1.8.2: resolution: {integrity: sha512-gk/Z852D2Wtb//0I+kRFNKKE9dIIVirjoqPoA1wJU+XePVXZfGeBpk45+A1rKO4Q43prqWBNY/MiIeRLbPWUaA==} engines: {node: '>=4.0.0'} @@ -8633,6 +8728,13 @@ packages: resolution: {integrity: sha512-dArJ32hchFsrQ8uMiTBLq256MpnZjeuBtdHpaDlYuQyjU0CVzCJl/BVW+SkszaAeH95D/8gxqAhgx0ouAWAfRg==} dev: false + /d@1.0.1: + resolution: {integrity: sha512-m62ShEObQ39CfralilEQRjH6oAMtNCV1xJyEx5LpRYUVN+EviphDgUc/F3hnYbADmkiNs67Y+3ylmlG7Lnu+FA==} + dependencies: + es5-ext: 0.10.62 + type: 1.2.0 + dev: true + /damerau-levenshtein@1.0.8: resolution: {integrity: sha512-sdQSFB7+llfUcQHUQO3+B8ERRj0Oa4w9POWMI/puGtuf7gFywGmkaLCElnudfTiKZV+NvHqL0ifzdrI8Ro7ESA==} dev: true @@ -8837,6 +8939,11 @@ packages: which-collection: 1.0.1 which-typed-array: 1.1.11 + /deep-extend@0.6.0: + resolution: {integrity: sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==} + engines: {node: '>=4.0.0'} + dev: true + /deep-is@0.1.4: resolution: {integrity: sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==} @@ -8936,6 +9043,11 @@ packages: engines: {node: '>=8'} dev: true + /detect-libc@2.0.2: + resolution: {integrity: sha512-UX6sGumvvqSaXgdKGUsgZWqcUyIXZ/vZTrlRT/iobiKhGL0zL4d3osHj3uqllWJK+i+sixDS/3COVEOFbupFyw==} + engines: {node: '>=8'} + dev: true + /detect-newline@3.1.0: resolution: {integrity: sha512-TLz+x/vEXm/Y7P7wn1EJFNLxYpUD4TgMosxY6fAVJUnJMbupHBOncxyWUG9OpTaH9EBD7uFI5LfEgmMOc54DsA==} engines: {node: '>=8'} @@ -8966,6 +9078,12 @@ packages: randombytes: 2.1.0 dev: false + /difflib@0.2.4: + resolution: {integrity: sha512-9YVwmMb0wQHQNr5J9m6BSj6fk4pfGITGQOOs+D9Fl+INODWFOfvhIU1hNv6GgR1RBoC/9NJcwu77zShxV0kT7w==} + dependencies: + heap: 0.2.7 + dev: true + /dir-glob@3.0.1: resolution: {integrity: sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==} engines: {node: '>=8'} @@ -9025,6 +9143,99 @@ packages: pify: 4.0.1 dev: false + /dreamopt@0.8.0: + resolution: {integrity: sha512-vyJTp8+mC+G+5dfgsY+r3ckxlz+QMX40VjPQsZc5gxVAxLmi64TBoVkP54A/pRAXMXsbu2GMMBrZPxNv23waMg==} + engines: {node: '>=0.4.0'} + dependencies: + wordwrap: 1.0.0 + dev: true + + /drizzle-kit@0.19.13: + resolution: {integrity: sha512-Rba5VW1O2JfJlwVBeZ8Zwt2E2us5oZ08PQBDiVSGlug53TOc8hzXjblZFuF+dnll9/RQEHrkzBmJFgqTvn5Rxg==} + hasBin: true + dependencies: + '@drizzle-team/studio': 0.0.5 + '@esbuild-kit/esm-loader': 2.5.5 + camelcase: 7.0.1 + chalk: 5.3.0 + commander: 9.5.0 + esbuild: 0.18.20 + esbuild-register: 3.4.2(esbuild@0.18.20) + glob: 8.1.0 + hanji: 0.0.5 + json-diff: 0.9.0 + minimatch: 7.4.6 + zod: 3.22.2 + transitivePeerDependencies: + - supports-color + dev: true + + /drizzle-orm@0.28.5(@types/better-sqlite3@7.6.4)(better-sqlite3@8.6.0): + resolution: {integrity: sha512-6r6Iw4c38NAmW6TiKH3TUpGUQ1YdlEoLJOQptn8XPx3Z63+vFNKfAiANqrIiYZiMjKR9+NYAL219nFrmo1duXA==} + peerDependencies: + '@aws-sdk/client-rds-data': '>=3' + '@cloudflare/workers-types': '>=3' + '@libsql/client': '*' + '@neondatabase/serverless': '>=0.1' + '@opentelemetry/api': ^1.4.1 + '@planetscale/database': '>=1' + '@types/better-sqlite3': '*' + '@types/pg': '*' + '@types/sql.js': '*' + '@vercel/postgres': '*' + better-sqlite3: '>=7' + bun-types: '*' + knex: '*' + kysely: '*' + mysql2: '>=2' + pg: '>=8' + postgres: '>=3' + sql.js: '>=1' + sqlite3: '>=5' + peerDependenciesMeta: + '@aws-sdk/client-rds-data': + optional: true + '@cloudflare/workers-types': + optional: true + '@libsql/client': + optional: true + '@neondatabase/serverless': + optional: true + '@opentelemetry/api': + optional: true + '@planetscale/database': + optional: true + '@types/better-sqlite3': + optional: true + '@types/pg': + optional: true + '@types/sql.js': + optional: true + '@vercel/postgres': + optional: true + better-sqlite3: + optional: true + bun-types: + optional: true + knex: + optional: true + kysely: + optional: true + mysql2: + optional: true + pg: + optional: true + postgres: + optional: true + sql.js: + optional: true + sqlite3: + optional: true + dependencies: + '@types/better-sqlite3': 7.6.4 + better-sqlite3: 8.6.0 + dev: true + /dset@3.1.2: resolution: {integrity: sha512-g/M9sqy3oHe477Ar4voQxWtaPIFw1jTdKZuomOjhCcBx9nHUNn0pu6NopuFFrTh/TRZIKEj+76vLWFu9BNKk+Q==} engines: {node: '>=4'} @@ -9220,10 +9431,44 @@ packages: is-date-object: 1.0.5 is-symbol: 1.0.4 + /es5-ext@0.10.62: + resolution: {integrity: sha512-BHLqn0klhEpnOKSrzn/Xsz2UIW8j+cGmo9JLzr8BiUapV8hPL9+FliFqjwr9ngW7jWdnxv6eO+/LqyhJVqgrjA==} + engines: {node: '>=0.10'} + requiresBuild: true + dependencies: + es6-iterator: 2.0.3 + es6-symbol: 3.1.3 + next-tick: 1.1.0 + dev: true + + /es6-iterator@2.0.3: + resolution: {integrity: sha512-zw4SRzoUkd+cl+ZoE15A9o1oQd920Bb0iOJMQkQhl3jNc03YqVjAhG7scf9C5KWRU/R13Orf588uCC6525o02g==} + dependencies: + d: 1.0.1 + es5-ext: 0.10.62 + es6-symbol: 3.1.3 + dev: true + /es6-object-assign@1.1.0: resolution: {integrity: sha512-MEl9uirslVwqQU369iHNWZXsI8yaZYGg/D65aOgZkeyFJwHYSxilf7rQzXKI7DdDuBPrBXbfk3sl9hJhmd5AUw==} dev: false + /es6-symbol@3.1.3: + resolution: {integrity: sha512-NJ6Yn3FuDinBaBRWl/q5X/s4koRHBrgKAu+yGI6JCBeiu3qrcbJhwT2GeR/EXVfylRk8dpQVJoLEFhK+Mu31NA==} + dependencies: + d: 1.0.1 + ext: 1.7.0 + dev: true + + /es6-weak-map@2.0.3: + resolution: {integrity: sha512-p5um32HOTO1kP+w7PRnB+5lQ43Z6muuMuIMffvDN8ZB4GcnjLBV6zGStpbASIMk4DCAvEaamhe2zhyCb/QXXsA==} + dependencies: + d: 1.0.1 + es5-ext: 0.10.62 + es6-iterator: 2.0.3 + es6-symbol: 3.1.3 + dev: true + /esbuild-android-64@0.15.18: resolution: {integrity: sha512-wnpt3OXRhcjfIDSZu9bnzT4/TNTDsOUvip0foZOUBG7QbSt//w3QV4FInVJxNhKc/ErhUxc5z4QjHtMi7/TbgA==} engines: {node: '>=12'} @@ -9368,6 +9613,17 @@ packages: dev: false optional: true + /esbuild-register@3.4.2(esbuild@0.18.20): + resolution: {integrity: sha512-kG/XyTDyz6+YDuyfB9ZoSIOOmgyFCH+xPRtsCa8W85HLRV5Csp+o3jWVbOSHgSLfyLc5DmP+KFDNwty4mEjC+Q==} + peerDependencies: + esbuild: '>=0.12 <1' + dependencies: + debug: 4.3.4 + esbuild: 0.18.20 + transitivePeerDependencies: + - supports-color + dev: true + /esbuild-sunos-64@0.15.18: resolution: {integrity: sha512-On22LLFlBeLNj/YF3FT+cXcyKPEI263nflYlAhz5crxtp3yRG1Ugfr7ITyxmCmjm4vbN/dGrb/B7w7U8yJR9yw==} engines: {node: '>=12'} @@ -9992,6 +10248,13 @@ packages: resolution: {integrity: sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==} engines: {node: '>= 0.6'} + /event-emitter@0.3.5: + resolution: {integrity: sha512-D9rRn9y7kLPnJ+hMq7S/nhvoKwwvVJahBi2BPmx3bvbsEdK3W9ii8cBSGjP+72/LnM4n6fo3+dkCX5FeTQruXA==} + dependencies: + d: 1.0.1 + es5-ext: 0.10.62 + dev: true + /event-target-shim@5.0.1: resolution: {integrity: sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ==} engines: {node: '>=6'} @@ -10063,6 +10326,11 @@ packages: engines: {node: '>= 0.8.0'} dev: true + /expand-template@2.0.3: + resolution: {integrity: sha512-XYfuKMvj4O35f/pOXLObndIRvyQ+/+6AhODh+OKWj9S9498pHHn/IMszH+gt0fBCRWMNfk1ZSp5x3AifmnI2vg==} + engines: {node: '>=6'} + dev: true + /expect@29.6.4: resolution: {integrity: sha512-F2W2UyQ8XYyftHT57dtfg8Ue3X5qLgm2sSug0ivvLRH/VKNRL/pDxg/TH7zVzbQB0tu80clNFy6LU7OS/VSEKA==} engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} @@ -10125,6 +10393,12 @@ packages: ext-list: 2.2.2 sort-keys-length: 1.0.1 + /ext@1.7.0: + resolution: {integrity: sha512-6hxeJYaL110a9b5TEJSj0gojyHQAmA2ch5Os+ySCiA1QGdS697XWY1pzsrSjqA9LDEEgdB/KypIlR59RcLuHYw==} + dependencies: + type: 2.7.2 + dev: true + /extend-shallow@2.0.1: resolution: {integrity: sha512-zCnTtlxNoAiDc3gqY2aYAWFx7XWWiasuF2K8Me5WbN8otHKTUKBwjPtNpRs/rbUZm7KxWAaNj7P1a/p52GbVug==} engines: {node: '>=0.10.0'} @@ -10356,6 +10630,10 @@ packages: engines: {node: '>=4'} dev: false + /file-uri-to-path@1.0.0: + resolution: {integrity: sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw==} + dev: true + /filename-reserved-regex@2.0.0: resolution: {integrity: sha512-lc1bnsSr4L4Bdif8Xb/qrtokGbq5zlsms/CYH8PP+WtCkGNF65DPiQY8vG3SakEdRn8Dlnm+gW/qWKKjS5sZzQ==} engines: {node: '>=4'} @@ -10547,7 +10825,6 @@ packages: /fs-constants@1.0.0: resolution: {integrity: sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==} - dev: false /fs-extra@7.0.1: resolution: {integrity: sha512-YJDaCJZEnBmcbw13fvdAM9AwNOJwOzrE4pqMqBq5nFiEqXUqHwlK4B+3pUw6JNvfSPtX05xFHtYy/1ni01eGCw==} @@ -10697,6 +10974,10 @@ packages: engines: {node: '>=4'} dev: false + /github-from-package@0.0.0: + resolution: {integrity: sha512-SyHy3T1v2NUXn29OsWdxmK6RwHD+vkj3v8en8AOBZ1wBQ/hCAQ5bAQTD02kW4W9tUp/3Qh6J8r9EvntiyCmOOw==} + dev: true + /github-slugger@2.0.0: resolution: {integrity: sha512-IaOQ9puYtjrkq7Y0Ygl9KDZnrf/aiUJYUpVf89y8kyaxbRG7Y1SrX/jaumrv81vc61+kiMempujsM3Yw7w5qcw==} dev: false @@ -10748,6 +11029,17 @@ packages: once: 1.4.0 path-is-absolute: 1.0.1 + /glob@8.1.0: + resolution: {integrity: sha512-r8hpEjiQEYlF2QU0df3dS+nxxSIreXQS1qRhMJM0Q5NDdR386C7jb7Hwwod8Fgiuex+k0GFjgft18yvxm5XoCQ==} + engines: {node: '>=12'} + dependencies: + fs.realpath: 1.0.0 + inflight: 1.0.6 + inherits: 2.0.4 + minimatch: 5.1.6 + once: 1.4.0 + dev: true + /globals@11.12.0: resolution: {integrity: sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==} engines: {node: '>=4'} @@ -11008,6 +11300,13 @@ packages: strip-bom-string: 1.0.0 dev: false + /hanji@0.0.5: + resolution: {integrity: sha512-Abxw1Lq+TnYiL4BueXqMau222fPSPMFtya8HdpWsz/xVAhifXou71mPh/kY2+08RgFcVccjG3uZHs6K5HAe3zw==} + dependencies: + lodash.throttle: 4.1.1 + sisteransi: 1.0.5 + dev: true + /hard-rejection@2.1.0: resolution: {integrity: sha512-VIZB+ibDhx7ObhAe7OVtoEbuP4h/MuOTHJ+J8h/eBXotJYl0fBgR72xDFCKgIh22OJZIOVNxBMWuhAr10r8HdA==} engines: {node: '>=6'} @@ -11215,6 +11514,10 @@ packages: tslib: 2.6.2 dev: true + /heap@0.2.7: + resolution: {integrity: sha512-2bsegYkkHO+h/9MGbn6KWcE45cHZgPANo5LXF7EvWdT0yT2EguSVO1nDgU5c8+ZOPwp2vMNa7YFsJhVcDR9Sdg==} + dev: true + /highlight.js@11.8.0: resolution: {integrity: sha512-MedQhoqVdr0U6SSnWPzfiadUcDHfN/Wzq25AkXiQv9oiOO/sG0S7XkvpFIqWBl9Yq1UYyYOOVORs5UW2XlPyzg==} engines: {node: '>=12.0.0'} @@ -11460,7 +11763,6 @@ packages: /ini@1.3.8: resolution: {integrity: sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==} - dev: false /inline-style-parser@0.1.1: resolution: {integrity: sha512-7NXolsK4CAS5+xvdj5OMMbI962hU/wvwoxk+LWR9Ek9bVtyuuYScDN6eS0rUm6TxApFpw7CX1o4uJzcd4AyD3Q==} @@ -11751,6 +12053,10 @@ packages: engines: {node: '>=0.10.0'} dev: false + /is-promise@2.2.2: + resolution: {integrity: sha512-+lP4/6lKUBfQjZ2pdxThZvLUAafmZb8OAxFb8XXtiQmS35INgr85hdOGoEs124ez1FCnZJt6jau/T+alh58QFQ==} + dev: true + /is-reference@3.0.1: resolution: {integrity: sha512-baJJdQLiYaJdvFbJqXrcGv3WU3QCzBlUcI5QhbesIm6/xPsvmO+2CDoi/GMOFBQEQm+PXkwOPrp9KK5ozZsp2w==} dependencies: @@ -12448,6 +12754,15 @@ packages: /json-buffer@3.0.1: resolution: {integrity: sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==} + /json-diff@0.9.0: + resolution: {integrity: sha512-cVnggDrVkAAA3OvFfHpFEhOnmcsUpleEKq4d4O8sQWWSH40MBrWstKigVB1kGrgLWzuom+7rRdaCsnBD6VyObQ==} + hasBin: true + dependencies: + cli-color: 2.0.3 + difflib: 0.2.4 + dreamopt: 0.8.0 + dev: true + /json-parse-even-better-errors@2.3.1: resolution: {integrity: sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==} dev: true @@ -12705,6 +13020,10 @@ packages: resolution: {integrity: sha512-+WKqsK294HMSc2jEbNgpHpd0JfIBhp7rEV4aqXWqFr6AlXov+SlcgB1Fv01y2kGe3Gc8nMW7VA0SrGuSkRfIEg==} dev: true + /lodash.throttle@4.1.1: + resolution: {integrity: sha512-wIkUCfVKpVsWo3JSZlc+8MB5it+2AN5W8J7YVMST30UrvcQNZ1Okbj+rbVniijTWE6FGYy4XJq/rHkas8qJMLQ==} + dev: true + /lodash.uniq@4.5.0: resolution: {integrity: sha512-xfBaXQd9ryd9dlSDvnvI0lvxfLJlYAZzXomUYzLKtUeOQvOP5piqAWuGtrhWeqaXK9hhoM/iyJc5AV+XfsX3HQ==} dev: false @@ -12842,6 +13161,12 @@ packages: resolution: {integrity: sha512-jumlc0BIUrS3qJGgIkWZsyfAM7NCWiBcCDhnd+3NNM5KbBmLTgHVfWBcg6W+rLUsIpzpERPsvwUP7CckAQSOoA==} engines: {node: '>=12'} + /lru-queue@0.1.0: + resolution: {integrity: sha512-BpdYkt9EvGl8OfWHDQPISVpcl5xZthb+XPsbELj5AQXxIC8IriDZIQYjBJPEm5rS420sjZ0TLEzRcq5KdBhYrQ==} + dependencies: + es5-ext: 0.10.62 + dev: true + /lru_map@0.3.3: resolution: {integrity: sha512-Pn9cox5CsMYngeDbmChANltQl+5pi6XmTrraMSzhPmMBbmgcxmqWry0U3PGapCU1yB4/LqCcom7qhHZiF/jGfQ==} dev: true @@ -13092,6 +13417,19 @@ packages: resolution: {integrity: sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==} engines: {node: '>= 0.6'} + /memoizee@0.4.15: + resolution: {integrity: sha512-UBWmJpLZd5STPm7PMUlOw/TSy972M+z8gcyQ5veOnSDRREz/0bmpyTfKt3/51DhEBqCZQn1udM/5flcSPYhkdQ==} + dependencies: + d: 1.0.1 + es5-ext: 0.10.62 + es6-weak-map: 2.0.3 + event-emitter: 0.3.5 + is-promise: 2.2.2 + lru-queue: 0.1.0 + next-tick: 1.1.0 + timers-ext: 0.1.7 + dev: true + /meow@6.1.1: resolution: {integrity: sha512-3YffViIt2QWgTy6Pale5QpopX/IvU3LPL03jOTqp6pGj3VjesdO/U8CuHMKpnQr4shCNCM5fd5XFFvIIl6JBHg==} engines: {node: '>=8'} @@ -13492,12 +13830,18 @@ packages: brace-expansion: 1.1.11 dev: true + /minimatch@5.1.6: + resolution: {integrity: sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==} + engines: {node: '>=10'} + dependencies: + brace-expansion: 2.0.1 + dev: true + /minimatch@7.4.6: resolution: {integrity: sha512-sBz8G/YjVniEz6lKPNpKxXwazJe4c19fEfV2GDMX6AjFz+MX9uDWIZW8XreVhkFW3fkIdTv/gxWr/Kks5FFAVw==} engines: {node: '>=10'} dependencies: brace-expansion: 2.0.1 - dev: false /minimatch@9.0.3: resolution: {integrity: sha512-RHiac9mvaRw0x3AYRgDC1CxAP7HTcNrrECeA8YYJeWnpo+2Q5CegtZjaotWTWxDG3UeGA1coE05iH1mPjT/2mg==} @@ -13577,6 +13921,10 @@ packages: engines: {node: '>= 8.0.0'} dev: true + /mkdirp-classic@0.5.3: + resolution: {integrity: sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A==} + dev: true + /mkdirp@1.0.4: resolution: {integrity: sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==} engines: {node: '>=10'} @@ -13640,6 +13988,10 @@ packages: engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} hasBin: true + /napi-build-utils@1.0.2: + resolution: {integrity: sha512-ONmRUqK7zj7DWX0D9ADe03wbwOBZxNAfF20PlGfCWQcD3+/MakShIHrMqx9YwPTfxDdF1zLeL+RGZiR9kGMLdg==} + dev: true + /native-url@0.3.4: resolution: {integrity: sha512-6iM8R99ze45ivyH8vybJ7X0yekIcPf5GgLV5K0ENCbmRcaRIDoj37BC8iLEmaaBfqqb8enuZ5p0uhY+lVAbAcA==} dependencies: @@ -13686,6 +14038,10 @@ packages: - utf-8-validate dev: true + /next-tick@1.1.0: + resolution: {integrity: sha512-CXdUiJembsNjuToQvxayPZF9Vqht7hewsvy2sOWafLvi2awflj9mOC6bHIg50orX8IJvWKY9wYQ/zB2kogPslQ==} + dev: true + /next-transpile-modules@8.0.0: resolution: {integrity: sha512-Q2f2yB0zMJ8KJbIYAeZoIxG6cSfVk813zr6B5HzsLMBVcJ3FaF8lKr7WG66n0KlHCwjLSmf/6EkgI6QQVWHrDw==} dependencies: @@ -13828,6 +14184,13 @@ packages: tslib: 2.6.2 dev: true + /node-abi@3.47.0: + resolution: {integrity: sha512-2s6B2CWZM//kPgwnuI0KrYwNjfdByE25zvAaEpq9IH4zcNsarH8Ihu/UuX6XMPEogDAxkuUFeZn60pXNHAqn3A==} + engines: {node: '>=10'} + dependencies: + semver: 7.5.4 + dev: true + /node-abort-controller@3.1.1: resolution: {integrity: sha512-AGK2yQKIjRuqnc6VkX2Xj5d+QW8xZ87pa1UK6yA6ouUyuxfHuMP6umE5QK7UmTeOAymo+Zx1Fxiuw9rVx8taHQ==} @@ -14620,6 +14983,25 @@ packages: picocolors: 1.0.0 source-map-js: 1.0.2 + /prebuild-install@7.1.1: + resolution: {integrity: sha512-jAXscXWMcCK8GgCoHOfIr0ODh5ai8mj63L2nWrjuAgXE6tDyYGnx4/8o/rCgU+B4JSyZBKbeZqzhtwtC3ovxjw==} + engines: {node: '>=10'} + hasBin: true + dependencies: + detect-libc: 2.0.2 + expand-template: 2.0.3 + github-from-package: 0.0.0 + minimist: 1.2.8 + mkdirp-classic: 0.5.3 + napi-build-utils: 1.0.2 + node-abi: 3.47.0 + pump: 3.0.0 + rc: 1.2.8 + simple-get: 4.0.1 + tar-fs: 2.1.1 + tunnel-agent: 0.6.0 + dev: true + /preferred-pm@3.1.2: resolution: {integrity: sha512-nk7dKrcW8hfCZ4H6klWcdRknBOXWzNQByJ0oJyX97BOupsYD+FzLS4hflgEu/uPUEHZCuRfMxzCBsuWd7OzT8Q==} engines: {node: '>=10'} @@ -14908,6 +15290,16 @@ packages: iconv-lite: 0.4.24 unpipe: 1.0.0 + /rc@1.2.8: + resolution: {integrity: sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==} + hasBin: true + dependencies: + deep-extend: 0.6.0 + ini: 1.3.8 + minimist: 1.2.8 + strip-json-comments: 2.0.1 + dev: true + /react-dom@17.0.2(react@17.0.2): resolution: {integrity: sha512-s4h96KtLDUQlsENhMn1ar8t2bEa+q/YAtj8pPPdIjPDGBDIVNsrD9aXNWqspUe6AzKCIG0C1HZZLqLV7qpOBGA==} peerDependencies: @@ -15634,6 +16026,18 @@ packages: resolution: {integrity: sha512-6+eerH9fEnNmi/hyM1DXcRK3pWdoMQtlkQ+ns0ntzunjKqp5i3sKCc80ym8Fib3iaYhdJUOPdhlJWj1tvge2Ww==} dev: true + /simple-concat@1.0.1: + resolution: {integrity: sha512-cSFtAPtRhljv69IK0hTVZQ+OfE9nePi/rtJmw5UjHeVyVroEqJXP1sFztKUy1qU+xvz3u/sfYJLa947b7nAN2Q==} + dev: true + + /simple-get@4.0.1: + resolution: {integrity: sha512-brv7p5WgH0jmQJr1ZDDfKDOSeWWg+OVypG99A/5vYGPqJ6pxiaHLy8nxtFjBA7oMa01ebA9gfh1uMCFqOuXxvA==} + dependencies: + decompress-response: 6.0.0 + once: 1.4.0 + simple-concat: 1.0.1 + dev: true + /sisteransi@1.0.5: resolution: {integrity: sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==} dev: true @@ -16082,6 +16486,11 @@ packages: min-indent: 1.0.1 dev: true + /strip-json-comments@2.0.1: + resolution: {integrity: sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ==} + engines: {node: '>=0.10.0'} + dev: true + /strip-json-comments@3.1.1: resolution: {integrity: sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==} engines: {node: '>=8'} @@ -16282,6 +16691,15 @@ packages: engines: {node: '>=6'} dev: false + /tar-fs@2.1.1: + resolution: {integrity: sha512-V0r2Y9scmbDRLCNex/+hYzvp/zyYjvFbHPNgVTKfQvVrb6guiE/fxP+XblDNR011utopbkex2nM4dHNV6GDsng==} + dependencies: + chownr: 1.1.4 + mkdirp-classic: 0.5.3 + pump: 3.0.0 + tar-stream: 2.2.0 + dev: true + /tar-stream@1.6.2: resolution: {integrity: sha512-rzS0heiNf8Xn7/mpdSVVSMAWAoy9bfb1WOTYC78Z0UQKeKa/CWS8FOq0lKGNa8DWKAn9gxjCvMLYc5PGXYlK2A==} engines: {node: '>= 0.8.0'} @@ -16295,6 +16713,17 @@ packages: xtend: 4.0.2 dev: false + /tar-stream@2.2.0: + resolution: {integrity: sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ==} + engines: {node: '>=6'} + dependencies: + bl: 4.1.0 + end-of-stream: 1.4.4 + fs-constants: 1.0.0 + inherits: 2.0.4 + readable-stream: 3.6.2 + dev: true + /tar@6.2.0: resolution: {integrity: sha512-/Wo7DcT0u5HUV486xg675HtjNd3BXZ6xDbzsCUZPt5iw8bTQ63bP0Raut3mvro9u+CUyq7YQd8Cx55fsZXxqLQ==} engines: {node: '>=10'} @@ -16372,6 +16801,13 @@ packages: setimmediate: 1.0.5 dev: false + /timers-ext@0.1.7: + resolution: {integrity: sha512-b85NUNzTSdodShTIbky6ZF02e8STtVVfD+fu4aXXShEELpozH+bCpJLYMPZbsABN2wDH7fJpqIoXxJpzbf0NqQ==} + dependencies: + es5-ext: 0.10.62 + next-tick: 1.1.0 + dev: true + /tiny-lru@11.0.1: resolution: {integrity: sha512-iNgFugVuQgBKrqeO/mpiTTgmBsTP0WL6yeuLfLs/Ctf0pI/ixGqIRm8sDCwMcXGe9WWvt2sGXI5mNqZbValmJg==} engines: {node: '>=12'} @@ -16732,6 +17168,12 @@ packages: yargs: 17.7.2 dev: true + /tunnel-agent@0.6.0: + resolution: {integrity: sha512-McnNiV1l8RYeY8tBgEpuodCC1mLUdbSN+CYBL7kJsJNInOP8UjDDEwdk6Mw60vdLLrr5NHKZhMAOSrR2NZuQ+w==} + dependencies: + safe-buffer: 5.2.1 + dev: true + /turbo-darwin-64@1.10.13: resolution: {integrity: sha512-vmngGfa2dlYvX7UFVncsNDMuT4X2KPyPJ2Jj+xvf5nvQnZR/3IeDEGleGVuMi/hRzdinoxwXqgk9flEmAYp0Xw==} cpu: [x64] @@ -16849,6 +17291,14 @@ packages: media-typer: 0.3.0 mime-types: 2.1.35 + /type@1.2.0: + resolution: {integrity: sha512-+5nt5AAniqsCnu2cEQQdpzCAh33kVx8n0VoFidKpB1dVVLAN/F+bgVOqOJqOnEnrhp222clB5p3vUlD+1QAnfg==} + dev: true + + /type@2.7.2: + resolution: {integrity: sha512-dzlvlNlt6AXU7EBSfpAscydQ7gXB+pPGsPnfJnZpiNJBDj7IaJzQlBZYGdEi4R9HmPdBv2XmWJ6YUtoTa7lmCw==} + dev: true + /typed-array-buffer@1.0.0: resolution: {integrity: sha512-Y8KTSIglk9OZEr8zywiIHG/kmQ7KWyjseXs1CbSo8vC42w7hg2HgYTxSWwP0+is7bWDc1H+Fo026CpHFwm8tkw==} engines: {node: '>= 0.4'} @@ -17925,6 +18375,10 @@ packages: resolution: {integrity: sha512-CjpbqNtBGNAeyNS/9W6q3kSkKE52+FjIj7AkFlLr11s/VWGUu6a2CdYSdGxocIhIVjaW/zchesBQUKPVU69Cqg==} dev: false + /wordwrap@1.0.0: + resolution: {integrity: sha512-gvVzJFlPycKc5dZN4yPkP8w7Dc37BtP1yczEneOb4uq34pXZcvrtRTmWV8W+Ume+XCxKgbjM+nevkyFPMybd4Q==} + dev: true + /wrap-ansi@6.2.0: resolution: {integrity: sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==} engines: {node: '>=8'} diff --git a/tsconfig.eslint.json b/tsconfig.eslint.json index 19fbab8d9..de42f64c7 100644 --- a/tsconfig.eslint.json +++ b/tsconfig.eslint.json @@ -17,6 +17,7 @@ "packages/*/src/**/*.js", "packages/*/test/**/*.js", "packages/*/tests/**/*.js", + "packages/plugin-drizzle/drizzle.config.ts", "examples/**/*.ts", "examples/**/*.js", "examples/**/*.tsx",