Skip to content

chore(deps): update dependencies#139

Merged
sasagar merged 8 commits intodevfrom
chore/update-deps
Jan 28, 2026
Merged

chore(deps): update dependencies#139
sasagar merged 8 commits intodevfrom
chore/update-deps

Conversation

@sasagar
Copy link
Collaborator

@sasagar sasagar commented Jan 28, 2026

Summary

Update all dependencies to latest versions.

Changes

Frontend

Package Before After
waku 1.0.0-alpha.2 1.0.0-alpha.3
jotai 2.16.0 2.17.0
@lingui/* 5.7.0 5.9.0
react/react-dom 19.2.3 19.2.4
react-hook-form 7.68.0 7.71.1
lucide-react 0.561.0 0.563.0
katex 0.16.27 0.16.28

Backend

Package Before After
hono 4.11.1 4.11.7
zod 4.2.1 4.3.6
pino 10.1.0 10.3.0
pg 8.16.3 8.17.2
ioredis 5.8.2 5.9.2
bullmq 5.66.1 5.67.2
@aws-sdk/client-s3 3.954.0 3.975.0
mysql2 3.16.0 3.16.2
better-sqlite3 12.5.0 12.6.2

Dev Tools

Package Before After
oxlint 1.33.0 1.42.0
@playwright/test 1.57.0 1.58.0
@types/bun 1.3.4 1.3.7
typedoc 0.28.15 0.28.16
vitest 4.0.16 4.0.18

Notes

  • Jotai v2.17.0: Deprecates loadable, setSelf, and removes unstable_onInit. These APIs are not used in our codebase, so no migration is needed.
  • Waku 1.0.0-alpha.3: Minor alpha update

Test Plan

  • TypeScript compilation passes
  • All 1113 tests pass

Summary by CodeRabbit

  • New Features

    • プラグインシステムを追加:イベントフック、プラグインごとのカスタムAPIルート、フロントエンドスロット、管理UIでの有効/無効切替と状態表示、サンプル「Hello World」プラグイン
  • Backend

    • プラグイン設定のデータベース永続化と公開プラグインAPI(一覧・詳細・有効化/無効化)
  • Frontend

    • 管理画面に「Plugins」項目を追加、プラグインスロット描画とレジストリ、管理用ページを実装
  • Documentation

    • プラグイン開発ガイドとREADMEを追加
  • Tests

    • プラグインローダーと設定ストレージの単体テストを追加
  • Chores

    • 依存パッケージのバージョン更新多数

✏️ Tip: You can customize this high-level summary in your review settings.

- Add typed EventBus class with before/after event patterns
- Define plugin type interfaces (PluginUser, PluginNote, PluginFollow, PluginActivity)
- Use Visibility union type for type safety in note visibility
- Implement event payload types for all lifecycle events
- Add note lifecycle events: beforeCreate, afterCreate, beforeDelete, afterDelete
- Add user lifecycle events: beforeRegister, afterRegister, beforeLogin, afterLogin, afterLogout
- Add follow lifecycle events: afterCreate, afterDelete
- Add ActivityPub events: beforeInbox, afterInbox, beforeDelivery, afterDelivery
- Add moderation events: userSuspended, noteDeleted
- Integrate EventBus into DI container and middleware
- Emit events in NoteService, AuthService, FollowService
- Apply modifiedPayload from beforeCreate/beforeRegister events
- Remove PII (email) from user:beforeRegister payload
- Remove PII (ipAddress, userAgent) from user:beforeLogin payload
- Add shared toPluginUser/toPluginNote utility functions
- Add test helper factory functions
- Document composite followId format for delete events
- Document emitBefore error handling behavior
- Add PluginLoader class for plugin discovery and lifecycle management
- Implement DatabasePluginConfigStorage for persistent config
- Add plugin routes for admin API
- Support both array and object formats for plugin dependencies
- Provide proper PluginContext to ActivityPub handlers
- Fix TypeScript errors in test files
…plugin (#138)

Frontend Plugin System:
- Add PluginRegistry singleton for managing plugin state
- Create PluginSlot component for rendering plugin-provided components
- Define slot types (19 locations: note, compose, profile, settings, admin, etc.)
- Export public API from lib/plugins/index.ts

Admin Plugin Management:
- Add /admin/plugins page for viewing and managing plugins
- Enable/disable plugins through admin interface
- Display plugin status and version
- Add Plugins link to AdminNav

Sample Plugin:
- Add hello-world sample plugin demonstrating:
  - Plugin manifest (plugin.json)
  - Event subscriptions (note:afterCreate)
  - Custom API routes (/api/x/hello-world/)
  - Configuration storage
- Add plugins/README.md with development documentation
- Add comment explaining context unavailability in onUnload

Backend Updates:
- Add plugin routes at /api/plugins
- Add enabled check middleware for plugin routes (404 when disabled)
- Add enabled check for ActivityPub handlers
- Fix logger signature to accept variadic args
- Add manifest.id vs plugin.id mismatch validation
- Add PII comment for afterLogin event
- Remove sensitive fields (error, loadedAt) from public plugin endpoints
- Add description to plugin list API response

README Documentation:
- Update Phase 7 status to Complete
- Add Plugin System feature description
- Add Plugin Development documentation link
- Fix slot count (19 locations)
Frontend:
- waku: 1.0.0-alpha.2 → 1.0.0-alpha.3
- jotai: 2.16.0 → 2.17.0
- @lingui/*: 5.7.0 → 5.9.0
- react/react-dom: 19.2.3 → 19.2.4
- react-hook-form: 7.68.0 → 7.71.1
- lucide-react: 0.561.0 → 0.563.0
- katex: 0.16.27 → 0.16.28

Backend:
- hono: 4.11.1 → 4.11.7
- zod: 4.2.1 → 4.3.6
- pino: 10.1.0 → 10.3.0
- pg: 8.16.3 → 8.17.2
- ioredis: 5.8.2 → 5.9.2
- bullmq: 5.66.1 → 5.67.2
- @aws-sdk/client-s3: 3.954.0 → 3.975.0
- mysql2: 3.16.0 → 3.16.2
- better-sqlite3: 12.5.0 → 12.6.2

Dev Tools:
- oxlint: 1.33.0 → 1.42.0
- @playwright/test: 1.57.0 → 1.58.0
- @types/bun: 1.3.4 → 1.3.7
- typedoc: 0.28.15 → 0.28.16
- vitest: 4.0.16 → 4.0.18

Jotai v2.17.0 deprecates loadable, setSelf, and removes
unstable_onInit, but these are not used in our codebase.
@coderabbitai
Copy link
Contributor

coderabbitai bot commented Jan 28, 2026

📝 Walkthrough

Walkthrough

バックエンドとフロントエンドにプラグインシステムを追加。ローダー、永続設定ストレージ、初期化/ミドルウェア、管理API、フロントエンドのスロット・レジストリ、管理UI、ドキュメント、サンプルプラグイン、単体テストを含む実装です。(50語以内)

Changes

コホート / ファイル(s) 変更内容
ドキュメント
README.md, README.ja.md, plugins/README.md
フェーズ7完了化、プラグイン機能・実装チェックリスト、プラグイン開発ガイドリンク、プラグイン README を追加
モノレポ依存更新
package.json, packages/backend/package.json, packages/frontend/package.json
開発用・ランタイム依存のマイナー/パッチアップデート(例: Playwright, zod, lingui 等)
DBスキーマ
packages/backend/src/db/schema/pg.ts
plugin_configs テーブル追加、PluginConfig / NewPluginConfig 型をエクスポート
バックエンド:プラグイン基盤実装
packages/backend/src/plugins/loader.ts, packages/backend/src/plugins/config-storage.ts, packages/backend/src/plugins/init.ts, packages/backend/src/plugins/index.ts
PluginLoader(発見・依存解決・ロード/アンロード)、IPluginConfigStorage/InMemory、DBベースの設定ストレージ、initializePluginSystem、ActivityPub ハンドラ集約、ステータス取得ユーティリティを追加
バックエンド:統合・DI・ルーティング
packages/backend/src/index.ts, packages/backend/src/middleware/di.ts, packages/backend/src/routes/plugins.ts
起動時プラグイン初期化、pluginLoader のコンテキスト注入、/api/plugins 管理API(一覧、詳細、有効化/無効化)を追加
バックエンド:テスト
packages/backend/src/tests/unit/PluginLoader.test.ts
PluginLoader と InMemoryPluginConfigStorage の単体テスト群を追加
バックエンド:軽微注記
packages/backend/src/services/AuthService.ts
user:afterLogin イベントに関する PII/監査の注記を追加(動作変更なし)
フロントエンド:型・スロット・レジストリ
packages/frontend/src/lib/plugins/types.ts, .../slots.ts, .../registry.ts, .../index.ts
プラグイン用の型定義、PLUGIN_SLOTS 定義、PluginRegistry 実装、公開 API を追加
フロントエンド:レンダリング・フック・管理UI
packages/frontend/src/lib/plugins/PluginSlot.tsx, packages/frontend/src/pages/admin/plugins.tsx, packages/frontend/src/components/admin/AdminNav.tsx
PluginSlot コンポーネントとフック、管理ページ(有効化/無効化操作)、管理ナビ項目を追加
サンプルプラグイン
plugins/hello-world/plugin.json, plugins/hello-world/index.js
hello-world サンプルプラグイン(manifest、onLoad/onUnload、イベント購読、ルート)を追加

Sequence Diagram(s)

sequenceDiagram
    participant Client as Client
    participant Frontend as FrontendApp
    participant Backend as Backend
    participant PluginSys as PluginSystem
    participant DB as Database
    participant PluginCode as PluginCode

    rect rgba(100, 200, 255, 0.5)
    Note over Backend,PluginSys: 起動時プラグイン初期化
    Backend->>PluginSys: initializePluginSystem(options)
    PluginSys->>PluginSys: scan plugins ディレクトリ / read manifests
    PluginSys->>PluginCode: import plugin index.js
    PluginCode->>PluginSys: onLoad(context)
    PluginSys->>DB: init/config storage (plugin_configs)
    PluginSys-->>Backend: return PluginSystem (loader, routes, shutdown)
    end

    rect rgba(100, 255, 100, 0.5)
    Note over Client,Backend: 管理API操作
    Client->>Backend: GET /api/plugins or POST /api/plugins/:id/enable
    Backend->>PluginSys: query loader / setEnabled(pluginId)
    PluginSys-->>Backend: status/result
    Backend-->>Client: JSON レスポンス
    end

    rect rgba(255, 200, 100, 0.5)
    Note over Frontend,PluginCode: フロントエンド描画フロー
    Frontend->>Frontend: PluginSlot が registry を参照
    Frontend->>PluginCode: render slot component(s) (ErrorBoundary/Suspense)
    PluginCode-->>Frontend: JSX を返す
    end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~75 分

Possibly related PRs

Suggested labels

foundation

Poem

🐰 ぴょんと来たよ、新しい窓口
プラグインが風に乗って舞い込むよ
ローダーで呼んで、スロットで並べて
管理画面で鍵を回せば準備OK
みんなで広げる拡張の野原 🌱

🚥 Pre-merge checks | ✅ 2 | ❌ 1
❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Title check ⚠️ Warning The PR title 'chore(deps): update dependencies' is generic and does not accurately describe the actual changes, which include substantial new plugin system implementation across frontend and backend, not just dependency updates. Revise the title to reflect both dependency updates and the new plugin system features, such as 'feat: add plugin system with dependency updates' or split into separate PRs for feature and dependency changes.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Docstring Coverage ✅ Passed Docstring coverage is 86.67% which is sufficient. The required threshold is 80.00%.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing touches
  • 📝 Generate docstrings

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 4

🤖 Fix all issues with AI agents
In `@packages/backend/src/index.ts`:
- Around line 202-210: The initializePluginSystem call is missing the persistent
database, causing plugin settings to be saved only in memory; call getDatabase()
to obtain the db and pass it as the db parameter into initializePluginSystem
(inside the same pluginsEnabled block where initializePluginSystem is called),
e.g., retrieve db via getDatabase() and include db: db in the options passed to
initializePluginSystem so plugin state persists across restarts.
- Around line 194-200: Move the pluginLoader middleware (the app.use("*", async
(c, next) => { if (pluginSystem) { c.set("pluginLoader", pluginSystem.loader); }
await next(); })) so it is registered with the other global middlewares (near
the metrics/global middlewares) instead of after route definitions; this ensures
routes in routes/plugins.ts can reliably read c.get("pluginLoader") without
needing defensive checks — locate the existing pluginLoader middleware and
re-register it earlier in the bootstrap sequence alongside the global
middlewares that run before any route handlers.

In `@packages/backend/src/plugins/config-storage.ts`:
- Around line 86-100: DatabasePluginConfigStorage defines getAll and setMultiple
but IPluginConfigStorage doesn't and there are no call sites; either add these
methods to the IPluginConfigStorage interface (declare getAll(pluginId: string):
Promise<Record<string, unknown>> and setMultiple(pluginId: string, entries:
Record<string, unknown>): Promise<void>) so the implementation conforms, or
remove getAll and setMultiple from the DatabasePluginConfigStorage class (and
any related helper code) if they are unused; if you keep them, also update any
callers or add unit tests to exercise getAll/setMultiple so their presence is
justified.

In `@packages/frontend/package.json`:
- Around line 19-38: The package.json lists a non-existent Waku version; update
the "waku" dependency (symbol: "waku") to a valid published version such as
"1.0.0-alpha.2" (or a caret range like "^1.0.0-alpha.2") and run your package
manager to lock the correct version; also verify the listed "jotai" and "react"
versions (symbols: "jotai", "react") are intentionally chosen—keep Jotai 2.17.0
only if you accept the deprecation warnings (loadable and setSelf in atom read)
and keep React 19.2.4 if you only want the patch release, otherwise adjust
accordingly.
🧹 Nitpick comments (11)
packages/frontend/src/lib/plugins/registry.ts (1)

188-197: useSyncExternalStore との組み合わせで参照安定性の問題が発生する可能性があります。

getSlotComponents は呼び出しごとに新しい配列を生成するため、useSyncExternalStore のスナップショット比較で常に変更があると判定され、不要な再レンダリングが発生する可能性があります。

現時点で実用上の問題がなければ後回しでも構いませんが、パフォーマンスに影響が出た場合はキャッシュ機構の導入を検討してください。

💡 キャッシュ機構の例
// クラスにキャッシュフィールドを追加
private slotComponentsCache: Map<SlotName, { version: number; result: SlotEntry[] }> = new Map();
private cacheVersion = 0;

// notifyListeners() 内でバージョンをインクリメント
private notifyListeners(): void {
  this.cacheVersion++;
  // ... existing code
}

// getSlotComponents でキャッシュを活用
getSlotComponents<T extends SlotName>(slotName: T): SlotEntry<T>[] {
  const cached = this.slotComponentsCache.get(slotName);
  if (cached && cached.version === this.cacheVersion) {
    return cached.result as SlotEntry<T>[];
  }
  // ... compute result
  this.slotComponentsCache.set(slotName, { version: this.cacheVersion, result });
  return result as SlotEntry<T>[];
}
packages/frontend/src/lib/plugins/PluginSlot.tsx (1)

120-129: 同一プラグインが同一スロットに複数コンポーネントを登録した場合、キーが重複します。

pluginId をキーとして使用していますが、プラグインが同一スロットに複数のコンポーネントを登録する場合、React のキー重複警告が発生します。

現在の types.ts の定義で1プラグイン1スロットに1コンポーネントのみを想定しているのであれば問題ありませんが、将来の拡張性のためにインデックスを含めたキーの使用を検討してください。

💡 キー重複回避の例
- const rendered = components.map(({ pluginId, component }) => {
+ const rendered = components.map(({ pluginId, component }, index) => {
     const DynamicComponent = component as unknown as React.ComponentType<Record<string, unknown>>;
     return (
-      <PluginErrorBoundary key={pluginId} pluginId={pluginId}>
+      <PluginErrorBoundary key={`${pluginId}-${index}`} pluginId={pluginId}>
         <Suspense fallback={<PluginLoading />}>
           <DynamicComponent {...props} pluginId={pluginId} />
         </Suspense>
       </PluginErrorBoundary>
     );
   });
packages/backend/src/db/schema/pg.ts (1)

936-955: 複合主キーの使用でid導出値の冗長性を解決してください。

現在の設計では、id フィールドが {pluginId}:{key} 形式の派生値として保守され、同時に (pluginId, key) の一意インデックスが存在します。これは二重管理となっており、id 形式が正しくない場合でもデータベース制約では検出できないリスクがあります。

Drizzle ORM は複合主キーをサポートしているため、以下のように設計を変更することをお勧めします:

primaryKey({ columns: [table.pluginId, table.key] })

この方式により、id 派生値を削除し、(pluginId, key) を直接主キーにすることで、整合性を強制でき、コード上の導出ロジックの負担を軽減できます。

packages/frontend/src/pages/admin/plugins.tsx (1)

27-39: 型定義の重複について確認が必要です。

PluginPluginSystemStatus インターフェースは packages/frontend/src/lib/plugins/types.ts で既に定義されている PluginManifestResponsePluginSystemStatus と重複しています。一貫性と保守性のため、既存の型定義をインポートして使用することを検討してください。

♻️ 提案される修正
-interface Plugin {
-  id: string;
-  name: string;
-  version: string;
-  description?: string;
-  enabled: boolean;
-}
-
-interface PluginSystemStatus {
-  enabled: boolean;
-  loaded: number;
-  plugins: Plugin[];
-}
+import type { PluginSystemStatus } from "../../lib/plugins/types";
plugins/hello-world/index.js (1)

29-37: サンプルプラグインでのユーザー名ログ出力について。

payload.author.username をログに記録していますが、これはPII(個人識別情報)に該当する可能性があります。サンプルプラグインとして、ユーザー名の代わりに匿名化された情報(例:ユーザーIDの一部やイベント発生回数のみ)をログに記録することを検討してください。

♻️ 提案される修正
    context.events.on(
      "note:afterCreate",
      (payload) => {
        context.logger.info(
-          `New note created by @${payload.author.username} (${payload.note.text?.length || 0} chars)`,
+          `New note created (${payload.note.text?.length || 0} chars)`,
        );
      },
      "hello-world",
    );
packages/backend/src/routes/plugins.ts (1)

92-107: requireAuth()requireAdmin() の冗長な使用について。

requireAdmin() ミドルウェアは内部で認証チェックを行っているため(packages/backend/src/middleware/auth.ts の実装を参照)、requireAuth() の呼び出しは冗長です。ただし、明示的な意図の表現として残すことも許容されます。

♻️ 提案される修正(オプション)
-plugins.post("/:id/enable", requireAuth(), requireAdmin(), async (c: Context) => {
+plugins.post("/:id/enable", requireAdmin(), async (c: Context) => {
packages/backend/src/plugins/loader.ts (1)

456-461: package.jsondependencies フィールド解析について。

package.jsondependencies フィールドを Object.keys() で解析していますが、これにはnpmパッケージ(honozodなど)も含まれる可能性があります。プラグイン依存関係専用のフィールド(例:roxDependenciespluginDependencies)を使用することを検討してください。

♻️ 提案される修正
          dependencies: json.dependencies
            ? Array.isArray(json.dependencies)
              ? json.dependencies
-              : Object.keys(json.dependencies)
+              : undefined // Prefer explicit pluginDependencies field
            : undefined,
+          // Or add support for roxDependencies field:
+          // dependencies: json.roxDependencies || json.pluginDependencies,
packages/backend/src/plugins/init.ts (4)

43-50: configStoragePluginSystem インターフェースに追加することを検討してください

registerPluginActivityPubHandlersActivityPubHandlerOptionsconfigStorage を必要としますが、PluginSystem インターフェースはそれを公開していません。これにより、initializePluginSystem の呼び出し元が ActivityPub ハンドラーを登録する際に configStorage を別途管理する必要があります。

♻️ 提案する修正
 export interface PluginSystem {
   /** Plugin loader */
   loader: PluginLoader;
   /** Plugin routes Hono sub-app */
   routes: Hono;
+  /** Config storage for plugin configuration */
+  configStorage: IPluginConfigStorage;
   /** Shutdown function */
   shutdown: () => Promise<void>;
 }

そして、initializePluginSystem の戻り値でも configStorage を含めてください:

   return {
     loader,
     routes: pluginRoutes,
+    configStorage,
     shutdown: async () => {

152-154: as never 型アサーションは型安全性を損ないます

as never キャストは TypeScript の型チェックを完全にバイパスします。これは実行時エラーを隠す可能性があります。

ミドルウェアの型を適切に定義するか、Hono の MiddlewareHandler 型を使用することを検討してください。

♻️ 提案する修正
+import type { MiddlewareHandler } from "hono";
-        app.use("*", mw as never);
+        app.use("*", mw as MiddlewareHandler);

または、getPluginsWithMiddleware の戻り値の型を MiddlewareHandler[] に変更することで、キャストを不要にできます。


237-252: プラグインコンテキスト作成ロジックが重複しています

このコンテキスト作成ロジックは PluginLoader.createPluginContext() と重複しています。将来的に片方だけ更新され、動作の不整合が生じる可能性があります。

PluginLoader にコンテキスト作成メソッドを公開するか、共通のヘルパー関数を抽出することを検討してください。


254-263: 複合ハンドラーは常に成功を返しますが、エラーを隠す可能性があります

個々のハンドラーがエラーをスローしても、複合ハンドラーは常に { success: true } を返します。これはフォールトトレランスとしては良いですが、呼び出し元が部分的な失敗を認識できません。

部分的な失敗を報告するために、失敗したハンドラーの情報を戻り値に含めることを検討してください。

♻️ 提案する修正
+        const errors: Array<{ pluginId: string; error: string }> = [];
         for (const { pluginId, handler } of pluginHandlers) {
           // Skip disabled plugins
           if (!loader.getPlugin(pluginId)?.enabled) {
             continue;
           }
           try {
             // ... existing code ...
             await handler(activity as PluginActivity, pluginContext);
           } catch (error) {
             const message = error instanceof Error ? error.message : "Unknown error";
             pluginLogger.error(
               { pluginId, activityType, error: message },
               "Plugin ActivityPub handler error",
             );
+            errors.push({ pluginId, error: message });
             // Continue with other handlers even if one fails
           }
         }
-        return { success: true };
+        return {
+          success: errors.length === 0,
+          message: errors.length > 0 ? `${errors.length} handler(s) failed` : undefined,
+        };

- Move pluginLoader middleware before routes for proper execution order
- Add db parameter to initializePluginSystem for persistent config storage
- Remove unused getAll/setMultiple methods from DatabasePluginConfigStorage
- Fix Database type to use NodePgDatabase instead of PostgresJsDatabase
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🤖 Fix all issues with AI agents
In `@packages/backend/src/index.ts`:
- Around line 203-225: The plugin system is initialized asynchronously with
initializePluginSystem(...) which can cause race conditions because the server
(and its routes like /api/plugins and /api/x/*) may start before pluginSystem is
ready; change this to synchronous initialization by awaiting
initializePluginSystem (assign to pluginSystem) before continuing to start the
server or print the startup banner, wrapping the surrounding module entrypoint
in an async function or using top-level await as appropriate; keep the existing
try/catch around the await and retain the logger.info/logger.error calls
(referencing pluginSystem, initializePluginSystem, logger.info, logger.error) so
the server only proceeds after the plugin loader is fully initialized.

In `@packages/backend/src/plugins/init.ts`:
- Around line 150-160: The code is casting middleware items to never because
loader.getPluginsWithMiddleware() returns unknown[]; change the return type of
getPluginsWithMiddleware in packages/backend/src/plugins/loader.ts from
unknown[] to MiddlewareHandler[] so the array items are correctly typed, then
remove the unnecessary cast in init.ts (where pluginsWithMiddleware is iterated
and mw is passed to app.use). Ensure you import/retain the MiddlewareHandler
type in both loader.ts and init.ts and keep pluginId/middleware structure
unchanged so pluginLogger.debug/error logic remains valid.
- Around line 222-265: The composite handler always returns { success: true }
even when individual plugin handlers fail, which causes
InboxService.recordInboxActivity() to misreport metrics; update the
compositeHandler.handle implementation (the async handle function inside the
handlers loop) to track plugin handler failures (e.g., count errors or collect
messages) and, after iterating pluginHandlers, return success: false when any
plugin threw an error and include an errors or failureCount field with details;
ensure you still catch and log per-plugin errors via pluginLogger but propagate
aggregate failure info in the returned object so callers like
InboxService.recordInboxActivity() can record accurate metrics.
🧹 Nitpick comments (2)
packages/backend/src/plugins/config-storage.ts (2)

31-44: 型安全性に関する軽微な懸念

row.value as T のキャストは、データベースから取得した値が実際に T 型であることを保証しません。プラグインが不正な型の値を保存した場合、ランタイムエラーが発生する可能性があります。

現状の実装は多くのORMストレージで採用されている一般的なパターンであり、プラグインAPIの使用契約として許容できますが、将来的にスキーマバリデーション(例:Zodとの統合)を検討する価値があります。


49-68: new Date() の2回呼び出しによるタイムスタンプの微小なずれ

updatedAt が59行目と65行目で2回 new Date() を呼び出しています。通常は問題になりませんが、厳密にはINSERTとUPDATE時で異なるタイムスタンプになる可能性があります。

♻️ 提案される修正
 async set<T>(pluginId: string, key: string, value: T): Promise<void> {
   const id = `${pluginId}:${key}`;
+  const now = new Date();

   await this.db
     .insert(pluginConfigs)
     .values({
       id,
       pluginId,
       key,
       value: value as unknown,
-      updatedAt: new Date(),
+      updatedAt: now,
     })
     .onConflictDoUpdate({
       target: pluginConfigs.id,
       set: {
         value: value as unknown,
-        updatedAt: new Date(),
+        updatedAt: now,
       },
     });
 }

Comment on lines 150 to 160
for (const { pluginId, middleware } of pluginsWithMiddleware) {
try {
for (const mw of middleware) {
app.use("*", mw as never);
}
pluginLogger.debug({ pluginId, count: middleware.length }, "Registered plugin middleware");
} catch (error) {
const message = error instanceof Error ? error.message : "Unknown error";
pluginLogger.error({ pluginId, error: message }, "Failed to register plugin middleware");
}
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Check plugin types and middleware definitions
rg -nA 20 'middleware' packages/backend/src/plugins/types.ts packages/backend/src/plugins/loader.ts 2>/dev/null | head -100

Repository: Love-Rox/rox

Length of output: 3566


🏁 Script executed:

#!/bin/bash
# Check the actual init.ts file around the problematic lines
sed -n '140,165p' packages/backend/src/plugins/init.ts

Repository: Love-Rox/rox

Length of output: 996


🏁 Script executed:

#!/bin/bash
# Check Hono app.use() type definition to understand expected middleware type
rg -nB 5 -A 10 'app\.use.*\*' packages/backend/src/plugins/init.ts

Repository: Love-Rox/rox

Length of output: 708


🏁 Script executed:

#!/bin/bash
# Check MiddlewareHandler definition and its import
rg -nB 5 -A 5 'MiddlewareHandler' packages/backend/src/plugins/types.ts | head -50

Repository: Love-Rox/rox

Length of output: 691


🏁 Script executed:

#!/bin/bash
# Check imports in types.ts to understand MiddlewareHandler origin
head -50 packages/backend/src/plugins/types.ts | grep -E '^import|^export|MiddlewareHandler'

Repository: Love-Rox/rox

Length of output: 260


🏁 Script executed:

#!/antml/bash
# Check why loader.ts uses unknown[] instead of MiddlewareHandler[]
sed -n '375,395p' packages/backend/src/plugins/loader.ts

Repository: Love-Rox/rox

Length of output: 630


ミドルウェアの型を MiddlewareHandler で明確に定義してください

Line 153 の mw as never キャストが必要な理由は、loader.getPluginsWithMiddleware()unknown[] 型で返しているためです。プラグイン型定義では正しく MiddlewareHandler[] として定義されていますが、loader でわざと型を広げられています。

この型の不一致を解決するには、packages/backend/src/plugins/loader.tsgetPluginsWithMiddleware() メソッドの戻り値型を unknown[] から MiddlewareHandler[] に変更し、init.ts のキャストを削除してください。

🤖 Prompt for AI Agents
In `@packages/backend/src/plugins/init.ts` around lines 150 - 160, The code is
casting middleware items to never because loader.getPluginsWithMiddleware()
returns unknown[]; change the return type of getPluginsWithMiddleware in
packages/backend/src/plugins/loader.ts from unknown[] to MiddlewareHandler[] so
the array items are correctly typed, then remove the unnecessary cast in init.ts
(where pluginsWithMiddleware is iterated and mw is passed to app.use). Ensure
you import/retain the MiddlewareHandler type in both loader.ts and init.ts and
keep pluginId/middleware structure unchanged so pluginLogger.debug/error logic
remains valid.

- Use top-level await for plugin initialization to avoid race conditions
- Fix middleware type in loader.ts (unknown[] → MiddlewareHandler[])
- Remove unnecessary type cast in init.ts
- Track plugin handler errors in compositeHandler for accurate metrics
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🤖 Fix all issues with AI agents
In `@packages/backend/src/plugins/init.ts`:
- Around line 148-159: Registered plugin middleware is never skipped after a
plugin is disabled because app.use("*", mw) persists; wrap each middleware with
a runtime enabled check before registering so the wrapper queries the plugin's
current enabled state (e.g., via loader.getPlugin(pluginId)?.isEnabled() or an
equivalent runtime check) and only invokes the original mw when enabled; replace
direct app.use("*", mw) with app.use("*", (req, res, next) => { if
(pluginEnabledCheck(pluginId)) return mw(req, res, next); return next(); }) and
keep existing error logging around registration using pluginId and middleware
references.

In `@packages/backend/src/plugins/loader.ts`:
- Around line 432-470: The loadManifest function currently converts object-form
dependencies from package.json into plugin IDs; update it so that when reading
package.json (detect via the current path variable or manifestPaths), if
json.dependencies is an object, ignore it (set dependencies undefined) and only
accept an array-form dependencies (and for plugin.json continue to accept an
array), so we don't treat npm package names as plugin IDs; adjust the
dependencies assignment accordingly in loadManifest (use the path/file name
check and only map Array.isArray(json.dependencies) => json.dependencies,
otherwise undefined).

Comment on lines 148 to 159
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

find . -name "init.ts" -path "*/plugins/*" | head -20

Repository: Love-Rox/rox

Length of output: 94


🏁 Script executed:

cat -n packages/backend/src/plugins/init.ts | sed -n '140,170p'

Repository: Love-Rox/rox

Length of output: 1305


🏁 Script executed:

# Find the plugin loader and understand the getPlugin and setEnabled methods
rg -A 5 "setEnabled|getPlugin" packages/backend/src/plugins/ -t ts

Repository: Love-Rox/rox

Length of output: 4155


🏁 Script executed:

# Check for any existing middleware filtering or plugin state checks
rg -B 3 -A 3 "getPluginsWithMiddleware|middleware.*enabled" packages/backend/src/plugins/ -t ts

Repository: Love-Rox/rox

Length of output: 1125


プラグインミドルウェアを無効化時に実行させないようにする

setEnabled(false) 後もミドルウェアが実行され続けます。getPluginsWithMiddleware() は有効なプラグインのみを返しますが、一度 app.use("*", mw) で登録されたミドルウェアは残ります。ルート (routes) にはランタイムの enabled チェックが実装されていますが、ミドルウェアにはありません。ミドルウェア登録時に enabled チェック付きラッパーで保護してください。

修正案
      for (const mw of middleware) {
-       app.use("*", mw);
+       app.use("*", async (c, next) => {
+         if (!loader.getPlugin(pluginId)?.enabled) {
+           return next();
+         }
+         return mw(c, next);
+       });
      }
🤖 Prompt for AI Agents
In `@packages/backend/src/plugins/init.ts` around lines 148 - 159, Registered
plugin middleware is never skipped after a plugin is disabled because
app.use("*", mw) persists; wrap each middleware with a runtime enabled check
before registering so the wrapper queries the plugin's current enabled state
(e.g., via loader.getPlugin(pluginId)?.isEnabled() or an equivalent runtime
check) and only invokes the original mw when enabled; replace direct
app.use("*", mw) with app.use("*", (req, res, next) => { if
(pluginEnabledCheck(pluginId)) return mw(req, res, next); return next(); }) and
keep existing error logging around registration using pluginId and middleware
references.

- Wrap plugin middleware with runtime enabled check to skip when disabled
- Fix loadManifest to ignore npm object-form dependencies in package.json
  (only array-form plugin IDs are accepted from package.json)
@sasagar sasagar merged commit 8791c92 into dev Jan 28, 2026
5 checks passed
@sasagar sasagar deleted the chore/update-deps branch January 28, 2026 05:47
@sasagar sasagar mentioned this pull request Feb 1, 2026
4 tasks
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant

Comments