mirror of
https://github.com/mauriceboe/TREK.git
synced 2026-10-20 22:16:26 +00:00
refactor(plugins): wire PluginHostDepsFactory through DI, drop tags/categories bridges
This commit is contained in:
@@ -6,5 +6,7 @@ import { BudgetService } from './budget.service';
|
||||
@Module({
|
||||
controllers: [BudgetController],
|
||||
providers: [BudgetService],
|
||||
// For in-container consumers (PluginHostDepsFactory).
|
||||
exports: [BudgetService],
|
||||
})
|
||||
export class BudgetModule {}
|
||||
|
||||
@@ -1,21 +0,0 @@
|
||||
import type { Category } from '@trek/shared';
|
||||
import { db } from '../../db/database';
|
||||
import { DatabaseService } from '../database/database.service';
|
||||
import { CategoriesService } from './categories.service';
|
||||
|
||||
/**
|
||||
* Non-Nest entry point for the categories domain — for code running OUTSIDE
|
||||
* the Nest container (currently only the plugin RPC host; the MCP tool and
|
||||
* resource moved to the DI-discovered categories.mcp.ts). Exports only the
|
||||
* legacy services/categoryService names still consumed outside the container,
|
||||
* 1:1, so repointing a consumer is an import-path-only diff. Inside the
|
||||
* container, inject CategoriesService instead.
|
||||
*
|
||||
* Module-level construction is safe: `db` is the reinitialize-proof Proxy onto
|
||||
* the shared better-sqlite3 singleton (same pattern as tags.bridge.ts).
|
||||
*/
|
||||
const categories = new CategoriesService(new DatabaseService(db));
|
||||
|
||||
export function listCategories(): Category[] {
|
||||
return categories.list();
|
||||
}
|
||||
@@ -7,5 +7,7 @@ import { CategoriesService } from './categories.service';
|
||||
@Module({
|
||||
controllers: [CategoriesController],
|
||||
providers: [CategoriesService, CategoriesMcp],
|
||||
// For in-container consumers (PluginHostDepsFactory).
|
||||
exports: [CategoriesService],
|
||||
})
|
||||
export class CategoriesModule {}
|
||||
|
||||
@@ -5,8 +5,8 @@ import { DatabaseService } from '../database/database.service';
|
||||
/**
|
||||
* Categories domain service — owns the category SQL (moved 1:1 from the legacy
|
||||
* services/categoryService.ts: identical statements, the #6366f1/📍 defaults
|
||||
* and the COALESCE update semantics). Non-Nest consumers (plugin RPC host) go
|
||||
* through categories.bridge.ts instead of importing this class directly.
|
||||
* and the COALESCE update semantics). All consumers are in-container now — the
|
||||
* plugin RPC host injects this class via PluginHostDepsFactory.
|
||||
*/
|
||||
@Injectable()
|
||||
export class CategoriesService {
|
||||
|
||||
@@ -14,7 +14,8 @@ import { decrypt_api_key } from '../../services/apiKeyCrypto';
|
||||
import { PluginSupervisor, type PluginRouteInfo } from './supervisor/plugin-supervisor';
|
||||
import fs from 'node:fs';
|
||||
import path from 'node:path';
|
||||
import { createRealRpcHost, closePluginDataDb } from './host/create-rpc-host';
|
||||
import { PluginHostDepsFactory } from './host/plugin-host-deps.factory';
|
||||
import { closePluginDataDb } from './host/plugin-host-state';
|
||||
import { ForbiddenResource } from './host/rpc-host';
|
||||
import { removePluginData } from './host/plugin-data.service';
|
||||
import { isKnownPermission } from './protocol/envelope';
|
||||
@@ -130,8 +131,13 @@ export class PluginDependencyError extends Error {
|
||||
export class PluginRuntimeService implements OnModuleInit, OnModuleDestroy {
|
||||
// The rpc-host factory is bound to `this` as the inter-plugin router, so a
|
||||
// plugin's ctx.plugins.call / ctx.events.emit resolve through callPlugin/
|
||||
// emitPluginEvent below (which own the dependency-edge authorization).
|
||||
private readonly supervisor = new PluginSupervisor((id, granted) => createRealRpcHost(id, granted, this), {
|
||||
// emitPluginEvent below (which own the dependency-edge authorization). The
|
||||
// arrow reads this.hostDeps lazily at spawn time, so the field-initializer
|
||||
// ordering (it runs before the constructor params are assigned) is safe.
|
||||
private readonly supervisor = new PluginSupervisor((id, granted) => {
|
||||
if (!this.hostDeps) throw new Error('PluginHostDepsFactory not provided — tests that activate plugins must pass one');
|
||||
return this.hostDeps.create(id, granted, this);
|
||||
}, {
|
||||
// Both hooks run from child lifecycle EventEmitter callbacks (exit / stderr 'data'),
|
||||
// so a throw here becomes an uncaughtException that has no host-side handler. During a
|
||||
// restore the core DB is briefly CLOSED (closeDb → the this.db proxy throws on access), so a
|
||||
@@ -163,12 +169,13 @@ export class PluginRuntimeService implements OnModuleInit, OnModuleDestroy {
|
||||
// Coalesces overlapping erasure drains (the sweep and enqueue both trigger one).
|
||||
private drainInFlight: Promise<void> | null = null;
|
||||
|
||||
// The registry stays optional at the type level so tests can construct the
|
||||
// service without one; Nest always injects the real one (the provider is in
|
||||
// the module).
|
||||
// The registry and host-deps factory stay optional at the type level so tests
|
||||
// can construct the service without them; Nest always injects the real ones
|
||||
// (both providers are in the module).
|
||||
constructor(
|
||||
private readonly dbs: DatabaseService,
|
||||
private readonly registry?: PluginRegistryService,
|
||||
private readonly hostDeps?: PluginHostDepsFactory,
|
||||
) {}
|
||||
|
||||
private get db() {
|
||||
|
||||
@@ -23,6 +23,12 @@ import { PluginOAuthService } from './plugin-oauth.service';
|
||||
import { PluginsService } from './plugins.service';
|
||||
import { PluginRuntimeService } from './plugin-runtime.service';
|
||||
import { PluginRegistryService } from './registry/registry.service';
|
||||
import { PluginHostDepsFactory } from './host/plugin-host-deps.factory';
|
||||
import { TagsModule } from '../tags/tags.module';
|
||||
import { CategoriesModule } from '../categories/categories.module';
|
||||
import { BudgetModule } from '../budget/budget.module';
|
||||
import { ReservationsModule } from '../reservations/reservations.module';
|
||||
import { TodoModule } from '../todo/todo.module';
|
||||
|
||||
/**
|
||||
* Plugin system (#plugins). M0 read side + M2 isolated runtime + M3 frontend:
|
||||
@@ -32,8 +38,11 @@ import { PluginRegistryService } from './registry/registry.service';
|
||||
* widget assets at /plugin-frame/:id/*.
|
||||
*/
|
||||
@Module({
|
||||
// The DI-native domain services the plugin host wiring injects
|
||||
// (PluginHostDepsFactory); DatabaseModule is @Global, so not listed.
|
||||
imports: [TagsModule, CategoriesModule, BudgetModule, ReservationsModule, TodoModule],
|
||||
controllers: [PluginsController, PluginsFeedController, PluginsProxyController, PluginFrameController, PlaceDetailsController, TripWarningsController, ViewContributionsController, TripCardContributionsController, PluginPhotosController, PluginCalendarController, MapMarkersController, MapLayersController, PluginRoutesController, DayScheduleController, PdfSectionsController, AtlasLayersController, JournalEntryRowsController, PluginUserSettingsController, PluginOAuthController, PluginActivityController],
|
||||
providers: [PluginsService, PluginRuntimeService, PluginRegistryService, PluginOAuthService],
|
||||
providers: [PluginsService, PluginRuntimeService, PluginRegistryService, PluginOAuthService, PluginHostDepsFactory],
|
||||
// Exported so the admin addon-toggle handler can cascade-disable plugins whose
|
||||
// required addon was just turned off (#plugins dependencies).
|
||||
exports: [PluginRuntimeService],
|
||||
|
||||
@@ -6,7 +6,7 @@ import { devLinkEnabled } from './dev-link';
|
||||
import { maybe_encrypt_api_key, decrypt_api_key } from '../../services/apiKeyCrypto';
|
||||
import { readAudit } from './host/plugin-audit';
|
||||
import { keyFingerprint } from './signature-status';
|
||||
import { pluginBudgetUsage } from './host/create-rpc-host';
|
||||
import { pluginBudgetUsage } from './host/plugin-host-state';
|
||||
import { isAddonEnabled } from '../../services/adminService';
|
||||
import { parseDependencies, disabledRequiredAddons, resolveDependencyState, type PluginDepRow, type PluginDependencies, type VersionMismatch } from './dependencies';
|
||||
import { hostSatisfies, hostVersion } from './install/host-compat';
|
||||
|
||||
@@ -13,5 +13,7 @@ import { UpcomingReservationsController } from './upcoming-reservations.controll
|
||||
@Module({
|
||||
controllers: [ReservationsController, AccommodationsController, UpcomingReservationsController],
|
||||
providers: [ReservationsService, AccommodationsService],
|
||||
// For in-container consumers (PluginHostDepsFactory).
|
||||
exports: [ReservationsService],
|
||||
})
|
||||
export class ReservationsModule {}
|
||||
|
||||
@@ -1,37 +0,0 @@
|
||||
import type { Tag } from '@trek/shared';
|
||||
import { db } from '../../db/database';
|
||||
import { DatabaseService } from '../database/database.service';
|
||||
import { TagsService } from './tags.service';
|
||||
|
||||
/**
|
||||
* Non-Nest entry point for the tags domain — for code running OUTSIDE the Nest
|
||||
* container (currently only the plugin RPC host; the MCP tools moved to the
|
||||
* DI-discovered tags.mcp.ts). Exports the legacy services/tagService function
|
||||
* names 1:1 so repointing a consumer is an import-path-only diff. Inside the
|
||||
* container, inject TagsService instead.
|
||||
*
|
||||
* Module-level construction is safe: `db` is the reinitialize-proof Proxy onto
|
||||
* the shared better-sqlite3 singleton (same pattern as
|
||||
* plugins/host/create-rpc-host.ts).
|
||||
*/
|
||||
const tags = new TagsService(new DatabaseService(db));
|
||||
|
||||
export function listTags(userId: number): Tag[] {
|
||||
return tags.list(userId);
|
||||
}
|
||||
|
||||
export function createTag(userId: number, name: string, color?: string): Tag {
|
||||
return tags.create(userId, name, color);
|
||||
}
|
||||
|
||||
export function getTagByIdAndUser(tagId: number | string, userId: number): Tag | undefined {
|
||||
return tags.getByIdAndUser(tagId, userId);
|
||||
}
|
||||
|
||||
export function updateTag(tagId: number | string, name?: string, color?: string): Tag | undefined {
|
||||
return tags.update(tagId, name, color);
|
||||
}
|
||||
|
||||
export function deleteTag(tagId: number | string): void {
|
||||
tags.remove(tagId);
|
||||
}
|
||||
@@ -7,5 +7,7 @@ import { TagsService } from './tags.service';
|
||||
@Module({
|
||||
controllers: [TagsController],
|
||||
providers: [TagsService, TagsMcp],
|
||||
// For in-container consumers (PluginHostDepsFactory).
|
||||
exports: [TagsService],
|
||||
})
|
||||
export class TagsModule {}
|
||||
|
||||
@@ -5,8 +5,8 @@ import { DatabaseService } from '../database/database.service';
|
||||
/**
|
||||
* Tags domain service — owns the tag SQL (moved 1:1 from the legacy
|
||||
* services/tagService.ts: identical statements, the #10b981 default and the
|
||||
* COALESCE update semantics). Non-Nest consumers (MCP tools, plugin RPC host)
|
||||
* go through tags.bridge.ts instead of importing this class directly.
|
||||
* COALESCE update semantics). All consumers are in-container now — the plugin
|
||||
* RPC host injects this class via PluginHostDepsFactory.
|
||||
*/
|
||||
@Injectable()
|
||||
export class TagsService {
|
||||
|
||||
@@ -4,15 +4,16 @@ import { TodoService } from './todo.service';
|
||||
|
||||
/**
|
||||
* Non-Nest entry point for the todo domain — for code running OUTSIDE the
|
||||
* Nest container (the plugin RPC host and the legacy get_trip_summary
|
||||
* registrar in src/mcp/tools/trips.ts; the todo MCP tools and resource moved
|
||||
* to the DI-discovered todo.mcp.ts). Exports only the legacy
|
||||
* services/todoService names still consumed outside the container, 1:1, so
|
||||
* repointing a consumer is an import-path-only diff. Inside the container,
|
||||
* inject TodoService instead.
|
||||
* Nest container (the legacy get_trip_summary registrar in
|
||||
* src/mcp/tools/trips.ts; the todo MCP tools and resource moved to the
|
||||
* DI-discovered todo.mcp.ts, and the plugin RPC host injects TodoService via
|
||||
* PluginHostDepsFactory). Exports only the legacy services/todoService names
|
||||
* still consumed outside the container, 1:1, so repointing a consumer is an
|
||||
* import-path-only diff. Inside the container, inject TodoService instead.
|
||||
* Delete this file when the legacy MCP trips registrar migrates.
|
||||
*
|
||||
* Module-level construction is safe: `db` is the reinitialize-proof Proxy onto
|
||||
* the shared better-sqlite3 singleton (same pattern as categories.bridge.ts).
|
||||
* the shared better-sqlite3 singleton.
|
||||
*/
|
||||
const todos = new TodoService(new DatabaseService(db));
|
||||
|
||||
|
||||
Reference in New Issue
Block a user