aaspai-integrakit
v0.0.1
Published
Universal integration hub: OAuth2 connections, actions, triggers, webhooks, logs. Express & Nest adapters. OpenAPI action generator.
Maintainers
Readme
integrakit-universal
Universal Integration Hub (modeled on the legacy integration_module) with equivalent APIs and behavior:
- Providers registry and dynamic registration from OpenAPI
- OAuth2 apps and user Connections
- Actions and Triggers (poll/webhook) with scheduler
- Trigger Logs, analytics, cleanup
- Dynamic OAuth helper endpoints
- Dynamic API endpoints and Execution helpers
- Nodes facade (list/describe/execute, load-options)
- Express & Nest adapters; Storage (Memory, MongoDB)
Install
npm i integrakit-universal simple-oauth2 mongodb js-yamlCore concepts
- App: OAuth client (e.g., Google/GitHub app)
- Connection: per-user/tenant account with tokens
- Provider:
{ id, name, actions, triggers, oauth?, webhook? } - Action: async function
(ctx, params)=>any - Trigger: async function
(ctx, trigger)=>{ events: any[]; cursor?: any } - Log: audit entry
{ id, ts, level, message, data? }
Quick start (Express)
import express from "express";
import { MongoClient } from "mongodb";
import { createHub, MongoStorage } from "integrakit-universal/core";
import { createIntegrationRouter, createWebhookRouter } from "integrakit-universal/express";
const client = new MongoClient(process.env.MONGO_URI!);
await client.connect();
const storage = new MongoStorage(client.db(process.env.MONGO_DB));
const hub = createHub({
storage,
baseUrl: process.env.PUBLIC_BASE_URL!,
providers: [],
});
const app = express();
app.use(express.json());
app.use("/api/integrations", createIntegrationRouter(hub));
app.use("/api/integrations/webhook", createWebhookRouter(hub));
hub.startScheduler();
app.listen(3000);Nest
import { nest } from "integrakit-universal/nest";
nest.mountIntegrationRouters(app, hub, { mountBasePath: "/api/integrations" });
hub.startScheduler();Data model (Storage)
- App:
{ id, providerId, name, oauth?, apiKeyAuth?, createdAt } - Connection:
{ id, providerId, appId, tenantId?, userId?, accountLabel?, auth, createdAt, updatedAt? } - Trigger:
{ id, providerId, type, connectionId, scheduleMs, cursor?, config?, active, status?, accountId?, userId?, description?, createdAt, updatedAt? } - Log:
{ id, ts, level, providerId?, subject?, message, data?, tenantId?, userId? }
Mongo collections: integration_apps, integration_connections, integration_triggers, integration_logs
Express Router API
Health
- GET
/health→{ status: "ok" } - GET
/healthz→{ status: "ok" }
Providers
- GET
/providers→[{ id, name, oauth }]- Response item:
idstring: provider id (e.g.github)namestring: human readableoauthboolean: whether provider definesoauthmetadata
- Response item:
- POST
/dynamic/providers/openapi- Request body:
providerIdstring (required)namestring (required)openapiobject (required): OpenAPI v3 document (JSON)
- Response:
{ ok: true, provider: { id, name } }
- Request body:
OAuth and Webhooks
- GET
/oauth/:provider/start- Query:
tenantIdstring|number (optional)
- Response:
{ url: string }(authorization URL)
- Query:
- GET
/oauth/:provider/callback- Query:
codestring (required)tenantIdstring|number (optional)userIdstring|number (optional)
- Response:
{ ok: true, connectionId: string }
- Query:
- POST
/webhook/:provider- Headers: provider-specific signature headers
- Body: raw Buffer (any content type)
- Response:
{ ok: boolean, events?: any[] }(provider-defined)
Connections (Connected Accounts parity)
- GET
/connections/:provider- Query:
tenantId?,userId? - Response:
Connection[]id,providerId,appId?,tenantId?,userId?,accountLabel?,auth({ accessToken?, refreshToken?, expiresAt?, data? }),createdAt,updatedAt?
- Query:
- POST
/connections- Body:
providerIdstring (required)appIdstring (optional)tenantIdstring|number (optional)userIdstring|number (optional)accountLabelstring (optional)authobject (optional):{ accessToken?, refreshToken?, expiresAt?, data? }
- Response:
Connection
- Body:
- PUT
/connections/:id- Body: any subset of
Connection - Response: updated
Connection
- Body: any subset of
- DELETE
/connections/:id- Response:
{ success: boolean }
- Response:
Connected-accounts shortcuts:
- GET
/connected-accounts/userintegrationapp/:integrationId - GET
/connected-accounts/connectionsByUserId/:userId - GET
/connected-accounts/:id - DELETE
/connected-accounts/:id
Integrations (allappsintigrations parity)
- POST
/allappsintigrations- Body:
{ id?, name, actions?, triggers?, oauth? }actionsobject:{ [actionName]: (ctx, params)=>Promise<any> }(register programmatically)triggersobject:{ [triggerType]: (ctx, trigger)=>Promise<{ events, cursor? }>}
- Response: provider object
- Body:
- GET
/allappsintigrations/allApps→ provider[] - GET
/allappsintigrations/user/:userId→Connection[] - GET
/allappsintigrations/:type→ provider - GET
/allappsintigrations/fetchNodeDefinition/:type→{ name, actions: string[] } - GET
/allappsintigrations/fetchTriggerDefinition/:type→{ name, triggers: string[] } - DELETE
/allappsintigrations/:id→{ success: true }
Dynamic API (OpenAPI registry)
- POST
/dynamic-api/integrations- Body:
{ name: string, openApiSpec: object, description?: string }
- Body:
- GET
/dynamic-api/integrations→{ name: string }[] - GET
/dynamic-api/integrations/:name→ provider - PUT
/dynamic-api/integrations/:name- Body:
{ openApiSpec: object } - Response: provider
- Body:
- DELETE
/dynamic-api/integrations/:name→{ success: true } - PUT
/dynamic-api/integrations/:name/auth-config- Body:
{ authConfig: any }(persisted as app oauth config) - Response:
{ ok: true }
- Body:
- POST
/dynamic-api/integrations/upload- Body (JSON; for multipart, attach multer in host app):
{ name, description?, openApiSpec, authConfig? } - Response:
{ name, description }
- Body (JSON; for multipart, attach multer in host app):
Dynamic OAuth
- POST
/dynamic-oauth/configuren8ntype/:name- Body:
{ clientId?, clientSecret?, tokenUrl?, authorizeUrl?, scopes?, redirectPath? } - Response:
{ ok: true }
- Body:
- POST
/dynamic-oauth/configure/:name→ same as above - GET
/dynamic-oauth/authorize/:id- Query:
tenantId?state? - Response:
{ url }
- Query:
- GET
/dynamic-oauth/authorize/:integrationName/:authSchemeName- Query: same
- Response:
{ url }
- GET
/dynamic-oauth/callback- Query:
code,state - Response: HTML page (success/error)
- Query:
- POST
/dynamic-oauth/refresh/:accountId→{ ok: true } - GET
/dynamic-oauth/apps→ App[] (storage-dependent) - GET
/dynamic-oauth/apps/:integrationName→ App - DELETE
/dynamic-oauth/apps/:integrationName→{ success } - GET
/dynamic-oauth/apps/:integrationName/auth-schemes→{ integration, auth_schemes: any[] } - POST
/dynamic-oauth/validate-schema- Expects
req.file(YAML/JSON) - Response:
{ valid: boolean, message: string, schema?: any }
- Expects
- POST
/dynamic-oauth/store-credentials/:id/:authMethod- Body:
{ userName: string, credentials: Record<string,any>, accountName?: string } - Response:
{ ok: true }
- Body:
- GET
/dynamic-oauth/credential-fields/:integrationName/:authSchemeName→{ fields: any[] } - POST
/dynamic-oauth/test-credentials/:integrationName/:authSchemeName→{ ok: true } - GET
/dynamic-oauth/accounts/:userId/:integrationName→Connection[] - DELETE
/dynamic-oauth/accounts/:accountId→{ success: boolean }
Actions & Execution
- POST
/actions/:provider/:action- Body:
{ connectionId: string, params?: any } - Response: any (provider-defined)
- Body:
Execution helpers (legacy parity):
- POST
/execution/direct/execute- Body:
{ accountId, integrationName, method, path, pathParams?, queryParams?, body?, headers? } - Response: backend response
{ ... }or{ status, body }
- Body:
- POST
/execution/actions/execute- Body:
{ accountId, actionName, integrationName, params?, pathParams?, queryParams?, body?, headers? } - Response:
{ success: true, data }
- Body:
- POST
/execution/endpoints/execute→ same shape as direct/execute butendpoint - POST
/execution/direct/list- Body:
{ integrationName } - Response:
{ integrationName, paths: string[], totalPaths: number }
- Body:
- POST
/execution/actions/list- Body:
{ integrationName } - Response:
{ integrationName, actions: string[], totalActions: number }
- Body:
- POST
/execution/actions/:actionName/details→{ name: string } - POST
/execution/actions/batch- Body:
{ accountId, actions: { integrationName, actionName, params }[], parallel?: boolean } - Response:
{ results: { success:boolean, data?|error? }[], summary: { total, successful, failed, executionMode } }
- Body:
Nodes Facade
- GET
/nodes→[{ name, providerId, type }] - GET
/nodes/type/:type→[{ name, providerId }]wheretypein (action,trigger) - GET
/nodes/:name→{ name, type, providerId } - POST
/nodes/:name/execute- Body:
{ inputData?: any, parameters?: any, AccountId?: string, metadata?: any[] } - Response:
{ success: true, data }
- Body:
- POST
/nodes/:nodeName/load-options/:methodName→{ success: true, data: any[] }
Triggers
- POST
/triggers- Body:
Triggerminimal- required:
providerId,type,connectionId,scheduleMs - optional:
cursor,config,active?,status?,accountId?,userId?,description?
- required:
- Response: created
Trigger
- Body:
- GET
/triggers/management/list→{ triggers: Trigger[], total }- Query:
status?in (active,inactive,paused,error,stopped),userId?,accountId?,limit?
- Query:
- GET
/triggers/management/:triggerId→Trigger - PUT
/triggers/management/:triggerId- Body: Partial<
Trigger> - Response: updated
Trigger
- Body: Partial<
- DELETE
/triggers/management/:triggerId→{ success } - POST
/triggers/management/:triggerId/(start|stop|pause|resume)→{ ok: true } - POST
/triggers/bulk/(start|stop)- Body:
{ triggerIds: string[] } - Response:
{ success: true, data: { results: { triggerId, success, error? }[], summary: { total, successful, failed } } }
- Body:
- GET
/triggers/:triggerId/status→Trigger - GET
/triggers/:triggerId/status-with-logs- Query:
logLimit?number (default 20) - Response:
{ success, data: { status: Trigger, stats: { totalEvents, uniqueEvents }, recentLogs }, message }
- Query:
- GET
/triggers/user/:userId/active→{ success, data: { triggers, total } } - GET
/triggers/account/:accountId/triggers→{ success, data: { triggers, total } } - GET
/triggers/types/available→{ success, data: { type, name, description, category }[] } - POST
/triggers/:type/test- Body:
{ providerId: string, connectionId: string } - Response:
{ success, data: { recentLogs } }
- Body:
- GET
/triggers/analytics/overview→{ total, active, paused } - GET
/triggers/analytics/by-type→{ triggersByType: Record<string,number>, total } - POST
/triggers/validate- Body:
{ ...any } - Response:
{ success, data: { valid: true, warnings: string[], suggestions: string[] }, message }
- Body:
Trigger Logs
- POST
/trigger-logs- Body:
LogEntrywithoutid/ts(server assigns) - Response:
{ ok: true }
- Body:
- GET
/trigger-logs- Query:
triggerId(required),limit?number - Response:
LogEntry[]
- Query:
- GET
/trigger-logs/:triggerId/stats→{ success: true, data: { totalEvents, uniqueEvents } } - GET
/trigger-logs/errors/recent?limit=10→{ success, data: LogEntry[] } - GET
/trigger-logs/:id→{ success, data: LogEntry } - DELETE
/trigger-logs/:id→{ success } - DELETE
/trigger-logs/trigger/:triggerId→{ success, data: { deletedCount } } - POST
/trigger-logs/cleanup- Body:
{ daysToKeep?: number }(default 30) - Response:
{ success, data: { deletedCount, daysToKeep }, message }
- Body:
- GET
/trigger-logs/dashboard/overview→{ success, data: { stats, recentErrors, timestamp }, message } - GET
/trigger-logs/queue/health→{ success, data: { status, message, timestamp }, message }
Programmatic API (Core)
createHub(options): returnsHubhub.addProvider(p),hub.removeProvider(id),hub.listProviders()hub.oauthStart(id, tenantId?, state?),hub.oauthCallback(id, code, tenantId?, userId?)hub.callAction(providerId, action, connectionId, params)- Triggers:
create/update/delete/get/find,start/stop/pause/resume,runTrigger,startScheduler - Logs:
log,findLogsByTriggerId,getTriggerStats,getRecentErrors,deleteLog,deleteLogsByTriggerId
Storage
Implement the Storage interface (Memory and Mongo adapters included). Mongo creates helpful indexes.
Notes on parity with legacy integration_module
- Route surfaces are equivalent. Authentication/guards are intentionally excluded; add them in your host app.
- Nodes runtime is provided as a facade mapping providers’ actions/triggers; extend if you rely on node-specific metadata/behaviors.
- Dynamic API multipart uploads can be enabled by adding multer on your app and passing files to the same endpoints.
MIT
