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. Usefor…ofto stream, or[...reader.find(...)]to collect.explain()says how a query will run: here, through theidindex, 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
selectdecodes only the columns you list (and the ones the filter uses). On wide tables this is the single biggest saving.offset/limitpage 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
- Core Concepts: what is inside a file, and why queries read so little
- Writing Files and Reading and Querying
- Filter Language: every operator, with examples
- Encryption and Keys and Access Control
- Recipes: complete solutions to common jobs