DEV Community

Fernando Paladini
Fernando Paladini

Posted on

Build Typed Gear-List Automation with the LighterPack SDK

Planning a gear list in a browser is convenient until the list becomes data you need to generate, review, or update repeatedly. A spreadsheet can hold the rows, but it does not automatically create categories, preserve LighterPack's rules, or calculate the result you actually see in the app.

This tutorial shows a small TypeScript workflow built with @paladini/lighterpack. It creates a complete backpacking list in one batch operation, reads the resulting totals, and demonstrates the safety boundaries you need to understand before automating writes to a personal account.

TL;DR

The SDK is an unofficial, ESM-only TypeScript client for LighterPack. It requires Node.js 20 or newer, uses your normal LighterPack username and password, and has no runtime dependencies. Its batch.createListWithItems() method can scaffold categories and items in one save round-trip.

The example below is a real API shape from the current 0.1.0 package and repository. Replace the placeholder credentials with environment variables, run it against an account you control, and inspect the returned totals before adding more automation.

Prerequisites

You need:

  • Node.js 20 or newer
  • A LighterPack account you are allowed to modify
  • A new TypeScript project using ESM
  • Credentials supplied through the environment, not committed to source control

The SDK is published under the MIT license. It is not affiliated with or endorsed by LighterPack. The project documents the package as an unofficial client that talks to the web application's own API.

Create a project and install the package:

mkdir typed-gear-list
cd typed-gear-list
npm init -y
npm install @paladini/lighterpack
npm install --save-dev tsx typescript
Enter fullscreen mode Exit fullscreen mode

The package metadata declares "type": "module", exposes TypeScript declarations, and currently publishes version 0.1.0 as the npm latest tag. The repository's README and package metadata are the source for those details.

Create a complete list in one call

Set credentials in your shell. PowerShell users can run:

$env:LIGHTERPACK_USERNAME = "your-username"
$env:LIGHTERPACK_PASSWORD = "your-password"
Enter fullscreen mode Exit fullscreen mode

Create index.ts:

import { LighterPackClient } from '@paladini/lighterpack';

const username = process.env.LIGHTERPACK_USERNAME;
const password = process.env.LIGHTERPACK_PASSWORD;

if (!username || !password) {
  throw new Error('Set LIGHTERPACK_USERNAME and LIGHTERPACK_PASSWORD first.');
}

const lp = new LighterPackClient({ username, password });

const detail = await lp.batch.createListWithItems({
  name: 'Weekend shelter system',
  description: 'A small example generated from TypeScript.',
  categories: [
    {
      name: 'Shelter',
      items: [
        { name: 'Two-person tent', weight: 1450, weightUnit: 'g', price: 249.99 },
        { name: 'Groundsheet', weight: 180, weightUnit: 'g', price: 24.5 },
      ],
    },
    {
      name: 'Sleep system',
      items: [
        { name: 'Sleeping bag', weight: 780, weightUnit: 'g', price: 189 },
        { name: 'Sleeping pad', weight: 420, weightUnit: 'g', price: 99 },
      ],
    },
  ],
});

console.log({
  listId: detail.listId,
  name: detail.name,
  weightGrams: detail.totals.weightGrams,
  price: detail.totals.price,
});
Enter fullscreen mode Exit fullscreen mode

Run it with npx tsx index.ts.

The expected result is an object containing the created list ID, its name, and computed totals. The four item weights sum to 2,830 grams. The price total should be 562.49 in the account's configured currency representation, subject to the service's own handling of optional fields.

This example uses grams explicitly. The SDK accepts g, oz, lb, or kg as input units and returns weightGrams at its output boundary. Making the unit explicit is useful when data comes from different sources and avoids silently mixing ounces and grams.

Verify the result with a fresh read

The client keeps a library cache for the lifetime of the instance. That makes several reads efficient, but a script that needs to confirm server state can call refresh() and then fetch the list again:

await lp.account.refresh();
const lists = await lp.lists.list();
const created = lists.find((list) => list.listId === detail.listId);

if (!created) {
  throw new Error(`List ${detail.listId} was not found after refresh.`);
}

console.log(`Verified ${created.name}: ${created.totals.weightGrams}g`);
Enter fullscreen mode Exit fullscreen mode

For a disposable test account, add a cleanup step after verification:

await lp.lists.delete(detail.listId);
console.log('Deleted the test list.');
Enter fullscreen mode Exit fullscreen mode

Do not add that deletion to a production workflow unless the list is intentionally disposable. The SDK documents deletion as irreversible and also protects the account from deleting its only list.

Why the batch API matters

LighterPack does not expose a granular CRUD API for each list mutation. The SDK documentation describes the library as one JSON document saved through POST /saveLibrary with an optimistic-concurrency sync_token.

That implementation detail explains the design of the client. Every mutation goes through one sync engine that loads the library, applies a change to a clone, and saves the result. If another writer changes the library first, the engine refetches and retries once. A second conflict becomes a typed SyncConflictError instead of silently overwriting newer data.

Calling items.add() repeatedly is still valid, but batch.addItems(), batch.createListWithItems(), and batch.updateItems() are better fits for generated data because they group related changes into one save operation. The repository's batch API implementation is the precise reference for the accepted fields and validation behavior.

Update many items safely

After importing a gear inventory, you may need to mark worn items or adjust quantities. Flags belong to an item's placement in a category, so the batch update accepts a categoryId when flags are involved:

const updated = await lp.batch.updateItems([
  {
    itemId: 123,
    categoryId: 456,
    flags: { worn: true, qty: 1 },
  },
  {
    itemId: 789,
    fields: { weight: 410, weightUnit: 'g' },
  },
]);

console.log(updated.map((item) => ({ name: item.name, weight: item.weightGrams })));
Enter fullscreen mode Exit fullscreen mode

The SDK enforces documented rules such as requiring weightUnit whenever a weight is changed. It also models worn and consumable as mutually exclusive flags. These checks happen before the save, so an invalid input should fail without a partial library update.

Authentication and security boundaries

There is no separate LighterPack API-key system in this SDK. Authentication uses the regular account username and password. The client keeps the resulting session cookie in memory for its lifetime and does not provide a persistent token to revoke individually. The project's README recommends changing the account password if access must be cut off.

Treat credentials as secrets. Use environment variables or a local secret manager, exclude .env files from Git, and never paste a real password into a tutorial or issue. For a self-hosted LighterPack deployment, the client accepts a baseUrl option, but you must verify that the instance is trusted and that its API behavior matches the client assumptions.

The SDK also supports image uploads and public share-link generation. Both can create external effects, so add explicit confirmation and cleanup rules before exposing them to an automated job.

Common failure modes

The command fails before any network request

Check the environment variables and input validation first. Bad units, malformed colors, invalid stars, and missing category IDs are represented by typed validation errors.

A save reports a synchronization conflict

Another browser tab or process may have changed the library. Retry from fresh state only after deciding which writer should win. The SDK already retries one stale-token conflict; a second conflict is surfaced as SyncConflictError.

The output is not what the web UI displays

The SDK treats weights as grams at its input and output boundaries, while LighterPack can display another unit in the UI. Compare weightGrams and the account's display settings instead of comparing formatted strings.

The example creates data in the wrong account

Stop and inspect the configured username before performing another write. For repeatable tests, use a dedicated account and delete only the lists created by that test.

FAQ

Is this an official LighterPack SDK?

No. It is an unofficial open-source client maintained in paladini/lighterpack-sdk. Verify that its assumptions still match your target service before relying on it.

Can I use JavaScript instead of TypeScript?

Yes. The package ships ESM JavaScript, and the TypeScript declarations are optional at runtime. TypeScript is useful here because list, category, item, unit, and error shapes become part of the editor feedback loop.

Does the tutorial run without credentials?

The repository's unit tests run without a live account. The end-to-end example requires credentials because it creates and modifies real remote data.

Takeaway

The useful boundary is not a clever wrapper around HTTP. It is a typed workflow that makes list structure, units, batch writes, conflict handling, and credential boundaries visible in code. Start with a disposable account, create one small list, refresh it, verify the totals, and only then automate larger imports.

Have you found a safer input format for generating backpacking lists - CSV, a typed JSON file, or a spreadsheet export? Share the validation and cleanup rules that made your workflow trustworthy.

AI assistance disclosure

AI assistance was used to help organize and edit this tutorial. The repository README, package metadata, implementation, tests, and published package metadata were checked against the claims and examples before publication.

Top comments (0)