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

@valink-solutions-ltd/ozhi

v0.1.2

Published

Simple pluggable audit middleware for Svelte 5 and SvelteKit

Downloads

3

Readme

Ozhi - Audit System

License Test Publish NPM Version

A comprehensive pluggable audit system for SvelteKit applications with seamless integration for Drizzle ORM, Better-Auth, Resend, and Stripe.

Features

  • 🔌 Pluggable Architecture - Easy to integrate without modifying existing code
  • 🔐 Automatic Context Capture - User, session, IP, and request details via AsyncLocalStorage
  • 📝 Type-Safe Events - Full TypeScript support with categorized events
  • 🗄️ Optimized Database Schema - Efficient Drizzle schema with proper indexing
  • 🪝 Multiple Integration Points - Middleware, wrapper functions and decorators (future)
  • 🔔 Built-in Plugins - Notifications, Stripe enrichment, and security monitoring
  • 🎯 Flexible Targeting - Track changes to any entity with before/after states

Installation & Usage

1. Install

pnpm add @valink-solutions-ltd/ozhi

2. Setup drizzle schema

In your src/lib/server/db/index.ts:

import { drizzle } from 'drizzle-orm/postgres-js';
import { postgres } from 'drizzle-orm/postgres-js';
import * as schema from "./schema";
import * as ohziSchema from '@valink-solutions-ltd/ozhi/db/schema';

const client = postgres(process.env.DATABASE_URL!);
export const db = drizzle(client, {{ ...schema, ...ohziSchema }});

3. Configure Hooks

In your src/hooks.server.ts:

import { createAuditMiddleware } from '@valink-solutions-ltd/ozhi';
import { auditor } from '@valink-solutions-ltd/ozhi';
import {
	createNotificationPlugin,
	createStripePlugin,
	createSecurityPlugin
} from '@valink-solutions-ltd/ozhi/plugins';
import { resend } from 'resend';
import { stripe } from 'stripe';

// Configure plugins (optional)
auditor.registerPlugin(
	createSecurityPlugin({
		maxFailedAttempts: 5,
		timeWindowMs: 300000 // 5 minutes
	})
);

auditor.registerPlugin(
	createNotificationPlugin({
		resend,
		notifyEmail: '[email protected]',
		severityThreshold: AuditSeverity.HIGH
	})
);

auditor.registerPlugin(createStripePlugin({ stripe }));

// Initialize auditor
await auditor.initialize();

// Apply middleware
export const handle = createAuditMiddleware();

4. Start Auditing

Manual Audit Logging

import { audit } from '@valink-solutions-ltd/ozhi';
import { AuditCategory, AuditResult } from '@valink-solutions-ltd/ozhi/types';

// Log a successful action
await audit({
	action: 'user.profile.updated',
	category: AuditCategory.DATA_MODIFICATION,
	result: AuditResult.SUCCESS,
	target: {
		type: 'user',
		id: userId,
		name: userEmail
	},
	changes: {
		before: { role: 'user' },
		after: { role: 'admin' },
		fields: ['role']
	}
});

Wrapped Server Endpoints

import { withAudit } from '@valink-solutions-ltd/ozhi/middleware';
import type { RequestHandler } from './$types';

export const POST: RequestHandler = withAudit(
	'invoice.complete',
	AuditCategory.INVOICE,
	async ({ request }) => {
		const { invoiceId } = await request.json();
		// Your business logic here
		return json({ success: true });
	}
);

Form Actions with Decorators

import { auditFormAction } from '@valink-solutions-ltd/ozhi/helpers';
import type { Actions } from './$types';

export const actions = {
	delete: auditFormAction(
		'user.delete',
		AuditCategory.USER_MANAGEMENT
	)(async ({ params }) => {
		// Delete user logic
	})
} satisfies Actions;

Better-Auth Integration

Automatically audit authentication events:

import { betterAuth } from 'better-auth';
import { audit } from '@valink-solutions-ltd/ozhi';
import { AuditCategory, AuditResult } from '@valink-solutions-ltd/ozhi/types';

export const auth = betterAuth({
	// ... your config
	hooks: {
		after: {
			signIn: async ({ user, session }) => {
				await audit({
					action: 'auth.signin',
					category: AuditCategory.AUTH,
					result: AuditResult.SUCCESS,
					target: {
						type: 'user',
						id: user.id,
						name: user.email
					}
				});
			},
			signOut: async ({ user }) => {
				await audit({
					action: 'auth.signout',
					category: AuditCategory.AUTH,
					result: AuditResult.SUCCESS,
					target: {
						type: 'user',
						id: user.id,
						name: user.email
					}
				});
			}
		}
	}
});

Audit Categories

  • AUTH - Authentication events (login, logout, password changes)
  • PAYMENT - Payment processing events
  • DATA_ACCESS - Read operations on sensitive data
  • DATA_MODIFICATION - Create, update, delete operations
  • SYSTEM - System-level events
  • INVOICE - Invoice-related operations
  • USER_MANAGEMENT - User administration events

Severity Levels

  • LOW - Routine operations
  • MEDIUM - Important business operations
  • HIGH - Security-relevant or critical operations
  • CRITICAL - High-risk operations requiring immediate attention

Plugin Development

Create custom plugins by implementing the AuditPlugin interface:

import type { AuditPlugin, AuditEvent } from '@valink-solutions-ltd/ozhi/types';

export function createCustomPlugin(): AuditPlugin {
	return {
		name: 'custom-plugin',

		initialize: async () => {
			// Setup code
		},

		beforeAudit: async (event: AuditEvent) => {
			// Modify or cancel events
			// Return null to cancel, or modified event
			return event;
		},

		afterAudit: async (event: AuditEvent) => {
			// React to audit events
		}
	};
}

Built-in Plugins

Notification Plugin

Sends email alerts for high-severity events:

createNotificationPlugin({
	resend: resendClient,
	notifyEmail: '[email protected]',
	severityThreshold: AuditSeverity.HIGH // Only alert for high and critical
});

Security Plugin

Monitors and alerts on suspicious activity:

createSecurityPlugin({
	maxFailedAttempts: 5,
	timeWindowMs: 300000 // 5 minutes
});

Stripe Plugin

Enriches payment events with Stripe metadata:

createStripePlugin({
	stripe: stripeClient
});

Querying Audit Logs

Use the built-in query builder:

import { queryAuditLogs } from '@valink-solutions-ltd/ozhi/query-builder';

const logs = await queryAuditLogs({
	userId: 'user_123',
	category: AuditCategory.PAYMENT,
	severity: [AuditSeverity.HIGH, AuditSeverity.CRITICAL],
	startDate: new Date('2024-01-01'),
	endDate: new Date('2024-12-31'),
	limit: 50,
	offset: 0
});

Best Practices

  1. Use Consistent Action Names: Follow a dot-notation pattern (e.g., entity.action.detail)
  2. Include Context: Always provide meaningful target information
  3. Track Changes: Use the changes object for data modifications
  4. Set Appropriate Severity: Let critical events trigger notifications
  5. Leverage Metadata: Store additional context in metadata fields

Security Considerations

  • Audit logs are tamper-evident once written
  • Consider encrypting sensitive data in metadata fields
  • Implement retention policies for compliance
  • Use read-only database users for audit queries
  • Monitor for unusual patterns using the security plugin

TypeScript Support

Full TypeScript support with exported types:

import type {
	AuditEvent,
	AuditContext,
	AuditCategory,
	AuditSeverity,
	AuditResult,
	AuditTarget,
	AuditChanges,
	AuditPlugin
} from '@valink-solutions-ltd/ozhi/types';

Architecture

The system uses:

  • AsyncLocalStorage for request-scoped context
  • Drizzle ORM for database operations
  • Plugin Architecture for extensibility
  • Middleware Pattern for automatic context injection

License

MIT

Contributing

Contributions are welcome! Please ensure all changes maintain backward compatibility and include appropriate tests.