alloc / tusken

100% type-safe query builder compatible with any Postgres client 🐘 Generated table/function types, tree-shakable, implicit type casts, and more

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Plugin packages

aleclarson opened this issue · comments

  • Plugins found in node_modules are loaded automatically
  • Plugins can alter the generated client, which means they can:
    • Inject runtime code
    • Modify the db, pg, and t objects
    • Modify any TypeScript interface with declaration merging
  • Plugins can add their own config options to tusken.config.js
  • Plugins can alter the database schema with explicit permission
    • tusken apply brings up a multi-select prompt of unapplied plugins?
  • Plugins can add their own database schema for metadata purposes

Database extensions

Any module in a plugin package's dist/database folder is read during tusken generate command. These modules are bundled and seamlessly plugged into the generated client. Private instance properties and global variables are renamed to avoid collisions between plugins.

import { Database, DatabaseConfig } from 'tusken'

const DEFAULT_FOO = 1

export default class extends Database {
  // Instance properties
  private _foo: number

  // Read and manipulate the config and/or initialize properties
  constructor(config: DatabaseConfig & { foo?: number }) {
    super(config)
    this._foo = config.foo ?? DEFAULT_FOO
  }

  // Define new methods
  foo() {...}
}

Similar runtime extensions will be supported as well. Like dist/select modules can extend the Select class. We might even allow plugin packages to provide their own extension namespaces, so dist/<plugin> could be supported too.

→ Compiled extension

When the runtime extension above is compiled by tusken generate, it's injected into the generated client as roughly the following code:

import * as tusken from 'tusken'

export interface DatabaseConfig extends tusken.DatabaseConfig {
  foo?: number
}

const DEFAULT_FOO = 1

class Database extends tusken.Database {
  private _foo: number

  constructor(config: DatabaseConfig) {
    super(config)
    this._foo = config.foo ?? DEFAULT_FOO
  }

  foo() {...}
}

export default new Database({
   /* generated options go here */
})

The generated Database subclass is shared by all extension modules. As said before, any conflicting private instance properties and global variables are renamed.

Partial plugins

In your Tusken config, you can specify which extensions of a plugin you wish to use:

export default defineConfig({
  include: [
    // The default if a plugin isn't specified, but required if the package name 
    // doesn't include "tusken-plugin-" or similar
    'tusken-plugin-admin/*',
    // Use only the "map" extension from the tusken-plugin-array package.
    'tusken-plugin-array/map',
})

The include array will have its types generated so it can warn you about typos and give you auto-completion.

Probably also want support for shorthand objects:

{ 'tusken-plugin-array': ['map', 'forEach'] },

And we should probably just omit the tusken-plugin part?

'array/*',
'array/map',
{ array: ['map', 'filter'] },