npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

coda-mapper

v1.2.3

Published

The bridge between your code and the Coda API.

Readme

CodaMapper

Welcome to CodaMapper, the bridge between your code and the Coda API. This library provides an elegant and type-safe way to interact with your Coda docs, mapping rows into objects and vice versa.

Overview

Thanks to some clever proxy magic and caching, once you fetch something, it stays in memory and automatically connects to related rows. No redundant requests, no headaches.

Persistent Instance References

When you pull a row from Coda, it doesn’t just sit there as a disconnected object. The CodaMapper keeps track of every instance and ensures that:

  • If a row is fetched again, you get the same instance (reference), not a duplicate.
  • Relationships between tables are automatically linked.
  • If you've already fetched related tables, those relationships are instantly accessible — no need to refetch anything.

Automatic Relationship Linking

Let's say you fetch both Contacts and Companies tables. Any Contact that references a Company will already have that relationship set up.

const contact = await mapper.get(Contact, 'row-123');
const companies = await mapper.all(Company);
const myCompany = await contact.company;
console.log(myCompany.name);
// Instantly available, no extra API call.

Because the same instances are referenced everywhere, you never deal with stale or duplicated data. Everything stays in sync.

Lazy Loading with CodaReference<T>

Of course, you won’t always fetch related tables up front. That’s where the CodaReference<T> type comes in. It returns a Relation | Promise type, just in case a relation isn't yet fetched. That means:

  • If the related data is already fetched, it acts like a normal object.
  • If it hasn’t been fetched yet, accessing it will trigger a fetch on demand.
const contacts = await mapper.all(Contact);

const company = await contacts[0].company; // Company | Promise<Company>
// Lazy Loading
console.log(company); // Company

This way, you don’t make unnecessary API calls, but you also don’t have to worry about whether something has been fetched or not. It just works.

Be Careful With Circular References

Since relationships are automatically mapped, you can create circular references. For example, a Company has employees, and each Employee has a reference back to Company. If you're not careful, you can end up in an infinite loop.

const company = await mapper.get(Company, 'row-123');
console.log(company.employees[0].company.employees[0].company...);
// This can go on forever if you're not careful.

If you're dealing with recursive structures, make sure to break the chain when needed.


With this approach, you get seamlessly linked, cached, and lazy-loaded data, without ever worrying about manually stitching things together or making extra API calls. Just fetch what you need, and let the mapper handle the rest.

That's amazing! But how do I use it?

Come take a look at what's under the hood.

CodaTable

The CodaTable class is an abstract base class that represents a row in a Coda table. Think of it as your model, handling state management, change detection, and lazy fetching of related data with a hint of wizardry. Key methods include:

  • pull(): Refreshes the row from Coda.
  • push(): Updates the row in Coda.
  • pushAndWait(): Updates the row in Coda and waits for the mutation to complete.
  • delete(): Deletes the row from Coda.
  • deleteAndWait(): Deletes the row from Coda and waits for the mutation to complete.
  • isDirty(): Returns true if any of the rows have been changed.
  • getValues(): Retrieves a copy of the current values of the row.
  • getDirtyValues(): Retrieves a copy of the values that have been modified.

CodaMapper

The CodaMapper class is your API communication hub. It handles the nitty-gritty of HTTP requests to Coda's API and maps the responses back into CodaTable objects. Methods include:

  • get(table, id): Retrieves a specific row by ID.
  • find(table, column, value): Searches for rows based on a column value.
  • all(table): Fetches all rows from a table.
  • insert(rows) & insertAndWait(rows): Inserts new rows into Coda.
  • upsert(rows, upsertBy): Upserts rows based on specified key columns.
  • update(row) & updateAndWait(row): Updates an existing row.
  • updateBatch(rows, updateBy) & updateBatchAndWait(rows, updateBy): Updates multiple rows at once.
  • delete(rows) & deleteAndWait(rows): Deletes one or more rows.

Installation

Simply add the library to your project:

npm install coda-mapper

and run off into the sunset!

Usage

Creating a Custom CodaTable

Extend CodaTable to create a class representing your data structure. For example:

import { CodaTable } from './CodaTable';

/**
 * Represents a row in your "Contacts" table in Coda.
 */
@TableId('table-id')
export class Contact extends CodaTable {
  id: string;
  ⁣@ColumnId('column-id') name: string;
  ⁣@ColumnId('column-id') @Multiple emails: string[];
  ⁣@ColumnId('column-id') ⁣@References(() => Note) note: CodaRelation<Note>;
  @ColumnId('column-id') ⁣@References(() => Company) ⁣@Multiple companies: CodaRelation<Company[]>;
}

// Usage example:
const myContact = new Contact();
console.log(myContact.id); // Initially empty

Using CodaMapper

Initialize the mapper with your Coda document ID and API key:

import { CodaMapper } from './CodaMapper';
import { Contact } from './Contact';

const docId = 'your-doc-id';
const apiKey = 'your-api-key';
const mapper = new CodaMapper(docId, apiKey);

Inserting Rows

const newContact = new Contact();
newContact.name = 'John Doe';
newContact.emails = ['[email protected]'];

await mapper.insert(newContact);
console.log('Contact inserted with ID:', newContact.id);

// Or wait for mutation to complete:
await mapper.insertAndWait(newContact);
console.log('Contact insertion confirmed!');
console.log('Contact inserted with ID:', newContact.id);

Fetching a Row

const fetchedContact = await mapper.get(Contact, 'row-123');

if (fetchedContact) {
    console.log('Fetched contact:', fetchedContact.getValues());
} else {
    console.log('Contact not found.');
}

Updating a Row

fetchedContact.name = 'Jane Doe'; // Modify the property

// Check if any rows have changed
if (fetchedContact.isDirty()) {
    await fetchedContact.updateAndWait();
    console.log('Contact updated successfully!');
}

Deleting a Row

await mapper.deleteAndWait(fetchedContact);
console.log('Contact deleted from Coda.');

Finding Rows

Search for contacts with a specific email:

const results = await mapper.find(Contact, 'email', '[email protected]');
console.log(
    'Found contacts:',
    results.map((contact) => contact.getValues())
);

Fetching All Rows

const allContacts = await mapper.all(Contact);
console.log(
    'All contacts:',
    allContacts.map((contact) => contact.getValues())
);

Batch Updates

Update multiple rows at once based on a key property:

const contactsToUpdate = [
    /* array of Contact instances with valid IDs */
];
await mapper.updateBatchAndWait(contactsToUpdate);
console.log('Batch update complete!');

Contributing

Contributions, bug fixes, and feature requests are more than welcome. Feel free to open an issue or submit a pull request. We’re all about making interactions with Coda as smooth as possible.

License

This project is licensed under the MIT License.


Happy coding! And may your rows always be in sync with Coda.