npm package: @measuremetrics/measure-sdk
Description: Official Node.js/TypeScript SDK for the Measure API
Author: Measure Team
Latest version: 1.0.1

# Measure Node.js SDK

Official Node.js client for the [Measure](https://measure.dev) API, so you can define custom metrics, record measurements, and query time series analytics from your server-side code.

## Installation

```bash
bun add @measuremetrics/measure-sdk
```

```bash
npm install @measuremetrics/measure-sdk
```

## Quick start

```typescript
import { MeasureClient, QueryBuilder } from '@measuremetrics/measure-sdk';

const client = new MeasureClient({
  apiKey: process.env.MEASURE_API_KEY,
  organizationId: 'org_123',
  environmentId: 'env_456',
});

// Create a metric
const metric = await client.metrics.create({
  name: 'Monthly Recurring Revenue',
  unit: 'currency',
  precision_recorded: 2,
});

// Record a measurement
await client.metricAdjustments.create(metric.id, {
  timestamp: '2025-03-01T00:00:00Z',
  amount: 5000.00,
});

// Query the data
const result = await client.queries.execute(
  new QueryBuilder()
    .addMetric(metric.id, 'MRR')
    .timeRangePreset('Last30Days')
    .granularity('day')
    .build()
);
```

## Configuration

Pass a `MeasureClientConfig` object when creating the client:

| Field | Type | Default | Description |
|-------|------|---------|-------------|
| `apiKey` | `string` | *required* | API key for authentication |
| `baseURL` | `string` | `https://measure.dev` | Base URL for the API |
| `organizationId` | `string` | -- | Organization ID for request context |
| `environmentId` | `string` | -- | Environment ID for request context |
| `timeout` | `number` | `30000` | Request timeout in milliseconds |
| `retry` | `Partial<RetryConfig>` | see below | Retry configuration |
| `headers` | `Record<string, string>` | -- | Additional headers for all requests |
| `fetch` | `typeof fetch` | -- | Custom fetch implementation |

## Resources

Every resource follows the same pattern: `list`, `create`, `get`, `update`, `delete`, and `search` where applicable. Some resources have additional methods.

```typescript
// List with pagination
const metrics = await client.metrics.listPaginated({ limit: 50 });

// Create
const metric = await client.metrics.create({ name: 'Revenue', unit: 'currency' });

// Get by ID
const metric = await client.metrics.get('met_123');

// Update
await client.metrics.update('met_123', { name: 'Total Revenue' });

// Delete
await client.metrics.delete('met_123');

// Search
const results = await client.metrics.search({ search: { q: 'revenue' } });
```

### Available resources

| Resource | Property | Description |
|----------|----------|-------------|
| Annotations | `client.annotations` | Time-based annotations for dashboards and charts |
| API Keys | `client.apiKeys` | Manage API keys for authentication |
| Business Reviews | `client.businessReviews` | Periodic business review snapshots |
| Calculated Metrics | `client.calculatedMetrics` | Expression-based derived metrics |
| Chat | `client.chat` | AI-powered metric insights |
| Cohorts | `client.cohorts` | User and entity cohort definitions |
| Dashboards | `client.dashboards` | Dashboard configuration and layout |
| Entities | `client.entities` | Business object types (customers, products, etc.) |
| Entity Members | `client.entityMembers` | Data instances of entity types |
| Environments | `client.environments` | Workspace isolation (dev, staging, production) |
| Event Definitions | `client.eventDefinitions` | Event schema definitions |
| Event Topics | `client.eventTopics` | Event topic groupings |
| Goals | `client.goals` | Metric targets and goal tracking |
| Health | `client.health` | API health check |
| Integrations | `client.integrations` | Third-party integration configuration |
| Managed Entities | `client.managedEntities` | System-managed entity types |
| Metric Adjustments | `client.metricAdjustments` | Record measurements with dimensional context |
| Metrics | `client.metrics` | Measurable quantities to track over time |
| Narratives | `client.narratives` | AI-generated metric narratives |
| Organizations | `client.organizations` | Top-level tenant management |
| People | `client.people` | User profiles and identity |
| Public API Keys | `client.publicApiKeys` | Write-only keys for client-side tracking |
| Queries | `client.queries` | Time series analytics queries |
| System Logs | `client.systemLogs` | Audit and system event logs |
| Teams | `client.teams` | Team management within organizations |
| Tracking | `client.tracking` | High-volume event tracking |
| Users | `client.users` | User account management |

## Querying analytics

Use `QueryBuilder` to construct time series queries with a fluent API:

```typescript
import { QueryBuilder } from '@measuremetrics/measure-sdk';

const query = new QueryBuilder()
  .addMetric('met_revenue', 'Revenue', 2)
  .addYearOverYear('met_revenue', 'Revenue YoY')
  .timeRange('2025-01-01', '2025-03-31')
  .granularity('month')
  .addDimension('ent_customer', 'prop_region', 'Region')
  .filterBy('ent_customer', 'prop_tier', 'Equals', { String: 'Enterprise' })
  .build();

const result = await client.queries.execute(query);
```

### Temporal metric types

| Method | Description |
|--------|-------------|
| `addMetric(id, name?, precision?)` | Base metric value |
| `addYearOverYear(metricId, alias)` | Year-over-year comparison |
| `addPeriodOverPeriod(metricId, alias, offset)` | Custom period comparison |
| `addGrowthRate(metricId, alias, offset)` | Growth rate calculation |
| `addMovingAverage(metricId, alias, granularity, windowSize)` | Moving average |
| `addCumulative(metricId, alias, fromPeriod)` | Cumulative total |

### Time range options

```typescript
// Preset
.timeRangePreset('Last30Days')  // Last7Days, ThisMonth, ThisYear, etc.

// Custom dates
.timeRange('2025-01-01', '2025-03-31')
```

### Granularity

```typescript
.granularity('day')  // day, week, month, quarter, year
```

## Filtering

Use `FilterBuilder` to construct type-safe property filters:

```typescript
import { FilterBuilder } from '@measuremetrics/measure-sdk';

const filters = FilterBuilder.create()
  .in('ent_customer', 'prop_region', ['US-West', 'US-East'])
  .greaterThan('ent_customer', 'prop_mrr', 1000)
  .isNotNull('ent_customer', 'prop_company')
  .build();
```

### Available operators

| Operator | Method | Value type |
|----------|--------|------------|
| Equals | `.equals()` | string, number, boolean, Date |
| NotEquals | `.notEquals()` | string, number, boolean, Date |
| GreaterThan | `.greaterThan()` | number |
| GreaterThanOrEqual | `.greaterThanOrEqual()` | number |
| LessThan | `.lessThan()` | number |
| LessThanOrEqual | `.lessThanOrEqual()` | number |
| Like | `.like()` | string (SQL pattern with %) |
| ILike | `.ilike()` | string (case-insensitive) |
| Contains | `.contains()` | string |
| IContains | `.icontains()` | string (case-insensitive) |
| In | `.in()` | string[] or number[] |
| NotIn | `.notIn()` | string[] or number[] |
| IsNull | `.isNull()` | -- |
| IsNotNull | `.isNotNull()` | -- |

Combine multiple filter builders:

```typescript
const combined = FilterBuilder.and([builder1, builder2]); // AND logic
```

## Error handling

The SDK throws typed error classes for different failure scenarios:

```typescript
import {
  MeasureClient,
  AuthenticationError,
  ValidationError,
  NotFoundError,
  RateLimitError,
  isRetryableError,
} from '@measuremetrics/measure-sdk';

try {
  await client.metrics.create({ name: 'Revenue', unit: 'currency' });
} catch (error) {
  if (error instanceof ValidationError) {
    console.log('Invalid input:', error.validationErrors);
  } else if (error instanceof AuthenticationError) {
    console.log('Bad API key');
  } else if (error instanceof NotFoundError) {
    console.log('Resource not found');
  } else if (error instanceof RateLimitError) {
    console.log('Rate limited, retry after:', error.retryAfter);
  } else if (isRetryableError(error)) {
    console.log('Server error, will retry');
  }
}
```

### Error classes

| Class | Status | Description |
|-------|--------|-------------|
| `BadRequestError` | 400 | Malformed request |
| `AuthenticationError` | 401 | Invalid or missing API key |
| `PermissionDeniedError` | 403 | Insufficient permissions |
| `NotFoundError` | 404 | Resource does not exist |
| `ConflictError` | 409 | Duplicate resource |
| `UnprocessableEntityError` | 422 | Semantically invalid request |
| `ValidationError` | 422 | Field-level validation errors |
| `RateLimitError` | 429 | Rate limit exceeded |
| `InternalServerError` | 500 | Server error |
| `BadGatewayError` | 502 | Bad gateway |
| `ServiceUnavailableError` | 503 | Service temporarily unavailable |
| `GatewayTimeoutError` | 504 | Gateway timeout |
| `APIConnectionError` | -- | Connection failed |
| `APIConnectionTimeoutError` | -- | Request timed out |
| `APINetworkError` | -- | Network-level failure |

### Type guards

- `isAPIError(error)` -- any API error
- `isClientError(error)` -- 4xx errors
- `isServerError(error)` -- 5xx errors
- `isRetryableError(error)` -- 5xx and network errors

## Retry behavior

The SDK automatically retries failed requests for retryable errors (408, 429, 500, 502, 503, 504) with exponential backoff.

| Option | Type | Default | Description |
|--------|------|---------|-------------|
| `maxRetries` | `number` | `3` | Maximum retry attempts |
| `initialDelayMs` | `number` | `500` | Delay before first retry |
| `maxDelayMs` | `number` | `60000` | Maximum delay between retries |
| `jitter` | `JitterStrategy` | `'full'` | Jitter strategy |

### Jitter strategies

- `full` -- Random delay between 0 and the exponential delay (recommended)
- `equal` -- Half deterministic, half random
- `decorrelated` -- Each retry delay is independent (recommended for high concurrency)
- `none` -- Pure exponential backoff

Rate limit errors (429) automatically respect the `Retry-After` header.

## Context switching

Switch organization and environment context without creating a new client:

```typescript
client.setContext('org_456', 'env_staging');
// All subsequent requests use the new context
```

## TypeScript

The SDK is written in TypeScript with strict mode. All request and response types are exported:

```typescript
import type {
  Metric,
  MetricCreateRequest,
  Entity,
  EntityCreateRequest,
  MeasureClientConfig,
  RetryConfig,
} from '@measuremetrics/measure-sdk';
```

Dual ESM and CommonJS output is provided.

## Requirements

- Node.js >= 18