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

@openverb/geostory

v0.1.1

Published

GeoStory profile for OpenVerb — a portable geographic narrative format (.ov)

Readme

@openverb/geostory

npm license

A portable geographic narrative format, built as a profile of OpenVerb.

A GeoStory is a spatial workflow whose steps happen in geographic space. Each scene is a macro that compiles into atomic OpenVerb verb actions. The .ov document is the portable, AI-editable, format-agnostic representation of the whole story — where the camera goes, what is said, what is drawn, and what it all sits on.

The same document should render in a QGIS plugin, a Leaflet page, and a desktop app, and come out meaning the same thing.

The core invariant

A valid story must be playable from its declarative story data alone. External asset paths are optional and must never be required for core narration or rendering.

Narration lives in the document as text. A player is expected to speak it — with text-to-speech, or a pre-recorded clip if one happens to be there. A story that loads and plays silently because a .wav did not travel with it is a bug, not expected behaviour.

Everything else follows from this. Geocoding results are committed to the file at authoring time, so playback never calls a geocoding API. Basemaps carry their own tile URL and attribution, so a reader that has never heard of an id can still draw and credit it. External files are enhancements and caches, never prerequisites.

What a .ov looks like

name: iceland-still-building
version: 1.0.0
profile: geostory

story:
  title: "Iceland: A Country Still Being Built"
  crs: "EPSG:4326"

settings:
  default_basemap: bm_dark
  default_duration: 9

basemaps:
  - id: bm_dark
    name: "CartoDB Dark Matter"
    type: xyz
    url: "https://basemaps.cartocdn.com/dark_all/{z}/{x}/{y}.png"
    max_zoom: 20
    attribution: "© OpenStreetMap contributors © CARTO"

places:
  - id: pl_thingvellir
    as_written: "Þingvellir"
    resolved:
      name: "Þingvellir National Park, Iceland"
      point: [-21.13, 64.255]
      bounds: [[-21.22, 64.22], [-21.02, 64.30]]
      kind: landmark
      provider: nominatim
      resolved_at: "2026-08-20T00:00:00Z"
      confidence: 0.95

scenes:
  - id: sc_02
    sequence: 2
    title: "Where the Plates Part"
    place_id: pl_thingvellir
    basemap: bm_dark
    transition: fly
    timing: { mode: derived, intended: 12 }
    content:
      caption: "Þingvellir — the rift you can walk into."
      narration: >-
        At Þingvellir you can walk down into the rift itself. The valley floor
        has dropped as the plates draw apart, leaving a wall of basalt on
        either side.

attribution:
  - "© OpenStreetMap contributors © CARTO"

That document needs nothing else on disk. See examples/ for complete stories.

Installation

npm install @openverb/geostory

@openverb/format comes with it. @openverb/runtime is an optional peer dependency, pinned to 2.0.0-alpha.6, and is only needed if you execute compiled documents rather than rendering them yourself:

npm install @openverb/[email protected]

Usage

Parse and validate

import { parseGeoStory, validateGeoStory } from '@openverb/geostory';
import fs from 'fs';

const doc = parseGeoStory(fs.readFileSync('hurricane-katrina.ov', 'utf-8'));

const { valid, errors } = validateGeoStory(doc);
if (!valid) {
  console.error(errors);
}

parseGeoStory accepts YAML or JSON and throws GeoStoryParseError on malformed input. Use parseGeoStoryRaw when you want the untyped object without profile checks.

Serialise

import { serializeGeoStory } from '@openverb/geostory';

fs.writeFileSync('out.ov', serializeGeoStory(doc));

Unrecognised fields survive the round trip. A tool that does not understand a field must still write it back, so a document edited in one application does not come out thinner than it went in.

Compile to OpenVerb actions

A GeoStory uses high-level scenes. To execute one in an OpenVerb runtime, compile it into a standard OVDocument of atomic verb actions:

import { compileToOVDocument } from '@openverb/geostory';
import { bridgeToRuntime } from '@openverb/format';

const ovDoc = compileToOVDocument(doc);
const requests = bridgeToRuntime(ovDoc, {});
// -> geostory.basemap.set
// -> geostory.camera.fly_to
// -> geostory.media.show

Use compileScene for a single scene, or compileTimeline for the flattened action timeline without wrapping it in a document.

API

| Export | What it does | | --- | --- | | parseGeoStory(text) | YAML or JSON to a typed GeoStoryDocument. Throws GeoStoryParseError. | | parseGeoStoryRaw(text) | The same, without profile validation. | | serializeGeoStory(doc) | Back to YAML, preserving unknown fields. | | serializeGeoStoryToYAML(doc) | Explicit YAML. | | serializeGeoStoryToJSON(doc) | Explicit JSON. | | validateGeoStory(doc) | { valid, errors } against the JSON Schema. | | isGeoStoryDocument(value) | Type guard. | | compileToOVDocument(doc) | Scenes to a full OVDocument of verb actions. | | compileTimeline(doc) | Just the flattened action timeline. | | compileScene(scene, doc) | One scene's actions. | | GEOSTORY_SCHEMA | The JSON Schema object. | | verbLibrary | The verb definitions this profile emits. | | VERB_CATEGORIES | Verb category names. | | GeoStoryParseError | Thrown by the parsers. |

Types are exported for every part of the document — GeoStoryDocument, GeoStoryScene, GeoStoryPlace, GeoStoryCamera, GeoStoryTiming, and the rest. See src/types.ts.

How a document is put together

Scenes do not own their contents. Six top-level registries hold them, and scenes reference them by id:

| Registry | Holds | | --- | --- | | places | Geographic entities, with geocoding already resolved | | basemaps | Tile sources, each with its own URL and attribution | | layers | Data layers | | audio | Narration, music and effects | | media | Images and video | | annotations | Markers, circles, polygons and lines |

Two rules about time are worth knowing before you write a player:

  • audio, media and camera.track are document-absolute. Time 0.0 is the start of the story, not the start of a scene. This is what stops edits cascading down a flat timeline.
  • Scenes carry a length, not a start. A scene has sequence, timing and an optional delay; its start is the previous scene's end plus its own delay.

Format specification

The full profile is in spec/geostory-profile-1.0.md, and ships with the package. The JSON Schema is at schema/geostory-1.0.schema.json and is importable:

import schema from '@openverb/geostory/schema' with { type: 'json' };

Every document must declare profile: geostory at its root.

Examples

| File | What it demonstrates | | --- | --- | | iceland-still-building.ov | The core invariant: ten beats, full narration, no external files at all | | hurricane-katrina.ov | Bounds framing, camera keyframes, per-scene basemaps, data layers, timestamps | | downtown-redevelopment.ov | Annotations, a 3D camera request, layer visibility, and a local-government use case | | history-of-coffee.ov | A long-form narrative across thirteen countries, with a pinned scene |

Development

npm install
npm run build   # tsc, then regenerate the JSON Schema from the types
npm test

The schema is generated, not hand-written — edit src/types.ts and rebuild.

License

MIT