Skip to content

feat: Plugin architecture#58

Merged
Brayden merged 9 commits intoouterbase:mainfrom
Ehesp:ehesp/plugin
Jan 9, 2025
Merged

feat: Plugin architecture#58
Brayden merged 9 commits intoouterbase:mainfrom
Ehesp:ehesp/plugin

Conversation

@Ehesp
Copy link
Collaborator

@Ehesp Ehesp commented Dec 23, 2024

Purpose

This is a PR primarily designed to discuss Starbase plugins.

At it's core, the main purpose of Starbase is to query a database, whether that be a DO or external one. Other functionality such as studio, import/export, websockets etc are additional functionality. Currently it's possible to disable/enable this functionality through feature flags... but it's not possible to extend Starbase. Plugins breakout this functionality and allow users to extend Starbase how they see fit.

This PR proposes being able to do the following:

new StarbaseDB({
  dataSource,
  config,
  plugins: [new WebSocketPlugin(), new StudioPlugin({ username: '', password: '', apiKey: '' })],
})

The signature for a function is currently:

export abstract class StarbasePlugin {
    constructor(public name: string) {
        console.log(`Plugin ${name} loaded`)
    }

    public async register(app: StarbaseApp): Promise<void> {
        throw new UnimplementedError('register')
    }
}

If implemented, the register method of a plugin will be called early on in the lifecycle, where app is the Hono app instance - this allows users to add routes, apply middleware etc as they normally would.

This PR also passes the dataSource and config into Hono context, allowing plugins to access (or modify) this. Plugins are applied in-order and have an async register API.

Later on, it may make sense to pass this context (StarbaseContext) around everywhere, rather than dealing with individual function arguments - but one for later.

Other plugins can be created easily (assuming this api is ok) for import, export, litrest, cors, etc.

## Other use-cases

### Auth

There is currently a manual auth template which users would be expected to bind to the worker and call themselves. This could be instead extracted out into a plugin:

new StarbaseAuthPlugin(...)

Hooks

In this PR there is a StarbasePluginRegistry class. The idea here is that we could export useful methods to allow plugin authors to subscribe to events, e.g.

class MyPlugin extends StarbasePlugin {
  override async onBeforeQuery(args) {
    // log, mutate.. whatever!
  }

  override async onAfterQuery(args) {
    // log, mutate.. whatever!
  }
}

By having a registry, we could simply call registry.onBeforeQuery(args) where needed, and internally it'd call every plugins onBeforeQuery method.

@Brayden
Copy link
Member

Brayden commented Dec 24, 2024

This is exactly what I had in mind. It makes it incredibly easy to plug in features quickly that integrate directly with the data source. I was able to create this example in a minutes time to test it and verify that a GET call to /test would return the expected JSON result.

const plugins = [new TestPlugin()] satisfies StarbasePlugin[]
class TestPlugin extends StarbasePlugin {
    private prefix = '/test'

    constructor(opts?: { prefix?: string }) {
        super('starbasedb:test')
        this.prefix = opts?.prefix ?? this.prefix
    }

    override async register(app: StarbaseApp) {
        app.get(this.prefix, (c) => {
            return this.handle(c)
        })
    }

    private handle(ctx: StarbaseContext): Response {
        return createResponse({
            wasTestSuccess: true
        }, undefined, 200)
    }
}

My first thought would be for us to remove the templates/auth folder in the project once this is merged in and convert it to a plugin that anyone can use. Will likely need to create a new folder in the project of plugins available so they're easily accessible to those getting started.

Some superb work here 👏

@Brayden Brayden added the enhancement New feature or request label Dec 24, 2024
@Ehesp
Copy link
Collaborator Author

Ehesp commented Dec 24, 2024

Great to hear!

What are your thoughts on making the plugins workspace packages? If we add tests and things it may make it easier to manage, or publish them as external packages etc?

@Brayden
Copy link
Member

Brayden commented Dec 24, 2024

What are your thoughts on making the plugins workspace packages? If we add tests and things it may make it easier to manage, or publish them as external packages etc?

For clarity, are you imagining the folder structure being the the following but still kept within this starbasedb repo?

/plugins
-> /plugin-a
-> /plugin-b

@Ehesp
Copy link
Collaborator Author

Ehesp commented Dec 24, 2024

Yes, there's a couple like that already in my branch, but they're not packages, just separate directories with the code in them

@Brayden
Copy link
Member

Brayden commented Dec 24, 2024

Yes, there's a couple like that already in my branch, but they're not packages, just separate directories with the code in them

I'm perfectly content with them becoming packages. That's how the current /templates/auth folder is as well so follows the same pattern there.

Would love to get your idea of the beforeQuery and afterQuery hooks in, too, so a plugin can extend the functionality on a per-plugin basis as a query goes through its workflow. All for moving forward with this structure!

🚀

@Brayden Brayden marked this pull request as ready for review January 7, 2025 18:30
@Brayden Brayden self-requested a review January 9, 2025 20:46
@Brayden Brayden merged commit c6df745 into outerbase:main Jan 9, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants