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

@urlspec/language

v0.4.0

Published

Core language implementation for URLSpec using Langium

Readme

@urlspec/language

Core language implementation for URLSpec, built with Langium

npm version License: MIT

Overview

@urlspec/language is the core package that implements the URLSpec domain-specific language. It provides parsing, validation, and resolution capabilities for .urlspec files, built on the powerful Langium framework.

Features

  • Parse URLSpec files: Convert .urlspec text into structured AST
  • Type-safe resolution: Transform AST into developer-friendly resolved structures
  • Validation: Catch syntax and semantic errors
  • Pretty printing: Generate formatted .urlspec text from AST
  • Langium services: Expose language services for IDE integration

Installation

# npm
npm install @urlspec/language

# yarn
yarn add @urlspec/language

# pnpm
pnpm add @urlspec/language

Usage

Basic Parsing

import { parse } from '@urlspec/language';

const urlspecContent = `
page list = /jobs {
  category?: string;
}
`;

const document = await parse(urlspecContent);

// Access the Langium AST
console.log(document.parseResult.value.pages[0].name); // "list"

Resolved API (Recommended)

The resolve() function provides a higher-level API with merged global parameters and resolved type references:

import { resolve } from '@urlspec/language';

const spec = resolve(urlspecContent);

// Easier to work with resolved structure
console.log(spec.pages[0].name); // "list"
console.log(spec.pages[0].path); // "/jobs"
console.log(spec.pages[0].parameters);
// Includes both page-specific and global parameters

Type Definitions

The package exports comprehensive TypeScript types for the resolved structure:

import type {
  ResolvedURLSpec,
  ResolvedPage,
  ResolvedParameter,
  ResolvedType,
  ResolvedPathSegment,
} from '@urlspec/language';

function processSpec(spec: ResolvedURLSpec) {
  spec.pages.forEach((page: ResolvedPage) => {
    console.log(`Page: ${page.name}`);
    console.log(`Path: ${page.path}`);

    page.pathSegments.forEach((segment: ResolvedPathSegment) => {
      if (segment.kind === 'dynamic') {
        console.log(`  Dynamic param: ${segment.name}`);
      }
    });

    Object.entries(page.parameters).forEach(([name, param]) => {
      console.log(`  Query param: ${name} (${param.optional ? 'optional' : 'required'})`);
    });
  });
}

API Reference

parse(input: string): Promise<URLSpecDocument>

Parses URLSpec text into a Langium document with AST.

Parameters:

  • input - URLSpec source code as a string

Returns:

  • Promise resolving to a URLSpecDocument containing the Langium AST

Example:

const document = await parse(`
page home = /home {
  query?: string;
}
`);

const ast = document.parseResult.value;
console.log(ast.pages[0].name); // "home"
console.log(ast.pages[0].parameters[0].name); // "query"

resolve(input: string): ResolvedURLSpec

Parses and resolves URLSpec into a developer-friendly structure with:

  • Global parameters merged into each page
  • Type references resolved to actual types
  • Path segments parsed and categorized

Parameters:

  • input - URLSpec source code as a string

Returns:

  • ResolvedURLSpec object with fully resolved structure

Example:

const spec = resolve(`
param category = "electronics" | "clothing" | "food";

global {
  utm_source?: string;
}

page products = /products {
  cat: category;
}
`);

console.log(spec.paramTypes[0]);
// { name: 'category', type: { kind: 'union', values: ['electronics', 'clothing', 'food'] } }

const productsPage = spec.pages[0];
console.log(productsPage.parameters.cat.type);
// Resolved to actual union type, not a reference
console.log(productsPage.parameters.utm_source);
// Global parameter merged into page

print(urlSpec: URLSpecModel): string

Converts a Langium AST back to formatted URLSpec text.

Parameters:

  • urlSpec - The Langium AST model to print

Returns:

  • Formatted URLSpec source code as a string

Example:

import { parse, print } from '@urlspec/language';

const document = await parse('page home = /home { query?: string; }');
const ast = document.parseResult.value;

const formatted = print(ast);
console.log(formatted);
// Output:
// page home = /home {
//   query?: string;
// }

createURLSpecServices(context?: DefaultSharedModuleContext): URLSpecServices

Creates Langium language services for URLSpec. Used primarily for language server and IDE integration.

Parameters:

  • context - Optional Langium module context

Returns:

  • URLSpecServices object with Langium services

Type Reference

ResolvedURLSpec

Top-level resolved structure representing an entire URLSpec document.

interface ResolvedURLSpec {
  endpoint?: string;
  paramTypes: ResolvedParamType[];
  globalParameters: ResolvedParameter[];
  pages: ResolvedPage[];
}

ResolvedPage

Represents a single page definition with resolved parameters.

interface ResolvedPage {
  name: string;
  path: string;
  pathSegments: ResolvedPathSegment[];
  parameters: ResolvedParameter[];
  description?: string;
}

ResolvedParameter

Represents a query or path parameter with its type and optionality.

interface ResolvedParameter {
  name: string;
  optional: boolean;
  type: ResolvedType;
}

ResolvedType

Union type representing all possible parameter types.

type ResolvedType =
  | { kind: 'string' }
  | { kind: 'literal'; value: string }
  | { kind: 'union'; values: string[] };

ResolvedPathSegment

Represents a segment of a URL path.

type ResolvedPathSegment =
  | { kind: 'static'; value: string }
  | { kind: 'dynamic'; name: string };

Two-Level API Design

@urlspec/language provides two APIs for different use cases:

Low-Level API: parse()

Use when you need:

  • Direct access to Langium AST
  • Building IDE tools or language servers
  • Low-level manipulation of syntax tree
  • Custom validation or transformation

Example:

const doc = await parse(input);
const ast = doc.parseResult.value;

// Direct AST access
ast.pages.forEach(page => {
  console.log(page.$type); // Langium type
  console.log(page.name);
});

High-Level API: resolve()

Use when you need:

  • Simple, flattened data structure
  • Type references resolved to actual types
  • Global parameters automatically merged
  • Building application features

Example:

const spec = resolve(input);

// Easy access to resolved data
spec.pages.list.parameters.forEach(param => {
  // Type is already resolved, no need to lookup references
  console.log(param.type);
});

Advanced Usage

Error Handling

import { parse } from '@urlspec/language';

const document = await parse(invalidInput);

if (document.parseResult.lexerErrors.length > 0) {
  console.error('Lexer errors:', document.parseResult.lexerErrors);
}

if (document.parseResult.parserErrors.length > 0) {
  console.error('Parser errors:', document.parseResult.parserErrors);
}

Custom Validation

import { createURLSpecServices } from '@urlspec/language';

const services = createURLSpecServices();
const validator = services.validation.ValidationRegistry;

// Add custom validation rules
validator.register({
  URLSpecModel(model, accept) {
    if (model.pages.length === 0) {
      accept('warning', 'URLSpec should have at least one page', {
        node: model,
      });
    }
  },
});

Working with Path Segments

const spec = resolve(`
page article = /blog/:category/:article_id {
  category: string;
  article_id: string;
}
`);

const articlePage = spec.pages[0];

articlePage.pathSegments.forEach(segment => {
  if (segment.type === 'static') {
    console.log(`Static: ${segment.value}`);
  } else if (segment.type === 'parameter') {
    console.log(`Dynamic: ${segment.value}`);
    // Get the parameter details
    const param = articlePage.parameters.find(p => p.name === segment.value);
    console.log(`  Optional: ${param.optional}`);
  }
});

Language Grammar

URLSpec is defined using Langium grammar. Key syntax elements:

Parameter Types

param status = "active" | "inactive" | "pending";
param sort_order = "asc" | "desc";

Global Parameters

global {
  utm_source?: string;
  debug?: "true" | "false";
}

Pages

// Static path
page home = /;

// Dynamic path with parameters
page user = /users/:user_id {
  user_id: string;
  tab?: "posts" | "comments" | "likes";
}

// Multiple dynamic segments
page post = /blog/:category/:post_id {
  category: string;
  post_id: string;
  preview?: "true" | "false";
}

Type Syntax

// String type
string

// String literal
"active"

// Union of literals
"small" | "medium" | "large"

// Type reference
sort_order  // References a param type

Development

Building

# Generate Langium parser and build
yarn build

# Just generate Langium artifacts
yarn langium:generate

Testing

# Run tests
yarn test

# Watch mode
yarn test:watch

Grammar Location

The Langium grammar is defined in:

packages/language/src/urlspec.langium

Contributing

Contributions are welcome! Please see the root repository README for contribution guidelines.

Common Tasks

Modifying Grammar:

  1. Edit src/urlspec.langium
  2. Run yarn langium:generate
  3. Update TypeScript code as needed
  4. Run tests: yarn test

Adding Validation:

  1. Add validation logic in src/validator.ts (if exists)
  2. Update tests
  3. Update documentation

Related Packages

Resources

License

MIT License - see LICENSE for details