Skip to content

feat(connectors): pluggable connector system with manifest discovery - #604

Merged
alfredo1996 merged 2 commits into
release/2.0from
feat/issue-603-pluggable-connectors
Apr 24, 2026
Merged

feat(connectors): pluggable connector system with manifest discovery#604
alfredo1996 merged 2 commits into
release/2.0from
feat/issue-603-pluggable-connectors

Conversation

@alfredo1996

@alfredo1996 alfredo1996 commented Apr 24, 2026

Copy link
Copy Markdown
Owner

Summary

Mirror the chart plugin pattern for database connectors. External connectors (MongoDB, MySQL, ClickHouse, etc.) can now be installed as npm packages and registered via a neoboard-connectors.json manifest — no forking required.

What's new

  • neoboard-connectors.json — manifest file (same pattern as neoboard-plugins.json)
  • scripts/generate-connector-imports.mjs — build-time codegen (18 unit tests)
  • connection/src/external-connectors.generated.ts — auto-generated from manifest
  • connector-registry.ts — loads EXTERNAL_CONNECTORS after built-ins with conflict detection
  • predev/prebuild scripts updated to run both plugin + connector codegen

How to add an external connector

  1. Install the npm package: npm install @myorg/neoboard-mongodb
  2. Add to manifest:
{
  "connectors": [
    { "package": "@myorg/neoboard-mongodb", "overrides": false }
  ]
}
  1. Run npm run generate:connectors (or it runs automatically on dev/build)

The package exports a ConnectorPlugin with type, label, category, createModule(), and optional formFields for auto-generated connection forms.

Test plan

  • Codegen tests pass (18/18) — validate, render, manifest parsing
  • App tests pass (2180/2180) — import chain works
  • Empty manifest generates valid empty file
  • Existing Neo4j and PostgreSQL connectors unaffected

Closes #603

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features
    • External and community connectors are now automatically discovered and loaded during application startup.
    • External connectors can selectively override built-in connectors through explicit configuration.
    • Added configuration file support for centralized connector registration management.

…603)

Mirror the chart plugin pattern for connectors: external database
connectors can now be installed as npm packages and registered via
a neoboard-connectors.json manifest without forking the repo.

Infrastructure:
- neoboard-connectors.json manifest (same shape as neoboard-plugins.json)
- scripts/generate-connector-imports.mjs codegen (18 tests)
- connection/src/external-connectors.generated.ts auto-generated
- connector-registry.ts loads EXTERNAL_CONNECTORS after built-ins
- Conflict detection: same-type without overrides:true throws at startup
- predev/prebuild scripts run both plugin + connector codegen

Manifest format:
  { "connectors": [{ "package": "@myorg/mongodb", "overrides": false }] }

External connector npm package exports a ConnectorPlugin:
  { type, label, category, queryLanguage, createModule(), formFields, ... }

Closes #603

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@coderabbitai

coderabbitai Bot commented Apr 24, 2026

Copy link
Copy Markdown

Warning

Rate limit exceeded

@alfredo1996 has exceeded the limit for the number of commits that can be reviewed per hour. Please wait 39 minutes and 57 seconds before requesting another review.

Your organization is not enrolled in usage-based pricing. Contact your admin to enable usage-based pricing to continue reviews beyond the rate limit, or try again in 39 minutes and 57 seconds.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 5b27b9be-5041-4817-b579-f66156dac42e

📥 Commits

Reviewing files that changed from the base of the PR and between 771fe79 and 8f7662f.

📒 Files selected for processing (1)
  • connection/src/connector-registry.ts

Walkthrough

This PR implements a pluggable connector system with manifest-based discovery. A new code generator reads neoboard-connectors.json, validates external connector entries, and generates typed imports. The connector registry loads these external connectors during initialization with built-in conflict detection and optional override support.

Changes

Cohort / File(s) Summary
Build-time Generation
scripts/generate-connector-imports.mjs, scripts/__tests__/generate-connector-imports.test.mjs
New codegen script with validation (validateEntry, validateManifest) and source rendering (renderSource). Reads manifest, validates connector packages/exports/overrides, generates external-connectors.generated.ts with typed imports. Comprehensive test suite covering schema validation, duplicate detection, and generated output format.
Manifest & Auto-generated Output
neoboard-connectors.json, connection/src/external-connectors.generated.ts
New manifest schema defining empty connectors array. Generated TypeScript module exports ExternalConnectorEntry type and EXTERNAL_CONNECTORS array (initially empty, populated by generator).
Build System Integration
package.json
Adds generate:connectors script and chains node scripts/generate-connector-imports.mjs into predev and prebuild hooks.
Connector Registry Integration
connection/src/connector-registry.ts
Auto-loads EXTERNAL_CONNECTORS after built-ins during module init. Detects type conflicts: throws startup error unless overrides: true, then unregisters existing connector before replacing it.

Sequence Diagram(s)

sequenceDiagram
    participant manifest as neoboard-connectors.json
    participant generator as generate-connector-imports.mjs
    participant output as external-connectors.generated.ts
    participant pkg as package.json hooks
    
    pkg->>generator: predev/prebuild execute
    generator->>manifest: read & validate
    manifest-->>generator: entries array
    generator->>generator: validate schema<br/>(package, export, overrides)
    generator->>generator: detect duplicates
    generator->>generator: render imports & array
    generator->>output: write typed source
    output-->>generator: ✓ generated
Loading
sequenceDiagram
    participant registry as connector-registry<br/>(module init)
    participant external as EXTERNAL_CONNECTORS
    participant builtins as Built-in plugins
    participant storage as Registry storage
    
    registry->>builtins: register built-ins first
    builtins-->>storage: ✓ registered
    registry->>external: import & iterate
    external-->>registry: [ExternalConnectorEntry, ...]
    loop for each external connector
        registry->>storage: check type exists?
        alt type exists && no overrides
            registry->>registry: ✗ throw startup error
        else type exists && overrides
            registry->>storage: unregister old type
            storage-->>registry: ✓ removed
        end
        registry->>storage: register external plugin
        storage-->>registry: ✓ registered
    end
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

Suggested labels

enhancement

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately summarizes the main change: introducing a pluggable connector system with manifest-based discovery, mirroring the chart plugin pattern.
Linked Issues check ✅ Passed All Phase 1 infrastructure requirements from issue #603 are implemented: manifest, codegen script, generated file, registry changes, and unit tests covering registration and conflict detection.
Out of Scope Changes check ✅ Passed All changes are within scope: manifest file, codegen script with tests, generated connector file, registry changes, and build script updates directly implement the linked issue requirements.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% 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 unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/issue-603-pluggable-connectors

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.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🧹 Nitpick comments (5)
connection/src/connector-registry.ts (1)

2-11: Header JSDoc is stale — mention the manifest path.

The top-of-file docblock only describes the "import and register it here" workflow, but the primary path for external connectors is now neoboard-connectors.json + codegen. Add a bullet so operators don't miss it.

📝 Suggested tweak
 /**
  * Global connector registry — singleton that auto-registers built-in
  * connectors (Neo4j, PostgreSQL) on first import.
  *
- * To add a new connector:
- *   1. Create a plugin file implementing ConnectorPlugin
- *   2. Import and register it here
+ * To add a new connector:
+ *   1. Built-in: implement ConnectorPlugin and register() it below.
+ *   2. External: publish a package exporting a ConnectorPlugin, list it
+ *      in neoboard-connectors.json, and let `npm run generate:connectors`
+ *      wire it into EXTERNAL_CONNECTORS.
  *
  * External/community connectors can call registerConnector() from
  * their own package after importing this module.
  */
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@connection/src/connector-registry.ts` around lines 2 - 11, Update the header
JSDoc in connector-registry.ts to mention the new manifest/codegen workflow: add
a bullet that external/community connectors can be discovered via the
neoboard-connectors.json manifest (used by the codegen pipeline) in addition to
calling registerConnector() at runtime, and reference the ConnectorPlugin
interface and registerConnector function so operators know which shape and API
the manifest/codegen must produce.
scripts/__tests__/generate-connector-imports.test.mjs (2)

40-44: Nit: test name says "invalid export identifier" but asserts on identifier syntax.

Minor readability: the message in the error is "must be a valid JavaScript identifier". Could tighten by also asserting a case like { export: "123abc" } (leading digit) to make the intent crisper. Optional.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@scripts/__tests__/generate-connector-imports.test.mjs` around lines 40 - 44,
Update the test for validateEntry to better reflect and assert identifier-syntax
failures: rename the spec string from "rejects invalid export identifier" to
something like "rejects invalid export identifier syntax" and add an explicit
assertion that validateEntry({ package: "pkg", export: "123abc" }, 0) returns
the error message containing "must be a valid JavaScript identifier" (in
addition to the existing "not-valid" case) so the intent and a clear
leading-digit identifier failure are both covered.

99-128: Consider a multi-entry renderSource case and a runGenerator smoke test.

Current tests nail the branches of the pure helpers. A couple of additions would catch likely future regressions:

  • renderSource with 2+ entries → asserts newline joining and that aliases increment (externalConnector0, externalConnector1).
  • runGenerator with a tmp dir: (a) missing manifest → empty file written, (b) second run → wrote: false (idempotency), (c) malformed JSON → ok: false with useful error.
  • validateEntry rejecting non-boolean overrides and non-string export.

Not blocking — the 18 cases already pin the critical schema rules.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@scripts/__tests__/generate-connector-imports.test.mjs` around lines 99 - 128,
Add tests that cover multi-entry and generator/validation edge cases: for
renderSource, add a test invoking renderSource with 2+ entries and assert the
output contains both aliases (externalConnector0 and externalConnector1) and
that the imports/entries are separated by a newline; for runGenerator, add a
smoke test using a temp directory that verifies (a) when manifest is missing an
empty file is written, (b) a second run reports wrote: false (idempotency), and
(c) when the manifest contains malformed JSON the result is ok: false and
returns a useful error; and for validateEntry add tests that ensure it rejects
non-boolean overrides and non-string export values. Reference the existing test
suite/helpers renderSource, runGenerator, and validateEntry when adding these
cases.
scripts/generate-connector-imports.mjs (2)

248-252: Log path breaks on Windows.

OUTPUT_PATH.replace(REPO_ROOT + "/", "") hard-codes a forward slash. On Windows the separator is \, so the replace is a no-op and the log prints an absolute path. Cosmetic only, but trivial to fix.

🪟 Suggested fix
-import { dirname, resolve } from "node:path";
+import { dirname, relative, resolve } from "node:path";
@@
-    console.log(
-      `Generated ${OUTPUT_PATH.replace(REPO_ROOT + "/", "")} from manifest`,
-    );
+    console.log(
+      `Generated ${relative(REPO_ROOT, OUTPUT_PATH)} from manifest`,
+    );
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@scripts/generate-connector-imports.mjs` around lines 248 - 252, The log
prints an absolute path on Windows because OUTPUT_PATH.replace(REPO_ROOT + "/",
"") hard-codes "/" as the separator; change the logging to compute a
platform-safe relative path (e.g., using path.relative(REPO_ROOT, OUTPUT_PATH))
before printing so the console.log in scripts/generate-connector-imports.mjs
(the block checking result.wrote) outputs a repo-relative path cross-platform;
update the reference to use that relative path variable instead of the string
replace.

189-200: Consider a quiet-but-visible note when the manifest is absent.

Silently generating an empty file is the documented behavior, but if a contributor deletes neoboard-connectors.json by accident their external connectors vanish with zero signal. A single console.warn (still ok: true) would surface the situation without failing the build. Take it or leave it — the current behavior matches the PR spec.

🗣️ Optional nudge
   if (!existsSync(manifestPath)) {
     // No manifest = no external connectors. Generate empty file silently.
+    if (opts.manifestPath === undefined) {
+      console.warn(
+        `[generate-connector-imports] ${relative(REPO_ROOT, manifestPath)} not found; emitting empty EXTERNAL_CONNECTORS.`,
+      );
+    }
     const source = renderSource([]);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@scripts/generate-connector-imports.mjs` around lines 189 - 200, When
manifestPath is missing the function currently silently writes an empty
connector output; update the branch that handles !existsSync(manifestPath) to
emit a single visible warning (e.g., console.warn) indicating the manifest is
absent and external connectors will not be generated while preserving the
current return values ({ ok: true, errors: [], wrote: ...}). Locate the block
that calls renderSource([]), checks existing via existsSync(outputPath) /
readFileSync, and writes via writeFileSync, and insert the console.warn before
returning so the behavior of renderSource, existing/outputPath checks, and
writeFileSync remains unchanged except for the added warning.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@connection/src/connector-registry.ts`:
- Around line 31-44: The error message thrown when an external connector
conflicts currently hard-codes "built-in" and can be misleading for
external-vs-external clashes; update the logic around EXTERNAL_CONNECTORS so
when registry.has(plugin.type) you detect whether the existing entry came from a
built-in or another external source and throw a clearer error: if overrides is
false, throw an error referencing plugin.type and saying the connector conflicts
with an existing connector (specifying "built-in" only when the existing entry
is a built-in, otherwise indicate "another external connector"), and include
guidance to set "overrides": true in neoboard-connectors.json when appropriate;
use the existing symbols (EXTERNAL_CONNECTORS, plugin.type, overrides,
registry.has, registry.unregister, registry.register) to locate and change the
message.

---

Nitpick comments:
In `@connection/src/connector-registry.ts`:
- Around line 2-11: Update the header JSDoc in connector-registry.ts to mention
the new manifest/codegen workflow: add a bullet that external/community
connectors can be discovered via the neoboard-connectors.json manifest (used by
the codegen pipeline) in addition to calling registerConnector() at runtime, and
reference the ConnectorPlugin interface and registerConnector function so
operators know which shape and API the manifest/codegen must produce.

In `@scripts/__tests__/generate-connector-imports.test.mjs`:
- Around line 40-44: Update the test for validateEntry to better reflect and
assert identifier-syntax failures: rename the spec string from "rejects invalid
export identifier" to something like "rejects invalid export identifier syntax"
and add an explicit assertion that validateEntry({ package: "pkg", export:
"123abc" }, 0) returns the error message containing "must be a valid JavaScript
identifier" (in addition to the existing "not-valid" case) so the intent and a
clear leading-digit identifier failure are both covered.
- Around line 99-128: Add tests that cover multi-entry and generator/validation
edge cases: for renderSource, add a test invoking renderSource with 2+ entries
and assert the output contains both aliases (externalConnector0 and
externalConnector1) and that the imports/entries are separated by a newline; for
runGenerator, add a smoke test using a temp directory that verifies (a) when
manifest is missing an empty file is written, (b) a second run reports wrote:
false (idempotency), and (c) when the manifest contains malformed JSON the
result is ok: false and returns a useful error; and for validateEntry add tests
that ensure it rejects non-boolean overrides and non-string export values.
Reference the existing test suite/helpers renderSource, runGenerator, and
validateEntry when adding these cases.

In `@scripts/generate-connector-imports.mjs`:
- Around line 248-252: The log prints an absolute path on Windows because
OUTPUT_PATH.replace(REPO_ROOT + "/", "") hard-codes "/" as the separator; change
the logging to compute a platform-safe relative path (e.g., using
path.relative(REPO_ROOT, OUTPUT_PATH)) before printing so the console.log in
scripts/generate-connector-imports.mjs (the block checking result.wrote) outputs
a repo-relative path cross-platform; update the reference to use that relative
path variable instead of the string replace.
- Around line 189-200: When manifestPath is missing the function currently
silently writes an empty connector output; update the branch that handles
!existsSync(manifestPath) to emit a single visible warning (e.g., console.warn)
indicating the manifest is absent and external connectors will not be generated
while preserving the current return values ({ ok: true, errors: [], wrote:
...}). Locate the block that calls renderSource([]), checks existing via
existsSync(outputPath) / readFileSync, and writes via writeFileSync, and insert
the console.warn before returning so the behavior of renderSource,
existing/outputPath checks, and writeFileSync remains unchanged except for the
added warning.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 72469a16-8936-4d09-b749-eba7179f4bd5

📥 Commits

Reviewing files that changed from the base of the PR and between ee01f34 and 771fe79.

📒 Files selected for processing (6)
  • connection/src/connector-registry.ts
  • connection/src/external-connectors.generated.ts
  • neoboard-connectors.json
  • package.json
  • scripts/__tests__/generate-connector-imports.test.mjs
  • scripts/generate-connector-imports.mjs

Comment thread connection/src/connector-registry.ts
…essage

CodeRabbit correctly noted the error message hard-coded "built-in" even
for external-vs-external collisions. Now checks whether the conflicting
connector is neo4j/postgresql (built-in) or a previously-registered
external, and adjusts the message accordingly.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@sonarqubecloud

Copy link
Copy Markdown

Quality Gate Failed Quality Gate failed

Failed conditions
18.8% Coverage on New Code (required ≥ 80%)

See analysis details on SonarQube Cloud

@alfredo1996
alfredo1996 merged commit 45cd2b9 into release/2.0 Apr 24, 2026
12 of 13 checks passed
@alfredo1996
alfredo1996 deleted the feat/issue-603-pluggable-connectors branch April 24, 2026 18:30
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.

2 participants