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

@stacksolo/runtime

v0.1.4

Published

Runtime utilities for StackSolo applications

Downloads

35

Readme

@stacksolo/runtime

Runtime utilities for StackSolo applications. Provides environment detection, service-to-service calls, and auto-configured GCP SDK clients.

Installation

npm install @stacksolo/runtime

Usage

Environment Detection

import { env } from '@stacksolo/runtime';

// Check environment
if (env.isLocal) {
  console.log('Running locally with emulators');
}

if (env.isProduction) {
  console.log('Running in production');
}

// Access environment variables
const projectName = env.projectName;
const gcpProjectId = env.gcpProjectId;

// Get any env var with optional default
const apiKey = env.get('API_KEY', 'default-key');

// Get required env var (throws if missing)
const secret = env.require('SECRET_KEY');

Firestore Client

Auto-connects to the Firestore emulator when FIRESTORE_EMULATOR_HOST is set.

import { firestore } from '@stacksolo/runtime';

const db = firestore();

// Use Firestore as normal
const doc = await db.collection('users').doc('123').get();

Service-to-Service Calls

Call other services in your stack via the gateway.

import { services } from '@stacksolo/runtime';

// Simple call
const response = await services.call('hello', '/greet');

// With options
const response = await services.call('api', '/users', {
  method: 'POST',
  body: { email: '[email protected]' },
  timeout: 5000,
});

// Typed service client
const api = services.create<MyApiResponse>('api');
const users = await api.get('/users');
const created = await api.post('/users', { name: 'John' });

Secrets (GCP Secret Manager)

Fetch secrets from GCP Secret Manager in production, with automatic fallback to environment variables in local development.

import { getSecret, secrets } from '@stacksolo/runtime';

// Simple usage
const apiKey = await getSecret('api-key');

// With options
const dbPassword = await getSecret('db-password', {
  version: '2',           // Specific version (default: 'latest')
  cacheTtlMs: 60000,      // Cache for 1 minute (default: 5 minutes)
  envVar: 'DB_PASSWORD',  // Check this env var first in local dev
  fallback: 'dev-pass',   // Fallback if secret not found
});

// Required secret (throws if not found)
const jwtSecret = await secrets.require('jwt-secret');

// Batch fetch multiple secrets
const allSecrets = await secrets.getMany(['api-key', 'db-password', 'jwt-secret']);
console.log(allSecrets['api-key']);

// Cache management
secrets.clearCache();              // Clear all cached secrets
secrets.invalidate('api-key');     // Invalidate specific secret

Local Development:

  • Secrets are read from environment variables
  • api-key maps to API_KEY environment variable
  • Set secrets in .env.local or via stacksolo dev ConfigMap

Production:

  • Secrets are fetched from GCP Secret Manager
  • Results are cached for 5 minutes by default
  • Requires @google-cloud/secret-manager package

Kernel Client

Interact with the StackSolo kernel for auth validation, file operations, and events.

Auth Validation

Validate Firebase ID tokens via the kernel's internal /auth/validate endpoint.

import { kernel } from '@stacksolo/runtime';

// Validate a token
const result = await kernel.validateToken(req.headers.authorization);
if (result.valid) {
  console.log('User ID:', result.uid);
  console.log('Email:', result.email);
  console.log('Claims:', result.claims);
} else {
  console.log('Invalid:', result.code); // INVALID_TOKEN, TOKEN_EXPIRED, etc.
}

// Express middleware (adds req.user)
app.use('/api', kernel.authMiddleware());

app.get('/api/me', (req, res) => {
  res.json({ uid: req.user.uid, email: req.user.email });
});

File Operations (via NATS)

Full file management via signed URLs and direct GCS operations.

import { kernel } from '@stacksolo/runtime';

// Get signed upload URL
const { uploadUrl, expiresAt } = await kernel.files.getUploadUrl(
  'users/123/avatar.png',
  'image/png'
);
// Client uses uploadUrl to PUT directly to GCS

// Get signed download URL
const { downloadUrl } = await kernel.files.getDownloadUrl('users/123/avatar.png');

// List files in a prefix
const { files, nextPageToken } = await kernel.files.list({
  prefix: 'users/123/',
  maxResults: 50,
});

// Paginate through results
if (nextPageToken) {
  const nextPage = await kernel.files.list({
    prefix: 'users/123/',
    pageToken: nextPageToken,
  });
}

// Delete a file
await kernel.files.delete('users/123/old-avatar.png');

// Move/rename a file
await kernel.files.move(
  'uploads/temp/file.pdf',
  'documents/reports/2024/file.pdf'
);

// Get file metadata
const metadata = await kernel.files.getMetadata('users/123/avatar.png');
console.log('Size:', metadata.size);
console.log('Content-Type:', metadata.contentType);
console.log('Created:', metadata.created);
console.log('Updated:', metadata.updated);

Events (via NATS JetStream)

Publish and subscribe to events across your services. Events are stored durably in JetStream and delivered reliably.

Publishing events:

import { kernel } from '@stacksolo/runtime';

// Publish an event (stored in JetStream)
const result = await kernel.events.publish('user.created', {
  userId: '123',
  email: '[email protected]',
});
console.log('Event sequence:', result.seq);

For Containers (always-on services):

Use direct NATS subscription when your service maintains a persistent connection:

// Direct NATS subscription - containers only
const sub = await kernel.events.subscribe('user.*', (event) => {
  console.log('Event:', event.type, event.data);
});

// Cleanup on shutdown
sub.unsubscribe();
await kernel.closeConnection();

For Functions (serverless):

Register an HTTP endpoint to receive events. The kernel pushes events to your function:

// Register your function's endpoint to receive events
const sub = await kernel.events.registerSubscription({
  pattern: 'user.created',
  endpoint: 'https://my-function.run.app/events',
  serviceName: 'user-notifications',
  maxRetries: 3,        // Retry failed deliveries
  retryDelayMs: 1000,   // Exponential backoff
});

// Your function receives POST requests:
app.post('/events', (req, res) => {
  const event = req.body;
  // event.type = 'user.created'
  // event.data = { userId: '123', ... }
  // event.timestamp = '2024-01-01T00:00:00Z'
  // event.seq = 42

  console.log('Received event:', event.type);
  res.sendStatus(200); // Acknowledge receipt
});

// Unregister when no longer needed
await kernel.events.unregisterSubscription(sub.subscriptionId);

// List active subscriptions
const { subscriptions } = await kernel.events.listSubscriptions();

Event Patterns:

  • user.created - Exact match
  • user.* - Single wildcard (matches user.created, user.updated)
  • order.> - Multi-level wildcard (matches order.item.added, order.payment.completed)

Plugin Clients

The runtime has a plugin system that lets StackSolo plugins provide their own clients. This means when you install a plugin, you can use its client through the runtime.

What is a Plugin Client?

A plugin client is code that a plugin provides to help you interact with its features. For example, a payments plugin might provide a PaymentsClient that lets you charge credit cards.

Using a Plugin Client

When you install a plugin that provides a runtime client, you can access it like this:

import { getPluginClient } from '@stacksolo/runtime';

// Import the plugin to register it (this is required!)
import '@my-org/payments-plugin';

// Get the plugin's client
const payments = await getPluginClient('payments');

// Now use it
await payments.charge({ amount: 1000, currency: 'usd' });

Important: You must import the plugin package before using getPluginClient(). The import registers the plugin with the runtime.

Alternative Ways to Access Plugin Clients

import { plugins } from '@stacksolo/runtime';
import '@my-org/payments-plugin';

// Using the plugins namespace (same as getPluginClient)
const payments = await plugins.get('payments');

// Check if a plugin is available
if (plugins.has('payments')) {
  const payments = await plugins.get('payments');
}

// See what plugins are registered
console.log(plugins.list()); // ['payments', 'analytics', ...]

TypeScript: Adding Types

For better autocomplete and type checking, pass the client type as a generic:

import { getPluginClient } from '@stacksolo/runtime';
import type { PaymentsClient } from '@my-org/payments-plugin';
import '@my-org/payments-plugin';

// Now `payments` has full type information
const payments = await getPluginClient<PaymentsClient>('payments');
payments.charge({ amount: 1000 }); // TypeScript knows this method exists

Cleanup on Shutdown

If your app needs to gracefully shut down (close connections, etc.), call plugins.cleanup():

import { plugins } from '@stacksolo/runtime';

// When your app is shutting down
process.on('SIGTERM', async () => {
  await plugins.cleanup(); // Tells all plugins to clean up
  process.exit(0);
});

For Plugin Authors

If you're building a plugin that provides a runtime client, here's how to register it:

// In your plugin's main file (e.g., index.ts)
import { registerPlugin } from '@stacksolo/runtime';

// Create your client class
class MyServiceClient {
  constructor(private endpoint: string) {}

  async doSomething() {
    const response = await fetch(`${this.endpoint}/api/something`);
    return response.json();
  }

  close() {
    // Clean up any connections
  }
}

// Register with the runtime
registerPlugin('my-service', {
  // Factory function - called when someone first requests the client
  createClient: (config) => {
    // config.environment is 'development' or 'production'
    const endpoint = config.environment === 'development'
      ? 'http://localhost:3000'
      : process.env.MY_SERVICE_URL || 'https://my-service.example.com';

    return new MyServiceClient(endpoint);
  },

  // Optional: cleanup function called during shutdown
  cleanup: (client) => client.close(),

  // Optional: document what env vars your plugin uses
  envKeys: ['MY_SERVICE_URL'],
});

// Export the client type so users can import it
export type { MyServiceClient };

The createClient function is only called once, when the client is first requested. After that, the same instance is reused.

Environment Variables

The runtime automatically reads these environment variables:

| Variable | Description | |----------|-------------| | NODE_ENV | Environment mode (development/production) | | GATEWAY_URL | URL for inter-service calls | | KERNEL_URL | Kernel HTTP URL (default: http://kernel:8090) | | NATS_URL | Kernel NATS URL (default: nats://kernel:4222) | | STACKSOLO_PROJECT_NAME | Project name | | GCP_PROJECT_ID | Google Cloud project ID | | FIRESTORE_EMULATOR_HOST | Firestore emulator host | | FIREBASE_AUTH_EMULATOR_HOST | Firebase Auth emulator host | | PUBSUB_EMULATOR_HOST | Pub/Sub emulator host |

When running with stacksolo dev, these are automatically injected via Kubernetes ConfigMap.

Peer Dependencies

The following are optional peer dependencies:

  • firebase-admin - Required for firestore() helper
  • @google-cloud/pubsub - For Pub/Sub integration
  • @google-cloud/storage - For Cloud Storage integration
  • @google-cloud/secret-manager - For getSecret() / secrets helpers
  • nats - For kernel NATS features (files, events)

Install only what you need:

# For Firestore
npm install firebase-admin

# For secrets
npm install @google-cloud/secret-manager

# For kernel NATS features (files, events)
npm install nats

License

MIT