Giter VIP home page Giter VIP logo

pothos-plugin-effect's Introduction

pothos-plugin-effect

npm version codecov biome

Seamless integration between the Pothos GraphQL and Effect.

Getting Started

You can use Effect.Effect<A, E, R> within resolver functions using t.effect. and that's it.

import EffectPlugin from "pothos-plugin-effect";

const builder = new SchemaBuilder({
  plugins: [EffectPlugin],
});

builder.queryFields((t) => ({
  roll: t.int({
    resolve: () => t.effect(Effect.succeed(6)),
  }),
}));

Have you been used since 0.x? See MIGRATION.md.

Installtation

Install pothos-effect-plugin and effect. Effect is a peer-dependency.

yarn add pothos-plugin-effect effect

Requirements

  • @pothos/core^3
  • effect>=3.0.0

Key Features

  • 🪄 Ridiculously simple API, the only thing added is t.effect.
  • 🌿 Can be used by any Pothos field resolver.
  • 📦 Supports Option data type.
  • ⚙️ Custom Runtime can be configured.
  • ⏳ Promise objects can be used as result values.
  • ✅ Well-written test cases

Guide

Using with Option data types

t.effect returns the given Option.Option<T> data by converting its value to (T | null). This is useful for creating fields that are nullable, while actively using the Option data type in Effect.

t.effect(Effect.succeed(1));
// ^? Promise<number>

t.effect(Effect.succeedSome(1));
// ^? Promise<number | null>

t.effect(
  Effect.succeedSome([
    //
    Option.some(1),
    Option.none(),
    Option.some(3),
  ])
);
// ^? Promise<(number | null)[] | null>

Configure Custom Runtime

You can configure a custom runtime when you configure SchemaBuilder.

declare const effectRuntime: Runtime.Runtime<UserService>;

const builder = new SchemaBuilder<{
  EffectRuntime: typeof effectRuntime;
}>({
  plugins: [EffectPlugin],
  effectOptions: { effectRuntime },
});

builder.queryFields((t) => ({
  user: t.field({
    type: User,
    resolve: () =>
      t.effect(
        pipe(
          UserService,
          Effect.flatMap((userService) => userService.getUser(1))
        )
      ),
  }),
}));

Promises can be used as Effect result

By default, you can't use promises directly in Effect; you need to convert them to Effect via Effect.tryPromise. However, promises are available by default within GraphQL resolvers.

If t.effect returns a Promise from an Effect, it will return that Promise as the result, making it available for use within the resolver. It will also correctly infer the type of the Promise.

This is useful when using an ORM like Prisma or Drizzle with it.

t.effect(Effect.succeed(Promise.resolve({ id: 1, name: "John" })));
// ^? Promise<{ id: number; name: string; }>

Well-written test cases

When I rewrote this library, I put a lot of effort into the test cases. You can find test cases for the core functionality of the library, as well as simple E2E test cases.

Acknowledges

  • Pothos by @hayes (GitHub/Docs) - A nice GraphQL Schema builder. I heavily relied on the README for this project and The documentation of the plugin implementation is excellent.
  • Effect (GitHub/Docs) - This is a pretty amazing growth trend. A library that has the charm to bring you back. (including me, of course)

Contributors

Made with contrib.rocks.

Licenses

MIT

pothos-plugin-effect's People

Contributors

enalmada avatar github-actions[bot] avatar hayes-mysten avatar iamchanii avatar vecerek avatar xiniha avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar

pothos-plugin-effect's Issues

Bug: type of the error failure is not preserved

When the failure/left type is an instance of an Error class, the original type of the error is not preserved. The plugin wraps the error cause in a new Error class.

Steps to reproduce

import SchemaBuilder from '@pothos/core';
import { Effect } from 'effect';
import { execute, parse } from 'graphql';
import EffectPlugin from 'pothos-plugin-effect';

interface SchemaTypes {
  Context: {
    diceResult: number;
  };
}

const builder = new SchemaBuilder<SchemaTypes>({
  plugins: [EffectPlugin],
  relayOptions: {},
});

class CustomError extends Error {
  readonly _tag = 'CustomError';
}

builder.queryField('roll', t =>
  t.effect({
    // @ts-expect-error
    resolve() {
      return Effect.fail(new CustomError("Boom!"));
    },
    type: ['Int'],
  })
);

const schema = builder.toSchema();
const document = parse(`{ roll }`);

execute({ document, schema }).then(console.log, console.error);

Expected behavior

The pothos-plugin-effect resolver throws an error of an instance of CustomError.

Actual behavior

The pothos-plugin-effect resolver wraps the CustomError in an instance of Error, or failErrorConstructor, or defaultFailErrorConstructor when so configured.

I think the current behavior is faulty mostly because the example code here suggests that the expected behavior described above should be the intended behavior, as well.

Motivation

I'm using the pothos-errors plugin, and I need the custom error class to be preserved for Pothos to be able to return the error as part of the expected error set in the data response.

Attempted import error: 'Cause'.'unannotate' is not exported from 'effect' (imported as 'Cause').

I have been using pothos but I just discovered Effect.

When I try to use pothos-plugin-effect with Effect v2.2.2 I get the error:
Attempted import error: 'Cause'.'unannotate' is not exported from 'effect' (imported as 'Cause').

I see unannotate being used on pothos-plugin-effect/src/field-builder.ts

function checkAndThrowResultIfFailure<E, A>(
  result: Exit.Exit<E, A>,
  FailErrorConstructor: { new(message: string): unknown } = Error,
): asserts result is Exit.Success<E, A> {
  // Check if result is a failure
  if (Exit.isFailure(result)) {
    const cause = Cause.unannotate(result.cause);
    ...

I don't seem to see Cause.unannotate being available in Effect v2.2.2. I can't tell when it might have stopped being supported. Could pothos-plugin-effect be updated to work with current version of Effect?

I have attempted a PR here which makes build and tests pass.
#28

Thanks for your help getting it reviewed and released so everyone can use the latest Effect version which everyone is going to get unless they explicitly specify something lower during install (which I don't think is practical since it doesn't match docs).

Q: How does one `setConfigProvider` globally?

When one has an Effect, they can change the default config provider like so:

Effect.succeed(42),
Effect.provideLayer(Effect.setConfigProvider(ConfigProvider.fromMap(new Map([["key", "value"]]))))

However, I don't see how I can do this in the context of this plugin. If I add it to the effect resolver, it messes up my types because all the layers need to be provided before I can set a new config provider. I cannot do it in the effect.layers option either for a similar reason, and when I add that to the globalLayer effect options like so:

effectOptions: {
  globalLayer: Layer.provide(
    Effect.setConfigProvider(ConfigProvider.fromMap(new Map([["key", "value"]]))),
    Layer.context()
  ),
},

it type checks but does not seem to work at all. @iamchanii any idea how to configure your own config provider when using this plugin? 🙏

Peerdeps has invalid ranges for Effect-TS packages

Since the Effect-TS packages are currently at 0.x version, minor version bumps can break the interface.
And it's actually happening right now (the latest version of @effect/io provides a new interface for Effect.Do and it's breaking the plugin since the plugin is expecting the old interface)
I'd suggest you lock down the minor version of Effect-TS peer dependencies, rather than marking every 0.x version as usable.

Improve prisma integration

  • Add prisma generator
generator effect {
  provider = "..."
}
  • Provide generated context
declare const Prisma: Context.Tag<EffectPrisma>;

// $ExpectType Effect.Effect<never, PrismaError, User>
Prisma.user.findUniqueOrThrow({ ... })
  • Provide PothosEffectPrismaClient tag
  • #25
  • I think ts-morph is too heavy. should I replace it to typescript?

CJS support

@iamchanii, I've just migrated one of my projects over to ESM just so I could consume this plugin. However, not all my projects are in a state where such migration is straightforward. Would you consider also exporting a CJS build of the library?

`@Effect/schema` integration

Hi there, great work on the integration so far!

I'm wondering if it would ever be possible to use @Effect/schema to specify the args to a query/mutation?

Perhaps by hooking into the validation plugin?

I know there is already some basic validation when parsing to/from graphql (string, number, null, array, etc. are checked), but it would be great to leverage @Effect/schema.

`yarn prisma generate` is running on install, even if I'm not using prisma

[email protected] is broken, I cannot install it, it fails with:

👾 install | pnpm i --no-frozen-lockfile
Scope: all 10 workspace projects
 WARN  7 deprecated subdependencies found: @sinonjs/[email protected], [email protected], [email protected], [email protected], [email protected], [email protected], [email protected]
Packages: +34
++++++++++++++++++++++++++++++++++
Progress: resolved 1455, reused 1425, downloaded 0, added 0, done
node_modules/.pnpm/[email protected]_@[email protected][email protected][email protected]/node_modules/pothos-plugin-effect: Running postinstall script, failed in 283ms
.../node_modules/pothos-plugin-effect postinstall$ yarn prisma generate
│ yarn run v1.22.19
│ error Could not open cafile: EISDIR: illegal operation on a directory, read
│ error Command "prisma" not found.
│ info Visit https://yarnpkg.com/en/docs/cli/run for documentation about this command.
└─ Failed in 283ms at /Users/victor/Projects/PMS/pms-core/node_modules/.pnpm/[email protected]_@[email protected][email protected][email protected]/node_modules/pothos-plugin-effect
 ELIFECYCLE  Command failed with exit code 1.
/Users/victor/Projects/PMS/pms-core/node_modules/.pnpm/[email protected][email protected]/node_modules/projen/src/task-runtime.ts:204
          throw new Error(
                ^

installing prisma as a dependency does not help either

imo postintall script should not be part of this package.

Integration with the relay plugin

Hi there 👋 I'm just about to start using Effect in one of my existing projects and would love to try your plugin. One thing I noticed during the migration was that there's a missing integration with the relay plugin that I use for declaring mutations like so:

builder.relayMutationField(
  "resourceCreate",
  {
    inputFields: (t) => ({
      a: t.string(),
    }),
  },
  {
    errors: {
      directResult: true,
      types: [
        FeatureUnavailableError,
        ForbiddenError,
        InvalidInputError,
        LimitReachedError,
      ],
    },
    resolve: async (_root, _args, _env) => ({
      resource: { /* blah blah blah */ },
    }),
  },
  {
    outputFields: (t) => ({
      resource: t.expose("resource", {
        nullable: false,
        type: "Resource",
      }),
    }),
  }
);

How would I write this mutation using the effect plugin?

Add `useDefaultRuntime` options

In the future, this plugin will be make a Effect runtime, uses it in field resolver for isolated effect runtime environment. but I think there should be want to use default effect runtime (Effect.defaultRuntime). so I decided provide this option to opt out

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.