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

@happyvertical/smrt-profiles

v0.40.63

Published

Profile management system with relationships, metadata, and reciprocal associations for SMRT framework

Readme

@happyvertical/smrt-profiles

Central identity system with multi-auth (Nostr/OIDC/API keys/magic links), relationships, controlled metadata, and audit logging.

Installation

pnpm add @happyvertical/smrt-profiles @happyvertical/sql

Usage

import {
  backfillProfileEmailKeys,
  createProfileFromOidc,
} from '@happyvertical/smrt-profiles';
import { getDatabase } from '@happyvertical/sql';

// Connect after applying schema migrations.
const persistence = { type: 'sqlite' as const, url: 'file:profiles.db' };
// Mark migrated Profile email keys ready before enabling OIDC provisioning.
const db = await getDatabase(persistence);
await backfillProfileEmailKeys(db);

// Provision a new OIDC Profile or reuse its exact issuer/subject link.
const { profile, oidcIdentity, created } = await createProfileFromOidc(
  {
    iss: 'https://accounts.google.com',
    sub: 'abc123',
    email: '[email protected]',
    email_verified: true,
    name: 'Olivia Smith',
  },
  'google',
  { db },
);

Owned assets

import { AssetCollection } from '@happyvertical/smrt-assets';
import { ProfileCollection } from '@happyvertical/smrt-profiles';

const profiles = await ProfileCollection.create({ db });
const assets = await AssetCollection.create({ db });

const headshot = await assets.create({
  name: 'alice-headshot.jpg',
  sourceUri: 'file:///tmp/alice-headshot.jpg',
  mimeType: 'image/jpeg',
});

await profile.addAsset(headshot, 'avatar');
await profiles.addAsset(profile.id!, headshot, 'gallery', 1);

const avatarAssets = await profile.getAssets('avatar');
const galleryAssets = await profiles.getAssets(profile.id!, 'gallery');

API

Models

| Export | Description | |--------|------------| | Profile | Core identity (STI base for Bot/Organization/Person) | | Bot | STI subclass for automated agents | | Organization | STI subclass for companies/groups | | Person | STI subclass for individuals | | ProfileType | Profile classification lookup table | | ProfileMetadata | Per-profile metadata values | | ProfileMetafield | Controlled vocabulary with validation schema | | ProfileRelationship | Directional link between two profiles | | ProfileRelationshipType | Relationship classification with reciprocal flag | | ProfileRelationshipTerm | Time-bounded relationship periods | | ProfileAsset | Dedicated owned-asset join stored in profile_assets with relationship and sortOrder |

Auth Models

| Export | Description | |--------|------------| | OidcIdentity | OIDC provider identity (issuer + subject) | | NostrIdentity | Nostr keypair with AES-256-GCM encryption | | ApiKey | SHA-256 hashed API key with scope and expiry | | MagicLinkToken | One-time passwordless auth token | | AuditLog | Action/resource audit trail with source tracking |

Collections

| Export | Description | |--------|------------| | ProfileCollection | CRUD and query for profiles | | ProfileAssetCollection | Direct access to profile_assets rows plus asset helper wrappers | | ProfileTypeCollection | Profile type management | | ProfileMetadataCollection | Metadata value operations | | ProfileMetafieldCollection | Metafield vocabulary management | | ProfileRelationshipCollection | Relationship queries | | ProfileRelationshipTypeCollection | Relationship type management | | ProfileRelationshipTermCollection | Term period management | | ApiKeyCollection | API key lookup and management | | AuditLogCollection | Audit log queries | | MagicLinkTokenCollection | Magic link token operations | | NostrIdentityCollection | Nostr identity lookup (includes NIP-05) | | OidcIdentityCollection | OIDC identity lookup |

ProfileCollection.findUniqueGlobalPersonByEmail(email) is the supported verified-identity lookup. It reads across tenant scopes and returns a Profile only when the case-insensitive email has exactly one match and that row is a global Person; tenant-scoped, non-Person, and duplicate matches throw a CanonicalPersonProfileError. Matching uses readonly, indexed Profile.emailKey, derived with the exported TypeScript normalizeIdentityEmail() helper so Unicode casing and whitespace behave the same on every database adapter. Use requireCanonicalGlobalPerson(profileId, email?) to validate an application-selected Profile against the same invariant. When email is omitted, the helper validates uniqueness using the Profile's current stored email. reserveCanonicalIdentityEmail(profileId, email) additionally claims the normalized address in the private oidc_profile_email_reservations table. Omit email to synchronize the reservation from the Profile's current stored email; changing the address moves the reservation, and clearing it removes the reservation. Legacy simple Person STI discriminators remain valid and are handled by core's normal upgrade path.

OidcIdentity.identityKey stores a nullable, unique issuer/subject natural key for transaction-safe first-login races. The model derives it from issuer and subject on every save, so callers cannot desynchronize it. New links populate it and legacy rows backfill it when reused. Issuer and subject are opaque, case-sensitive OIDC identifiers: surrounding whitespace is preserved and is part of the key; trimming is used only to reject an all-whitespace claim. The generated REST, MCP, and CLI surfaces are read-only because identity mutation is an authentication authority change; trusted callers link identities through the transactional provisioning APIs. The legacy OidcIdentity.findOrCreate() method is deprecated: it transactionally reuses one exact safe issuer/subject link for compatibility but refuses to create a new authentication link. Use createProfileFromOidc() or the users package's owner-aware provisioning API for creation. The deprecated OidcIdentityCollection.linkToProfile() and Profile.linkOidcIdentity() helpers delegate to the same exact-reuse-only path and cannot plant or rebind a link. Existing installations must stop or upgrade legacy Profile writers, run smrt db:status, smrt db:migrate, and then smrt db:status before deploying this version to add the identity keys and private email-reservation table. Then run backfillProfileEmailKeys(db) once from a single deploy process before enabling verified-email provisioning. The transaction-safe backfill is idempotent; canonical email lookup and reservation fail closed with email_key_backfill_required while the standard _smrt_backfills readiness marker is absent. Exact issuer/subject reuse does not depend on email-key readiness because it does not perform email-based linking. The identity-boundary helpers use the readiness guard and indexed key. The general-purpose ProfileCollection.findByEmail() retains its compatible legacy lookup behavior and is not suitable for identity linking. Only the explicit deploy-time backfill scans the Profile table and records readiness; guarded runtime identity lookups use the indexed key and validate returned candidates. createProfileFromOidc() requires a transaction-capable database. Pass the root database, which must expose beginTransaction, and s-m-r-t owns the transaction; an already transaction-bound handle is supported through a savepoint. Paths that perform canonical email lookup or reservation require _smrt_backfills to exist first. Provisioning never attempts tracker DDL on a caller-owned transaction; for those paths, if the table is absent, pass the root database so s-m-r-t can initialize it outside the transaction. Exact issuer/subject reuse skips the email-key readiness-marker lookup, but root coordination still initializes the shared tracker table. Caller-owned exact reuse does not consult the tracker and therefore does not require that table. A handle exposing only transaction() is ambiguous and fails closed. For adapters without nested savepoint support, including DuckDB, pass the root database rather than calling the helper inside an outer transaction. Provisioning fails before durable writes when neither safe path is available. The typed OIDC provisioning decision matrix is the canonical package-by-package behavior contract. It records exact reuse, new identity and resolver outcomes, readiness, retries, adapter support, public errors, and permitted row creation; the Profiles and Users suites execute its applicable rows directly. In particular, Profile-only exact reuse preserves an established legacy link because it creates no User/session authentication state, while UserCollection.getOrCreateFromOidc() remains owner-aware and fail-closed. Use the Users API for verified-email reuse and application reconciliation. Its authorizeProfileOwner option is the only supported exception for a new issuer/subject targeting a pre-provisioned owned Person: the users package atomically verifies the canonical Profile, sole approved owner, matching User email, and non-rebindable identity. Without that explicit application authorization, the owned Profile still returns profile_owned.

Profile and ProfileCollection both expose getAssets(), addAsset(), and removeAsset() helpers backed by profile_assets. Typical relationships are avatar, gallery, and attachment.

Auth Functions

| Export | Description | |--------|------------| | resolveIdentity | Resolve profile from any auth method | | createProfileFromOidc | Create a Profile + OIDC identity or reuse an exact issuer/subject link; existing email matches fail closed | | normalizeIdentityEmail | Adapter-independent Unicode/whitespace email canonicalizer used by identity keys | | backfillProfileEmailKeys | Transactionally populate normalized keys for migrated Profiles | | PROFILE_EMAIL_KEY_BACKFILL_NAME | Durable readiness-marker name recorded by the Profile email-key backfill | | createProfileFromNostr | Create profile + Nostr identity in one call | | createAuthEvent | Create a Nostr auth event | | verifyAuthEvent | Verify a Nostr auth event signature | | createMagicLinkService | Factory for magic link auth service | | createNip05Handler | Factory for NIP-05 address handler |

Nostr Crypto

| Export | Description | |--------|------------| | generateNostrKeypair | Generate new Nostr keypair | | encryptPrivkey / decryptPrivkey | AES-256-GCM key encryption | | deriveEncryptionKey | Derive encryption key from master secret | | getPublicKey | Derive pubkey from privkey | | signEvent / computeEventId | Nostr event signing | | verifyNostrSignature | Verify Nostr signature | | pubkeyToNpub / npubToPubkey | Bech32 pubkey conversion | | privkeyToNsec / nsecToPrivkey | Bech32 privkey conversion | | isValidPubkey / isValidPrivkey | Key validation | | parseNip05Identifier / isValidNip05Identifier | NIP-05 parsing |

Key Types

ProfileOptions, ProfileTypeOptions, ProfileMetadataOptions, ProfileMetafieldOptions, ProfileRelationshipOptions, ProfileRelationshipTypeOptions, ProfileRelationshipTermOptions, OidcIdentityOptions, CanonicalPersonProfileErrorCode, NostrIdentityOptions, ApiKeyOptions, GenerateKeyResult, MagicLinkTokenOptions, GenerateTokenResult, AuditLogOptions, AuditSource, AuthContext, ResolveIdentityResult, InitiateResult, VerifyResult, MagicLinkConfig, MagicLinkService, Nip05HandlerConfig, Nip05HandlerResult, Nip05Request, Nip05Response, NostrEvent, NostrKeypair, EncryptedKey, ValidationSchema, ValidatorFunction, ReciprocalHandler

Dependencies

  • @happyvertical/smrt-core -- ORM base classes
  • @happyvertical/ai -- AI client (SDK)
  • @happyvertical/sql -- Database operations (SDK)
  • @happyvertical/files -- Filesystem utilities (SDK)
  • @happyvertical/logger -- Structured logging (SDK)
  • @happyvertical/utils -- Shared utilities (SDK)
  • @noble/curves -- Nostr cryptography
  • bech32 -- Bech32 encoding for Nostr keys
  • Peer: @happyvertical/smrt-tenancy