@qumra/pos
v0.3.2
Published
Qumra's all-in-one facade: connect to the printer, build an Arabic receipt, and print
Readme
@qumra/pos
Qumra's all-in-one, Arabic-first facade: connect, build the receipt, print.
Part of the @qumra suite. It hides the connection, ESC/POS encoding, and Arabic rendering behind a single fluent API, and ships 8 built-in receipt designs. Bundles @qumra/webusb + @qumra/receipt-encoder + @qumra/arabic-canvas.
Features
- 🚀 One package — install once, no wiring required.
- ⛓️ Fluent builder —
store().items().total().cut(). - 🅰️ Arabic-first — correct shaping + RTL, printed as an image.
- 🔢 Quantities, sequential invoice number, currency, and auto-computed total.
- 🎨 8 built-in templates selectable via the
Templatesenum, or inject your own. - 🌐 Dual-environment — the same code runs in the browser (WebUSB) and Electron (node-usb) via auto-detection; inject a custom transport when you need one.
- 🗓️ Gregorian or Hijri dates. Defaults to 58mm paper.
Installation
npm install @qumra/posQuick start
import { Pos, Templates } from '@qumra/pos';
const printer = await Pos.connect(); // WebUSB, any printer (inside a user gesture)
const receipt = printer.receipt({ template: Templates.Tax, currency: 'ر.س' }) // 58mm default
.store({ name: 'متجر قمرة', address: 'الرياض', vat: '300000000000003' })
.serial(1024) // sequential invoice number → "فاتورة #01024"
.date(new Date(), { calendar: 'hijri' }) // Hijri date
.items([ // an array of objects — easy to read, edit, validate
{ name: 'قهوة عربية', price: 15.00, qty: 2 },
{ name: 'كرواسون', price: 12.50 }, // qty defaults to 1
])
// .total(...) is optional — computed automatically from the items
.footer('شكراً لزيارتكم')
.cut();
await printer.print(receipt);
await printer.openDrawer();API
Pos.connect(options?) → Promise<QumraPrinter>
Connects to a printer. By default it auto-detects the transport: the Electron native bridge if window.qumraNative is present, otherwise WebUSB. Pass { transport } to override. In the browser it must be called inside a user gesture.
await Pos.connect(); // auto: browser → WebUSB, Electron → node-usb
await Pos.connect({ transport: webUsb() }); // force WebUSB
await Pos.connect({ transport: myTransport }); // a custom Transport (bluetooth/network/tests)options also accepts WebUSB filters (forwarded to the auto-detected WebUSB transport).
QumraPrinter
| Method | Description |
|---|---|
| receipt(options?) | Starts a receipt builder. options: { width?: 58 \| 80, currency?: string, template?: TemplateName \| Template }. |
| print(input) | Prints a ReceiptBuilder, an encoder, or a Uint8Array. |
| openDrawer() | Opens the cash drawer. |
| disconnect() | Closes the connection. |
| device | Connected device info. |
ReceiptBuilder (fluent)
| Method | Description |
|---|---|
| store({ name, address?, vat? }) | Store details. |
| title(text) | Receipt title. |
| serial(value) | Sequential invoice number. Numbers are padded to 5 digits. |
| date(value, { calendar? }) | Date: 'gregorian' (default) or 'hijri'. |
| item(name, price, qty?) | A single line item. qty defaults to 1; line total = price × qty. |
| items([{ name, price, qty? }]) | Add many items from an array of objects (validated). |
| total(amount) | The total. Optional — auto-computed from items if omitted. |
| footer(text) | Footer line. |
| cut() · drawer() | Cut paper · open the cash drawer. |
| encode() | Returns Uint8Array (called automatically by print). |
Templates
Pick a built-in design with the Templates enum (safe + autocomplete), or inject your own Template function. Defaults to Templates.Standard.
import { Pos, Templates, registerTemplate, type Template } from '@qumra/pos';
printer.receipt({ template: Templates.Coupon }); // built-in by name
printer.receipt({ template: myDesign }); // custom: (content, { width }) => HTMLCanvasElement
registerTemplate('brand', myDesign); // register once, then use by name
printer.receipt({ template: 'brand' });| Built-in | Description |
|---|---|
| Standard | Clean default layout (via @qumra/arabic-canvas). |
| Modern | Black header band + boxed total. |
| Classic | Double-ruled rounded frame with dotted leaders. |
| Elegant | Circular monogram + diamond ornaments. |
| Compact | Tight spacing, ideal for 58mm. |
| Tax | Simplified tax invoice with 15% VAT breakdown. |
| Coupon | Dashed “cut here” frame with promo band. |
| Kitchen | Large item lines with check boxes, no prices. |
Transports (dual-environment)
pos depends on a small Transport abstraction, so the same renderer code works in both environments:
| Transport | Runs in | Backed by |
|---|---|---|
| webUsb(options?) | Browser / Electron renderer | @qumra/webusb |
| bridge(window.qumraNative) | Electron renderer → main | IPC → @qumra/node-usb |
| your own | anywhere | implements Transport |
Transport is just { connect(): Promise<DeviceInfo>; print(bytes: Uint8Array): Promise<void>; disconnect?(): Promise<void> }.
Electron — expose a bridge in preload, print in main via @qumra/node-usb; Pos.connect() then auto-selects it:
// preload.cjs
contextBridge.exposeInMainWorld('qumraNative', {
print: (bytes) => ipcRenderer.invoke('printer:print', bytes),
});
// main.cjs
ipcMain.handle('printer:print', async (_e, bytes) => {
const { QumraNodeUsbPrinter } = await import('@qumra/node-usb');
const p = new QumraNodeUsbPrinter();
if (!p.connected) await p.connect();
await p.print(Uint8Array.from(bytes));
});
// renderer — identical to the web code
const printer = await Pos.connect(); // detects window.qumraNative → node-usbThe Arabic receipt is always rendered to a canvas in the renderer, then the bytes are sent to whichever transport is active.
Related packages
- @qumra/webusb · @qumra/node-usb — transports.
- @qumra/receipt-encoder — ESC/POS encoding.
- @qumra/arabic-canvas — Arabic rendering.
License
MIT © Qumra
