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

apcore-js

v0.21.0

Published

AI-Perceivable Core — schema-driven module standard

Readme

apcore

TypeScript License OpenSSF Best Practices

AI-Perceivable Core

Build once, invoke by Code or AI.

A schema-enforced module standard for the AI-Perceivable era.

apcore is an AI-Perceivable module standard that makes every interface naturally perceivable and understandable by AI through enforced Schema definitions and behavioral annotations. It provides schema validation, access control, middleware pipelines, and observability built in.

Features

  • Schema-driven modules — Define input/output schemas with TypeBox for runtime validation
  • Executor pipeline — Secured execution lifecycle: context → call chain guard → lookup → ACL → approval gate → middleware before → validation → execute → output validation → middleware after → return
  • Registry system — File-based module discovery with metadata, dependencies, and topological ordering; multi-class discovery from a single file
  • Binding loader — YAML-based module registration for no-code integration
  • Access control (ACL) — Pattern-based rules with identity types, roles, and call-depth conditions
  • Approval system — Pluggable approval gate in the executor pipeline with sync and async (polling) flows, built-in handlers, and tracing integration
  • Middleware — Onion-model middleware with before/after/onError hooks and error recovery; built-in CircuitBreakerMiddleware (CLOSED/OPEN/HALF_OPEN) and OTel-compatible TracingMiddleware
  • Observability — Tracing (spans + BatchSpanProcessor + exporters), metrics (counters + histograms + Prometheus export with /metrics//healthz//readyz), structured logging with RedactionConfig
  • System modules — Built-in system.* modules for AI bidirectional introspection: health, manifest, usage, and runtime control (update_config, reload_module, toggle_feature). Audit trail via AuditStore, config persistence via overridesPath, usage metrics in Prometheus, bulk reload via path_filter glob
  • Event systemEventEmitter with subscriber-level CircuitBreakerWrapper, built-in FileSubscriber, StdoutSubscriber, FilterSubscriber, and pluggable custom types
  • Async tasksAsyncTaskManager with injectable TaskStore (bring your own Redis/Postgres backend), RetryConfig with exponential backoff, and opt-in background reaper
  • Schema export — JSON/YAML schema export with strict and compact modes
  • Caching & pagination annotationscacheable, cacheTtl, cacheKeyFields for result caching; paginated, paginationStyle for paginated modules
  • Config Bus — Namespace-based configuration registry with typed access, env prefix dispatch, hot-reload, and external config mounting (Config.registerNamespace(), config.namespace(), config.bind<T>(), config.mount())

Documentation

For full documentation, including Quick Start guides and API reference, visit: https://aiperceivable.github.io/apcore/getting-started.html

Requirements

  • Node.js >= 18.0.0
  • TypeScript >= 5.5

Installation

npm install apcore-js

Note: The npm package is published as apcore-js (the apcore name is reserved on npm). Python uses apcore, Rust uses the apcore crate.

Quick Start

Simplified Client (Recommended)

The APCore client provides a unified entry point that manages Registry and Executor for you:

import { Type } from '@sinclair/typebox';
import { APCore } from 'apcore-js';

const client = new APCore();

// Register a module
client.module({
  id: 'math.add',
  description: 'Add two numbers',
  inputSchema: Type.Object({ a: Type.Number(), b: Type.Number() }),
  outputSchema: Type.Object({ sum: Type.Number() }),
  execute: (inputs) => ({ sum: (inputs.a as number) + (inputs.b as number) }),
});

// Call, validate, stream — all from one client
const result = await client.call('math.add', { a: 10, b: 5 });
// => { sum: 15 }

const preflight = await client.validate('math.add', { a: 10, b: 5 });
// => { valid: true, checks: [...], requiresApproval: false, errors: [] }

Enabling sys_modules for control/events APIs

APCore.disable, APCore.enable, APCore.on, and APCore.off are gated on the optional system-modules subsystem. They require a Config instance with sys_modules.enabled: true to be passed into the APCore constructor — otherwise the calls throw with a clear "sys_modules must be enabled" message.

import { APCore, Config } from 'apcore-js';

// Inline config (equivalent to writing the same YAML and calling Config.load):
const config = new Config({
  sys_modules: {
    enabled: true,
    // Optional sub-systems:
    events: { enabled: true },     // required for on/off (event emitter)
    control: { enabled: true },    // required for disable/enable (toggle)
  },
});

const client = new APCore({ config });

// Now safe to call:
await client.disable('math.add');
await client.enable('math.add');
client.on('apcore.module.disabled', (event) => {
  console.log('Disabled:', event.data);
});

If you do not need these control/events APIs, omit the Config entirely (as in the basic Quick Start above).

Advanced: Manual Registry + Executor

import { Type } from '@sinclair/typebox';
import { FunctionModule, Registry, Executor } from 'apcore-js';

const greet = new FunctionModule({
  execute: (inputs) => ({ greeting: `Hello, ${inputs.name}!` }),
  moduleId: 'example.greet',
  inputSchema: Type.Object({ name: Type.String() }),
  outputSchema: Type.Object({ greeting: Type.String() }),
  description: 'Greet a user',
});

const registry = new Registry();
registry.register('example.greet', greet);

const executor = new Executor({ registry });
const result = await executor.call('example.greet', { name: 'World' });
// => { greeting: 'Hello, World!' }

API Overview

| Class | Description | |-------|-------------| | APCore | High-level client — register modules, call, stream, validate, listModules, describe, on/off, disable/enable. Note: disable, enable, on, and off require sys_modules.enabled: true in the Config passed to APCore (see Quick Start: Enabling sys_modules). | | Registry | Module storage — discover, register, get, list, watch | | Executor | Execution engine — call with middleware pipeline, ACL, approval | | Context | Request context — trace ID, identity, call chain, cancel token | | Config | Configuration — load from YAML, namespace bus, get/set/bind values | | ACL | Access control — rule-based caller/target authorization | | Middleware | Pipeline hooks — before/after/onError interception | | CircuitBreakerMiddleware | Per-(module, caller) circuit breaker — CLOSED/OPEN/HALF_OPEN with configurable threshold and cooldown | | TracingMiddleware | OTel-compatible span tracing — accepts any OtelTracer/OtelSpan without runtime @opentelemetry/* dependency | | EventEmitter | Event system — subscribe, emit, flush | | CircuitBreakerWrapper | Subscriber-level circuit breaker — protects EventEmitter subscribers from cascading failures | | AsyncTaskManager | Background task execution — injectable store, retry with backoff, opt-in reaper | | PrometheusExporter | HTTP metrics server — /metrics, /healthz, /readyz; optional usageCollector for usage gauges | | InMemoryAuditStore | Control module audit log — records actor, action, before/after change for every control call |

Configuration

Config Bus

Config acts as an ecosystem-level Config Bus. Any package can register a namespace with optional JSON Schema validation, environment variable prefix, and defaults.

import { Config } from 'apcore-js';

// Register a namespace (class-level, shared across all Config instances)
Config.registerNamespace('myPlugin', {
  envPrefix: 'MY_PLUGIN',
  defaults: { timeout: 5000, retries: 3 },
  schema: {
    type: 'object',
    properties: {
      timeout: { type: 'number' },
      retries: { type: 'number' },
    },
  },
});

const config = Config.load('apcore.yaml');

// Dot-path access with namespace resolution
const timeout = config.get('myPlugin.timeout');   // 5000 (or env override)

// Full namespace subtree
const pluginConfig = config.namespace('myPlugin');

// Typed access — pass a class constructor; its constructor receives the namespace dict
class MyPluginConfig {
  timeout: number;
  retries: number;
  constructor(data: Record<string, unknown>) {
    this.timeout = (data['timeout'] as number) ?? 5000;
    this.retries = (data['retries'] as number) ?? 3;
  }
}
const typed = config.bind('myPlugin', MyPluginConfig);

// Mount an external config source (e.g. an existing config file)
config.mount('myPlugin', { fromFile: './my-plugin.yaml' });
// Or from an in-memory object:
config.mount('myPlugin', { fromDict: { timeout: 10000 } });

// Introspect registered namespaces
const names = Config.registeredNamespaces(); // string[]

Environment Variable Overrides

Merge priority (highest wins): environment variables > config file > namespace defaults.

Two prefix conventions are supported:

| Convention | Applies to | Example | |------------|------------|---------| | APCORE_ + KEY_PATH (single _.) | Legacy flat keys | APCORE_EXECUTOR_DEFAULT_TIMEOUT=5000 | | APCORE_ + namespace prefix | apcore sub-package namespaces | APCORE_OBSERVABILITY_TRACING_ENABLED=true |

apcore pre-registers the following namespaces and env prefixes:

| Namespace | Env prefix | Wraps | |-----------|-----------|-------| | observability | APCORE_OBSERVABILITY | apcore.observability.* keys | | sysModules | APCORE_SYS | apcore.sys_modules.* keys |

Sub-packages use their own APCORE_ prefixed name (e.g. APCORE_MCP for apcore-mcp). The longest-prefix-match dispatch algorithm disambiguates correctly.

Hot Reload

config.reload() re-reads the source YAML, re-detects legacy/namespace mode, re-applies all namespace defaults and env overrides, re-validates, and re-reads any mounted files.

const config = Config.load('apcore.yaml');
// ... runtime config change on disk ...
config.reload(); // picks up all changes

YAML File Format

Configuration files support two modes. Legacy mode (no apcore: key) is fully backward compatible. Namespace mode is activated when an apcore: top-level key is present; each namespace occupies its own top-level section. The _config reserved namespace controls validation behavior.

# Namespace mode
apcore:
  version: "0.20.0"

_config:
  strict: true

observability:
  tracing:
    enabled: true
    samplingRate: 1.0

myPlugin:
  timeout: 10000
  retries: 5

System Modules

registerSysModules() auto-registers the built-in system.* modules that let AI agents query, monitor, and control the apcore runtime. Enable them via sys_modules.enabled: true in config, and pass the optional hardening options for production use:

import { registerSysModules, InMemoryAuditStore } from 'apcore-js';

const auditStore = new InMemoryAuditStore();

registerSysModules(registry, executor, config, null, {
  failOnError: true,              // throw on any registration failure (default: false)
  overridesPath: '/etc/apcore/overrides.yaml',  // persist runtime changes across restarts
  auditStore,                     // record every control-module action with actor + change
});

// Available system modules:
// system.health.summary / system.health.module     — health status + error rates
// system.manifest.module / system.manifest.full    — module introspection
// system.usage.summary / system.usage.module       — call counts + latency trends
// system.control.update_config                     — hot-patch config values
// system.control.reload_module                     — hot-reload from disk; supports path_filter glob
// system.control.toggle_feature                    — disable/enable modules at runtime

// Query the audit log after control calls:
const entries = auditStore.query({ moduleId: 'system.control.update_config' });
// entries[0] = { timestamp, action, targetModuleId, actorId, actorType, traceId, change }

Prometheus usage metrics — wire PrometheusExporter with the UsageCollector returned by registerSysModules:

import { PrometheusExporter, MetricsCollector } from 'apcore-js';

const ctx = registerSysModules(registry, executor, config);
const exporter = new PrometheusExporter({
  collector: new MetricsCollector(),
  usageCollector: ctx.usageCollector,  // adds apcore_usage_* metrics to /metrics
});
exporter.start({ port: 9090 });
// GET /metrics now includes:
//   apcore_usage_calls_total{module_id="math.add",status="success"} 5000
//   apcore_usage_error_rate{module_id="math.add"} 0.0004
//   apcore_usage_p99_latency_ms{module_id="math.add"} 45.0

Error Codes

New error codes added in v0.15.0:

| Code | Description | |------|-------------| | CONFIG_NAMESPACE_DUPLICATE | Config.registerNamespace() called with an already-registered name | | CONFIG_NAMESPACE_RESERVED | Config.registerNamespace() called with a reserved name (e.g. _config) | | CONFIG_ENV_PREFIX_CONFLICT | Two namespaces declare the same envPrefix | | CONFIG_MOUNT_ERROR | config.mount() cannot read or parse the external source | | CONFIG_BIND_ERROR | config.bind<T>() or config.getTyped<T>() type guard fails | | ERROR_FORMATTER_DUPLICATE | ErrorFormatterRegistry.register() called for an already-registered surface |

New error codes added in v0.20.0:

| Code | Description | |------|-------------| | CIRCUIT_BREAKER_OPEN | CircuitBreakerMiddleware short-circuited a call because the circuit is OPEN | | MODULE_RELOAD_CONFLICT | Both module_id and path_filter supplied to system.control.reload_module | | SYS_MODULE_REGISTRATION_FAILED | registerSysModules() with failOnError: true and a module failed to register | | MODULE_ID_CONFLICT | Two classes in the same file produce the same module ID segment (discoverMultiClass) | | INVALID_SEGMENT | A derived class segment does not match ^[a-z][a-z0-9_]*$ | | ID_TOO_LONG | A derived module ID exceeds 192 characters |

Event Type Canonical Names

apcore 0.15.0 resolved two event-type collisions in favor of dot-namespaced canonical names. The legacy short-form aliases (module_health_changed, config_changed) were emitted during the 0.15.x transition and have been removed as of 0.18.0.

| Event name | Meaning | |------------|---------| | "apcore.module.toggled" | Module enabled/disabled toggle | | "apcore.health.recovered" | Error-rate recovery after spike | | "apcore.config.updated" | Config key updated at runtime | | "apcore.module.reloaded" | Module reloaded from disk |

Naming convention: apcore.* is reserved for core events. Adapter packages use their own prefix (apcore-mcp.*, apcore-a2a.*, apcore-cli.*).


Examples

The examples/ directory contains runnable demos:


simple-client — APCore client with module registration and calls

Initializes an APCore client, registers modules inline, and calls them.

import { Type } from '@sinclair/typebox';
import { APCore } from 'apcore-js';

const client = new APCore();

client.module({
  id: 'math.add',
  description: 'Add two integers',
  inputSchema: Type.Object({ a: Type.Number(), b: Type.Number() }),
  outputSchema: Type.Object({ sum: Type.Number() }),
  execute: (inputs) => ({ sum: (inputs.a as number) + (inputs.b as number) }),
});

client.module({
  id: 'greet',
  description: 'Greet a user by name',
  inputSchema: Type.Object({
    name: Type.String(),
    greeting: Type.Optional(Type.String()),
  }),
  outputSchema: Type.Object({ message: Type.String() }),
  execute: (inputs) => ({
    message: `${(inputs.greeting as string) || 'Hello'}, ${inputs.name}!`,
  }),
});

const result = await client.call('math.add', { a: 10, b: 5 });
console.log(result); // { sum: 15 }

const greetResult = await client.call('greet', { name: 'Alice' });
console.log(greetResult); // { message: 'Hello, Alice!' }

greet — Minimal FunctionModule

Demonstrates the core FunctionModule structure with TypeBox schemas.

import { Type } from '@sinclair/typebox';
import { FunctionModule } from 'apcore-js';

export const greetModule = new FunctionModule({
  moduleId: 'greet',
  description: 'Greet a user by name',
  inputSchema: Type.Object({ name: Type.String() }),
  outputSchema: Type.Object({ message: Type.String() }),
  execute: (inputs) => ({ message: `Hello, ${inputs.name}!` }),
});

get-user — Readonly + idempotent annotations

Shows behavioral annotations and simulated database lookup.

import { Type } from '@sinclair/typebox';
import { FunctionModule } from 'apcore-js';

const users: Record<string, { id: string; name: string; email: string }> = {
  'user-1': { id: 'user-1', name: 'Alice', email: '[email protected]' },
  'user-2': { id: 'user-2', name: 'Bob', email: '[email protected]' },
};

export const getUserModule = new FunctionModule({
  moduleId: 'user.get',
  description: 'Get user details by ID',
  inputSchema: Type.Object({ userId: Type.String() }),
  outputSchema: Type.Object({
    id: Type.String(),
    name: Type.String(),
    email: Type.String(),
  }),
  annotations: {
    readonly: true,
    destructive: false,
    idempotent: true,
    requiresApproval: false,
    openWorld: true,
    streaming: false,
  },
  execute: (inputs) => {
    const user = users[inputs.userId as string];
    if (!user) {
      return { id: inputs.userId as string, name: 'Unknown', email: '[email protected]' };
    }
    return { ...user };
  },
});

send-email — Full-featured: annotations, examples, metadata, ContextLogger

Demonstrates destructive annotations, ModuleExample for AI-perceivable documentation, metadata, and ContextLogger usage.

import { Type } from '@sinclair/typebox';
import { FunctionModule, ContextLogger } from 'apcore-js';
import type { Context } from 'apcore-js';

export const sendEmailModule = new FunctionModule({
  moduleId: 'email.send',
  description: 'Send an email message',
  inputSchema: Type.Object({
    to: Type.String(),
    subject: Type.String(),
    body: Type.String(),
    apiKey: Type.String(),
  }),
  outputSchema: Type.Object({
    status: Type.String(),
    messageId: Type.String(),
  }),
  tags: ['email', 'communication', 'external'],
  version: '1.2.0',
  metadata: { provider: 'example-smtp', maxRetries: 3 },
  annotations: {
    readonly: false,
    destructive: true,
    idempotent: false,
    requiresApproval: false,
    openWorld: true,
    streaming: false,
  },
  examples: [
    {
      title: 'Send a welcome email',
      inputs: { to: '[email protected]', subject: 'Welcome!', body: '...', apiKey: 'sk-xxx' },
      output: { status: 'sent', messageId: 'msg-12345' },
      description: 'Sends a welcome email to a new user.',
    },
  ],
  execute: (inputs, context: Context) => {
    const logger = ContextLogger.fromContext(context, 'send_email');
    logger.info('Sending email', { to: inputs.to as string, subject: inputs.subject as string });
    const hash = Math.abs(
      (inputs.to as string).split('').reduce((a, c) => a + c.charCodeAt(0), 0) % 100000,
    );
    const messageId = `msg-${String(hash).padStart(5, '0')}`;
    logger.info('Email sent successfully', { messageId });
    return { status: 'sent', messageId };
  },
});

decorated-addmodule() function for creating modules

import { Type } from '@sinclair/typebox';
import { module } from 'apcore-js';

export const addModule = module({
  id: 'math.add',
  description: 'Add two integers',
  inputSchema: Type.Object({ a: Type.Number(), b: Type.Number() }),
  outputSchema: Type.Object({ sum: Type.Number() }),
  execute: (inputs) => ({ sum: (inputs.a as number) + (inputs.b as number) }),
});

Development

# Install dependencies
npm install

# Type check
npm run typecheck

# Run tests
npm test

# Run tests in watch mode
npm run test:watch

# Build
npm run build

Testing

  • Core executor pipeline
  • Schema validation (strict mode, type coercion)
  • Middleware chain (ordering, transforms, error recovery, circuit breaker)
  • ACL enforcement (patterns, conditions, identity types)
  • Registry system (scanner, metadata, entry points, dependencies, multi-class discovery)
  • Binding loader (YAML loading, target resolution, schema modes)
  • Observability (tracing, BatchSpanProcessor, metrics, Prometheus export, structured logging with redaction)
  • Event system (circuit breaker wrapper, subscriber types, filter/file/stdout)
  • System modules (health, manifest, usage, control, audit trail, overrides persistence, Prometheus usage metrics)
  • Async tasks (pluggable store, retry backoff, reaper)
  • Cross-language conformance suite (tests/conformance.test.ts) — canonical JSON fixtures from apcore/conformance/fixtures/ run identically across Python, TypeScript, and Rust SDKs

Links

License

Apache-2.0