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

@autorix/storage

v0.1.0

Published

Storage adapters for Autorix policy evaluator

Readme

@autorix/storage

Policy Storage Providers for Autorix - Flexible storage adapters for managing and retrieving authorization policies.

📋 Overview

@autorix/storage provides storage abstractions and implementations for managing Autorix policies. It defines the PolicyProvider interface and includes built-in providers for different storage backends.

✨ Features

  • 🔌 Provider Interface - Extensible abstraction for policy storage
  • 💾 Memory Provider - In-memory storage for development and testing
  • 🎯 Scope-aware - Multi-tenant and hierarchical scope support
  • 👥 Principal Types - Support for users, roles, and groups
  • 🔗 Policy Attachments - Flexible policy-to-principal binding
  • 📦 Type-safe - Full TypeScript support
  • 🚀 Zero dependencies - Lightweight (only depends on @autorix/core)

📦 Installation

npm install @autorix/storage @autorix/core
pnpm add @autorix/storage @autorix/core
yarn add @autorix/storage @autorix/core

🚀 Quick Start

Using Memory Provider

import { MemoryPolicyProvider } from '@autorix/storage';
import type { PolicyDocument } from '@autorix/core';

// Create provider
const provider = new MemoryPolicyProvider();

// Add policies
provider.addPolicy({
  id: 'policy-1',
  scope: { type: 'TENANT', id: 'tenant-123' },
  document: {
    Statement: [
      {
        Effect: 'Allow',
        Action: 'document:*',
        Resource: 'document/*',
      },
    ],
  },
});

// Attach policy to a user
provider.attachPolicy({
  policyId: 'policy-1',
  scope: { type: 'TENANT', id: 'tenant-123' },
  principal: { type: 'USER', id: 'user-456' },
});

// Retrieve policies for a principal
const policies = await provider.getPolicies({
  scope: { type: 'TENANT', id: 'tenant-123' },
  principal: { type: 'USER', id: 'user-456' },
});

console.log(policies);
// [{ id: 'policy-1', document: { ... } }]

📚 Core Concepts

Policy Provider Interface

The base interface that all storage providers must implement:

interface PolicyProvider {
  getPolicies(input: GetPoliciesInput): Promise<PolicySource[]>;
}

Scope

Defines the organizational boundary for policies:

type ScopeType = 'PLATFORM' | 'TENANT' | 'WORKSPACE' | 'APP';

interface AutorixScope {
  type: ScopeType;
  id?: string;
}

Scope Examples:

  • { type: 'PLATFORM' } - Global/platform-wide policies
  • { type: 'TENANT', id: 'tenant-123' } - Tenant-specific policies
  • { type: 'WORKSPACE', id: 'ws-456' } - Workspace-specific policies
  • { type: 'APP', id: 'app-789' } - Application-specific policies

Principal

Represents an entity that can have policies attached:

type PrincipalType = 'USER' | 'ROLE' | 'GROUP';

interface PrincipalRef {
  type: PrincipalType;
  id: string;
}

Principal Examples:

  • { type: 'USER', id: 'user-123' } - Individual user
  • { type: 'ROLE', id: 'admin' } - Role-based
  • { type: 'GROUP', id: 'engineering' } - Group-based

Policy Source

The stored policy with its document:

interface PolicySource {
  id: string;
  document: PolicyDocument;
}

Get Policies Input

Parameters for retrieving policies:

interface GetPoliciesInput {
  scope: AutorixScope;
  principal: PrincipalRef;
  roleIds?: string[];
  groupIds?: string[];
}

🔧 Memory Provider API

Constructor

const provider = new MemoryPolicyProvider();

addPolicy(policy: PolicyRecord): this

Adds a policy to the provider.

provider.addPolicy({
  id: 'read-policy',
  scope: { type: 'TENANT', id: 'tenant-123' },
  document: {
    Statement: [
      {
        Effect: 'Allow',
        Action: ['document:read', 'document:list'],
        Resource: '*',
      },
    ],
  },
});

attachPolicy(params): this

Attaches a policy to a principal within a scope.

provider.attachPolicy({
  policyId: 'read-policy',
  scope: { type: 'TENANT', id: 'tenant-123' },
  principal: { type: 'USER', id: 'user-456' },
});

getPolicies(input: GetPoliciesInput): Promise<PolicySource[]>

Retrieves all policies applicable to a principal.

const policies = await provider.getPolicies({
  scope: { type: 'TENANT', id: 'tenant-123' },
  principal: { type: 'USER', id: 'user-456' },
  roleIds: ['admin', 'editor'],
  groupIds: ['engineering'],
});

🎯 Advanced Usage

Role-based Policies

const provider = new MemoryPolicyProvider();

// Add admin policy
provider.addPolicy({
  id: 'admin-policy',
  scope: { type: 'TENANT', id: 'tenant-123' },
  document: {
    Statement: [
      {
        Effect: 'Allow',
        Action: '*',
        Resource: '*',
      },
    ],
  },
});

// Attach to admin role
provider.attachPolicy({
  policyId: 'admin-policy',
  scope: { type: 'TENANT', id: 'tenant-123' },
  principal: { type: 'ROLE', id: 'admin' },
});

// Get policies for a user with admin role
const policies = await provider.getPolicies({
  scope: { type: 'TENANT', id: 'tenant-123' },
  principal: { type: 'USER', id: 'user-123' },
  roleIds: ['admin'], // User has admin role
});

// Returns: [{ id: 'admin-policy', document: { ... } }]

Group-based Policies

const provider = new MemoryPolicyProvider();

// Add engineering group policy
provider.addPolicy({
  id: 'eng-policy',
  scope: { type: 'TENANT', id: 'tenant-123' },
  document: {
    Statement: [
      {
        Effect: 'Allow',
        Action: ['repo:*', 'deploy:*'],
        Resource: '*',
      },
    ],
  },
});

// Attach to engineering group
provider.attachPolicy({
  policyId: 'eng-policy',
  scope: { type: 'TENANT', id: 'tenant-123' },
  principal: { type: 'GROUP', id: 'engineering' },
});

// Get policies for a user in engineering group
const policies = await provider.getPolicies({
  scope: { type: 'TENANT', id: 'tenant-123' },
  principal: { type: 'USER', id: 'user-456' },
  groupIds: ['engineering'],
});

Multiple Policy Attachments

const provider = new MemoryPolicyProvider();

// Add policies
provider
  .addPolicy({
    id: 'base-user-policy',
    scope: { type: 'TENANT', id: 'tenant-123' },
    document: {
      Statement: [
        {
          Effect: 'Allow',
          Action: ['document:read', 'document:list'],
          Resource: '*',
        },
      ],
    },
  })
  .addPolicy({
    id: 'editor-policy',
    scope: { type: 'TENANT', id: 'tenant-123' },
    document: {
      Statement: [
        {
          Effect: 'Allow',
          Action: ['document:create', 'document:update'],
          Resource: '*',
        },
      ],
    },
  })
  .addPolicy({
    id: 'team-policy',
    scope: { type: 'TENANT', id: 'tenant-123' },
    document: {
      Statement: [
        {
          Effect: 'Allow',
          Action: 'project:*',
          Resource: 'project/*',
        },
      ],
    },
  });

// Attach to user directly
provider.attachPolicy({
  policyId: 'base-user-policy',
  scope: { type: 'TENANT', id: 'tenant-123' },
  principal: { type: 'USER', id: 'user-123' },
});

// Attach to editor role
provider.attachPolicy({
  policyId: 'editor-policy',
  scope: { type: 'TENANT', id: 'tenant-123' },
  principal: { type: 'ROLE', id: 'editor' },
});

// Attach to team group
provider.attachPolicy({
  policyId: 'team-policy',
  scope: { type: 'TENANT', id: 'tenant-123' },
  principal: { type: 'GROUP', id: 'team-alpha' },
});

// Get all policies for user with role and group
const policies = await provider.getPolicies({
  scope: { type: 'TENANT', id: 'tenant-123' },
  principal: { type: 'USER', id: 'user-123' },
  roleIds: ['editor'],
  groupIds: ['team-alpha'],
});

// Returns 3 policies: base-user-policy, editor-policy, team-policy

Hierarchical Scopes

const provider = new MemoryPolicyProvider();

// Platform-wide policy
provider
  .addPolicy({
    id: 'platform-policy',
    scope: { type: 'PLATFORM' },
    document: {
      Statement: [
        {
          Effect: 'Deny',
          Action: 'system:delete',
          Resource: '*',
        },
      ],
    },
  })
  .attachPolicy({
    policyId: 'platform-policy',
    scope: { type: 'PLATFORM' },
    principal: { type: 'USER', id: 'user-123' },
  });

// Tenant-specific policy
provider
  .addPolicy({
    id: 'tenant-policy',
    scope: { type: 'TENANT', id: 'tenant-123' },
    document: {
      Statement: [
        {
          Effect: 'Allow',
          Action: 'document:*',
          Resource: '*',
        },
      ],
    },
  })
  .attachPolicy({
    policyId: 'tenant-policy',
    scope: { type: 'TENANT', id: 'tenant-123' },
    principal: { type: 'USER', id: 'user-123' },
  });

// Get tenant policies only (platform policies need separate query)
const tenantPolicies = await provider.getPolicies({
  scope: { type: 'TENANT', id: 'tenant-123' },
  principal: { type: 'USER', id: 'user-123' },
});

🔍 Examples

Example 1: Simple User Permissions

import { MemoryPolicyProvider } from '@autorix/storage';

const provider = new MemoryPolicyProvider();

// Create read-only policy
provider.addPolicy({
  id: 'readonly',
  scope: { type: 'TENANT', id: 'acme-corp' },
  document: {
    Statement: [
      {
        Effect: 'Allow',
        Action: ['*:read', '*:list'],
        Resource: '*',
      },
    ],
  },
});

// Assign to user
provider.attachPolicy({
  policyId: 'readonly',
  scope: { type: 'TENANT', id: 'acme-corp' },
  principal: { type: 'USER', id: 'viewer-1' },
});

// Query policies
const policies = await provider.getPolicies({
  scope: { type: 'TENANT', id: 'acme-corp' },
  principal: { type: 'USER', id: 'viewer-1' },
});

Example 2: Role-based Access Control

const provider = new MemoryPolicyProvider();

// Admin policy
provider
  .addPolicy({
    id: 'admin-full-access',
    scope: { type: 'TENANT', id: 'acme-corp' },
    document: {
      Statement: [{ Effect: 'Allow', Action: '*', Resource: '*' }],
    },
  })
  .attachPolicy({
    policyId: 'admin-full-access',
    scope: { type: 'TENANT', id: 'acme-corp' },
    principal: { type: 'ROLE', id: 'admin' },
  });

// Editor policy
provider
  .addPolicy({
    id: 'editor-access',
    scope: { type: 'TENANT', id: 'acme-corp' },
    document: {
      Statement: [
        {
          Effect: 'Allow',
          Action: ['document:*', 'file:*'],
          Resource: '*',
        },
      ],
    },
  })
  .attachPolicy({
    policyId: 'editor-access',
    scope: { type: 'TENANT', id: 'acme-corp' },
    principal: { type: 'ROLE', id: 'editor' },
  });

// Viewer policy
provider
  .addPolicy({
    id: 'viewer-access',
    scope: { type: 'TENANT', id: 'acme-corp' },
    document: {
      Statement: [
        {
          Effect: 'Allow',
          Action: ['*:read', '*:list'],
          Resource: '*',
        },
      ],
    },
  })
  .attachPolicy({
    policyId: 'viewer-access',
    scope: { type: 'TENANT', id: 'acme-corp' },
    principal: { type: 'ROLE', id: 'viewer' },
  });

// Get policies for editor
const editorPolicies = await provider.getPolicies({
  scope: { type: 'TENANT', id: 'acme-corp' },
  principal: { type: 'USER', id: 'user-123' },
  roleIds: ['editor'],
});

Example 3: Multi-tenant with Workspace Isolation

const provider = new MemoryPolicyProvider();

// Tenant A workspace policy
provider
  .addPolicy({
    id: 'workspace-policy-a',
    scope: { type: 'WORKSPACE', id: 'ws-tenant-a' },
    document: {
      Statement: [
        {
          Effect: 'Allow',
          Action: 'project:*',
          Resource: 'project/*',
        },
      ],
    },
  })
  .attachPolicy({
    policyId: 'workspace-policy-a',
    scope: { type: 'WORKSPACE', id: 'ws-tenant-a' },
    principal: { type: 'USER', id: 'user-tenant-a' },
  });

// Tenant B workspace policy
provider
  .addPolicy({
    id: 'workspace-policy-b',
    scope: { type: 'WORKSPACE', id: 'ws-tenant-b' },
    document: {
      Statement: [
        {
          Effect: 'Allow',
          Action: 'project:read',
          Resource: 'project/*',
        },
      ],
    },
  })
  .attachPolicy({
    policyId: 'workspace-policy-b',
    scope: { type: 'WORKSPACE', id: 'ws-tenant-b' },
    principal: { type: 'USER', id: 'user-tenant-b' },
  });

// Each user only sees their workspace policies
const policiesA = await provider.getPolicies({
  scope: { type: 'WORKSPACE', id: 'ws-tenant-a' },
  principal: { type: 'USER', id: 'user-tenant-a' },
});

const policiesB = await provider.getPolicies({
  scope: { type: 'WORKSPACE', id: 'ws-tenant-b' },
  principal: { type: 'USER', id: 'user-tenant-b' },
});

🔌 Custom Provider Implementation

Implement the PolicyProvider interface for custom storage backends:

import { PolicyProvider, PolicySource, GetPoliciesInput } from '@autorix/storage';
import type { PolicyDocument } from '@autorix/core';

export class DatabasePolicyProvider implements PolicyProvider {
  constructor(private db: Database) {}

  async getPolicies(input: GetPoliciesInput): Promise<PolicySource[]> {
    const { scope, principal, roleIds = [], groupIds = [] } = input;

    // Query your database for policies
    const policies = await this.db.query(`
      SELECT p.id, p.document
      FROM policies p
      JOIN policy_attachments pa ON p.id = pa.policy_id
      WHERE p.scope_type = $1
        AND p.scope_id = $2
        AND (
          (pa.principal_type = 'USER' AND pa.principal_id = $3)
          OR (pa.principal_type = 'ROLE' AND pa.principal_id = ANY($4))
          OR (pa.principal_type = 'GROUP' AND pa.principal_id = ANY($5))
        )
    `, [scope.type, scope.id, principal.id, roleIds, groupIds]);

    return policies.map(row => ({
      id: row.id,
      document: JSON.parse(row.document) as PolicyDocument,
    }));
  }
}

MongoDB Provider Example

import { PolicyProvider, PolicySource, GetPoliciesInput } from '@autorix/storage';
import { MongoClient, Db } from 'mongodb';

export class MongoPolicyProvider implements PolicyProvider {
  constructor(private db: Db) {}

  async getPolicies(input: GetPoliciesInput): Promise<PolicySource[]> {
    const { scope, principal, roleIds = [], groupIds = [] } = input;

    const principals = [
      { type: 'USER', id: principal.id },
      ...roleIds.map(id => ({ type: 'ROLE', id })),
      ...groupIds.map(id => ({ type: 'GROUP', id })),
    ];

    const attachments = await this.db.collection('policy_attachments')
      .find({
        'scope.type': scope.type,
        'scope.id': scope.id,
        $or: principals.map(p => ({
          'principal.type': p.type,
          'principal.id': p.id,
        })),
      })
      .toArray();

    const policyIds = [...new Set(attachments.map(a => a.policyId))];

    const policies = await this.db.collection('policies')
      .find({ _id: { $in: policyIds } })
      .toArray();

    return policies.map(p => ({
      id: p._id.toString(),
      document: p.document,
    }));
  }
}

🧪 Testing

Testing with Memory Provider

import { describe, it, expect } from 'vitest';
import { MemoryPolicyProvider } from '@autorix/storage';

describe('MemoryPolicyProvider', () => {
  it('should return policies for user', async () => {
    const provider = new MemoryPolicyProvider();
    
    provider
      .addPolicy({
        id: 'test-policy',
        scope: { type: 'TENANT', id: 'test-tenant' },
        document: {
          Statement: [
            { Effect: 'Allow', Action: 'test:action', Resource: '*' },
          ],
        },
      })
      .attachPolicy({
        policyId: 'test-policy',
        scope: { type: 'TENANT', id: 'test-tenant' },
        principal: { type: 'USER', id: 'test-user' },
      });

    const policies = await provider.getPolicies({
      scope: { type: 'TENANT', id: 'test-tenant' },
      principal: { type: 'USER', id: 'test-user' },
    });

    expect(policies).toHaveLength(1);
    expect(policies[0].id).toBe('test-policy');
  });

  it('should return policies from roles', async () => {
    const provider = new MemoryPolicyProvider();
    
    provider
      .addPolicy({
        id: 'role-policy',
        scope: { type: 'TENANT', id: 'test-tenant' },
        document: {
          Statement: [
            { Effect: 'Allow', Action: 'admin:*', Resource: '*' },
          ],
        },
      })
      .attachPolicy({
        policyId: 'role-policy',
        scope: { type: 'TENANT', id: 'test-tenant' },
        principal: { type: 'ROLE', id: 'admin' },
      });

    const policies = await provider.getPolicies({
      scope: { type: 'TENANT', id: 'test-tenant' },
      principal: { type: 'USER', id: 'test-user' },
      roleIds: ['admin'],
    });

    expect(policies).toHaveLength(1);
    expect(policies[0].id).toBe('role-policy');
  });

  it('should respect scope isolation', async () => {
    const provider = new MemoryPolicyProvider();
    
    provider
      .addPolicy({
        id: 'tenant-1-policy',
        scope: { type: 'TENANT', id: 'tenant-1' },
        document: {
          Statement: [
            { Effect: 'Allow', Action: '*', Resource: '*' },
          ],
        },
      })
      .attachPolicy({
        policyId: 'tenant-1-policy',
        scope: { type: 'TENANT', id: 'tenant-1' },
        principal: { type: 'USER', id: 'user-1' },
      });

    const policies = await provider.getPolicies({
      scope: { type: 'TENANT', id: 'tenant-2' }, // Different tenant
      principal: { type: 'USER', id: 'user-1' },
    });

    expect(policies).toHaveLength(0);
  });
});

📊 Best Practices

  1. Use appropriate scope types

    • PLATFORM for global policies
    • TENANT for multi-tenant isolation
    • WORKSPACE for workspace-level permissions
    • APP for application-specific policies
  2. Organize policies by role

    • Create role-based policies for common permission sets
    • Attach policies to roles rather than individual users when possible
    • Use groups for team-based permissions
  3. Keep policy documents focused

    • One policy per logical permission set
    • Avoid overly complex policy documents
    • Use multiple policies instead of one large policy
  4. Implement caching for production

    • Cache policy lookups to reduce database queries
    • Invalidate cache when policies are modified
    • Consider using Redis or similar for distributed caching
  5. Monitor policy performance

    • Track policy retrieval times
    • Optimize database queries for large datasets
    • Consider indexing strategy for your storage backend

🔗 Integration with Other Packages

With @autorix/core

import { evaluate } from '@autorix/core';
import { MemoryPolicyProvider } from '@autorix/storage';

const provider = new MemoryPolicyProvider();
// ... setup policies

const policies = await provider.getPolicies({
  scope: { type: 'TENANT', id: 'tenant-123' },
  principal: { type: 'USER', id: 'user-456' },
});

const decision = evaluate({
  action: 'document:read',
  resource: 'document/123',
  policy: policies[0].document,
  ctx: { principal: { id: 'user-456' } },
});

With @autorix/nestjs

import { AutorixModule } from '@autorix/nestjs';
import { MemoryPolicyProvider } from '@autorix/storage';

@Module({
  imports: [
    AutorixModule.forRoot({
      policyProvider: new MemoryPolicyProvider()
        .addPolicy({ ... })
        .attachPolicy({ ... }),
    }),
  ],
})
export class AppModule {}

🔗 Related Packages

📄 License

MIT © Autorix

🤝 Contributing

Contributions are welcome! Please feel free to submit a Pull Request.

📞 Support

For issues and questions, please use the GitHub Issues page.