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

@objectstack/service-analytics

v17.4.0

Published

Analytics Service for ObjectStack — implements IAnalyticsService with multi-driver strategy pattern (NativeSQL, ObjectQL, InMemory)

Readme

@objectstack/service-analytics

The shipped provider for the kernel's analytics service slot — a cube/dataset query engine implementing IAnalyticsService over a priority-ordered strategy chain.

Slot criticality: optional (ServiceRequirementDef in @objectstack/spec/system). Without it, /api/v1/analytics/* answers 404 rather than degrading.

Installation

pnpm add @objectstack/service-analytics

Usage

The entry point is the kernel plugin AnalyticsServicePlugin. Construct it and hand it to the kernel; it registers the service under 'analytics' during init.

import { LiteKernel } from '@objectstack/core';
import type { Cube } from '@objectstack/spec/data';
import type { IAnalyticsService } from '@objectstack/spec/contracts';
import { AnalyticsServicePlugin } from '@objectstack/service-analytics';

const ordersCube: Cube = {
  name: 'orders',
  title: 'Orders',
  sql: 'orders',
  measures: {
    count: { name: 'count', label: 'Count', type: 'count', sql: '*' },
    total_amount: { name: 'total_amount', label: 'Total Amount', type: 'sum', sql: 'amount' },
  },
  dimensions: {
    status: { name: 'status', label: 'Status', type: 'string', sql: 'status' },
  },
};

const kernel = new LiteKernel();
kernel.use(new AnalyticsServicePlugin({ cubes: [ordersCube] }));
await kernel.bootstrap();

const analytics = kernel.getService<IAnalyticsService>('analytics');
const result = await analytics.query({ cube: 'orders', measures: ['orders.count'] });

LiteKernel.use() is synchronous; ObjectKernel.use() returns a promise — await it there.

Plugin options

Every field of AnalyticsServicePluginOptions is optional. The plugin bridges the host's engine into AnalyticsServiceConfig; anything left unset falls back to what the plugin can auto-discover from the kernel.

| Option | Type | Default | Purpose | |:---|:---|:---|:---| | cubes | Cube[] | none | Cube definitions registered at init. | | queryCapabilities | (cubeName: string) => AnalyticsDriverCapabilities | in-memory only | Which execution paths a cube's backing driver supports. | | executeRawSql | (objectName, sql, params) => Promise<Record<string, unknown>[]> | auto-bridged to the ObjectQL engine | Enables NativeSQLStrategy. | | executeAggregate | (objectName, options) => Promise<Record<string, unknown>[]> | auto-bridged to the ObjectQL engine | Enables ObjectQLStrategy. | | getReadScope | (objectName, context?) => FilterCondition \| null \| undefined \| Promise<…> | auto-bridges to a registered 'security' service exposing getReadFilter | Per-object tenant/RLS read scope (ADR-0021 D-C). | | getAllowedRelationships | (cubeName: string) => Set<string> \| undefined | supplied by compiled datasets | Join allowlist per cube. | | debug | boolean | false | Server-side log verbosity only. | | debugSql | boolean | development only (NODE_ENV === 'development') | Echo the executed statement back to callers in AnalyticsResult.sql. |

debug and debugSql are deliberately separate: raising log verbosity must never widen what travels to a tenant.

Service API

IAnalyticsService (from @objectstack/spec/contracts) declares four members — two required, two optional:

import type { IAnalyticsService } from '@objectstack/spec/contracts';

// query(query, context?)             -> Promise<AnalyticsResult>     (required)
// getMeta(cubeName?)                 -> Promise<CubeMeta[]>          (required)
// generateSql?(query, context?)      -> Promise<{ sql, params }>     (optional)
// queryDataset?(dataset, selection, context?, options?)              (optional)

This package implements all four. Pass the caller's ExecutionContext as the second argument: without it the per-object read scope resolves to no filter and the query runs unscoped.

AnalyticsQuery

AnalyticsQuery is a strict schema (AnalyticsQuerySchema, @objectstack/spec/data) with exactly these fields; measures is the only required one, and an undeclared key is rejected rather than dropped.

| Field | Type | Notes | |:---|:---|:---| | cube | string? | Optional when supplied by the request wrapper. | | measures | string[] | Required. | | dimensions | string[]? | | | where | FilterCondition? | Canonical Query DSL filter — the same shape find() takes. | | timeDimensions | { dimension, granularity?, dateRange? }[]? | Also strict per item. | | order | Record<string, 'asc' \| 'desc'>? | | | limit | number? | | | offset | number? | | | timezone | string? | IANA name. No default — an absent timezone means the engine resolves it. |

There is no filters key and no aggregations key. filters is rejected at the REST door with a 400 naming where. There is no per-metric filter key either — the cube metric's filters was removed (#10414: no strategy ever read it); fold a per-metric condition into the metric's own sql expression, or use an ADR-0021 dataset measure's structured filter.

const revenueByStatus = await analytics.query({
  cube: 'orders',
  measures: ['orders.total_amount'],
  dimensions: ['orders.status'],
  where: { is_active: true },
  order: { 'orders.total_amount': 'desc' },
  limit: 10,
});
// result.rows  — Record<string, unknown>[]
// result.fields — column metadata (name, type, label?, format?, currency?, percentScale?)

Strategy chain

AnalyticsService delegates to a priority-ordered chain; the first strategy whose canHandle returns true serves the query.

| Priority | Strategy | Condition | |:---:|:---|:---| | 10 | NativeSQLStrategy | driver supports raw SQL (executeRawSql) | | 20 | ObjectQLStrategy | driver supports aggregate AST (executeAggregate) | | 30 | custom strategies, or the internal delegate added when fallbackService is set | injected by the host |

InMemoryStrategy is not built in — it ships from @objectstack/driver-memory and is injected through AnalyticsServiceConfig.strategies (or fallbackService).

REST API

Served by the runtime dispatcher's /analytics domain when this service occupies the slot. These four routes are the whole surface:

POST   /api/v1/analytics/query           # execute an AnalyticsQuery
GET    /api/v1/analytics/meta[?cube=]    # cube metadata for discovery
POST   /api/v1/analytics/sql             # generate SQL without executing (dry-run)
POST   /api/v1/analytics/dataset/query   # run a dataset selection (ADR-0021)

POST /analytics/sql answers 404 when the slot's occupant does not implement the optional generateSql.

Exports

import {
  AnalyticsService, AnalyticsServicePlugin, CubeRegistry, DatasetExecutor,
  NativeSQLStrategy, ObjectQLStrategy,
  compileDataset, compileScopedFilterToSql,
  combineFilters, evaluateDerivedMeasures, fillEmptyGroups, mergeByDimensions, shiftRange,
  createOrderLabelResolver, pickDisplayField, resolveDimensionLabels, withLabelFetchCache,
} from '@objectstack/service-analytics';

Types: AnalyticsServiceConfig, AnalyticsServicePluginOptions, AnalyticsStrategy, StrategyContext, AnalyticsDriverCapabilities, CompiledDataset, DatasetCompileOptions, DatasetSelection, CompareTo, DerivedMeasureSpec, RelationshipResolver, RelationshipTarget, DimensionLabelDeps, FieldMetaLite, OrderLabelResolver.

Advanced: constructing the service directly

AnalyticsService is exported for hosts that wire their own kernel integration. AnalyticsServiceConfig is the wider surface the plugin builds — it adds logger, strategies, fallbackService, coerceTemporalFilterValue, coerceTemporalFilterColumn, isExternalObject, getObjectDatasource, isRegisteredObject and the dataset resolvers on top of the plugin options above.

import { AnalyticsService, CubeRegistry } from '@objectstack/service-analytics';

const registry = new CubeRegistry();
registry.registerAll([ordersCube]);

const service = new AnalyticsService({ cubes: [ordersCube] });

License

Apache-2.0. See LICENSING.md.

See Also