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

@geolonia/geonicdb-sdk

v0.8.0

Published

GeonicDB JavaScript SDK — NGSI-LD Context Broker client for browser and Node.js

Readme

@geolonia/geonicdb-sdk

GeonicDB JavaScript SDK — NGSI-LD Context Broker client for browser and Node.js.

Installation

npm install @geolonia/geonicdb-sdk

Quick Start

Node.js / TypeScript

import GeonicDB from '@geolonia/geonicdb-sdk';

const db = new GeonicDB({
  apiKey: 'your-api-key',
  tenant: 'your-tenant',
  baseUrl: 'https://your-geonicdb.example.com',
});

// Query entities
const rooms = await db.getEntities({ type: 'Room' });
console.log(rooms);

// Create entity
await db.createEntity({
  id: 'urn:ngsi-ld:Room:001',
  type: 'Room',
  temperature: { type: 'Property', value: 23.5 },
});

TypeScript

The SDK ships type definitions (geonicdb.d.ts) in the published package. Named imports of error classes and types resolve cleanly even under strict tsconfig settings (strict, verbatimModuleSyntax, etc.):

import GeonicDB, {
  AuthorizationError,
  AuthenticationError,
  type EntityEvent,
  type RefreshedCredentials,
} from '@geolonia/geonicdb-sdk';

try {
  await db.deleteEntity(entityId);
} catch (err) {
  if (err instanceof AuthorizationError) {
    // Type-safe branch on XACML denial.
  }
}

See Error Handling below for the full list of exported error classes.

Browser (script tag)

<script src="https://unpkg.com/@geolonia/geonicdb-sdk/geonicdb.iife.js"></script>
<script>
  var db = new GeonicDB({
    apiKey: 'your-api-key',
    tenant: 'your-tenant',
    baseUrl: 'https://your-geonicdb.example.com',
  });
  db.getEntities({ type: 'Room' }).then(console.log);
</script>

Authentication

API Key (DPoP + Proof of Work)

const db = new GeonicDB({
  apiKey: 'your-api-key',
  tenant: 'your-tenant',
  baseUrl: 'https://your-geonicdb.example.com',
});
// Authentication is automatic — DPoP key pair generation,
// PoW solving, and token exchange happen transparently.

Bearer JWT (Email/Password Login)

const db = new GeonicDB({
  tenant: 'your-tenant',
  baseUrl: 'https://your-geonicdb.example.com',
});

await db.login('[email protected]', 'password');
// Token refresh is automatic via refresh token.

External Token

db.setCredentials({
  token: 'your-jwt-token',
  tokenType: 'Bearer',
  expiresIn: 3600,
  refreshToken: 'your-refresh-token',
});

Anonymous Mode (public viewers)

For public-facing apps such as GeoJSON viewers or BI dashboards that should serve unregistered visitors without forcing a login, pass anonymous: true to skip token acquisition entirely:

const db = new GeonicDB({
  baseUrl: 'https://your-geonicdb.example.com',
  tenant: 'public',
  anonymous: true, // no Authorization header, no /auth/nonce, no /oauth/token
});

// The server treats this as `role: 'anonymous'` and the XACML policy engine
// decides whether to permit. Tenant admins must register a custom policy that
// permits the anonymous role for the resources to expose — the default
// ANONYMOUS_DEFAULT_POLICY denies everything.
const entities = await db.getEntities({ type: 'GeoJSON' });
  • apiKey and anonymous: true cannot be combined (constructor throws).
  • db.login() upgrades the SDK out of anonymous mode (Bearer JWT).
  • db.setCredentials({ token, tokenType: 'Bearer', ... }) upgrades from anonymous. tokenType: 'DPoP' is rejected for anonymous-built instances (no DPoP key pair is generated for them) — use a non-anonymous SDK instance for DPoP credentials.
  • db.logout() reverts back to anonymous (db.isAnonymous() === true again).
  • WebSocket db.connect() is not supported in anonymous mode — call login() / setCredentials() first.

Entity CRUD

// Create
await db.createEntity({ id: 'urn:ngsi-ld:Room:001', type: 'Room', ... });

// Read
const entity = await db.getEntity('urn:ngsi-ld:Room:001');

// Query (NGSI-LD §5.7.2 のクエリパラメータを網羅)
const entities = await db.getEntities({
  type: 'Room',
  limit: 10,
  // scopeQ: '/OpenData;/OpenData/#',     // exact + descendants
  // georel: 'near;maxDistance==1000',
  // geometry: 'Point',
  // coordinates: '139.7,35.6',
  // attrs: 'name,location',
  // pick: 'name', omit: 'createdAt',
  // lang: 'ja',
  // orderBy: 'modifiedAt', orderDirection: 'desc',
  // count: true,
});

// Update
await db.updateEntity('urn:ngsi-ld:Room:001', { temperature: { type: 'Property', value: 25 } });

// Delete
await db.deleteEntity('urn:ngsi-ld:Room:001');

Types / Attributes Discovery

const types = await db.getTypes();
const roomType = await db.getType('Room');
const attributes = await db.getAttributes();
const tempAttr = await db.getAttribute('temperature');

Temporal API

const history = await db.getTemporalEntities({
  type: 'Room',
  timerel: 'between',
  timeAt: '2026-01-01T00:00:00Z',
  endTimeAt: '2026-01-02T00:00:00Z',
});

const entityHistory = await db.getTemporalEntity('urn:ngsi-ld:Room:001');

Batch Operations

// Create multiple entities
await db.batchCreate([
  { id: 'urn:ngsi-ld:Room:001', type: 'Room', ... },
  { id: 'urn:ngsi-ld:Room:002', type: 'Room', ... },
]);

// Upsert, Update, Delete
await db.batchUpsert(entities);
await db.batchUpdate(entities);
await db.batchDelete(['urn:ngsi-ld:Room:001', 'urn:ngsi-ld:Room:002']);

Generic API Request

// Access any API endpoint with automatic authentication
const result = await db.request('GET', '/ngsi-ld/v1/subscriptions');

WebSocket Event Streaming

db.subscribe({ entityTypes: ['Room'] });

db.on('entityUpdated', (event) => {
  // event.entity contains the complete NGSI-LD entity ({ id, type, ...attributes })
  console.log(`${event.entityId} updated:`, event.entity);
});

db.on('error', (err) => console.error(err));

await db.connect();

Events

| Event | Payload | Description | |-------|---------|-------------| | entityCreated | EntityEvent | Entity was created | | entityUpdated | EntityEvent | Entity was updated | | entityDeleted | EntityEvent | Entity was deleted | | open | — | WebSocket connection opened | | connected | — | WebSocket ready | | close | { code, reason } | WebSocket closed | | disconnected | — | Unexpected disconnect | | reconnecting | { attempt, delay } | Auto-reconnect starting | | error | Error | Error occurred | | tokenRefresh | RefreshedCredentials | Bearer token was refreshed | | cacheHit | CacheEvent | Request served from in-memory cache (304 received) | | cacheMiss | CacheEvent | Cache miss — fresh response fetched from origin | | cacheInvalidated | CacheEvent | Cache entry dropped via explicit clearCache(). WebSocket entity events do not trigger cache invalidation — see "Client-side Cache & Polling" |

Client-side Cache & Polling

The SDK includes an in-memory cache that handles ETag / If-None-Match negotiation transparently. Subsequent reads of an unchanged resource are served as 304 Not Modified — the SDK presents them to your code as a normal 200 response with the cached body.

// First call: fresh fetch, ETag stored
const rooms1 = await db.getEntities({ type: 'Room' });

// Second call: server returns 304, SDK returns the cached body
const rooms2 = await db.getEntities({ type: 'Room' });

// Concurrent calls to the same path are deduplicated:
const [a, b, c] = await Promise.all([
  db.getEntities({ type: 'Room' }),
  db.getEntities({ type: 'Room' }),
  db.getEntities({ type: 'Room' }),
]);  // → only one HTTP request hits the network

Cache is enabled by default; pass cache: false to opt out.

Cache lifetime under WebSocket activity

WebSocket entity events (entityCreated / entityUpdated / entityDeleted) do not automatically invalidate the SDK cache. This is intentional:

  • Data endpoints respond with Cache-Control: private, no-cache, so every cached read already revalidates with the origin via If-None-Match.
  • If the entity actually changed, the server's freshly computed ETag will not match the client's If-None-Match → server returns 200 with the fresh body → SDK replaces the cache entry.
  • If the WebSocket event was noisy / unrelated to a particular cached query (or the data hasn't propagated to the read replica yet), the server returns 304 Not Modified → SDK serves the cached body with no body bytes transferred.

Deleting cache entries on every WebSocket event would force the next read to miss the cache and fetch a full 200 body — defeating the bandwidth-saving 304 path. To force a full reset (e.g. on auth change), call clearCache() explicitly.

Polling

db.poll(params, options) repeats getEntities() at an interval and reports back whether the data actually changed. Internally it leverages the cache's ETag negotiation, so unchanged ticks transfer almost no bytes.

const handle = db.poll({ type: 'Room' }, {
  interval: 5000,
  onData: (rooms) => render(rooms),
  onNoChange: () => {},  // server returned 304
  onError: (err) => console.error(err),
});

// Stop later
handle.stop();

API Reference

Constructor

| Method | Parameters | Returns | Description | |--------|-----------|---------|-------------| | new GeonicDB(options?) | GeonicDBOptions | GeonicDB | Create a new SDK instance |

GeonicDBOptions

| Property | Type | Required | Description | |----------|------|----------|-------------| | apiKey | string | No | API key (or use data-api-key attribute on script tag) | | tenant | string | No | Tenant name (or use data-tenant attribute on script tag) | | baseUrl | string | No | API base URL (auto-detected from script src if omitted) | | wsEndpoint | string | No | WebSocket endpoint URL (auto-detected from baseUrl if omitted) | | debug | boolean | No | Enable debug logging to console (default: false) | | cache | boolean | No | Enable in-memory cache with ETag/304 + request dedup (default: true) | | cacheMaxEntries | number | No | LRU cache capacity when cache is enabled (default: 1000) | | anonymous | boolean | No | Anonymous mode — skip token acquisition and send no Authorization header. Cannot be combined with apiKey. See Anonymous Mode (default: false) |

Authentication

| Method | Parameters | Returns | Description | |--------|-----------|---------|-------------| | login(email, password) | string, string | Promise<LoginResponse> | Login with email and password (Bearer JWT). Upgrades the SDK out of anonymous mode | | setCredentials(opts) | CredentialsOptions | this | Set credentials externally (e.g. from a login API response). Upgrades the SDK out of anonymous mode when tokenType: 'Bearer'. Throws when tokenType: 'DPoP' is passed to an anonymous-built instance — anonymous instances do not generate a DPoP key pair | | logout() | — | void | Clear all credentials and disconnect WebSocket. Reverts to anonymous mode if anonymous: true was set at construction | | isAnonymous() | — | boolean | Whether explicit anonymous mode is active. Returns the explicit mode flag — not derived from credential presence — so a refresh failure that clears _token does NOT silently flip this back to true |

Entity CRUD

| Method | Parameters | Returns | Description | |--------|-----------|---------|-------------| | createEntity(entity) | Record<string, unknown> | Promise<{ created: true }> | Create a new entity | | getEntity(entityId) | string | Promise<Record<string, unknown>> | Get a single entity by ID | | getEntities(params?) | GetEntitiesParams | Promise<Record<string, unknown>[]> | Query entities with optional filters | | count(params?) | CountEntitiesParams | Promise<number> | Count entities matching the given filters | | updateEntity(entityId, attrs) | string, Record<string, unknown> | Promise<{ updated: true }> | Update entity attributes (partial) | | deleteEntity(entityId) | string | Promise<{ deleted: true }> | Delete an entity |

GetEntitiesParams

| Property | Type | Description | |----------|------|-------------| | type | string | Entity type filter | | limit | number | Max results | | offset | number | Pagination offset | | q | string | NGSI-LD query expression |

CountEntitiesParams

| Property | Type | Description | |----------|------|-------------| | type | string | Entity type filter | | q | string | NGSI-LD query expression |

Types / Attributes Discovery

| Method | Parameters | Returns | Description | |--------|-----------|---------|-------------| | getTypes() | — | Promise<Record<string, unknown>[]> | List all entity types | | getType(typeName) | string | Promise<Record<string, unknown>> | Get details for a specific entity type | | getAttributes() | — | Promise<Record<string, unknown>[]> | List all attributes | | getAttribute(attrName) | string | Promise<Record<string, unknown>> | Get details for a specific attribute |

Temporal API

| Method | Parameters | Returns | Description | |--------|-----------|---------|-------------| | getTemporalEntities(params?) | GetTemporalEntitiesParams | Promise<Record<string, unknown>[]> | Query temporal entities | | getTemporalEntity(entityId) | string | Promise<Record<string, unknown>> | Get temporal representation of a single entity |

GetTemporalEntitiesParams

| Property | Type | Description | |----------|------|-------------| | type | string | Entity type filter | | id | string | Entity ID | | idPattern | string | Entity ID pattern (regex) | | attrs | string | Attribute names to retrieve | | q | string | NGSI-LD query expression | | timeproperty | string | Time property (observedAt, createdAt, modifiedAt) | | timeAt | string | Start time (ISO 8601) | | endTimeAt | string | End time (ISO 8601) | | timerel | string | Temporal relation (before, after, between) | | limit | number | Max results | | offset | number | Pagination offset |

Batch Operations

| Method | Parameters | Returns | Description | |--------|-----------|---------|-------------| | batchCreate(entities) | Record<string, unknown>[] | Promise<Record<string, unknown>> | Create multiple entities | | batchUpsert(entities) | Record<string, unknown>[] | Promise<Record<string, unknown>> | Upsert multiple entities | | batchUpdate(entities) | Record<string, unknown>[] | Promise<Record<string, unknown>> | Update multiple entities | | batchDelete(entityIds) | string[] | Promise<Record<string, unknown>> | Delete multiple entities by ID |

Generic Request

| Method | Parameters | Returns | Description | |--------|-----------|---------|-------------| | request(method, path, body?) | string, string, unknown | Promise<unknown> | Authenticated request with automatic JSON parsing | | requestRaw(method, path, body?) | string, string, unknown | Promise<Response> | Authenticated request returning raw Response (for accessing headers) |

Cache & Polling

| Method | Parameters | Returns | Description | |--------|-----------|---------|-------------| | poll(params, options) | GetEntitiesParams \| undefined, PollOptions<T> | PollHandle | ETag-based polling — fires onData only when the data actually changes. options is required (onData must be specified to receive data) | | clearCache() | — | void | Drop all cached responses |

PollOptions<T>

| Property | Type | Description | |----------|------|-------------| | interval | number | Polling interval in milliseconds (default: 5000) | | onData | (data: T) => void | Called whenever the server returns fresh data (200) | | onNoChange | () => void | Called when the server returns 304 Not Modified | | onError | (err: Error) => void | Called on any fetch/parse failure |

PollHandle

| Method | Parameters | Returns | Description | |--------|-----------|---------|-------------| | stop() | — | void | Stop the polling timer |

CacheEvent

| Property | Type | Description | |----------|------|-------------| | key | string | Cache key (METHOD:path) | | path | string | Request path |

WebSocket

| Method | Parameters | Returns | Description | |--------|-----------|---------|-------------| | connect() | — | Promise<void> | Establish WebSocket connection | | reconnect() | — | Promise<void> | Force-reconnect (preserves subscriptions) | | disconnect() | — | void | Disconnect WebSocket | | isConnected() | — | boolean | Check if WebSocket is currently open | | subscribe(options?) | SubscribeOptions | void | Subscribe to entity events |

SubscribeOptions

| Property | Type | Description | |----------|------|-------------| | entityTypes | string[] | Entity types to subscribe to | | idPattern | string | Entity ID regex pattern |

Event Handling

| Method | Parameters | Returns | Description | |--------|-----------|---------|-------------| | on(event, listener) | string, Function | this | Register an event listener | | off(event, listener) | string, Function | this | Remove an event listener |

Error Handling

All async methods throw typed errors extending GeonicDBError. Use instanceof to distinguish error types.

import { GeonicDBError, AuthenticationError, NotFoundError } from '@geolonia/geonicdb-sdk';

try {
  await db.getEntity('urn:ngsi-ld:Room:404');
} catch (err) {
  if (err instanceof AuthenticationError) {
    // 401 — re-login needed
  } else if (err instanceof NotFoundError) {
    // 404 — entity not found
  } else if (err instanceof GeonicDBError) {
    console.error(err.message, err.statusCode);
  } else {
    console.error(err);
  }
}

| Class | Status | Description | |-------|--------|-------------| | GeonicDBError | — | Base class (statusCode property) | | AuthenticationError | 401 | Invalid credentials or expired token | | AuthorizationError | 403 | Insufficient permissions | | NotFoundError | 404 | Resource not found | | ConflictError | 409 | Entity already exists | | ValidationError | 422 | Bad request payload | | RateLimitError | 429 | Rate limited (retryAfter property) | | NetworkError | — | Fetch failure, DNS error, timeout |

License

MIT