@cincoai/vox-api
v0.3.10
Published
NestJS library for Vox campaign management (contributor responses, manager synthesis, sharing)
Readme
@cincoai/vox-api
NestJS library for the Vox organizational voice campaign system: catalog (packs, items, policies), contributor responses, manager synthesis, sharing, cron notifications, and REST agent endpoints.
Installation
npm install @cincoai/vox-apiPeer dependencies (install in the host app):
npm install @nestjs/common @nestjs/core @nestjs/graphql @nestjs/mongoose \
@nestjs/config @nestjs/schedule @nestjs/event-emitter @nestjs/platform-express \
@nestjs/swagger @nestjs/axios mongoose graphql graphql-subscriptions dataloader \
class-validator class-transformer rxjs reflect-metadata graphql-scalarsQuick integration
import { Module } from '@nestjs/common';
import { MongooseModule } from '@nestjs/mongoose';
import { GraphQLModule } from '@nestjs/graphql';
import { EventEmitterModule } from '@nestjs/event-emitter';
import { ScheduleModule } from '@nestjs/schedule';
import {
VoxManagementModule,
VoxDataLoaderFactory,
PUBSUB_PROVIDER,
} from '@cincoai/vox-api';
@Module({
imports: [
MongooseModule.forRoot(process.env.MONGODB_URI!),
EventEmitterModule.forRoot(),
ScheduleModule.forRoot(),
HostModule,
VoxManagementModule.forRoot({
imports: [HostModule],
simulation: process.env.VOX_SIMULATION_ENABLED !== 'false',
}),
GraphQLModule.forRootAsync({
inject: [VoxDataLoaderFactory, PUBSUB_PROVIDER],
useFactory: (loaderFactory: VoxDataLoaderFactory, pubSub) => ({
autoSchemaFile: true,
subscriptions: { 'graphql-ws': true },
context: ({ req, extra }) => ({
req: req ?? extra?.request,
loaders: loaderFactory.create(),
pubSub,
}),
}),
}),
],
})
export class AppModule {}The host must provide port implementations (VOX_IDENTITY_PORT, VOX_AUTHORIZATION_PORT, …) — see exemples/vox-demo/INTEGRATION.md.
Host prerequisites
Integration ports (required)
The package does not store host users. Implement and register:
| Token | Responsibility |
|-------|----------------|
| VOX_IDENTITY_PORT | Resolve users by host id / external id (Keycloak sub) |
| VOX_AUTHORIZATION_PORT | Global VOX roles, roleCode, admin checks |
| VOX_MEMBERSHIP_PORT | Source user↔org memberships (optional listAll for sync) |
| VOX_ORG_MIGRATION_PORT | Export host organization tree for initial import |
| VOX_VIRTUAL_ORG_PORT | Optional. Required when simulation: true — create virtual orgs/users, assign memberships, update virtual roleCode |
Reference adapters: exemples/vox-demo/apps/api/src/host/adapters/.
Authentication guards
Resolvers use decorators exported by this package:
@Public()— skip auth (e.g. subscriptions with server-side filter)@Roles({ roles: [UserRole.user] })— role check@ApiKeyAllowed()— REST agent endpoints (/vox-manager/*)
The host app must register global guards that read these metadata keys (same pattern as owliance CustomAuthGuard / CustomRoleGuard).
MongoDB collections
The library registers Mongoose models for:
| Collection area | Models |
|-----------------|--------|
| Vox domain | VoxCampaign, VoxPack, VoxItem, VoxQuestion, VoxPolicy, VoxResponse, VoxManagerShare, … |
| Package-owned | Organization (with externalId), VoxMembership (cache), NotificationRecord, File |
Run VoxOrganizationImportService.runFullMigration() after seeding host data to import organizations and sync memberships.
Notifications (optional)
Campaign and share services emit NOTIFICATION_CREATED_EVENT via EventEmitter2. Listen in the host app:
The event payload is a plain object whose shape depends on the emitting
service (it always includes at least a type discriminator):
import { OnEvent } from '@nestjs/event-emitter';
import { NOTIFICATION_CREATED_EVENT } from '@cincoai/vox-api';
@OnEvent(NOTIFICATION_CREATED_EVENT)
handleNotification(payload: { type: string; [key: string]: unknown }) {
// route to your NotificationModule
}Real-time subscriptions
Subscription voxResponseItemUpdated requires pubSub in the GraphQL context (see integration example above).
Environment variables
File uploads (attachments)
| Variable | Description |
|----------|-------------|
| AWS_S3_ENDPOINT | S3 endpoint (default: s3.amazonaws.com) |
| AWS_ACCESS_KEY_ID | AWS access key |
| AWS_SECRET_ACCESS_KEY | AWS secret |
| AWS_REGION | AWS region (default: us-east-1) |
| AWS_S3_BUCKET | Bucket name |
| MINIO_ENDPOINT, MINIO_PORT, MINIO_ACCESS_KEY, MINIO_SECRET_KEY, MINIO_BUCKET | MinIO alternative |
Manager AI synthesis (optional)
| Variable | Description |
|----------|-------------|
| TASK_FRAMEWORK_ENDPOINT | Task Framework base URL |
| TASK_FRAMEWORK_ADMIN_API_KEY | API key for thread creation |
| TASK_FRAMEWORK_APP_ID | App ID sent in synthesis requests |
Without Task Framework configuration, synthesis mutations will fail at runtime.
Simulation / virtual subsidiaries (optional)
| Variable / option | Description |
|-------------------|-------------|
| forRoot({ simulation }) / VOX_SIMULATION_ENABLED | Enable virtual-org generation + agent APIs (default off in the package) |
| VOX_SIMULATION_WEBHOOK_URL | POST on VOX_CAMPAIGN_CREATED: { campaignId, campaignName, isDemo, packsInclus, orgUnitIds } (example: http://localhost:8003/webhooks/campaign-created) |
| VOX_SIMULATION_WEBHOOK_SECRET | Optional X-Vox-Webhook-Secret header |
| OPENAI_API_KEY | Optional fallback persona LLM when users is omitted (template otherwise). Preferred path: generate agent sends the roster. |
Virtual users (isVirtual: true) are created without roleCode. Optional input users: [{ firstName, lastName, persona, department? }] is persisted as-is when users.length === userCount; otherwise name pools + PersonaAgent. The host must not backfill or manager-sync roleCode on those users. The orchestrator assigns pack roleCodes after createVoxCampaign(isDemo: true) via assignVoxVirtualUserRoleCodes and reads users.persona for demo answers.
GraphQL (simulation enabled): voxSimulationEnabled, generateVoxVirtualSubsidiary, voxPackRoleCodes, assignVoxVirtualUserRoleCodes.
Public API
export {
VoxManagementModule,
VoxModule,
VoxRoleModule,
VoxService,
VoxRoleService,
VoxDataLoaderFactory,
Public, Roles, Resource, AuthenticatedUser, AuthToken,
ApiKeyAllowed, ApiKeyOnly, TrackApiCall,
PUBSUB_PROVIDER,
PUB_SUB_TRIGGERS,
NOTIFICATION_CREATED_EVENT,
VOX_VIRTUAL_ORG_PORT,
};REST endpoints
| Prefix | Purpose |
|--------|---------|
| POST /vox-response-attachments/upload | Contributor file upload (JWT) |
| GET/POST /vox-manager/* | AI agent context and synthesis payloads (API key) |
Development
npm install
npm run build
npm testMigration
src/migrations/vox-campaign-access-init.ts initializes VoxCampaignAccess from existing campaigns and shares.
Note: migration scripts are not shipped in the published package (they are excluded from the build). Run them from a checkout of this repository with
MONGODB_URIset:
MONGODB_URI=mongodb://localhost:27017/mydb \
npx ts-node -r tsconfig-paths/register src/migrations/vox-campaign-access-init.tsPackage structure
src/
├── vox/ # Core Vox module (resolvers, services, DTOs)
├── vox-role/ # VoxRole CRUD
├── schemas/ # Mongo + GraphQL entity definitions
├── enums/
├── infrastructure/ # Decorators, pubsub, dataloaders, types
├── adapters/ # Pluggable file, user, synthesis integrations
└── migrations/Replacing adapters
Default adapters are sufficient for standalone use. Override in the host app:
@Module({
imports: [
VoxManagementModule,
// your custom FileModule / UserModule
],
})
export class AppModule {}Use NestJS module overrides if you need custom FileService or UserService implementations.
Limitations & host adaptation
Must provide
| Item | Detail |
|------|--------|
| Ports | VOX_IDENTITY_PORT, VOX_AUTHORIZATION_PORT, VOX_MEMBERSHIP_PORT, VOX_ORG_MIGRATION_PORT (+ VOX_VIRTUAL_ORG_PORT when simulation is on) |
| ID contract | JWT id = IdP sub → findByExternalId → VoxUserRef.id = host DB id (membership keys on that id — not email) |
| Global guards | Honor @Public, @Roles, @ApiKeyAllowed metadata |
| Mongo + PubSub | Domain collections + pubSub in GraphQL context for voxResponseItemUpdated |
| File storage | VOX_FILE_STORAGE_PORT (default MinIO/S3 adapter); override for S3/GCS/Azure/local |
Reference adapters: exemples/vox-demo/apps/api/src/host/adapters/ and portal-kit graphql/src/vox-host/adapters/.
Hard limitations (today)
| Limitation | Detail |
|------------|--------|
| GraphQL type names | Package ships User / Organization-related shapes — risk of collision with host schema (portal-kit uses HostOrganization + compat) |
| Agent ApiKey | Relies on host ApiKey validation pattern (not a pluggable port) |
| Coach | Coach-bridge lives in the host (e.g. portal-kit), not in this package |
| Agents | Synthesis/coach need Task Framework (or equivalent) outside the package |
Host walkthrough: portal-kit/docs/VOX_INTEGRATION.md.
Suggested package improvements (roadmap)
Goals: portability across portals. Advantages if delivered:
| Benefit | Effect |
|---------|--------|
| Time-to-integrate ↓ | Ports/adapters instead of forking a reference host |
| Less host debt | No fragile GraphQL compat layers |
| Cloud / region portability | S3/Azure, configurable timezone, other IdP |
| Multi-client product | Same @cincoai/vox-api binary for several tenants |
| HA coach / agents | Persisted coach sessions; swappable agent runner |
| Priority | Suggestion | Status / advantage |
|----------|------------|-------------------|
| P0 | Port VoxFileStoragePort (MinIO = default) | Done — inject VOX_FILE_STORAGE_PORT |
| P0 | Configurable timezone (default Europe/Paris) | Done — forRoot({ timezone }) / VOX_TIMEZONE / setVoxTimeZone |
| P0 | Helper ID contract (resolveActor) | Done — VoxActorService.resolveActor + JSDoc contract |
| P1 | Port VoxApiKeyValidatorPort + VoxApiKeyGuard | Done — env default + injectable validator |
| P1 | Typed notifications DTO (VoxNotificationCreatedEvent) | Done — stable event shape + optional VOX_NOTIFICATION_PORT |
| P1 | Isolate GraphQL types (Vox* / host-fields) | Partial — prefer host HostOrganization compat; full rename deferred (breaking) |
| P1 | Optional CoachBridge + persisted sessions | Partial — remains host-side; front uses createHttpCoachAdapter |
| P2 | VoxAgentRunnerPort (synthesis) | Done — default TF adapter via VOX_AGENT_RUNNER_PORT |
| P2 | Mongo collectionPrefix | Done — VOX_COLLECTION_PREFIX / forRoot({ collectionPrefix }) (set before schema import) |
| P2 | Golden-path Nest module + in-memory adapters | Done — createInMemoryVoxPorts + exemples/vox-demo |
UI package roadmap: front/README.md.
