A small library for working with 0- and 1-based offsets in a type-checked way.
npx @tessl/cli install tessl/npm-ob1@0.83.0ob1 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.
npm install ob1const { 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";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)); // 85ob1's architecture is built around Flow's opaque type system:
Number0 and Number1 are distinct numeric types that prevent accidental mixingCore 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;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)); // 0Functions 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;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)); // 42Additional 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)); // -5ob1's type system allows these safe combinations:
Number0 + Number0 = Number0 - Adding two 0-based offsetsNumber0 + Number1 = Number1 - Adding 0-based to 1-based yields 1-basedNumber1 + Number0 = Number1 - Adding 1-based to 0-based yields 1-basedNumber1 - Number1 = Number0 - Distance between 1-based positionsNumber0/Number1 + number - Adding plain numbers preserves offset typeFlow will prevent these unsafe operations at compile time:
Number1 + Number1 - Adding two 1-based offsets is mathematically meaninglessNumber0 - Number1 - Would create negative 0-based offsetsget0(Number1) - Cross-type extractionget1(Number0) - Cross-type extractionadd1(Number1) - Converting already 1-based offsetsub1(Number0) - Converting already 0-based offsetoffset + 1, offset - 1) - Must use provided functionsob1 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