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

@kaspernj/api-maker

v1.0.2092

Published

My new module

Readme

@kaspernj/api-maker

Client-side library for ApiMaker-powered Rails APIs. It provides model classes, collections, and UI helpers for web and React Native apps, backed by the JSON payloads generated by the ApiMaker server gem.

Installation

npm install @kaspernj/api-maker

Quick start

ApiMaker models are generated from model-recipes.json (usually emitted by the Rails gem). Import the models and set up the shared config before making requests.

import config from "@kaspernj/api-maker/build/config"
import Models from "@kaspernj/api-maker/build/models"
import history from "./history"
import routes from "./routes"

const {Account, Project, User} = Models

config.setHistory(history)
config.setLinkTo((path) => history.push(path))
config.setNavigation({
  navigate: (routeName, params) => history.push(routes[routeName](params))
})
config.setRoutes(routes)
config.setCurrenciesCollection([
  ["US Dollar", "usd"],
  ["Euro", "eur"]
])

const users = await User.ransack({name_cont: "sam"}).per(20).toArray()
const user = await User.find("a0f3842b-1e4c-4e9d-8f2d-cd021e5a9b6a")

Models and collections

Models are generated from the server recipes and inherit BaseModel. Collections let you compose queries fluently and then execute them with toArray().

const activeProjects = await Project
  .ransack({state_eq: "active"})
  .preload(["owner"])
  .sort("created_at desc")
  .toArray()

const firstProject = await Project.ransack({name_cont: "api"}).first()
const totalCount = await Project.ransack().count()

Relationship loading and preloading

Relationship accessors are generated from the recipe. For a relationship named account, you get:

  • user.account() to read a cached relationship.
  • user.loadAccount() to fetch it from the API.

Reading a relationship before it is loaded raises NotLoadedError unless the model is new.

Requesting preloads from the server

Use preload on a collection to have the server include related records in the response. ApiMaker stores those models in the relationship cache automatically.

const users = await User
  .ransack()
  .preload(["account", "roles"])
  .toArray()

users[0].account()

Manually preloading with preloadRelationship

When you already have related model instances (for example after creating records or from another response), you can mark a relationship as loaded without another request.

const user = await User.find("a0f3842b-1e4c-4e9d-8f2d-cd021e5a9b6a")
const account = await Account.find("f6c0b2b3-0a37-4e6e-ae3a-2e6a38e3953d")

user.preloadRelationship("account", account)
user.account()

preloadRelationship accepts:

  • A single model (for belongs_to / has_one relationships).
  • An array of models (for has_many relationships).
  • null when the relationship is known to be empty.

The relationship name is stored in snake_case, so preloadRelationship("Account", account) and preloadRelationship("account", account) are equivalent. After preloading, both account() and loadAccount() return the cached value without hitting the network.

Configuration notes

config is a shared singleton. Some helpers (forms, routing, navigation, and money inputs) require config values like history, linkTo, navigation, and currenciesCollection to be set. If a required value is missing, the getter throws an error to avoid silent failures.

API reference

ApiMaker generates model classes from your recipes. The classes inherit BaseModel and expose relationship helpers based on your server-side associations.

Relationship helpers (generated)

For a relationship named account, the model class gets:

  • user.account() to read the cached relationship.
  • user.loadAccount() to fetch it from the API.

For has_many, the getter returns a Collection for that relationship:

const user = await User.find("a0f3842b-1e4c-4e9d-8f2d-cd021e5a9b6a")

const tasksCollection = user.tasks()
const tasks = await tasksCollection.toArray()

BaseModel static methods

  • attributes(): Returns attribute metadata for the model. Example: User.attributes()
  • hasAttribute(attributeName): Checks if the attribute exists on the model. Example: User.hasAttribute("email")
  • modelClassData(): Returns recipe data for the model class. Example: User.modelClassData()
  • newCustomEvent(validationErrors): Creates a validation-errors event. Example: BaseModel.newCustomEvent(errors)
  • sendValidationErrorsEvent(validationErrors, options): Dispatches the validation error event to a form. Example: BaseModel.sendValidationErrorsEvent(errors, {form})
  • find(id): Loads a record by primary key. Example: User.find("a0f3842b-1e4c-4e9d-8f2d-cd021e5a9b6a")
  • findOrCreateBy(findOrCreateByArgs, args): Find or create a record server-side. Example: User.findOrCreateBy({email: "[email protected]"}, {additionalData: {invite: true}})
  • modelName(): Returns a ModelName helper for inflection. Example: User.modelName().human()
  • primaryKey(): Returns the primary key column name. Example: User.primaryKey()
  • ransack(query): Starts a collection query. Example: User.ransack({email_cont: "@example.com"})
  • select(select): Adds a select clause to a new query. Example: User.select({users: ["id", "email"]})
  • ransackableAssociations(): Returns associations that are searchable. Example: User.ransackableAssociations()
  • ransackableAttributes(): Returns attributes that are searchable. Example: User.ransackableAttributes()
  • ransackableScopes(): Returns scopes that are searchable. Example: User.ransackableScopes()
  • reflections(): Returns relationship metadata. Example: User.reflections()
  • reflection(name): Looks up a relationship by name. Example: User.reflection("account")
  • _token(): Reads the CSRF token from the page. Example: BaseModel._token()
  • all(): Alias for ransack(). Example: User.all().toArray()
  • parseValidationErrors({error, model, options}): Converts server validation errors to ValidationErrors. Example: BaseModel.parseValidationErrors({error, model: user, options: {form}})
  • humanAttributeName(attributeName): Returns a translated attribute name. Example: User.humanAttributeName("email")
  • snakeCase(string): Underscores a string. Example: BaseModel.snakeCase("AccountOwner")
  • _objectDataFromGivenRawData(rawData, options): Normalizes form data or objects. Example: BaseModel._objectDataFromGivenRawData(form, {})
  • _callCollectionCommand(args, commandArgs): Executes a command and parses validation errors. Example: BaseModel._callCollectionCommand({args: {save: payload}}, {})
  • _postDataFromArgs(args): Serializes args for a command request. Example: BaseModel._postDataFromArgs({save: payload})

BaseModel instance methods

  • constructor(args): Creates a model instance. Example: const user = new User({id: "123"})
  • modelClass(): Returns the model class. Example: user.modelClass().modelName().name
  • modelClassData(): Returns the recipe data for the model. Example: user.modelClassData().name
  • primaryKey(): Reads the primary key value. Example: user.primaryKey()
  • identifierKey(): Returns a stable key for UI lists. Example: user.identifierKey()
  • uniqueKey(): Generates a random unique key. Example: user.uniqueKey()
  • cacheKey(): Returns cache key based on id and updated_at. Example: user.cacheKey()
  • localCacheKey(): Returns a local cache key. Example: user.localCacheKey()
  • fullCacheKey(): Returns a full cache key. Example: user.fullCacheKey()
  • clone(): Returns a shallow clone of the model. Example: const clone = user.clone()
  • attributes(): Returns merged attributes and changes. Example: user.attributes()
  • getAttributes(): Returns attributes merged with changes. Example: user.getAttributes()
  • assignAttributes(newAttributes): Assigns changes and tracks diffs. Example: user.assignAttributes({email: "[email protected]"})
  • readAttribute(attributeName): Reads a camelCase or snake_case attribute. Example: user.readAttribute("email")
  • readAttributeUnderscore(attributeName): Reads a snake_case attribute. Example: user.readAttributeUnderscore("email")
  • isAttributeLoaded(attributeName): Checks if an attribute is loaded. Example: user.isAttributeLoaded("email")
  • isAttributeChanged(attributeName): Checks if an attribute changed. Example: user.isAttributeChanged("email")
  • savedChangeToAttribute(attributeName): Checks if an attribute changed in the last save. Example: user.savedChangeToAttribute("email")
  • isChanged(): Returns true if any attributes changed. Example: user.isChanged()
  • isNewRecord(): Returns true if the model is not persisted. Example: user.isNewRecord()
  • isPersisted(): Returns true if the model has been saved. Example: user.isPersisted()
  • can(abilityName): Checks a loaded ability. Example: user.can("update")
  • ensureAbilities(listOfAbilities): Loads missing abilities from the server. Example: await user.ensureAbilities(["update"])
  • isAssociationLoaded(associationName): Checks if a relationship is cached. Example: user.isAssociationLoaded("account")
  • isAssociationLoadedUnderscore(associationName): Checks a relationship cache using snake_case. Example: user.isAssociationLoadedUnderscore("account")
  • isAssociationPresent(associationName): Checks if the relationship is present in memory. Example: user.isAssociationPresent("account")
  • create(attributes, options): Creates the record on the server. Example: await user.create({email: "[email protected]"})
  • createRaw(rawData, options): Creates the record with raw form data. Example: await user.createRaw(formElement)
  • update(newAttributes, options): Updates only changed attributes. Example: await user.update({email: "[email protected]"})
  • updateRaw(rawData, options): Updates with raw form data. Example: await user.updateRaw(formElement)
  • save(): Creates or updates based on isNewRecord(). Example: await user.save()
  • saveRaw(rawData, options): Creates or updates using raw data. Example: await user.saveRaw(formElement)
  • destroy(): Deletes the record. Example: await user.destroy()
  • reload(): Reloads the record with the same query params. Example: await user.reload()
  • isValid(): Not implemented and throws. Example: user.isValid()
  • isValidOnServer(): Runs server-side validation. Example: await user.isValidOnServer()
  • handleResponseError(response): Raises a structured error from a response. Example: user.handleResponseError(response)
  • preloadRelationship(relationshipName, model): Marks a relationship as loaded. Example: user.preloadRelationship("account", account)
  • markForDestruction(): Marks a record for later deletion. Example: user.markForDestruction()
  • markedForDestruction(): Returns destruction flag. Example: user.markedForDestruction()
  • _callMemberCommand(args, commandArgs): Executes a member command. Example: user._callMemberCommand({args: {}}, {})
  • _isPresent(value): Presence helper used internally. Example: user._isPresent("hi")
  • _isDateChanged(oldValue, newValue): Date change helper. Example: user._isDateChanged("2024-01-01", "2024-02-01")
  • _isIntegerChanged(oldValue, newValue): Integer change helper. Example: user._isIntegerChanged(1, 2)
  • _isStringChanged(oldValue, newValue): String change helper. Example: user._isStringChanged("a", "b")
  • setNewModel(model): Copies data + relationships from another instance. Example: user.setNewModel(newUser)
  • setNewModelData(model): Copies only attributes from another instance. Example: user.setNewModelData(newUser)
  • _refreshModelFromResponse(response): Updates model from a command response. Example: user._refreshModelFromResponse(response)
  • _refreshModelDataFromResponse(response): Updates attributes from a command response. Example: user._refreshModelDataFromResponse(response)
  • _readModelDataFromArgs(args): Populates model from API payload. Example: user._readModelDataFromArgs({data: payload})
  • _readPreloadedRelationships(preloaded): Populates relationship cache from preloads. Example: user._readPreloadedRelationships(preloaded)
  • _loadBelongsToReflection(args, queryArgs): Loads a belongs_to relationship. Example: user._loadBelongsToReflection({reflectionName: "account", model: user, modelClass: Account})
  • _readBelongsToReflection({reflectionName}): Reads a belongs_to relationship. Example: user._readBelongsToReflection({reflectionName: "account"})
  • _loadHasManyReflection(args, queryArgs): Loads a has_many relationship. Example: user._loadHasManyReflection({reflectionName: "tasks", model: user, modelClass: Task})
  • _loadHasOneReflection(args, queryArgs): Loads a has_one relationship. Example: user._loadHasOneReflection({reflectionName: "profile", model: user, modelClass: Profile})
  • _readHasOneReflection({reflectionName}): Reads a has_one relationship. Example: user._readHasOneReflection({reflectionName: "profile"})

Collection methods

  • constructor(args, queryArgs): Creates a collection query. Example: new Collection({modelClass: User})
  • modelClass(): Returns the model class for the collection. Example: collection.modelClass()
  • clone(): Returns a copy of the collection. Example: const next = collection.clone()
  • abilities(abilities): Adds ability filters to the query. Example: User.ransack().abilities({User: ["update"]})
  • accessibleBy(abilityName): Restricts by ability. Example: User.ransack().accessibleBy("read")
  • ransack(params): Adds ransack params. Example: User.ransack().ransack({email_cont: "@example.com"})
  • search(params): Adds search params. Example: User.ransack().search({q: "sam"})
  • searchKey(searchKey): Sets the search key. Example: User.ransack().searchKey("query")
  • select(select): Selects attributes per model. Example: User.ransack().select({users: ["id", "email"]})
  • selectColumns(selectColumns): Selects columns per table. Example: User.ransack().selectColumns({users: ["id", "email"]})
  • preload(preloadValue): Preloads relationships. Example: User.ransack().preload(["account"])
  • groupBy(...columns): Adds group-by columns. Example: User.ransack().groupBy("users.id")
  • sort(sortBy): Adds sort to ransack params. Example: User.ransack().sort("created_at desc")
  • distinct(): Enables distinct results. Example: User.ransack().distinct()
  • limit(amount): Limits the result count. Example: User.ransack().limit(10)
  • page(page): Sets the page number. Example: User.ransack().page(2)
  • per(per): Sets the page size. Example: User.ransack().per(50)
  • pageKey(pageKey): Sets a custom page param key. Example: User.ransack().pageKey("p")
  • perKey(perKey): Sets a custom per param key. Example: User.ransack().perKey("per_page")
  • except(...keys): Removes query keys. Example: User.ransack().page(2).except("page")
  • params(): Returns request params. Example: User.ransack().limit(5).params()
  • isFiltered(): Returns true when filters are set. Example: User.ransack().limit(5).isFiltered()
  • count(): Returns count for the query. Example: await User.ransack().count()
  • first(): Returns the first record. Example: await User.ransack().first()
  • toArray(): Executes and returns models. Example: await User.ransack().toArray()
  • result(): Executes and returns a Result wrapper. Example: const result = await User.ransack().result()
  • each(callback): Iterates over the loaded array. Example: await User.ransack().each((user) => console.log(user.id()))
  • ensureLoaded(): Loads and sets relationship models. Example: await user.tasks().ensureLoaded()
  • isLoaded(): Checks if a relationship is loaded. Example: user.tasks().isLoaded()
  • preloaded(): Returns cached models for a relationship. Example: user.tasks().preloaded()
  • loaded(): Returns the loaded relationship value. Example: user.tasks().loaded()
  • loadedArray(): Returns loaded relationship array. Example: user.tasks().loadedArray()
  • set(newCollection): Replaces the relationship collection. Example: user.tasks().set([task])
  • push(newModel): Pushes a model onto the relationship collection. Example: user.tasks().push(task)
  • find(callback): Finds within the loaded array. Example: user.tasks().find((task) => task.id() == "1")
  • forEach(callback): Iterates the loaded array. Example: user.tasks().forEach((task) => console.log(task.id()))
  • map(callback): Maps the loaded array. Example: user.tasks().map((task) => task.title())
  • _addQueryToModels(models): Assigns the collection to returned models. Example: collection._addQueryToModels(models)
  • _merge(newQueryArgs): Merges query args. Example: collection._merge({limit: 5})
  • _response(): Executes the request and returns the raw response. Example: await collection._response()

Related docs

The server-side setup, resource definitions, and serializers live in the main ApiMaker repository README.