Plugins & overrides

Reusable modules ("plugins")

A plugin is just a module packaged as an npm package. Conventions:

  • Name: @merinaa-plugin/<name> (community) or @merinaa/<name> (official)

  • The package ships module.config.ts + server/client/ui folders, same layout as an in-tree module

  • Consumer installs and imports it in merinaa.config.ts

// merinaa.config.ts
import jobs from '@merinaa-plugin/jobs/module.config';

export default defineApp({
    name: 'my-server',
    modules: [jobs /* ... */],
});

For this to work, the plugin needs to ship as TypeScript source (not compiled) so the consumer's tspc picks up Deepkit reflection. Mark the package as "TypeScript-only" in its README and set "main": "src/index.ts" in the plugin's package.json.

Overriding a service

Deepkit DI supports provider substitution via useClass. If a plugin ships JobService and you want to replace it in your project, write a subclass and register it in a module that imports the plugin:

// modules/my-jobs/jobs-override.service.ts
import { Injectable } from '@merinaa/core';
import { JobService as PluginJobService } from '@merinaa-plugin/jobs';

@Injectable()
export class MyJobService extends PluginJobService {
    createJob(name: string): string {
        // custom behaviour
        return super.createJob(name.toUpperCase());
    }
}

// modules/my-jobs/my-jobs.module.ts
@Module({
    providers: [
        { provide: PluginJobService, useClass: MyJobService },
    ],
    exports: [PluginJobService],
})
export class MyJobsModule {}

List both modules in merinaa.config.ts, with your override module after the plugin so Deepkit resolves your useClass last.

Publishing a plugin

  1. Init an npm package with "main": "src/index.ts" and "types": "src/index.ts"

  2. Set "peerDependencies": { "@merinaa/core": "^0.3.0", "@merinaa/server": "^0.3.0" }

  3. Export a module.config.ts alongside your source

  4. Publish under the @merinaa-plugin scope if you want discovery via the npm registry