npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@venturialstd/organization

v0.0.14

Published

Organization Management Module for Venturial

Readme

@venturialstd/organization

A comprehensive NestJS module for managing organizations with multi-tenant capabilities, role-based access control, and isolated per-organization settings.

📋 Table of Contents


✨ Features

  • 🏢 Organization Management: Complete CRUD operations for organizations
  • 👥 Member Management: Add, invite, and manage organization members
  • 🔐 Role-Based Access Control: Owner, Admin, Member, and Viewer roles
  • ⚙️ Isolated Settings System: Per-organization configuration for modules
  • 🔒 Complete Isolation: No fallback between organizations
  • 🛡️ Guards & Decorators: Request-level access control
  • 🔄 TypeORM Integration: Full database support with migrations
  • 📦 Fully Typed: Complete TypeScript support
  • 🧪 Test Infrastructure: Standalone test server

📦 Installation

```bash npm install @venturialstd/organization ```

Peer Dependencies

```json { "@nestjs/common": "^11.0.11", "@nestjs/core": "^11.0.5", "@nestjs/typeorm": "^10.0.0", "@venturialstd/core": "^1.0.16", "class-transformer": "^0.5.1", "class-validator": "^0.14.1", "typeorm": "^0.3.20" } ```


🚀 Quick Start

1. Import the Module

```typescript import { Module } from '@nestjs/common'; import { OrganizationModule } from '@venturialstd/organization';

@Module({ imports: [OrganizationModule], }) export class AppModule {} ```

2. Run Migrations

```bash

Create and run migrations

npm run migration:generate -- CreateOrganizations npm run migration:run ```

3. Use the Services

```typescript import { Injectable } from '@nestjs/common'; import { OrganizationService, OrganizationUserService, ORGANIZATION_USER_ROLE } from '@venturialstd/organization';

@Injectable() export class YourService { constructor( private readonly organizationService: OrganizationService, private readonly organizationUserService: OrganizationUserService, ) {}

async createOrganization(userId: string) { return this.organizationService.createOrganization( 'My Organization', 'my-org', 'example.com', 'Description', userId ); }

async addMember(organizationId: string, userId: string) { return this.organizationUserService.addUserToOrganization( organizationId, userId, ORGANIZATION_USER_ROLE.MEMBER ); } } ```


💡 Core Concepts

Organizations

An organization represents a tenant in your multi-tenant application. Each organization:

  • Has a unique name and slug
  • Can have a custom domain
  • Has its own settings and configuration
  • Contains multiple members with different roles
  • Is completely isolated from other organizations

Organization Members

Users can belong to multiple organizations with different roles:

  • Owner: Full control, can delete organization
  • Admin: Can manage members and settings
  • Member: Standard access to organization resources
  • Viewer: Read-only access

Organization Settings

Each organization can have isolated settings for any module in your application:

  • Complete Isolation: No sharing between organizations
  • No Fallback: Each organization must configure its own settings
  • Type-Safe: Fully typed with TypeScript
  • Module-Agnostic: Works with any module (GitLab, Jira, Slack, etc.)

🏗️ Architecture

Module Structure

``` OrganizationModule ├── Entities │ ├── Organization # Organization entity │ ├── OrganizationUser # User-Organization relationship │ └── OrganizationSettings # Per-organization settings ├── Services │ ├── OrganizationService # CRUD operations │ ├── OrganizationUserService # Member management │ └── OrganizationSettingsService # Settings management ├── Guards │ ├── OrganizationGuard # Check user belongs to org │ └── OrganizationRoleGuard # Check user role └── Decorators ├── @OrganizationId() # Extract org ID from request ├── @UserId() # Extract user ID from request ├── @Roles() # Define required roles └── @OrganizationContext() # Extract full context ```

Settings Isolation

``` ┌─────────────────────────────────────────────────┐ │ Application │ │ │ │ ┌──────────────────────────────────────────┐ │ │ │ Organization A │ │ │ │ • GitLab: gitlab-a.com + token-a │ │ │ │ • Jira: jira-a.com + credentials-a │ │ │ └──────────────────────────────────────────┘ │ │ │ │ ┌──────────────────────────────────────────┐ │ │ │ Organization B │ │ │ │ • GitLab: gitlab.com + token-b │ │ │ │ • Jira: [Not configured] │ │ │ └──────────────────────────────────────────┘ │ │ │ └─────────────────────────────────────────────────┘ ```


📚 API Reference

Services

OrganizationService

```typescript class OrganizationService { // Create a new organization createOrganization( name: string, slug: string, domain: string, description: string, ownerId: string ): Promise

// Get organization by ID getOrganizationById(id: string): Promise

// Update organization updateOrganization(id: string, data: Partial): Promise

// Delete organization deleteOrganization(id: string): Promise

// Get user's organizations getUserOrganizations(userId: string): Promise<Organization[]> } ```

OrganizationUserService

```typescript class OrganizationUserService { // Add user to organization addUserToOrganization( organizationId: string, userId: string, role: ORGANIZATION_USER_ROLE ): Promise

// Remove user from organization removeUserFromOrganization( organizationId: string, userId: string ): Promise

// Update user role updateUserRole( organizationId: string, userId: string, role: ORGANIZATION_USER_ROLE ): Promise

// Get organization members getOrganizationMembers(organizationId: string): Promise<OrganizationUser[]>

// Check user role getUserRole( organizationId: string, userId: string ): Promise<ORGANIZATION_USER_ROLE> } ```

OrganizationSettingsService

```typescript class OrganizationSettingsService { // Get a single setting get(organizationId: string, key: string): Promise<string | undefined>

// Get multiple settings getManySettings( organizationId: string, keys: string[] ): Promise<Record<string, string>>

// Get all settings for organization getAllForOrganization( organizationId: string, module?: string ): Promise<OrganizationSettings[]>

// Set or update a setting setOrganizationSetting( organizationId: string, dto: Partial ): Promise

// Delete a setting deleteOrganizationSetting( organizationId: string, module: string, section: string, key: string ): Promise

// Reset all settings for a module resetModuleSettings( organizationId: string, module: string ): Promise

// Bulk update settings bulkUpdateSettings( organizationId: string, settings: Partial[] ): Promise<OrganizationSettings[]> } ```

Entities

Organization

```typescript class Organization { id: string; name: string; slug: string; domain: string; description: string; isActive: boolean; settings: Record<string, unknown>; ownerId: string; createdAt: Date; updatedAt: Date; } ```

OrganizationUser

```typescript class OrganizationUser { id: string; organizationId: string; userId: string; role: ORGANIZATION_USER_ROLE; isActive: boolean; createdAt: Date; updatedAt: Date; } ```

OrganizationSettings

```typescript class OrganizationSettings { id: string; organizationId: string; module: string; // e.g., 'GITLAB', 'JIRA' section: string; // e.g., 'CREDENTIALS', 'OAUTH' key: string; // e.g., 'TOKEN', 'API_KEY' value: string; type: SETTINGS_TYPE; label: string; description: string; options: object | null; sortOrder: number; createdAt: Date; updatedAt: Date; } ```

Decorators

```typescript // Extract organization ID from request @Get() getData(@OrganizationId() organizationId: string) { }

// Extract user ID from request @Get() getData(@UserId() userId: string) { }

// Extract full context @Get() getData(@OrganizationContext() context: OrganizationContextType) { }

// Define required roles @Roles(ORGANIZATION_USER_ROLE.ADMIN, ORGANIZATION_USER_ROLE.OWNER) @UseGuards(OrganizationRoleGuard) adminEndpoint() { } ```

Guards

```typescript // Check user belongs to organization @UseGuards(OrganizationGuard) @Get('organizations/:organizationId/data') getData() { }

// Check user has required role @Roles(ORGANIZATION_USER_ROLE.ADMIN) @UseGuards(OrganizationRoleGuard) adminEndpoint() { } ```

Constants

```typescript // User roles enum ORGANIZATION_USER_ROLE { OWNER = 'owner', ADMIN = 'admin', MEMBER = 'member', VIEWER = 'viewer', }

// Settings types enum SETTINGS_TYPE { TEXT = 'text', TEXTAREA = 'textarea', NUMBER = 'number', BOOLEAN = 'boolean', SELECT = 'select', MULTISELECT = 'multiselect', JSON = 'json', DATE = 'date', SECRET = 'secret', } ```


⚙️ Organization Settings System

Complete Isolation

Organization settings are completely isolated with no fallback mechanism:

```typescript // Organization A const tokenA = await orgSettings.get('org-a-id', 'GITLAB:CREDENTIALS:TOKEN'); // Returns: 'token-a'

// Organization B (not configured) const tokenB = await orgSettings.get('org-b-id', 'GITLAB:CREDENTIALS:TOKEN'); // Returns: undefined (NO FALLBACK) ```

Settings Types

Use `SETTINGS_TYPE` enum for type-safe settings:

```typescript import { SETTINGS_TYPE } from '@venturialstd/organization';

const settings = [ { module: 'GITLAB', section: 'CREDENTIALS', key: 'TOKEN', type: SETTINGS_TYPE.SECRET, // Hides value in UI // ... }, { module: 'GITLAB', section: 'PREFERENCES', key: 'AUTO_MERGE', type: SETTINGS_TYPE.BOOLEAN, // true/false toggle // ... }, ]; ```

Using Settings in Your Module

1. Define Settings for Your Module

```typescript // src/your-module/settings/your-module.settings.ts import { SETTINGS_TYPE } from '@venturialstd/organization';

export const SETTINGS = [ { scope: 'ORGANIZATION', module: 'YOUR_MODULE', section: 'CREDENTIALS', key: 'API_KEY', label: 'API Key', description: 'Your module API key', type: SETTINGS_TYPE.SECRET, value: '', options: { placeholder: 'Enter your API key', help: 'Get your API key from your module dashboard', }, sortOrder: 10, }, { scope: 'ORGANIZATION', module: 'YOUR_MODULE', section: 'CREDENTIALS', key: 'API_SECRET', label: 'API Secret', description: 'Your module API secret', type: SETTINGS_TYPE.SECRET, value: '', options: null, sortOrder: 11, }, ]; ```

2. Use Settings in Your Service

```typescript import { Injectable } from '@nestjs/common'; import { OrganizationSettingsService } from '@venturialstd/organization';

@Injectable() export class YourModuleService { constructor( private readonly orgSettings: OrganizationSettingsService ) {}

async connect(organizationId: string) { // Get organization-specific credentials const apiKey = await this.orgSettings.get( organizationId, 'YOUR_MODULE:CREDENTIALS:API_KEY' );

const apiSecret = await this.orgSettings.get(
  organizationId,
  'YOUR_MODULE:CREDENTIALS:API_SECRET'
);

// Check if configured
if (!apiKey || !apiSecret) {
  throw new Error('Module not configured for this organization');
}

// Use organization-specific credentials
return this.client.connect({ apiKey, apiSecret });

}

async getMultipleSettings(organizationId: string) { // Get multiple settings at once const settings = await this.orgSettings.getManySettings( organizationId, [ 'YOUR_MODULE:CREDENTIALS:API_KEY', 'YOUR_MODULE:CREDENTIALS:API_SECRET', 'YOUR_MODULE:PREFERENCES:TIMEOUT', ] );

return settings;

} } ```

3. Import OrganizationModule

```typescript import { Module } from '@nestjs/common'; import { OrganizationModule } from '@venturialstd/organization';

@Module({ imports: [OrganizationModule], providers: [YourModuleService], }) export class YourModule {} ```

Database Schema

```sql CREATE TABLE organization_settings ( id UUID PRIMARY KEY, organizationId UUID REFERENCES organization(id) ON DELETE CASCADE, module VARCHAR NOT NULL, -- 'GITLAB', 'JIRA', 'YOUR_MODULE' section VARCHAR NOT NULL, -- 'CREDENTIALS', 'OAUTH', 'PREFERENCES' key VARCHAR NOT NULL, -- 'TOKEN', 'API_KEY', 'HOST' value VARCHAR NOT NULL, -- Actual value type settings_type_enum NOT NULL, label VARCHAR NOT NULL, description VARCHAR, options JSONB, sortOrder INTEGER DEFAULT 0, createdAt TIMESTAMPTZ NOT NULL DEFAULT NOW(), updatedAt TIMESTAMPTZ NOT NULL DEFAULT NOW(), UNIQUE(organizationId, module, section, key) ); ```


🧪 Test Server

The module includes a standalone test server for development and testing.

Start the Test Server

```bash cd src/organization npm install npm run start:dev ```

The server runs on http://localhost:3002

Test Endpoints

```bash

Get all organizations

GET http://localhost:3002/test/organizations

Get organization by ID

GET http://localhost:3002/test/organizations/:id

Get organization settings

GET http://localhost:3002/test/organizations/:id/settings

Get settings for specific module

GET http://localhost:3002/test/organizations/:id/settings?module=GITLAB

Create/update setting

PUT http://localhost:3002/test/organizations/:id/settings Content-Type: application/json

{ "module": "GITLAB", "section": "CREDENTIALS", "key": "TOKEN", "value": "your-token", "label": "GitLab Token", "type": "secret" } ```

Test Controller

The test controller is for reference and development only. In production, implement your own controllers with:

  • Custom authentication
  • Custom authorization
  • Custom validation
  • Custom error handling

Location: `test/controllers/organization-settings-test.controller.ts`


📖 Migration Guide

Running Migrations

```bash

Generate migration

npm run migration:generate -- YourMigrationName

Run migrations

npm run migration:run

Revert migration

npm run migration:revert ```

Required Migrations

The module requires these migrations:

  1. Create organizations table
  2. Create organization_users table
  3. Create organization_settings table

Migrations are located in: `database/migrations/`


💻 Examples

Example 1: Create Organization with Owner

```typescript @Injectable() export class OnboardingService { constructor( private readonly orgService: OrganizationService, private readonly orgUserService: OrganizationUserService, ) {}

async onboardUser(userId: string, companyName: string) { // Create organization const org = await this.orgService.createOrganization( companyName, this.generateSlug(companyName), `${this.generateSlug(companyName)}.example.com`, `${companyName} organization`, userId );

// Owner is automatically added by createOrganization
return org;

}

private generateSlug(name: string): string { return name.toLowerCase().replace(/\s+/g, '-'); } } ```

Example 2: Check User Permission

```typescript @Controller('organizations/:organizationId/data') export class DataController { constructor( private readonly orgUserService: OrganizationUserService, ) {}

@Get() @UseGuards(OrganizationGuard) async getData( @OrganizationId() organizationId: string, @UserId() userId: string, ) { const role = await this.orgUserService.getUserRole( organizationId, userId );

// Check if user has required role
if (![ORGANIZATION_USER_ROLE.ADMIN, ORGANIZATION_USER_ROLE.OWNER].includes(role)) {
  throw new ForbiddenException('Insufficient permissions');
}

// Return data

} } ```

Example 3: Module with Organization Settings

```typescript // GitLab Service @Injectable() export class GitLabService { constructor( private readonly orgSettings: OrganizationSettingsService ) {}

async getClient(organizationId: string) { // Get org-specific GitLab credentials const [host, token] = await Promise.all([ this.orgSettings.get(organizationId, 'GITLAB:CREDENTIALS:HOST'), this.orgSettings.get(organizationId, 'GITLAB:CREDENTIALS:TOKEN'), ]);

// Validate configuration
if (!host || !token) {
  throw new Error(
    'GitLab not configured for this organization. ' +
    'Please configure it in organization settings.'
  );
}

// Return organization-specific client
return new GitLab({ host, token });

}

async getRepositories(organizationId: string) { const client = await this.getClient(organizationId); return client.Projects.all(); } } ```

Example 4: Bulk Configure Organization

```typescript @Injectable() export class OrganizationSetupService { constructor( private readonly orgSettings: OrganizationSettingsService, ) {}

async configureGitLab( organizationId: string, config: { host: string; token: string } ) { return this.orgSettings.bulkUpdateSettings(organizationId, [ { module: 'GITLAB', section: 'CREDENTIALS', key: 'HOST', value: config.host, label: 'GitLab Host', type: SETTINGS_TYPE.TEXT, }, { module: 'GITLAB', section: 'CREDENTIALS', key: 'TOKEN', value: config.token, label: 'GitLab Token', type: SETTINGS_TYPE.SECRET, }, ]); } } ```

Example 5: Settings Resolution Pattern

```typescript @Injectable() export class IntegrationService { constructor( private readonly orgSettings: OrganizationSettingsService, ) {}

async getIntegrationConfig(organizationId: string, integration: string) { // Get all settings for this integration const settings = await this.orgSettings.getAllForOrganization( organizationId, integration );

// Check if configured
if (settings.length === 0) {
  return {
    configured: false,
    message: \`\${integration} is not configured for this organization\`,
  };
}

// Convert to key-value object
const config = settings.reduce((acc, setting) => {
  acc[\`\${setting.section}:\${setting.key}\`] = setting.value;
  return acc;
}, {});

return {
  configured: true,
  config,
};

} } ```


📝 Best Practices

1. Always Check Configuration

```typescript // ❌ Don't assume settings exist const token = await orgSettings.get(orgId, 'MODULE:CREDENTIALS:TOKEN'); await client.connect(token); // Could be undefined!

// ✅ Always validate const token = await orgSettings.get(orgId, 'MODULE:CREDENTIALS:TOKEN'); if (!token) { throw new Error('Module not configured'); } await client.connect(token); ```

2. Use Bulk Operations

```typescript // ❌ Multiple single calls const host = await orgSettings.get(orgId, 'MODULE:CREDENTIALS:HOST'); const token = await orgSettings.get(orgId, 'MODULE:CREDENTIALS:TOKEN'); const secret = await orgSettings.get(orgId, 'MODULE:CREDENTIALS:SECRET');

// ✅ Single bulk call const settings = await orgSettings.getManySettings(orgId, [ 'MODULE:CREDENTIALS:HOST', 'MODULE:CREDENTIALS:TOKEN', 'MODULE:CREDENTIALS:SECRET', ]); ```

3. Implement Your Own Controllers

```typescript // ❌ Don't use test controller in production import { OrganizationSettingsTestController } from '@venturialstd/organization';

// ✅ Implement your own @Controller('api/organizations/:organizationId/settings') @UseGuards(JwtAuthGuard, OrganizationGuard) export class OrganizationSettingsController { // Your implementation with proper auth } ```

4. Use Type-Safe Settings Types

```typescript // ✅ Import and use the enum import { SETTINGS_TYPE } from '@venturialstd/organization';

const setting = { type: SETTINGS_TYPE.SECRET, // Type-safe // ... }; ```


📄 License

MIT


🤝 Contributing

Contributions welcome! Please read the contributing guidelines before submitting PRs.