Skip to content

Plugin System #514

@jasonkuhrt

Description

@jasonkuhrt

This issue is a WIP. The content here will be refined over time.

We've had feedback in the Nexus transition issue (1, 2), and committed to, keeping schema component usable on its own, and schema component plugins usable on their own, too.

This issue is a place to at least start design/discussion of the system we'll need to realize the commitment. Centralizing all the considerations into one place will help us design the system. It is very complex and tradeoffs are hard to analyze. It is easy to over-optimize a side at the expense of others, without noticing until it is too late, maybe a lot of work done that could have been avoided.

Capabilities

WIP - incomplete, sometimes ambiguous

  • access to makeSchema of @nexus/schema
    • todo: this means a wide set of capabilities, we should flatten/unroll them here.
  • Add middleware to the server, AGNOSTIC to the underlying engine
    • integrate APM like that from datadog or elastic search
    • short-circuit a request with an early response
    • CRUD headers on the resposne
    • add data to the graphql resolver context that is tied to this requst lifecycle
    • add data to the request object?
  • Replace the server with another implementation
  • Augment resolver context globally (not tied to request lifecycle)
  • Worktime
    • run side-effects on lifecycle events, pre/post build etc.
    • Add new commands to the CLI
      • todo: detail
  • Tap into logger
    • todo: detail
  • change any framework setting
  • dev mode
    • add files for the watcher to watch for changes
    • pause a refresh
    • force a refresh
    • log messages
  • testtime
    • add to test context

Questions

Social

  1. How will plugins be discovered by users?
  2. How will plugins be evaluated by users?
  3. How will users get a uniform read on all the different things different plugins do? Some augment servers, some replace servers, some attach request data, some attach response headers, ...
  4. How will users get a uniform read on all the options a plugin has (aka. config)?
  5. How will plugin quality be measured so users can make informed choices?
  6. How will we make as much as possible cosuming 10 plugins from 10 different developers feel like it could be from the same developer.

Versioning

  1. How will plugin compatibility be tracked so users can see which plugins support the version of the framework they are using, or would like to use (e.g. they are evaluating doing upgrading their framework to a major version)
  2. How will a user find out that the version of the plugin they are using is incompatible with the framework version?
    Worst: runtime crash and burn only under a set of production circumstances; Best: clear up front feedback while in dev mode?
  3. Will plugins need to depend on one another? Should they? If so, how will a plugin express that it depends on another? Peer dependency? Will the package manager feedback for that be a good enough dx? Probably not. What if the pin range leads to something not compatible with the user's framework version? Feels like entering a kind of dependency hell is pretty easy here.

Schema Component vs Framework

  1. How will framework plugins be documented on the website?
  2. How will schema plugins be documented on the website?
  3. How will documentation for authoring framework plugins be on the website?
  4. How will documentation for authoring schema plugins be on the website?
  5. When authoring a framework plugin, how will the schema subset be easily made easily consumable by nexus schema component users?
  6. How will schema plugin npm packages be differentiated from framework plugin npm packages?
  7. Will a plugin that wants to offer schema and framework level support be done so in a single npm package and github repo or multiple npm packages and multiple github repos?
  8. If the answer is one package, what happens if the dependencies of the framework plugin are much greater than the schema one? This is the case for the Prisma plugin. Do schema component users accept the bloat in their workflow?
  9. If the answer is one package, what happens if the dependencies of the framework plugin include deps that are, at the schema level, intended to be peer? This is the case for the Prisma plugin.
  10. If the answer is multiple packages, how are they differentiated on npm? github repos?

Integration

  1. It should be practically impossible to have silently incompatible plugins. This means a case where framework user installs two plugins and their app stops working because of an integration problem caused by the combination of the two plugins. How?

  2. We want the api to be built on top of the same internals that plugins are. This means anything the api let's users do is something that plugins can do, automate. That means a strong separation needs to exist in the API between escape hatches and official API surface that plugins rely on. This means for example that if a plugin A does (effectively, psudeo code):

    schema.addToContext(req => {
      req.foobar()
    })

    and plugin B does (effectively, psudeo code):

    server.custom(createSomeCustomServer)

    And req.foobar() is broken by createSomeCustomServer, then, bad.

    The features in the non-escape-hatch API need to be guaranteed compatible. Any features in schema.addToContext that rely on server need to be abstracted from the particulars of the actual server, and createSomeCustomServer must be required to fulfill all the framework features that are touching the server.

    This is one example but the principal is far reaching, general.

  3. Any plugin can augment the CLI. This means any CLI Invocation begins by loading the plugins that tap into it. How will we do that?

    1. Plugins will be configurable by users. This means:
      1. Read user config of plugins
      2. Read plugins, configure them, apply them
      3. Run cli
    2. User could configure cli plugins in dedicate config file
      1. some plugins, like prisma, augment runtime and worktime
      2. this means user would configure plugin in two places, some config file, and inside their app
      3. Furthermore, if not a single plugin, still for their app, some plugins would be configued one place, while others another
      4. This is a bad experience, so why invest in this direction?
    3. We could have the singular system, configuration in the app
    4. This would mean running app modules shallowly to acquire settings
    5. Shallowly means we don't need to run through everything, like building the schema or starting the server
    6. Settings is an API that can live anywhere in the app
    7. This means running all app modules
    8. This means requiring the app to not run substantial side-effects in modules
    9. This is ALREADY THE CASE with typegen
    10. So embrace it
    11. But what about added latency CLI invocation?
      1. Answer depends on what % of total time it accounts for
      2. 500ms added to 10s ~does not matter
      3. 500ms added to 1s ~does
  4. Should users have to enable the plugins explicitly after installing them?

    1. Why?
    2. What % of cases are there where a plugin will be installed but not used?
    3. Why not auto-use and allow opting-out? Doesn't that optimize for what most people are mostly doing?
    4. If plugins are not auto-used then that means, for plugins with cli augmentation, possibly only cli augmentation, user needs to add entry into their code in order for the cli to use it?
  5. Should plugins be imported or appear configurable via typegen?

    • if import-based, we more or less force ourselves down the must-be-explicitly-enabled-after-install design path
    • if typegen-based, we can go either way
    // user has to import some new concept
    import { use } from 'nexus-future'
    
    // user has to think, however little, about all the variances
    import * as Foo from 'foo'
    import foo from 'foo'
    import { createFoo } from 'foo'
    
    // user has to use it
    use(foo({ /* ... */ }))
    import { settings } from 'nexus-future'
    
    settings.change({
      plugins: {
        foo: {
          enabled: true | false // we can play with the defualt
          /* ... */
        }
      }
    })
  6. If we go the auto-use way, how does that square with schema level plugins?

    1. Do we enforce an npm package name for schema plugins so that they can be auto-used too?

      1. pattern of nexus-schema-plugin-*?
    2. Do we ignore them and say "publish a framework variant that wraps it"

      1. This seems to go against the first paragraph of this issue?
    3. Do we add a new API like:

      import { schema } from 'nexus-future'
      import someSchemaPlugin from 'some-schema-plugin'
      
      schema.use(someSchemaPlugin({ /* ... */ }))
    4. If we had such an API, would one not expect too then (this is compatible with the above framework use above, for framework):

      import { schema, server, logger } from 'nexus-future'
      import someSchemaPlugin from 'some-schema-plugin'
      import someServerPlugin from 'some-server-plugin'
      import someLoggerPlugin from 'some-logger-plugin'
      
      schema.use(someSchemaPlugin)
      server.use(someServerPlugin)
      logger.use(someLoggerPlugin)

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions