Quick Start JavaScript - SmithSoft-Studios/JAZMIN GitHub Wiki

Quick Start: JavaScript and TypeScript

From nothing to an encrypted, indexed file you can query, in about five minutes.

You need: Node.js 22 or later. The package has no dependencies of its own.

npm install @smithsoft-studios/jazmin

The package is an ES module, with TypeScript types included. Save the examples below as .mjs files (or set "type": "module" in your package.json) and run them with node.

1. Like JSON: rows in, bytes out

import { JAZMIN } from '@smithsoft-studios/jazmin';

const bytes = JAZMIN.stringify([{ id: 1, name: 'Ann' }, { id: 2, name: 'Bob' }]); // a Buffer
console.log(JAZMIN.parse(bytes));
[ { id: 1, name: 'Ann' }, { id: 2, name: 'Bob' } ]

The column types were worked out from the data: whole numbers became int, text became string.

2. A real file: a schema, indexes and encryption

import { JazminKey, open, write } from '@smithsoft-studios/jazmin';

const key = JazminKey.generate(); // in real code: store key.export() in your secret manager

write('customers.jzm', [
  { id: 1, name: 'Ann Johnson', country: 'ZA', balance: '1520.75', joined: new Date('2024-01-02') },
  { id: 2, name: 'Bob Smith', country: 'NA', balance: '99.10', joined: new Date('2024-03-04') },
  { id: 3, name: 'Thabo Ndlovu', country: 'ZA', balance: '0.00', joined: new Date('2025-06-07') },
], {
  columns: [
    { name: 'id', type: 'int', nullable: false, index: 'sorted' },
    { name: 'name', type: 'string', index: 'trigram' },      // fast text search
    { name: 'country', type: 'string', index: 'sorted' },   // fast lookups and ranges
    { name: 'balance', type: 'decimal' },                    // exact: never rounded
    { name: 'joined', type: 'datetime' },
  ],
  metadata: { source: 'crm' },
  key,
});

// Opening reads only the header. A query reads only the index pages and chunks that can match.
const reader = open('customers.jzm', { key: key.export() });
console.log(reader.rowCount, 'rows:', reader.columns.map((c) => `${c.name} (${c.type})`).join(', '));
for (const row of reader.find({ country: 'ZA', name: { icontains: 'ndlovu' } })) console.log(row);
console.log(reader.count({ country: 'ZA' }), 'customers in ZA');
console.log(reader.explain({ id: 3 }));
reader.close();
3 rows: id (int), name (string), country (string), balance (decimal), joined (datetime)
{
  id: 3,
  name: 'Thabo Ndlovu',
  country: 'ZA',
  balance: '0.00',
  joined: 2025-06-07T00:00:00.000Z
}
2 customers in ZA
{ strategy: 'index', candidateRows: 1 }
  • The key is 32 random bytes, written as text that starts with jzk1- and carries a checksum, so a typo is reported as a typo. Lose it and the data cannot be recovered. Never log it.
  • find() is a generator: rows come one at a time and only one chunk is held in memory. Use for…of to stream, or [...reader.find(...)] to collect.
  • explain() says how a query will run: here, through the id index, with one candidate row.

3. Page, pick columns, convert

import { open, toCSV, toJSON, write } from '@smithsoft-studios/jazmin';

write('orders.jzm', Array.from({ length: 1000 }, (_, i) => ({ id: i + 1, customer: `C${(i % 50) + 1}`, amount: (i % 97) * 2.5 })), {
  sortedBy: ['id'], // rows are stored in id order: id lookups and ranges skip straight to the right chunks
});

const orders = open('orders.jzm');
const page = [...orders.find({ amount: { gte: 200 } }, { select: ['id', 'amount'], offset: 10, limit: 3 })];
console.log(page);
console.log(toJSON(orders, { filter: { id: { lte: 2 } }, select: ['id', 'customer'] }));
console.log(toCSV(orders, { filter: { id: { lte: 2 } } }));
orders.close();
[
  { id: 91, amount: 225 },
  { id: 92, amount: 227.5 },
  { id: 93, amount: 230 }
]
[{"id":1,"customer":"C1"},{"id":2,"customer":"C2"}]
id,customer,amount
1,C1,0
2,C2,2.5
  • select decodes only the columns you list (and the ones the filter uses). On wide tables this is the single biggest saving.
  • offset / limit page through results; whole chunks before the offset are skipped unread when the reader can prove every row in them matches.

4. Big data: stream rows in, stream rows out

import { JazminWriter, open } from '@smithsoft-studios/jazmin';

const writer = new JazminWriter('readings.jzm', {
  columns: [
    { name: 'id', type: 'int', nullable: false },
    { name: 'reading', type: 'float' },
  ],
  sortedBy: ['id'],
});
for (let i = 0; i < 1_000_000; i++) writer.writeRow({ id: i, reading: Math.round(Math.sin(i / 1000) * 1000) / 1000 });
writer.finish(); // required: writes the indexes, header and trailer

const readings = open('readings.jzm');
console.log(readings.rowCount, 'rows');
console.log([...readings.find({ id: 765432 })]); // reads one chunk, not a million rows
let total = 0;
for (const row of readings.find({ id: { gte: 500000, lt: 500100 } }, { select: ['reading'] })) total += row.reading;
console.log('sum of 100 readings:', Math.round(total * 1000) / 1000);
readings.close();
1000000 rows
[ { id: 765432, reading: -0.899 } ]
sum of 100 readings: -51.072

Memory while writing is one chunk (4,096 rows by default) plus the index lists, however many rows you write.

5. TypeScript

The package ships its own type definitions:

import { JazminKey, JazminKeyError, open, type Filter, type JazminColumnInput } from '@smithsoft-studios/jazmin';

const columns: JazminColumnInput[] = [
  { name: 'id', type: 'int', nullable: false, index: 'sorted' },
  { name: 'country', type: 'string', index: 'sorted' },
];
const where: Filter = { country: 'ZA', id: { gte: 2 } }; // filters are type-checked

try {
  open('customers.jzm', { key: JazminKey.generate() });
} catch (e) {
  if (e instanceof JazminKeyError) console.log('wrong key rejected');
}

Where next