From 6fc7af9e63b00209079b2ea781dc2338f5989f8c Mon Sep 17 00:00:00 2001 From: Michael Hayes Date: Mon, 22 May 2023 00:47:49 -0700 Subject: [PATCH] initial drizzle prototype --- packages/plugin-drizzle/.npmignore | 7 + packages/plugin-drizzle/LICENSE | 6 + packages/plugin-drizzle/README.md | 20 + packages/plugin-drizzle/drizzle.config.ts | 10 + packages/plugin-drizzle/esm/.gitignore | 4 + packages/plugin-drizzle/esm/.npmignore | 0 packages/plugin-drizzle/esm/package.json | 3 + packages/plugin-drizzle/jest.config.js | 6 + packages/plugin-drizzle/package.json | 62 ++ .../src/drizzle-field-builder.ts | 485 ++++++++++++++++ packages/plugin-drizzle/src/field-builder.ts | 35 ++ packages/plugin-drizzle/src/global-types.ts | 352 +++++++++++ packages/plugin-drizzle/src/index.ts | 117 ++++ packages/plugin-drizzle/src/interface-ref.ts | 24 + packages/plugin-drizzle/src/model-loader.ts | 100 ++++ packages/plugin-drizzle/src/object-ref.ts | 51 ++ packages/plugin-drizzle/src/schema-builder.ts | 64 ++ packages/plugin-drizzle/src/types.ts | 435 ++++++++++++++ .../plugin-drizzle/src/utils/deep-equal.ts | 58 ++ .../plugin-drizzle/src/utils/loader-map.ts | 51 ++ .../plugin-drizzle/src/utils/map-query.ts | 546 ++++++++++++++++++ packages/plugin-drizzle/src/utils/refs.ts | 25 + .../plugin-drizzle/src/utils/selections.ts | 195 +++++++ .../tests/drizzle/0000_eager_pet_avengers.sql | 38 ++ .../tests/drizzle/meta/0000_snapshot.json | 218 +++++++ .../tests/drizzle/meta/_journal.json | 13 + .../plugin-drizzle/tests/example/builder.ts | 17 + packages/plugin-drizzle/tests/example/db.ts | 10 + .../plugin-drizzle/tests/example/db/dev.db | Bin 0 -> 245760 bytes .../plugin-drizzle/tests/example/db/schema.ts | 80 +++ .../tests/example/schema/index.ts | 127 ++++ packages/plugin-drizzle/tests/example/seed.ts | 97 ++++ .../plugin-drizzle/tests/example/server.ts | 10 + packages/plugin-drizzle/tests/tsconfig.json | 11 + packages/plugin-drizzle/tsconfig.json | 15 + packages/plugin-drizzle/tsconfig.type.json | 10 + .../plugin-prisma/src/prisma-field-builder.ts | 4 - packages/plugin-prisma/src/types.ts | 7 +- pnpm-lock.yaml | 509 +++++++++++++++- tsconfig.eslint.json | 1 + 40 files changed, 3788 insertions(+), 35 deletions(-) create mode 100644 packages/plugin-drizzle/.npmignore create mode 100644 packages/plugin-drizzle/LICENSE create mode 100644 packages/plugin-drizzle/README.md create mode 100644 packages/plugin-drizzle/drizzle.config.ts create mode 100755 packages/plugin-drizzle/esm/.gitignore create mode 100755 packages/plugin-drizzle/esm/.npmignore create mode 100755 packages/plugin-drizzle/esm/package.json create mode 100644 packages/plugin-drizzle/jest.config.js create mode 100644 packages/plugin-drizzle/package.json create mode 100644 packages/plugin-drizzle/src/drizzle-field-builder.ts create mode 100644 packages/plugin-drizzle/src/field-builder.ts create mode 100644 packages/plugin-drizzle/src/global-types.ts create mode 100644 packages/plugin-drizzle/src/index.ts create mode 100644 packages/plugin-drizzle/src/interface-ref.ts create mode 100644 packages/plugin-drizzle/src/model-loader.ts create mode 100644 packages/plugin-drizzle/src/object-ref.ts create mode 100644 packages/plugin-drizzle/src/schema-builder.ts create mode 100644 packages/plugin-drizzle/src/types.ts create mode 100644 packages/plugin-drizzle/src/utils/deep-equal.ts create mode 100644 packages/plugin-drizzle/src/utils/loader-map.ts create mode 100644 packages/plugin-drizzle/src/utils/map-query.ts create mode 100644 packages/plugin-drizzle/src/utils/refs.ts create mode 100644 packages/plugin-drizzle/src/utils/selections.ts create mode 100644 packages/plugin-drizzle/tests/drizzle/0000_eager_pet_avengers.sql create mode 100644 packages/plugin-drizzle/tests/drizzle/meta/0000_snapshot.json create mode 100644 packages/plugin-drizzle/tests/drizzle/meta/_journal.json create mode 100644 packages/plugin-drizzle/tests/example/builder.ts create mode 100644 packages/plugin-drizzle/tests/example/db.ts create mode 100644 packages/plugin-drizzle/tests/example/db/dev.db create mode 100644 packages/plugin-drizzle/tests/example/db/schema.ts create mode 100644 packages/plugin-drizzle/tests/example/schema/index.ts create mode 100644 packages/plugin-drizzle/tests/example/seed.ts create mode 100644 packages/plugin-drizzle/tests/example/server.ts create mode 100644 packages/plugin-drizzle/tests/tsconfig.json create mode 100644 packages/plugin-drizzle/tsconfig.json create mode 100644 packages/plugin-drizzle/tsconfig.type.json 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..b35a766d9 --- /dev/null +++ b/packages/plugin-drizzle/README.md @@ -0,0 +1,20 @@ +# Drizzle Plugin + +## TODO + +- [ ] connections +- [ ] connection helpers +- [ ] query usage checks +- [ ] normalize extras callback functions before merging +- [ ] with input integration +- [ ] errors integration +- [x] drizzle field methods on builder +- [x] drizzle refs +- [x] drizzle interface types +- [ ] tests +- [ ] docs +- [ ] variants +- [ ] fallback dataloader +- [ ] custom dataloader override +- [ ] default selections for id/data-loading +- [ ] interface check for compatibility of implementors diff --git a/packages/plugin-drizzle/drizzle.config.ts b/packages/plugin-drizzle/drizzle.config.ts new file mode 100644 index 000000000..70ae69255 --- /dev/null +++ b/packages/plugin-drizzle/drizzle.config.ts @@ -0,0 +1,10 @@ +import { Config } from 'drizzle-kit'; + +export default { + schema: './tests/example/db/schema.ts', + out: './tests/drizzle', + dbCredentials: { + url: './tests/example/db/dev.db', + }, + driver: 'better-sqlite', +} satisfies Config; 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..6974ad141 --- /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-relay": "workspace:*", + "@pothos/plugin-scope-auth": "workspace:*", + "@pothos/test-utils": "workspace:*", + "@types/better-sqlite3": "^7.6.5", + "better-sqlite3": "^8.7.0", + "drizzle-kit": "^0.19.13", + "drizzle-orm": "^0.28.6", + "graphql": "16.8.1", + "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..55e8e91ac --- /dev/null +++ b/packages/plugin-drizzle/src/drizzle-field-builder.ts @@ -0,0 +1,485 @@ +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, + graphqlKind: PothosSchemaTypes.PothosKindToGraphQLType[FieldKind] = 'Object', + ) { + super(builder, 'DrizzleObject', graphqlKind); + + 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) => {}) => { + const relQuery = { + with: { + [name]: { + ...nestedQuery(query), + ...((typeof query === 'function' + ? (query as (args: {}, context: {}) => {})(args, context) + : query) as {}), + }, + }, + }; + + return relQuery; + }; + + 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 && { + columns: { [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..c7d4db059 --- /dev/null +++ b/packages/plugin-drizzle/src/global-types.ts @@ -0,0 +1,352 @@ +/* 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, + FieldMap, + FieldNullability, + FieldRef, + InputFieldMap, + InterfaceParam, + NormalizeArgs, + OutputType, + PluginName, + SchemaTypes, + ShapeFromTypeParam, + TypeParam, +} from '@pothos/core'; +import { DrizzleObjectFieldBuilder } from './drizzle-field-builder'; +import { DrizzleInterfaceRef, DrizzleRef } from './interface-ref'; +import { DrizzleObjectRef, drizzleTableKey } from './object-ref'; +import type { + DrizzleConnectionFieldOptions, + DrizzleConnectionShape, + DrizzleFieldOptions, + DrizzleInterfaceOptions, + DrizzleObjectFieldOptions, + DrizzleObjectOptions, + DrizzlePluginOptions, + drizzleTableName, + ShapeFromConnection, +} from './types'; + +import type { 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, + ) => DrizzleObjectRef; + + drizzleInterface: < + 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: DrizzleInterfaceOptions, + ) => DrizzleInterfaceRef; + + drizzleObjectField: < + Type extends DrizzleObjectRef | keyof Types['DrizzleRelationSchema'], + TableConfig extends TableRelationalConfig = Type extends DrizzleObjectRef + ? Type[typeof drizzleTableKey] + : Types['DrizzleRelationSchema'][Type & keyof Types['DrizzleRelationSchema']], + Shape extends {} = Type extends DrizzleObjectRef< + Types, + keyof Types['DrizzleRelationSchema'], + infer S + > + ? S & { [drizzleTableName]?: TableConfig['tsName'] } + : BuildQueryResult & { + [drizzleTableName]?: Type; + }, + >( + type: Type, + fieldName: string, + field: (t: DrizzleObjectFieldBuilder) => FieldRef, + ) => void; + + drizzleInterfaceField: < + Type extends DrizzleInterfaceRef | keyof Types['DrizzleRelationSchema'], + TableConfig extends TableRelationalConfig = Type extends DrizzleObjectRef + ? Type[typeof drizzleTableKey] + : Types['DrizzleRelationSchema'][Type & keyof Types['DrizzleRelationSchema']], + Shape extends {} = Type extends DrizzleInterfaceRef< + Types, + keyof Types['DrizzleRelationSchema'], + infer S + > + ? S & { [drizzleTableName]?: TableConfig['tsName'] } + : BuildQueryResult & { + [drizzleTableName]?: Type; + }, + >( + type: Type, + fieldName: string, + field: (t: DrizzleObjectFieldBuilder) => FieldRef, + ) => void; + + drizzleObjectFields: < + Type extends DrizzleObjectRef | keyof Types['DrizzleRelationSchema'], + TableConfig extends TableRelationalConfig = Type extends DrizzleObjectRef + ? Type[typeof drizzleTableKey] + : Types['DrizzleRelationSchema'][Type & keyof Types['DrizzleRelationSchema']], + Shape extends {} = Type extends DrizzleObjectRef< + Types, + keyof Types['DrizzleRelationSchema'], + infer S + > + ? S & { [drizzleTableName]?: TableConfig['tsName'] } + : BuildQueryResult & { + [drizzleTableName]?: Type; + }, + >( + type: Type, + fields: (t: DrizzleObjectFieldBuilder) => FieldMap, + ) => void; + + drizzleInterfaceFields: < + Type extends DrizzleInterfaceRef | keyof Types['DrizzleRelationSchema'], + TableConfig extends TableRelationalConfig = Type extends DrizzleObjectRef + ? Type[typeof drizzleTableKey] + : Types['DrizzleRelationSchema'][Type & keyof Types['DrizzleRelationSchema']], + Shape extends {} = Type extends DrizzleInterfaceRef< + Types, + keyof Types['DrizzleRelationSchema'], + infer S + > + ? S & { [drizzleTableName]?: TableConfig['tsName'] } + : BuildQueryResult & { + [drizzleTableName]?: Type; + }, + >( + type: Type, + fields: (t: DrizzleObjectFieldBuilder) => FieldMap, + ) => void; + } + + 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>; + + drizzleConnection: 'relay' extends PluginName + ? < + Type extends + | DrizzleRef + | keyof Types['DrizzleRelationSchema'], + Nullable extends boolean, + ResolveReturnShape, + Args extends InputFieldMap = {}, + Shape = Type extends DrizzleRef + ? S + : BuildQueryResult< + Types['DrizzleRelationSchema'], + Types['DrizzleRelationSchema'][Type & keyof Types['DrizzleRelationSchema']], + true + >, + ConnectionInterfaces extends InterfaceParam[] = [], + EdgeInterfaces extends InterfaceParam[] = [], + >( + options: DrizzleConnectionFieldOptions< + Types, + ParentShape, + Type, + Types['DrizzleRelationSchema'][Type & keyof Types['DrizzleRelationSchema']], + ObjectRef, + Nullable, + Args, + ResolveReturnShape, + Kind + >, + ...args: NormalizeArgs< + [ + connectionOptions: + | ConnectionObjectOptions< + Types, + ObjectRef, + false, + false, + DrizzleConnectionShape, + ConnectionInterfaces + > + | ObjectRef< + Types, + ShapeFromConnection> + >, + edgeOptions: + | ConnectionEdgeObjectOptions< + Types, + ObjectRef, + false, + DrizzleConnectionShape, + EdgeInterfaces + > + | ObjectRef< + Types, + { + cursor: string; + node?: Shape | null | undefined; + } + >, + ], + 0 + > + ) => FieldRef>> + : '@pothos/plugin-relay is required to use this method'; + } + + 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..06c6045fd --- /dev/null +++ b/packages/plugin-drizzle/src/index.ts @@ -0,0 +1,117 @@ +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'; +import { TableConfig } from 'drizzle-orm'; + +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); + } + + const primaryKeys = ( + parentConfig.extensions!.pothosDrizzleTable as { primaryKey?: { name: string }[] } + ).primaryKey; + + if (!primaryKeys || primaryKeys.length === 0) { + throw new Error( + `Field ${fieldConfig.name} not resolved in initial query and no primary key found for type ${parentConfig.name}`, + ); + } + + const columnName = primaryKeys[0].name; + const modelName = parentConfig.extensions.pothosDrizzleModel as string; + + this.builder.options.drizzle.client.query[modelName] + .findFirst({ + // ...queryFromI, + where: (user, { eq }) => eq(user[columnName], parent[columnName]), + with: { + posts: { + limit: 1, + }, + }, + }) + .then(console.log); + + 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/interface-ref.ts b/packages/plugin-drizzle/src/interface-ref.ts new file mode 100644 index 000000000..ab54b73cb --- /dev/null +++ b/packages/plugin-drizzle/src/interface-ref.ts @@ -0,0 +1,24 @@ +import { InterfaceRef, SchemaTypes } from '@pothos/core'; +import { DrizzleObjectRef, drizzleTableKey } from './object-ref'; + +export type DrizzleRef< + Types extends SchemaTypes, + Table extends keyof Types['DrizzleRelationSchema'] = keyof Types['DrizzleRelationSchema'], + T = {}, +> = DrizzleInterfaceRef | DrizzleObjectRef; + +export class DrizzleInterfaceRef< + Types extends SchemaTypes, + Table extends keyof Types['DrizzleRelationSchema'] = keyof Types['DrizzleRelationSchema'], + T = {}, +> extends InterfaceRef { + [drizzleTableKey]!: Table; + + tableName: string; + + constructor(name: string, tableName: string) { + super(name); + + this.tableName = tableName; + } +} diff --git a/packages/plugin-drizzle/src/model-loader.ts b/packages/plugin-drizzle/src/model-loader.ts new file mode 100644 index 000000000..dba8fbc64 --- /dev/null +++ b/packages/plugin-drizzle/src/model-loader.ts @@ -0,0 +1,100 @@ +import { GraphQLResolveInfo } from 'graphql'; +import { createContextCache, SchemaTypes } from '@pothos/core'; +import { cacheKey, setLoaderMappings } from './utils/loader-map'; +import { selectionStateFromInfo } from './utils/map-query'; +import { + mergeSelection, + selectionCompatible, + SelectionMap, + SelectionState, + selectionToQuery, +} from './utils/selections'; + +interface ResolvablePromise { + promise: Promise; + resolve: (value: T) => void; + reject: (err: unknown) => void; +} +export class ModelLoader { + context: object; + + builder: PothosSchemaTypes.SchemaBuilder; + + modelName: string; + + queryCache = new Map(); + + staged = new Set<{ + state: SelectionState; + models: Map | null>>; + }>(); + + constructor(context: object, builder: PothosSchemaTypes.SchemaBuilder, modelName: string) { + this.context = context; + this.builder = builder; + this.modelName = modelName; + } + + static forModel( + modelName: string, + builder: PothosSchemaTypes.SchemaBuilder, + ) { + return createContextCache((model) => new ModelLoader(model, builder as never, modelName)); + } + + getSelection(info: GraphQLResolveInfo) { + const key = cacheKey(info.parentType.name, info.path); + if (!this.queryCache.has(key)) { + const selection = selectionStateFromInfo(this.context, info); + this.queryCache.set(key, { + selection, + query: selectionToQuery(selection), + }); + } + + return this.queryCache.get(key)!; + } + + async loadSelection(info: GraphQLResolveInfo, model: object) { + const { selection, query } = this.getSelection(info); + + const result = await this.stageQuery(selection, query, model); + + if (result) { + const mappings = selection.mappings[info.path.key]; + + if (mappings) { + setLoaderMappings(this.context, info, mappings.mappings); + } + } + + return result; + } + + async stageQuery(selection: SelectionState, query: SelectionMap, model: object) { + for (const entry of this.staged) { + if (selectionCompatible(entry.state, query)) { + mergeSelection(this.builder.options.drizzle.client._.schema!, entry.state, query); + + if (!entry.models.has(model)) { + entry.models.set(model, createResolvablePromise | null>()); + } + + return entry.models.get(model)!.promise; + } + } + + return this.initLoad(selection, model); + } +} + +function createResolvablePromise(): ResolvablePromise { + let resolveFn!: (value: T) => void; + let rejectFn!: (reason?: unknown) => void; + const promise = new Promise((resolve, reject) => { + resolveFn = resolve; + rejectFn = reject; + }); + + return { promise, resolve: resolveFn, reject: rejectFn }; +} diff --git a/packages/plugin-drizzle/src/object-ref.ts b/packages/plugin-drizzle/src/object-ref.ts new file mode 100644 index 000000000..9e349bbdd --- /dev/null +++ b/packages/plugin-drizzle/src/object-ref.ts @@ -0,0 +1,51 @@ +import { + abstractReturnShapeKey, + brandWithType, + ObjectRef, + SchemaTypes, + typeBrandKey, +} from '@pothos/core'; +import type { WithBrand } from './types'; + +export const drizzleTableKey = Symbol.for('Pothos.drizzleTableKey'); + +export class DrizzleObjectRef< + Types extends SchemaTypes, + Table extends keyof Types['DrizzleRelationSchema'] = keyof Types['DrizzleRelationSchema'], + T = {}, +> extends ObjectRef { + [drizzleTableKey]!: Types['DrizzleRelationSchema'][Table]; + + [abstractReturnShapeKey]!: WithBrand; + + tableName: string; + + constructor(name: string, tableName: string) { + super(name); + + this.tableName = tableName; + } + + addBrand( + value: V, + ): V extends T[] ? { [K in keyof V]: WithBrand } : WithBrand { + if (Array.isArray(value)) { + value.forEach((val) => void brandWithType(val, this.name as never)); + + return value as never; + } + + brandWithType(value, this.name as never); + + return value as never; + } + + hasBrand(value: unknown) { + return ( + typeof value === 'object' && + value !== null && + typeBrandKey in value && + (value as { [typeBrandKey]?: unknown })[typeBrandKey] === this.name + ); + } +} diff --git a/packages/plugin-drizzle/src/schema-builder.ts b/packages/plugin-drizzle/src/schema-builder.ts new file mode 100644 index 000000000..d3b084941 --- /dev/null +++ b/packages/plugin-drizzle/src/schema-builder.ts @@ -0,0 +1,64 @@ +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; +}; + +schemaBuilderProto.drizzleObjectField = function drizzleObjectField(type, fieldName, field) { + const ref = typeof type === 'string' ? getRefFromModel(type, this) : (type as never); + this.configStore.onTypeConfig(ref, ({ name }) => { + this.configStore.addFields(ref, () => ({ + [fieldName]: field(new DrizzleObjectFieldBuilder(name, this, ref.tableName)), + })); + }); +}; + +schemaBuilderProto.drizzleInterfaceField = function drizzleInterfaceField(type, fieldName, field) { + const ref = typeof type === 'string' ? getRefFromModel(type, this) : (type as never); + this.configStore.onTypeConfig(ref, ({ name }) => { + this.configStore.addFields(ref, () => ({ + [fieldName]: field(new DrizzleObjectFieldBuilder(name, this, ref.tableName, 'Interface')), + })); + }); +}; + +schemaBuilderProto.drizzleObjectFields = function drizzleObjectFields(type, fields) { + const ref = typeof type === 'string' ? getRefFromModel(type, this) : (type as never); + this.configStore.onTypeConfig(ref, ({ name }) => { + this.configStore.addFields(ref, () => + fields(new DrizzleObjectFieldBuilder(name, this, ref.tableName)), + ); + }); +}; + +schemaBuilderProto.drizzleInterfaceFields = function drizzleInterfaceFields(type, fields) { + const ref = typeof type === 'string' ? getRefFromModel(type, this) : (type as never); + this.configStore.onTypeConfig(ref, ({ name }) => { + this.configStore.addFields(ref, () => + fields(new DrizzleObjectFieldBuilder(name, this, ref.tableName, 'Interface')), + ); + }); +}; diff --git a/packages/plugin-drizzle/src/types.ts b/packages/plugin-drizzle/src/types.ts new file mode 100644 index 000000000..8631ccd49 --- /dev/null +++ b/packages/plugin-drizzle/src/types.ts @@ -0,0 +1,435 @@ +import { + BuildQueryResult, + DBQueryConfig, + Many, + Relation, + TableRelationalConfig, + TablesRelationalConfig, +} from 'drizzle-orm'; +import { FieldNode, GraphQLResolveInfo } from 'graphql'; +import { + FieldKind, + FieldMap, + FieldNullability, + FieldOptionsFromKind, + InputFieldMap, + InputFieldsFromShape, + InputShapeFromFields, + InterfaceParam, + InterfaceRef, + InterfaceTypeOptions, + ListResolveValue, + MaybePromise, + Normalize, + ObjectRef, + ObjectTypeOptions, + OutputType, + SchemaTypes, + ShapeFromTypeParam, + typeBrandKey, + TypeParam, +} from '@pothos/core'; +import type { DrizzleObjectFieldBuilder } from './drizzle-field-builder'; +import { type DrizzleRef } from './interface-ref'; +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 DrizzleInterfaceOptions< + Types extends SchemaTypes, + Table, + Shape, + Selection, + Interfaces extends InterfaceParam[], +> = Omit< + InterfaceTypeOptions, 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 DrizzleConnectionFieldOptions< + Types extends SchemaTypes, + ParentShape, + Type extends + | DrizzleRef + | keyof Types['DrizzleRelationSchema'], + TableConfig extends TableRelationalConfig, + Param extends OutputType, + Nullable extends boolean, + Args extends InputFieldMap, + ResolveReturnShape, + Kind extends FieldKind, + // eslint-disable-next-line @typescript-eslint/sort-type-constituents +> = Omit< + FieldOptionsFromKind< + Types, + ParentShape, + Param, + Nullable, + InputFieldsFromShape & + (InputFieldMap extends Args ? {} : Args), + Kind, + ParentShape, + ResolveReturnShape + >, + 'args' | 'resolve' | 'type' +> & + Omit< + PothosSchemaTypes.ConnectionFieldOptions< + Types, + ParentShape, + Param, + Nullable, + false, + false, + Args, + ResolveReturnShape + >, + 'resolve' | 'type' + > & + (InputShapeFromFields & + PothosSchemaTypes.DefaultConnectionArguments extends infer ConnectionArgs + ? { + type: Type; + cursor: Extract['name']; + defaultSize?: number | ((args: ConnectionArgs, ctx: Types['Context']) => number); + maxSize?: number | ((args: ConnectionArgs, ctx: Types['Context']) => number); + resolve: ( + query: { + // include?: Model['Include']; + // cursor?: Model['WhereUnique']; + // take: number; + // skip: number; + }, + parent: ParentShape, + args: ConnectionArgs, + context: Types['Context'], + info: GraphQLResolveInfo, + ) => ShapeFromTypeParam extends infer Shape + ? [Shape] extends [[readonly (infer Item)[] | null | undefined]] + ? ListResolveValue + : MaybePromise + : never; + totalCount?: ( + parent: ParentShape, + args: ConnectionArgs, + context: Types['Context'], + info: GraphQLResolveInfo, + ) => MaybePromise; + } + : never); + +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?: DrizzleRef; + cursor: Extract< + Types['DrizzleRelationSchema'][TableConfig['relations'][Field]['referencedTable']['_']['name']]['columns'], + { isUnique: true } + >['name']; + 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; + +export type WithBrand = T & { [typeBrandKey]: string }; 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..ac72c2c39 --- /dev/null +++ b/packages/plugin-drizzle/src/utils/refs.ts @@ -0,0 +1,25 @@ +import { SchemaTypes } from '@pothos/core'; +import { DrizzleRef } from '../interface-ref'; +import { DrizzleObjectRef } from '../object-ref'; + +export const refMap = new WeakMap>>(); + +export function getRefFromModel( + name: string, + builder: PothosSchemaTypes.SchemaBuilder, + type: 'interface' | 'object' = 'object', +): DrizzleRef { + if (!refMap.has(builder)) { + refMap.set(builder, new Map()); + } + const cache = refMap.get(builder)!; + + if (!cache.has(name)) { + cache.set( + name, + type === 'object' ? new DrizzleObjectRef(name, name) : new DrizzleObjectRef(name, 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..61e5735d6 --- /dev/null +++ b/packages/plugin-drizzle/src/utils/selections.ts @@ -0,0 +1,195 @@ +/* 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 = { + ...state.query, + 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..d4b036667 --- /dev/null +++ b/packages/plugin-drizzle/tests/example/db.ts @@ -0,0 +1,10 @@ +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 0000000000000000000000000000000000000000..1915f501f5d035410212e27dc65e52aae0897ee0 GIT binary patch literal 245760 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 literal 0 HcmV?d00001 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..ac079e091 --- /dev/null +++ b/packages/plugin-drizzle/tests/example/schema/index.ts @@ -0,0 +1,127 @@ +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", + // resolve: (user) => user.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', + select: { + columns: { + id: true, + }, + }, + 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..4e3a974d8 --- /dev/null +++ b/packages/plugin-drizzle/tests/example/seed.ts @@ -0,0 +1,97 @@ +import { faker } from '@faker-js/faker'; +import { db } from './db'; +import { comments, groups, posts, profileInfo, users, usersToGroups } from './db/schema'; + +faker.seed(123); + +async function seed() { + await db.delete(groups).run(); + await db.delete(usersToGroups).run(); + await db.delete(comments).run(); + await db.delete(posts).run(); + await db.delete(profileInfo).run(); + await db.delete(users).run(); + + await db + .insert(users) + .values( + Array.from({ length: 10 }).map((_, i) => ({ + name: faker.person.fullName(), + invitedBy: i > 1 ? faker.number.int({ min: 1, max: i + 1 }) : null, + })), + ) + .run(); + + const userRows = await db.select().from(users).all(); + + await Promise.all( + userRows.map((user) => + db + .insert(profileInfo) + .values( + Array.from({ length: 10 }).map(() => ({ + metadata: faker.lorem.paragraph(), + userId: user.id, + })), + ) + .run(), + ), + ); + + await Promise.all( + userRows.map((user) => + db + .insert(posts) + .values( + Array.from({ length: 10 }).map(() => ({ + content: faker.lorem.paragraph(), + authorId: user.id, + })), + ) + .run(), + ), + ); + + const postRows = await db.select().from(posts).all(); + + await Promise.all( + postRows.map((post) => + db + .insert(comments) + .values( + Array.from({ length: 10 }).map(() => ({ + text: faker.lorem.paragraph(), + authorId: faker.number.int({ min: 1, max: 10 }), + postId: post.id, + })), + ) + .run(), + ), + ); + + await db + .insert(groups) + .values(Array.from({ length: 3 }).map(() => ({ name: faker.lorem.word() }))) + .run(); + + const groupRows = await db.select().from(groups).all(); + + await Promise.all( + groupRows.map((group) => + db + .insert(usersToGroups) + .values( + [ + ...new Set(Array.from({ length: 5 }).map(() => faker.number.int({ min: 1, max: 10 }))), + ].map((userId) => ({ + groupId: group.id, + userId, + })), + ) + .run(), + ), + ); +} + +// eslint-disable-next-line unicorn/prefer-top-level-await +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..3c1520d5d --- /dev/null +++ b/packages/plugin-drizzle/tsconfig.json @@ -0,0 +1,15 @@ +{ + "compilerOptions": { + "noEmit": false, + "declaration": true, + "declarationMap": true, + "emitDeclarationOnly": true, + "skipLibCheck": true, + "outDir": "dts", + "rootDir": "src" + }, + "include": [ + "src/**/*" + ], + "extends": "../../tsconfig.options.json" +} 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/packages/plugin-prisma/src/types.ts b/packages/plugin-prisma/src/types.ts index d9172c749..81e95bbd5 100644 --- a/packages/plugin-prisma/src/types.ts +++ b/packages/plugin-prisma/src/types.ts @@ -22,7 +22,7 @@ import { typeBrandKey, TypeParam, } from '@pothos/core'; -import { PrismaInterfaceRef, PrismaRef } from './interface-ref'; +import { PrismaRef } from './interface-ref'; import type { PrismaObjectFieldBuilder } from './prisma-field-builder'; export interface PrismaDelegate { @@ -623,10 +623,7 @@ export type PrismaFieldResolver< export type PrismaConnectionFieldOptions< Types extends SchemaTypes, ParentShape, - Type extends - | PrismaInterfaceRef - | PrismaRef - | keyof Types['PrismaTypes'], + Type extends PrismaRef | keyof Types['PrismaTypes'], Model extends PrismaModelTypes, Param extends OutputType, Nullable extends boolean, diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 43576ac39..317caf674 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.38.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.38.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.5 + version: 7.6.5 + better-sqlite3: + specifier: ^8.7.0 + version: 8.7.0 + drizzle-kit: + specifier: ^0.19.13 + version: 0.19.13 + drizzle-orm: + specifier: ^0.28.6 + version: 0.28.6(@types/better-sqlite3@7.6.5)(better-sqlite3@8.7.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'} @@ -3412,7 +3449,7 @@ packages: /@fast-csv/format@4.3.5: resolution: {integrity: sha512-8iRn6QF3I8Ak78lNAa+Gdl5MJJBM5vRHivFtMRUWINdevNo00K7OXxS2PshawLKTejVwieIlPmK5YlLu6w4u8A==} dependencies: - '@types/node': 14.18.58 + '@types/node': 14.18.63 lodash.escaperegexp: 4.1.2 lodash.isboolean: 3.0.3 lodash.isequal: 4.5.0 @@ -3423,7 +3460,7 @@ packages: /@fast-csv/parse@4.3.6: resolution: {integrity: sha512-uRsLYksqpbDmWaSmzvJcuApSEe38+6NQZBUsuAyMZKqHxH0g1wcJgsKUvN3WC8tewaqFjBMMGrkHmC+T7k8LvA==} dependencies: - '@types/node': 14.18.58 + '@types/node': 14.18.63 lodash.escaperegexp: 4.1.2 lodash.groupby: 4.6.0 lodash.isfunction: 3.0.9 @@ -4309,7 +4346,7 @@ packages: engines: {node: ^8.13.0 || >=10.10.0} dependencies: '@grpc/proto-loader': 0.7.9 - '@types/node': 20.5.9 + '@types/node': 20.8.2 dev: true /@grpc/proto-loader@0.7.9: @@ -4779,8 +4816,8 @@ packages: newrelic: 11.0.0 dev: true - /@newrelic/native-metrics@10.0.0: - resolution: {integrity: sha512-0AFPPnj/wFT32mWK6vjc6LY0dZUOcHludZqPkHj2e3m+Zto3pWGk8PmRr1m3OGi+yVdHlEWbgHEAJ3T9lSAMBA==} + /@newrelic/native-metrics@10.0.1: + resolution: {integrity: sha512-XJlKF3mCiFS/tZj6C79gdRYj+vQQtFSxbL83MMOVK/N025UHk8Oo8lF1ir7GOWk+Ll2xH4WI/t7i9SqDouXX+g==} engines: {node: '>=16', npm: '>=6'} requiresBuild: true dependencies: @@ -5381,6 +5418,14 @@ packages: tslib: 2.6.2 dev: true + /@pothos/core@3.38.0(graphql@16.8.0): + resolution: {integrity: sha512-2jlnvkrCmbrHxK269745TXxl185LwJtC5oMz4nbFP40LmVV9zbDV3WKqbG7D+3rg9hvxBe0RmmwWrOjNcGpICA==} + 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.5: + resolution: {integrity: sha512-H3ZUx89KiPhYa9nalUXVVStSUFHuzYxt4yoazufpTTYW9rVUCzhh02V8CH2C8nE4libnK0UgFq5DFIe0DOhqow==} + dependencies: + '@types/node': 20.8.2 + dev: true + /@types/body-parser@1.19.2: resolution: {integrity: sha512-ALYone6pm6QmwZoAgeyNksccT9Q4AWZQ6PvfwR37GT6r6FWUPguq6sUmNGSMV2Wr761oQoBxwGGa6DR5o1DC9g==} dependencies: @@ -6229,7 +6280,7 @@ packages: dependencies: '@types/http-cache-semantics': 4.0.1 '@types/keyv': 3.1.4 - '@types/node': 20.5.9 + '@types/node': 20.8.2 '@types/responselike': 1.0.0 /@types/chai-subset@1.3.3: @@ -6245,7 +6296,7 @@ packages: /@types/cls-hooked@4.3.6: resolution: {integrity: sha512-Ys46tagI3aFwFizHYwG2v0oS+mMfp1nubY2ETU/RY/D0jLOXpqqVEItjhOmKMI8SklF3MI4Y7oSp9UFkBk4CXQ==} dependencies: - '@types/node': 20.5.9 + '@types/node': 20.8.2 dev: true /@types/columnify@1.5.2: @@ -6255,7 +6306,7 @@ packages: /@types/concat-stream@1.6.1: resolution: {integrity: sha512-eHE4cQPoj6ngxBZMvVf6Hw7Mh4jMW4U9lpGmS5GBPB9RYxlFg+CHaVN7ErNY4W9XfLIEn20b4VDYaIrbq0q4uA==} dependencies: - '@types/node': 20.5.9 + '@types/node': 8.10.66 dev: true /@types/connect@3.4.36: @@ -6272,7 +6323,7 @@ packages: /@types/cross-spawn@6.0.2: resolution: {integrity: sha512-KuwNhp3eza+Rhu8IFI5HUXRP0LIhqH5cAjubUvGXXthh4YYBuP2ntwEX+Cz8GJoZUHlKo247wPWOfA9LYEq4cw==} dependencies: - '@types/node': 20.5.9 + '@types/node': 20.8.2 dev: false /@types/debug@4.1.8: @@ -6294,7 +6345,7 @@ packages: /@types/express-serve-static-core@4.17.36: resolution: {integrity: sha512-zbivROJ0ZqLAtMzgzIUC4oNqDG9iF0lSsAqpOD9kbs5xcIM3dTiyuHvBc7R8MtWBp3AAWGaovJa+wzWPjLYW7Q==} dependencies: - '@types/node': 20.5.9 + '@types/node': 20.8.2 '@types/qs': 6.9.8 '@types/range-parser': 1.2.4 '@types/send': 0.17.1 @@ -6310,7 +6361,7 @@ packages: /@types/form-data@0.0.33: resolution: {integrity: sha512-8BSvG1kGm83cyJITQMZSulnl6QV8jqAGreJsc5tPu1Jq0vTSOiY/k24Wx82JRpWwZSqrala6sd5rWi6aNXvqcw==} dependencies: - '@types/node': 20.5.9 + '@types/node': 8.10.66 dev: true /@types/graceful-fs@4.1.6: @@ -6378,7 +6429,7 @@ packages: /@types/keyv@3.1.4: resolution: {integrity: sha512-BQ5aZNSCpj7D6K2ksrRCTmKRLEpnPvWDiLPfoGyhZ++8YtiK9d/3DBKPJgry359X/P1PfruyYwvnvwFjuEiEIg==} dependencies: - '@types/node': 20.5.9 + '@types/node': 20.8.2 /@types/lodash@4.14.198: resolution: {integrity: sha512-trNJ/vtMZYMLhfN45uLq4ShQSw0/S7xCTLLVM+WM1rmFpba/VS42jVUgaO3w/NOLiWR/09lnYk0yMaA/atdIsg==} @@ -6427,7 +6478,7 @@ packages: /@types/node-fetch@2.6.4: resolution: {integrity: sha512-1ZX9fcN4Rvkvgv4E6PAY5WXUFWFcRWxZa3EW83UjycOB9ljJCedb2CupIP4RZMEwF/M3eTcCihbBRgwtGbg5Rg==} dependencies: - '@types/node': 20.5.9 + '@types/node': 20.8.2 form-data: 3.0.1 /@types/node@10.17.60: @@ -6438,8 +6489,8 @@ packages: resolution: {integrity: sha512-J8xLz7q2OFulZ2cyGTLE1TbbZcjpno7FaN6zdJNrgAdrJ+DZzh/uFR6YrTb4C+nXakvud8Q4+rbhoIWlYQbUFQ==} dev: true - /@types/node@14.18.58: - resolution: {integrity: sha512-Y8ETZc8afYf6lQ/mVp096phIVsgD/GmDxtm3YaPcc+71jmi/J6zdwbwaUU4JvS56mq6aSfbpkcKhQ5WugrWFPw==} + /@types/node@14.18.63: + resolution: {integrity: sha512-fAtCfv4jJg+ExtXhvCkCqUKZ+4ok/JQk01qDKhL5BDDoS3AxKXhV5/MAVUZyQnSEd2GT92fkgZl0pz0Q0AzcIQ==} dev: false /@types/node@16.18.48: @@ -6449,6 +6500,9 @@ packages: /@types/node@20.5.9: resolution: {integrity: sha512-PcGNd//40kHAS3sTlzKB9C9XL4K0sTup8nbG5lC14kzEteTNuAFh9u5nA0o5TWnSG2r/JNPRXFVcHJIIeRlmqQ==} + /@types/node@20.8.2: + resolution: {integrity: sha512-Vvycsc9FQdwhxE3y3DzeIxuEJbWGDsnrxvMADzTDF/lcdR9/K+AQIeAghTQsHtotg/q0j3WEOYS/jQgSdWue3w==} + /@types/node@8.10.66: resolution: {integrity: sha512-tktOkFUA4kXx2hhhrB8bIFb5TbwzS4uOhKEmwiD+NoiL0qtP2OQ9mFldbgD4dV1djrlBYP6eBuQZiWjuHUpqFw==} dev: true @@ -6486,7 +6540,7 @@ packages: /@types/responselike@1.0.0: resolution: {integrity: sha512-85Y2BjiufFzaMIlvJDvTTB8Fxl2xfLo4HgmHzVBz08w4wDePCTjYw66PdrolO0kzli3yam/YCgRufyo1DdQVTA==} dependencies: - '@types/node': 20.5.9 + '@types/node': 20.8.2 /@types/scheduler@0.16.3: resolution: {integrity: sha512-5cJ8CB4yAx7BH1oMvdU0Jh9lrEXyPkar6F9G/ERswkCuvP4KQZfZkSjcMbAICCpQTN4OuZn8tz0HiKv9TGZgrQ==} @@ -6499,14 +6553,14 @@ packages: resolution: {integrity: sha512-Cwo8LE/0rnvX7kIIa3QHCkcuF21c05Ayb0ZfxPiv0W8VRiZiNW/WuRupHKpqqGVGf7SUA44QSOUKaEd9lIrd/Q==} dependencies: '@types/mime': 1.3.2 - '@types/node': 20.5.9 + '@types/node': 20.8.2 /@types/serve-static@1.15.2: resolution: {integrity: sha512-J2LqtvFYCzaj8pVYKw8klQXrLLk7TBZmQ4ShlcdkELFKGwGMfevMLneMMRkMgZxotOD9wg497LpC7O8PcvAmfw==} dependencies: '@types/http-errors': 2.0.1 '@types/mime': 3.0.1 - '@types/node': 20.5.9 + '@types/node': 20.8.2 /@types/shimmer@1.0.2: resolution: {integrity: sha512-dKkr1bTxbEsFlh2ARpKzcaAmsYixqt9UyCdoEZk8rHyE4iQYcDCyvSjDSf7JUWJHlJiTtbIoQjxKh6ViywqDAg==} @@ -7520,6 +7574,14 @@ packages: is-windows: 1.0.2 dev: true + /better-sqlite3@8.7.0: + resolution: {integrity: sha512-99jZU4le+f3G6aIl6PmmV0cxUIWqKieHxsiF7G34CVFiE+/UabpYqkU0NJIkY/96mQKikHeBjtR27vFfs5JpEw==} + 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 +7624,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 +8021,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 +8081,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 +8196,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 +8234,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 +8391,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 +8731,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 +8942,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 +9046,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 +9081,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 +9146,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.6(@types/better-sqlite3@7.6.5)(better-sqlite3@8.7.0): + resolution: {integrity: sha512-yBe+F9htrlYER7uXgDJUQsTHFoIrI5yMm5A0bg0GiZ/kY5jNXTWoEy4KQtg35cE27sw1VbgzoMWHAgCckUUUww==} + 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.5 + better-sqlite3: 8.7.0 + dev: true + /dset@3.1.2: resolution: {integrity: sha512-g/M9sqy3oHe477Ar4voQxWtaPIFw1jTdKZuomOjhCcBx9nHUNn0pu6NopuFFrTh/TRZIKEj+76vLWFu9BNKk+Q==} engines: {node: '>=4'} @@ -9220,10 +9434,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 +9616,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 +10251,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 +10329,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 +10396,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 +10633,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 +10828,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 +10977,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 +11032,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 +11303,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 +11517,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 +11766,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 +12056,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: @@ -12354,7 +12663,7 @@ packages: resolution: {integrity: sha512-mk0umAQ5lT+CaOJ+Qp01N6kz48sJG2kr2n1rX0koqKf6FIygQV0qLOdN9SCYID4IVeSigDOcPeGLozdMLYfb5g==} engines: {node: ^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0} dependencies: - '@types/node': 20.5.9 + '@types/node': 20.8.2 merge-stream: 2.0.0 supports-color: 8.1.1 dev: false @@ -12448,6 +12757,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 +13023,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 +13164,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 +13420,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 +13833,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 +13924,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 +13991,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: @@ -13676,7 +14031,7 @@ packages: winston-transport: 4.5.0 optionalDependencies: '@contrast/fn-inspect': 3.4.0 - '@newrelic/native-metrics': 10.0.0 + '@newrelic/native-metrics': 10.0.1 '@prisma/prisma-fmt-wasm': 4.17.0-16.27eb2449f178cd9fe1a4b892d732cc4795f75085 transitivePeerDependencies: - aws-crt @@ -13686,6 +14041,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 +14187,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 +14986,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'} @@ -14748,7 +15133,7 @@ packages: '@protobufjs/path': 1.1.2 '@protobufjs/pool': 1.1.0 '@protobufjs/utf8': 1.1.0 - '@types/node': 20.5.9 + '@types/node': 20.8.2 long: 5.2.3 dev: true @@ -14908,6 +15293,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 +16029,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 +16489,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 +16694,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 +16716,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 +16804,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 +17171,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 +17294,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 +18378,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",