@happy-technologies/connector-core
v0.3.0
Published
Shared connector substrate for Happy Technologies products: manifest/descriptor schema, a generic registry, config validation, and an OAuth/credential substrate. Domain-agnostic; products supply their own connector contract, crypto, and storage.
Readme
@happy-technologies/connector-core
The shared connector substrate for Happy Technologies products. It provides the parts of a connector platform that are identical across products, and nothing that is specific to one:
- Manifest / descriptor schema (
ConnectorDescriptor): the single source of truth for a connector's metadata, driving config validation, the catalog, and the registration form. - A generic
ConnectorRegistry<T>: register connectors, look them up, list the catalog, validate config. Replaces closed enums and DB CHECK constraints. - Descriptor-driven config validation, including a guard that rejects credential-shaped keys on the non-secret config path.
- An OAuth and credential substrate (
OAuthSubstrate): the authorization-code flow, token refresh, and encrypted token storage, behind injected interfaces. - An optional document-connector contract (
SourceConnector) for products that ingest documents.
Design boundary
This package owns metadata, registry mechanics, validation, and auth orchestration. It does not own:
- Product domain logic (compiling to artifacts, reconciling to CIs).
- Crypto. You inject a
SecretCipher. - Storage. You inject a
CredentialStoreand optionally anOAuthStateStore. - A specific OAuth library. You inject per-provider
OAuthClientinstances.
This is the seam that lets the auth backend be swapped later (for example to a self-hosted Nango) without touching connector code.
Wiring the OAuth substrate with Arctic
Arctic (arcticjs.dev, MIT) provides typed OAuth 2.0 clients for Microsoft
Entra ID, Google, Atlassian, GitHub, Slack, and more. Arctic's token object
exposes methods (accessToken()), so wrap each client to the OAuthClient
shape this package expects:
import * as arctic from "arctic";
import { OAuthSubstrate, type OAuthClient } from "@happy-technologies/connector-core";
function wrap(provider: {
createAuthorizationURL: (state: string, codeVerifier: string, scopes: string[]) => URL;
validateAuthorizationCode: (code: string, codeVerifier: string) => Promise<arctic.OAuth2Tokens>;
refreshAccessToken: (refreshToken: string, scopes?: string[]) => Promise<arctic.OAuth2Tokens>;
}): OAuthClient {
const toTokens = (t: arctic.OAuth2Tokens) => ({
accessToken: t.accessToken(),
refreshToken: t.hasRefreshToken() ? t.refreshToken() : undefined,
accessTokenExpiresAt: t.accessTokenExpiresAt(),
});
return {
createAuthorizationURL: (state, verifier, scopes) =>
provider.createAuthorizationURL(state, verifier, scopes),
validateAuthorizationCode: async (code, verifier) =>
toTokens(await provider.validateAuthorizationCode(code, verifier)),
refreshAccessToken: async (refreshToken, scopes) =>
toTokens(await provider.refreshAccessToken(refreshToken, scopes)),
};
}
const google = new arctic.Google(clientId, clientSecret, redirectUri);
const oauth = new OAuthSubstrate({
clients: new Map([["google", wrap(google)]]),
credentials: myCredentialStore, // backed by your DB + envelope crypto
cipher: myEnvelopeCipher, // encryptSecret / decryptSecret
});
// In a fetch path:
const cred = await oauth.resolve(sourceId);
const res = await fetch(url, { headers: cred.headers });For client-credentials / app-only grants (Microsoft Graph app-only, ServiceNow
service accounts), implement OAuthClient directly over a token endpoint, or
use openid-client.
Scripts
bun testruns the unit suite.bun run type-checkrunstsc --noEmit.bun run buildemitsdist/with declarations.bun run check:no-em-dashenforces the org content rule.
Status
v0.1: substrate core (descriptor, registry, validation, auth, document contract) with tests. Consumed first by Happy Knowledge; HappyCMDB's existing framework migrates onto the same manifest schema over time.
