or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

docs

index.md
tile.json

tessl/npm-ob1

A small library for working with 0- and 1-based offsets in a type-checked way.

Workspace
tessl
Visibility
Public
Created
Last updated
Describes
npmpkg:npm/ob1@0.83.x

To install, run

npx @tessl/cli install tessl/npm-ob1@0.83.0

index.mddocs/

ob1

ob1 is a small TypeScript library that provides type-safe operations for working with 0-based and 1-based numeric offsets using Flow's opaque types. It prevents common off-by-one errors by enforcing type distinctions at compile time while maintaining runtime efficiency through simple numeric operations.

Package Information

  • Package Name: ob1
  • Package Type: npm
  • Language: JavaScript with Flow types
  • Installation: npm install ob1

Core Imports

const { add, sub, get0, get1, add1, sub1, neg, add0, inc } = require("ob1");

ES Modules:

import { add, sub, get0, get1, add1, sub1, neg, add0, inc } from "ob1";

Flow Types:

import type { Number0, Number1 } from "ob1";

Basic Usage

const { add0, add1, add, get0, get1 } = require("ob1");

// Create typed offsets
const zeroBasedIndex = add0(42);  // Number0 type
const oneBasedLine = add1(41);    // Number1 type (42 in 1-based)

// Type-safe arithmetic
const sum = add(zeroBasedIndex, 3);     // Number0 + number = Number0
const mixed = add(zeroBasedIndex, oneBasedLine); // Number0 + Number1 = Number1

// Extract values
console.log(get0(sum));    // 45
console.log(get1(mixed));  // 85

Architecture

ob1's architecture is built around Flow's opaque type system:

  • Opaque Types: Number0 and Number1 are distinct numeric types that prevent accidental mixing
  • Type Safety: Operations preserve offset semantics through overloaded function signatures
  • Runtime Efficiency: All operations are simple numeric calculations with zero runtime overhead
  • Compile-time Validation: Flow prevents unsafe operations like adding two 1-based offsets

Capabilities

Type System

Core opaque types for representing different offset bases.

// 0-based offset type (for array indices, positions starting from 0)
export opaque type Number0 = number;

// 1-based offset type (for line numbers, columns starting from 1)  
export opaque type Number1 = number;

Arithmetic Operations

Type-safe addition and subtraction with automatic type propagation.

/**
 * Add two offsets or numbers with type-safe result types
 * @param a - First operand (Number0, Number1, or number)
 * @param b - Second operand (Number0, Number1, or number)  
 * @returns Result preserving appropriate offset type
 */
declare function add(a: Number1, b: number): Number1;
declare function add(a: number, b: Number1): Number1;
declare function add(a: Number0, b: number): Number0;
declare function add(a: number, b: Number0): Number0;
declare function add(a: Number1, b: Number0): Number1;
declare function add(a: Number0, b: Number1): Number1;
declare function add(a: Number0, b: Number0): Number0;

/**
 * Subtract a number or 0-based offset from a 1/0-based offset
 * @param a - Minuend (Number0, Number1, or number)
 * @param b - Subtrahend (Number0, Number1, or number)
 * @returns Result with appropriate offset type
 */
declare function sub(a: Number1, b: number): Number1;
declare function sub(a: Number0, b: number): Number0;
declare function sub(a: number, b: Number0): Number0;
declare function sub(a: Number0, b: number): Number0;
declare function sub(a: Number1, b: Number0): Number1;
declare function sub(a: Number0, b: Number0): Number0;
declare function sub(a: Number1, b: Number1): Number0;

Usage Examples:

const { add0, add1, add, sub, get0, get1 } = require("ob1");

const index = add0(5);     // Number0
const line = add1(10);     // Number1

// Safe additions
const newIndex = add(index, 3);        // Number0 + number = Number0
const newLine = add(line, index);      // Number1 + Number0 = Number1

// Safe subtractions  
const diff = sub(line, line);          // Number1 - Number1 = Number0 (distance)
const prevIndex = sub(index, 2);       // Number0 - number = Number0

console.log(get0(newIndex));  // 8
console.log(get1(newLine));   // 16
console.log(get0(diff));      // 0

Type Casting and Extraction

Functions for creating offset types and extracting underlying values.

/**
 * Cast a number to a 0-based offset
 * @param x - Number to cast
 * @returns Number0 offset type
 */
function add0(x: number): Number0;

/**
 * Get the underlying number of a 0-based offset, casting away the opaque type
 * @param x - Number0 offset or null/undefined
 * @returns Underlying number value or null/undefined
 */
declare function get0(x: Number0): number;
declare function get0(x: void | null): void | null;

/**
 * Get the underlying number of a 1-based offset, casting away the opaque type
 * @param x - Number1 offset or null/undefined  
 * @returns Underlying number value or null/undefined
 */
declare function get1(x: Number1): number;
declare function get1(x: void | null): void | null;

Offset Conversion

Functions for converting between 0-based and 1-based offset types.

/**
 * Add 1 to a 0-based offset, converting it to 1-based
 * @param x - Number0 offset or regular number
 * @returns Number1 offset (input + 1)
 */
function add1(x: Number0 | number): Number1;

/**
 * Subtract 1 from a 1-based offset, converting it to 0-based  
 * @param x - Number1 offset
 * @returns Number0 offset (input - 1)
 */
function sub1(x: Number1): Number0;

Usage Examples:

const { add0, add1, sub1, get0, get1 } = require("ob1");

// Array index to line number conversion
const arrayIndex = add0(4);           // 5th element (0-based)
const lineNumber = add1(arrayIndex);  // Line 5 (1-based)

// Line number to array index conversion  
const currentLine = add1(42);         // Line 43 (1-based)
const arrayPos = sub1(currentLine);   // Index 42 (0-based)

console.log(get0(arrayIndex));  // 4
console.log(get1(lineNumber));  // 5
console.log(get0(arrayPos));    // 42

Utility Operations

Additional operations for incrementing and negating offsets.

/**
 * Increment a 0-based or 1-based offset by 1
 * @param a - Offset to increment (Number0 or Number1)
 * @returns Same offset type incremented by 1
 */
declare function inc(a: Number0): Number0;
declare function inc(a: Number1): Number1;

/**
 * Negate a 0-based offset
 * @param x - Number0 offset to negate
 * @returns Negated Number0 offset
 */
function neg(x: Number0): Number0;

Usage Examples:

const { add0, add1, inc, neg, get0, get1 } = require("ob1");

const index = add0(5);
const line = add1(10);

// Increment operations preserve type
const nextIndex = inc(index);  // Number0: 6
const nextLine = inc(line);    // Number1: 11

// Negation (only for Number0)
const negative = neg(index);   // Number0: -5

console.log(get0(nextIndex)); // 6  
console.log(get1(nextLine));  // 11
console.log(get0(negative));  // -5

Type Safety Features

Allowed Operations

ob1's type system allows these safe combinations:

  • Number0 + Number0 = Number0 - Adding two 0-based offsets
  • Number0 + Number1 = Number1 - Adding 0-based to 1-based yields 1-based
  • Number1 + Number0 = Number1 - Adding 1-based to 0-based yields 1-based
  • Number1 - Number1 = Number0 - Distance between 1-based positions
  • Number0/Number1 + number - Adding plain numbers preserves offset type

Prevented Operations

Flow will prevent these unsafe operations at compile time:

  • Number1 + Number1 - Adding two 1-based offsets is mathematically meaningless
  • Number0 - Number1 - Would create negative 0-based offsets
  • get0(Number1) - Cross-type extraction
  • get1(Number0) - Cross-type extraction
  • add1(Number1) - Converting already 1-based offset
  • sub1(Number0) - Converting already 0-based offset
  • Direct arithmetic (offset + 1, offset - 1) - Must use provided functions

Error Handling

ob1 operations are purely mathematical and do not throw exceptions. Invalid combinations are prevented at compile time through Flow's type system rather than runtime checks.

For null/undefined handling, the get0 and get1 functions have overloaded signatures that preserve null/undefined values:

get0(null);      // returns null
get0(undefined); // returns undefined  
get1(null);      // returns null
get1(undefined); // returns undefined