Skip to content

feat(api): add ProjectConnector for end-user account connections - #4

Merged
BlackHole1 merged 2 commits into
mainfrom
feat/project-connector
Jun 25, 2026
Merged

feat(api): add ProjectConnector for end-user account connections#4
BlackHole1 merged 2 commits into
mainfrom
feat/project-connector

Conversation

@BlackHole1

Copy link
Copy Markdown
Member

Adds ProjectConnector, a new client for building a SaaS platform on OOMOL: connect a third-party account on behalf of each of your own end-users and run actions for them — the composio / pipedream "managed auth" model. The personal Connector is unchanged; this is purely additive.

The key design decision is to ship this as a separate client rather than a namespace on Connector. You construct it with a project API key (oo_proj_…) over the same Authorization: Bearer transport, and it exposes only project-scoped operations — disjoint methods and types, with no shared execute / service namespaces / proxy / catalog / apps / using. A project key unlocks only the project surface and a personal key only the personal one, so the two mental models can't bleed into each other (the type fixtures assert this disjointness).

Surface:

  • connect.oauth(externalUserId, …) returns a pending ConnectionRequest with an authorizationUrl to send the user to; connect.apiKey / connect.customCredential are synchronous and return a ready ConnectedAccount.
  • getConnectionRequest(id) and waitForConnection(requestOrId, …) poll an OAuth link to a terminal status (returning on connected / failed / natural expired; throwing the non-retryable client_wait_timeout only when the overall maxWaitMs elapses).
  • execute / executeRaw(externalUserId, actionId, input, …), reusing the existing @oomol-lab/connector-types action registry for precise per-action types.
  • forUser(externalUserId) binds one end-user and drops the repeated id.

The SDK surface speaks externalUserId and connectionName everywhere; the gateway's wire spellings (userId, alias) are mapped only inside request/response serialization.

Supporting changes: four new ConnectorError codes (provider_config_not_found, connection_request_not_found, connected_account_not_found, and the client-only, non-retryable client_wait_timeout); abortErrorFrom plus a new shared assertHeadersSafe are lifted into http and used by both clients; and ensureSelfLink in the type-acceptance harness now replaces a stale self-installed copy that could otherwise shadow the live build during type checks.

Docs and a runnable examples/project.ts lifecycle are included; tests cover the wire shapes, polling / timeout / abort, selector precedence, and client disjointness.

Add a SaaS-platform capability alongside the personal `Connector`: a
platform built on OOMOL can now connect a third-party account on behalf
of each of its own end-users and run actions for them — the composio /
pipedream "managed auth" model.

This ships as a SEPARATE `ProjectConnector` client, constructed with a
project API key (`oo_proj_…`) over the same Bearer transport. Keeping it
a distinct class — disjoint methods and types from `Connector`, with no
shared `execute` / namespace / `proxy` / `catalog` / `apps` / `using`
surface — means a project key only unlocks project-scoped operations and
the two mental models never bleed into each other.

The surface: `connect.oauth` (pending `ConnectionRequest` +
`authorizationUrl`) plus the synchronous `connect.apiKey` /
`connect.customCredential` (ready `ConnectedAccount`);
`getConnectionRequest` and a polling `waitForConnection`; `execute` /
`executeRaw` that reuse the existing action registry for precise
per-action types; and `forUser(externalUserId)` to bind one end-user.
The SDK speaks `externalUserId` and `connectionName` everywhere; the
wire spellings (`userId`, `alias`) live only in request serialization.

Supporting changes: add the `provider_config_not_found`,
`connection_request_not_found`, `connected_account_not_found`, and
client-only `client_wait_timeout` error codes; share `abortErrorFrom`
and a new `assertHeadersSafe` helper from `http`; and harden
`ensureSelfLink` in the type-acceptance harness so a stale self-copy
can no longer shadow the live build.

Signed-off-by: Kevin Cui <bh@bugs.cc>
@coderabbitai

coderabbitai Bot commented Jun 25, 2026

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 885f1511-f40d-470d-a84f-10a9e257a3bf

📥 Commits

Reviewing files that changed from the base of the PR and between 2c69e3e and d555ed5.

📒 Files selected for processing (3)
  • scripts/test-types.ts
  • src/project.ts
  • test/project.test.ts
🚧 Files skipped from review as they are similar to previous changes (3)
  • scripts/test-types.ts
  • src/project.ts
  • test/project.test.ts

Summary by CodeRabbit

  • New Features
    • Added ProjectConnector for project-scoped account linking and executing actions on behalf of end users.
    • Added OAuth, API key, and custom credential connect flows, plus scoped forUser workflows.
    • Added documentation and a new project example covering the full managed-auth flow.
  • Bug Fixes
    • Hardened request header validation to prevent malformed CR/LF input.
    • Improved connector error coverage with new error codes, including non-retryable client wait timeouts.
  • Tests
    • Expanded type tests and added an end-to-end ProjectConnector test suite.
  • Chores
    • Made the type-test symlink/self-link setup more robust.

Walkthrough

Adds ProjectConnector, a project-scoped client that uses a project API key to connect end-user accounts, poll connection requests, and execute actions on behalf of a user. It re-exports the new client and project-specific types, adds shared header validation and a new client timeout error code, updates type/runtime tests and test helpers, and includes README and example updates.

Sequence Diagram(s)

sequenceDiagram
  participant App
  participant ProjectConnector
  participant ProjectUser
  participant fetch

  App->>ProjectConnector: forUser(EXTERNAL_USER_ID)
  ProjectConnector-->>App: ProjectUser
  App->>ProjectUser: connect.oauth(...)
  ProjectUser->>fetch: POST connection request
  fetch-->>ProjectUser: authorizationUrl + connection request
  App->>ProjectUser: waitForConnection(connectionRequestId)
  loop until terminal status or maxWaitMs
    ProjectUser->>fetch: GET connection request status
    fetch-->>ProjectUser: status
  end
  App->>ProjectUser: execute(...)
  ProjectUser->>fetch: POST action execution
  fetch-->>ProjectUser: execution result
Loading

Possibly related PRs

  • oomol-lab/connector-sdk#1: Renames gateway account alias handling to connectionName, which matches this PR’s alias-to-connection-name mapping in the project client.
🚥 Pre-merge checks | ✅ 4
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title matches the required type(scope): subject format and clearly describes the ProjectConnector addition.
Description check ✅ Passed The description is directly related to the PR and accurately summarizes the new ProjectConnector surface and supporting changes.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

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

✨ Finishing Touches
✨ Simplify code
  • Create PR with simplified code
  • Commit simplified code in branch feat/project-connector

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

@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: 4

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@scripts/test-types.ts`:
- Line 13: The symlink handling in test-types.ts is too permissive because it
only checks whether node_modules/@oomol-lab/connector is a symlink, not whether
it points to the current checkout. Update the setup logic around the symlink
check and creation to verify the symlink target matches the expected live repo
location before reusing it, and if it points elsewhere, replace it so the
harness always uses the correct package source. Use the existing setup path
logic in the script’s symlinkSync/lstatSync flow to keep the live repo from
being shadowed by a stale checkout.

In `@src/project.ts`:
- Around line 367-387: The wait loop in waitForConnection is checking maxWaitMs
only after getConnectionRequest already starts a poll, so an over-budget poll
can still run past the wall-clock cap. Move the deadline check ahead of
getConnectionRequest in the loop, and pass a remaining-time budget into the
request path so the poll itself is clamped or aborted when maxWaitMs is reached;
keep using the existing symbols waitForConnection, getConnectionRequest, and
deps.sleep.
- Around line 300-303: The provider selector serialization in providerKeys
currently prefers providerConfigId and silently drops service when both are
present. Update providerKeys in src/project.ts to enforce the “exactly one”
contract by rejecting selectors that include both fields, and only serialize
when exactly one of providerConfigId or service is set. Keep the check close to
providerKeys so callers get a clear failure instead of sending the wrong
selector.

In `@test/project.test.ts`:
- Around line 7-25: The mocked gateway payload helper in requestPayload (and the
related test fixtures it feeds) is using externalUserId instead of the wire
userId field, so these tests bypass the ConnectionRequest/ConnectedAccount
mapping. Update the mocked responses to use userId at the wire boundary and keep
the domain translation verified through the existing mapper-focused test paths,
so regressions in the userId -> externalUserId conversion are caught.
🪄 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: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 8ecef99b-75da-4510-a930-4dc89a078fb3

📥 Commits

Reviewing files that changed from the base of the PR and between 5bd9fcc and 2c69e3e.

📒 Files selected for processing (13)
  • README.md
  • examples/README.md
  • examples/project.ts
  • fixtures/consumer/partial-b/index.test-d.ts
  • fixtures/consumer/with-b/index.test-d.ts
  • scripts/test-types.ts
  • src/connector.ts
  • src/errors.ts
  • src/http.ts
  • src/index.ts
  • src/project.ts
  • test/helpers.ts
  • test/project.test.ts

Comment thread scripts/test-types.ts Outdated
Comment thread src/project.ts Outdated
Comment thread src/project.ts Outdated
Comment thread test/project.test.ts
Address CodeRabbit review on the ProjectConnector PR.

`providerKeys` rejected a both-present (or neither-present) provider
selector by silently dropping `service` and connecting against the wrong
provider config — which also masked the conflict from the gateway's
exactly-one check. It now throws `client_invalid_request` locally, the
same precheck the client already does for a missing apiKey or a CRLF
header.

`waitForConnection` only checked the `maxWaitMs` budget after a poll, so
the final poll could run its own per-request timeout and retries past
the documented wall-clock cap. It now checks the deadline BEFORE each
poll and clamps the poll's timeout to the remaining budget, threading
the client's default timeout through a new `defaultTimeoutMs` seam.

`ensureSelfLink` returned on any symlink; a link pointing at a different
checkout would still shadow the live build. It now also requires the
symlink target to resolve to the repo root.

Tests: pin no-poll-past-cap, and the existing sleep-clamp test still
asserts the clamped final sleep.

Signed-off-by: Kevin Cui <bh@bugs.cc>
@BlackHole1
BlackHole1 merged commit 78f9595 into main Jun 25, 2026
2 checks passed
@BlackHole1
BlackHole1 deleted the feat/project-connector branch June 25, 2026 10:15
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