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

@pmate/log-sdk

v0.6.0

Published

PMate browser, React and Node logs with access and profile context

Readme

@pmate/log-sdk

Web: initialize once, then report user actions

import { web } from '@pmate/log-sdk/web'

web.init({
  key: 'KEY_FROM_DEVELOPERS',
  appId: 'pmate:hello',
  auto: { pv: true, webVitals: true }, // defaults to both enabled
})
web.track('signup.clicked', { entry: 'home' })
web.identify('profile-alice') // optional self-reported profile selection
web.reset() // clear profile on logout; preserve the anonymous visit
await web.flush()
// await web.close() on application teardown, not on every route

Create a Web key with pv, behavior and vitals at https://dev.pmate.chat/log-keys. Existing keys retain their original scopes. If your key excludes a topic, disable its automatic collection. web.pageView() records a manual visit; turn off auto.pv when your router explicitly records visits to avoid double counting. The SDK strips URL query strings/fragments and records available CLS, FCP, INP, LCP and TTFB separately from PV. SPA changes never move delayed document metrics to a new route. Metrics remain runtime logs (48h); user events remain event logs (1y).

init is synchronous local configuration; it makes no registration or login request. The default endpoint is https://log-api.pmate.chat; an optional endpoint supports another deployment (HTTPS, or HTTP localhost for tests). Same-configuration init is idempotent, changed configuration requires awaiting close(). Pre-init calls are ignored with a one-time warning. Importing is SSR-safe; initialize in a client lifecycle. createWebLogger() provides an isolated instance for tests or multiple independent applications; normally use the singleton web.

Properties/identity are captured at event time. identify does not sign in a user, and never takes an auth token. The in-memory queue holds 200 entries by default, batches at most 50 entries/48 KiB, attempts an entry at most 3 times using the original ID/time, and flushes every 5 seconds and on hide. flush drains all topics; close stops listeners/admission and drains. Delivery is best effort; no disk queue. Use explicit behavior events; do not send personal data in properties or path names.

Native Swift/Kotlin SDKs in parrot-native expose the equivalent PmateLog.initSDK, screen, track, identify, reset, flush, close API, with automatic app foreground/background events. Their examples are generated by the Key console for the selected platform.

Background

PMate logs need to connect anonymous page visits, signed-in Profiles and backend requests without merging different Profiles of one Account. This SDK adds access/profile context to the existing topic log protocol. It reuses @pmate/log for browser delivery and supports the same ingest protocol or a trusted Store sink on Node.

Runtime and Event APIs

Classification follows event meaning, not the runtime or the presence of HTTP. The lower-level browser/React APIs expose separate runtime/event scopes. The Web scenario entry provides direct pageView/track methods.

| API | Meaning | Chain | Retention | | --- | --- | --- | --- | | log.runtime.append(topic, entry) / log.runtime.captureError(error) | Program diagnostics, HTTP requests, bot activity, auth, heartbeat, polling, deployment, console, vitals and crashes | app-logs | 48 hours | | log.event.pageView() | Explicit browser page visit, with current pathname and generated page-view ID | web-logs | 1 year | | log.event.track(name, properties) | Explicitly defined user behavior | web-logs | 1 year |

React exposes both scopes. Node exposes only runtime; runtime.append rejects pv and behavior topics. Callers cannot set the category or destination chain. Retention is enforced by chain configuration, not by the SDK queue. Automatic page views use the Event route. Browser errors, console and vitals use Runtime even though they originate in the browser. Intake grants must explicitly allow each category/topic pair.

const log = useLog() // @pmate/log-sdk/react
log.runtime.append("application", { data: { event: "sync.failed" } })
log.event.track("signup.clicked", { button: "signup" })
log.event.pageView() // use when manually recording a page visit

Public event keys and initialization

Create a log intake key at https://dev.pmate.chat/log-keys. Bind a registered service, application, environment, platform, allowed topics and quota. Creation is the only provisioning step: there is no per-topic init or manual table creation. Instantiate the SDK once per application. The key determines service/environment server-side; no user login or account token is needed to append events. It cannot read logs.

import { createLogger } from '@pmate/log-sdk/browser'
const log = createLogger({
  endpoint: 'https://log-api.pmate.chat',
  eventKey: 'KEY_FROM_DEVELOPERS', appId: 'pmate:hello',
})
log.event.track('signup.clicked', { source: 'home' })
await log.flush()

For native JavaScript clients, use @pmate/log-sdk/event with the same options plus platform: 'ios' or 'android'. It has no browser/Node dependencies; call log.event.pageView('/screen') explicitly. Custom clients can POST {category:'event',topic:'behavior',items:[{id,time,data:{appId,platform,event}}]} to /event/logs/batch with x-pmate-log-key. IDs and timestamps must be retained on retries. Profile/access IDs are optional untrusted client claims.

Web keys may restrict exact HTTP(S) Origins. Native keys need no Origin. Quotas are per active intake process (production uses one process); no distributed quota is claimed. Disable/rotation writes are confirmed against the Store index before success; new requests then reject the old key. In-flight writes may complete. Storage errors fail closed. Plaintext keys are returned once; list responses omit digests. The current single management writer serializes mutations; deploying multiple management writers requires storage-level concurrency control first.

An eventKey only sends behavior/PV. Automatic runtime diagnostics require the existing scoped key plus serviceId/environment; diagnostics are not routed through the event key. Both keys can be provided to one browser/React logger.

The SDK now exposes only runtime / event. Update previous .app / .web call sites before upgrading. Existing static grants and old request categories are explicitly translated at legacy HTTP ingress during migration; physical app-logs / web-logs chains and historical rows are unchanged.

Identity

accessId is a random tab visit ID. Reload/history restore reuses sessionStorage; a new document navigation starts a visit (including new/duplicated tabs). An SPA route change retains it. After 30 minutes without interaction, the next interaction or explicit event starts another visit. Storage failures fall back to memory. Background vitals do not extend the visit. This is not a persistent visitor ID.

profileId is optional and scoped by appId. Logging in keeps accessId, logout clears profileId, and queued events retain their original identity. No Account-level identity merging occurs. Browser identity is client-reported; Node identity must come from verified server authentication. Neither accessId nor telemetry is an authorization proof.

React

import { LogProvider, useLog } from '@pmate/log-sdk/react'
const options = {
  appId: 'pmate:hello', serviceId: 'pmate:hello', environment: 'prod',
  endpoint: 'https://log-api.pmate.chat', key: 'REGISTERED_PUBLIC_INTAKE_KEY',
  apiOrigins: ['https://api.example.com'],
}
function App({ profileId }) {
  return <LogProvider options={options} profileId={profileId}><Content /></LogProvider>
}
function Content() {
  const log = useLog()
  return <button onClick={() => log.runtime.append('application', {data:{event:'feature.opened'}})}>Open</button>
}

Keep provider options stable. Initialization happens after mount, is SSR-safe, and suppresses React StrictMode's discarded initialization. Automatic PV/vitals/errors retain the existing browser SDK behavior. beforeSend can redact application-specific paths or drop an event. Never include credentials or request/response bodies.

Browser

import { createLogger } from '@pmate/log-sdk/browser'
const log = createLogger(options)
log.setProfileId(selectedProfileId) // null on logout
await log.fetch('https://api.example.com/resource')
await log.close()

log.fetch adds x-pmate-access-id and x-request-id only to explicitly allowed API origins. It also sends x-pmate-profile-id when a profile is selected; this is an untrusted selection claim, not authentication. The server must verify the selected profile belongs to the authenticated account and expected App before passing it to Node context. It refuses redirects on those requests. APIs must allow these headers in CORS. Global fetch is never patched. Only the wrapped requests carry correlation.

Entry points

React applications report through @pmate/log-sdk/react. Node services import createLogger and transport helpers from @pmate/log-sdk. The /node export remains a compatibility alias. @pmate/log is the SDK's internal browser transport; applications should not initialize a second logger with it.

Node

import { createLogger } from '@pmate/log-sdk'
const log = createLogger({
  appId: 'pmate:hello', serviceId: 'pmate:hello-api', environment: 'prod',
  endpoint: 'https://log-api.pmate.chat', token: process.env.LOG_INGEST_TOKEN,
})
await log.withRequest(request, {profileId: authenticatedProfile?.id}, async scope => {
  log.runtime.append('application', {data:{event:'http.request', requestId:scope.requestId}})
  // business work; report completion/error without raw bodies
})
await log.close()

Use authenticatedProfile from server authentication, never a client body/header. AsyncLocalStorage isolates concurrent requests and anonymous requests clear inherited identity. For private-network writes, use the SDK sink:

import { createLogger, createAppLogSink } from "@pmate/log-sdk"
const log = createLogger({
  appId: "pmate:hello", serviceId: "pmate:hello-api", environment: "prod",
  sink: createAppLogSink({
    blockchainUrl: process.env.BLOCKCHAIN_BASE_URL!, // node01: http://infra01:6801
    authorization: serviceSigner, // service-owned signature
    proxyToken: process.env.PMATE_CHAIN_PROXY_TOKEN, // for HTTPS chain-proxy
  }),
})

createAppAccessLogger and withFetchAccess use this same Node logger queue. They preserve server request IDs and allowlisted route templates. createAppLogWriter provides acknowledged append/appendMany for audit/deployment records; failures propagate instead of being silently queued. createAppAppendSink adapts an existing transactional appender. Do not replace required audit acknowledgement with best-effort telemetry. SDK stdout includes the original event ID and logUpload: "application", so the collector does not upload it again.

Alternatively provide a custom sink(batch, signal); the sink must preserve item IDs/time, respect cancellation and report acceptance. The queue is bounded, attempts at most three times with the original event ID, reports failures without payloads, and does not block business work. close() stops admission and drains outstanding events; telemetry is best effort.

Counting

The envelope id is the event deduplication key. PV counts pv events; sessions deduplicate (appId, accessId); Profile UV deduplicates (appId, profileId) in a defined time window/topic. Missing identifiers are reported separately. Never count HTTP requests or vitals as page views, or infer first-ever visits from a limited retention window.

Error and crash diagnostics

Caught exceptions need explicit reporting; a global rejection listener cannot see an exception already handled by application code. Use log.runtime.captureError(error, { operation: 'chat.send' }) on browser, React and Node loggers. Error records use the error topic and include bounded message/stack diagnostics with URL queries removed. Applications must redact domain-specific sensitive strings before calling this method.

Inside LogProvider, wrap the application in LogErrorBoundary with a fallback React node. It reports descendant render/lifecycle failures with component stacks. Event handlers, asynchronous operations and errors above the boundary require separate reporting. Explicitly allowlisted log.fetch requests report non-2xx and network failures without bodies or request URLs.

Node exports installCrashReporter({ directory, send }). Install it before starting the service. It monitors fatal exceptions without suppressing Node's fatal exit, persists up to 20 records, and exposes replay() for startup recovery. The send(entry) callback must resolve only after server acknowledgement, retain entry ID/time, and route records to the service's crash topic. Failed uploads remain on disk. Use a service-specific private persistent directory. It cannot capture SIGKILL, OOM kills, native faults, or failures before installation.