Efficient data encoding library for serializing various data types into compact string representations
Polynar is a powerful JavaScript/TypeScript library that provides efficient encoding and decoding of various data types (numbers, strings, booleans, dates, objects, and more) into compact string representations using polynary (multi-base) number encoding. Originally created in 2014, now modernized with TypeScript support.
Polynary numbers, or multi-state numbers, are the secret behind Polynar's efficiency. Any number has multiple representations in different numeral systems. Where we have a decimal 9, we could have 1001 in binary (base two). Notice how a single digit becomes four separate digits, each able to hold a different value.
But what happens when you want to store a gender as either male, female, or unknown? You could use two binary digits (allowing for 4 values, wasting one), or you could use a base-3 digit. This is where polynary numbers shine: by using multiple bases for different parts of the same number, we can perfectly fit our data without waste.
For example, you could represent data as base 2-2-3, where:
- First digit (base 2):
trueorfalse - Second digit (base 2):
yesorno - Third digit (base 3):
male,female, orunknown
This eliminates the storage waste of traditional binary encoding while maintaining easy composition and parsing. Polynar automatically handles all the math, so you just specify your constraints and get maximally efficient encoding.
Note: Polynar is not encryption. Encoded data can be recovered without knowledge of encoding options through analysis. Use proper encryption if security is needed.
- 📦 Multiple data types: Supports numbers, strings, booleans, dates, objects, arrays, and more
- 🎯 Type-safe: Full TypeScript support with comprehensive type definitions
- 🔧 Flexible encoding: Multiple character sets (Base64, alphanumeric, custom, etc.)
- 📏 Compact output: Efficient encoding minimizes output size
- 🌐 Universal: Works in Node.js and browsers
- 🔒 Strict mode: Optional strict validation for encoding/decoding
npm install polynarimport { Encoder, Decoder, CharSets } from 'polynar';
// Encode numbers
const encoder = new Encoder();
encoder.write(42, { type: 'number', min: 0, max: 100 });
const encoded = encoder.toString(); // Compact string representation
// Decode numbers
const decoder = new Decoder(encoded);
const decoded = decoder.read({ type: 'number', min: 0, max: 100 }); // 42import { Encoder } from 'polynar';
const enc = new Encoder();
// Simple range
enc.write(50, { type: 'number', min: 0, max: 100 });
// With step size
enc.write(2.5, { type: 'number', min: 0, max: 10, step: 0.5 });
// Unbounded numbers
enc.write(-42, { type: 'number', min: false, max: false });
const result = enc.toString();import { Encoder, CharSets } from 'polynar';
const enc = new Encoder();
// String with max length
enc.write('hello', { type: 'string', max: 20 });
// String with custom charset
enc.write('ABC123', {
type: 'string',
charset: CharSets.alphanumeric
});
// Variable length string
enc.write('dynamic', { type: 'string', max: false });
const result = enc.toString();import { Encoder } from 'polynar';
const enc = new Encoder();
enc.write([true, false, true], { type: 'boolean' });
const result = enc.toString();import { Encoder } from 'polynar';
const enc = new Encoder();
// Date with day precision
enc.write(new Date(), {
type: 'date',
interval: 'day',
min: new Date('2020-01-01'),
max: new Date('2030-12-31')
});
// Date with millisecond precision
enc.write(new Date(), {
type: 'date',
interval: 1 // milliseconds
});
const result = enc.toString();import { Encoder } from 'polynar';
const enc = new Encoder();
// Object with template
const user = { name: 'John', age: 30, active: true };
enc.write(user, {
type: 'object',
template: {
name: { type: 'string', max: 50 },
age: { type: 'number', min: 0, max: 150 },
active: { type: 'boolean' }
}
});
const result = enc.toString();import { Encoder } from 'polynar';
const enc = new Encoder();
// Encode from predefined list
const colors = ['red', 'green', 'blue'];
enc.write('green', {
type: 'item',
list: colors
});
const result = enc.toString();import { Encoder } from 'polynar';
const enc = new Encoder();
// The 'any' type automatically handles different types
enc.write([42, 'hello', true, new Date()], { type: 'any' });
const result = enc.toString();import { Decoder } from 'polynar';
// Decode single value
const dec = new Decoder(encodedString);
const value = dec.read({ type: 'number', min: 0, max: 100 });
// Decode multiple values
const dec2 = new Decoder(encodedString);
const values = dec2.read({ type: 'boolean' }, 3); // Read 3 booleansimport { Encoder, Decoder, CharSets } from 'polynar';
const enc = new Encoder();
enc.write('data', { type: 'string' });
// Use different charset for output
const base64 = enc.toString(CharSets.Base64);
const urlSafe = enc.toString(CharSets.urlSafe);
const hex = enc.toString(CharSets.hex);
// Decode with same charset
const dec = new Decoder(urlSafe, CharSets.urlSafe);
const result = dec.read({ type: 'string' });import { Encoder, Decoder } from 'polynar';
// Strict mode throws errors on invalid data
const strictEnc = new Encoder(true);
strictEnc.write(150, { type: 'number', min: 0, max: 100 }); // Throws!
// Non-strict mode coerces values
const lenientEnc = new Encoder(false);
lenientEnc.write(150, { type: 'number', min: 0, max: 100 }); // Clamps to 100import { Encoder, Decoder } from 'polynar';
const enc = new Encoder();
// Transform data before encoding
enc.write([1, 2, 3], {
type: 'number',
min: 0,
max: 10,
preProc: (x) => x * 2 // Doubles each number
});
const dec = new Decoder(enc.toString());
// Transform data after decoding
const values = dec.read({
type: 'number',
min: 0,
max: 10,
postProc: (x) => x / 2 // Halves each number
}, 3);CharSets.digit-0123456789CharSets.hex-0123456789ABCDEFCharSets.lowalpha-abcdefghijklmnopqrstuvwxyzCharSets.hialpha-ABCDEFGHIJKLMNOPQRSTUVWXYZCharSets.alpha- All lettersCharSets.alphanumeric- Letters and digitsCharSets.printable- All printable ASCII charactersCharSets.htmlSafe- HTML-safe charactersCharSets.Base64- Standard Base64 charactersCharSets.urlSafe- URL-safe characters
class Encoder {
constructor(strict?: boolean);
write(items: any | any[], options: EncodingOptions): void;
toString(charset?: Charset): string;
}class Decoder {
constructor(str: string, charset?: Charset, strict?: boolean);
read(options: EncodingOptions, count?: number): any;
}type: 'number'min?: number | false- Minimum value (false for unbounded)max?: number | false- Maximum value (false for unbounded)step?: number- Step size (default: 1)
type: 'string'max?: number | false- Max length (false for variable)charset?: Charset- Character set to use
type: 'boolean'
type: 'date'interval?: number | string- Time interval ('second', 'minute', 'hour', 'day', 'week', 'month', 'year', or milliseconds)min?: number | Date- Minimum datemax?: number | Date- Maximum date
type: 'object'template?: ObjectTemplate | false- Object structure templateoptional?: boolean- Allow optional propertiessort?: boolean- Sort keys
type: 'item'list: any[]- Array of possible valuessort?: boolean- Sort the list
type: 'any'- Automatically handles any supported type
Polynar allows you to create your own encoding types using the same registerModule API used internally:
import { registerModule, Encoder, Decoder } from 'polynar';
// Register a custom "color" encoding type
registerModule(
'color',
// Validator (optional - use false if not needed)
function (options) {
// Validate options
},
// Encoder
function (items, options) {
for (const i in items) {
const color = items[i];
this.compose(color.r, 256); // Red: 0-255
this.compose(color.g, 256); // Green: 0-255
this.compose(color.b, 256); // Blue: 0-255
}
},
// Decoder
function (options, count) {
const items = [];
for (let i = 0; i < count; i++) {
items.push({
r: this.parse(256),
g: this.parse(256),
b: this.parse(256),
});
}
return items;
}
);
// Now use it!
const enc = new Encoder();
enc.write({ r: 255, g: 0, b: 0 }, { type: 'color' });See examples/custom-module.ts for a complete working example.
npm install
npm run build# Run all tests
npm test
# Run tests in watch mode
npm run test:watch
# Run with coverage report
npm run test:coverageTests are organized by module in src/__tests__/. Each encoding module has its own test file. Run npm run test:coverage to see detailed coverage statistics and generate an HTML report in coverage/lcov-report/index.html.
MIT © Pablo Kebees
Contributions are welcome! Please feel free to submit a Pull Request.