My new module
npm install @kaspernj/api-makerClient-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.
``bash`
npm install @kaspernj/api-maker
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.
`js
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(routesrouteName)
})
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 are generated from the server recipes and inherit BaseModel. Collections let you compose queries fluently and then execute them with toArray().
`js
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 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.
Use preload on a collection to have the server include related records in the response. ApiMaker stores those models in the relationship cache automatically.
`js
const users = await User
.ransack()
.preload(["account", "roles"])
.toArray()
users[0].account()
`
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.
`js
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).has_many
- An array of models (for 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.
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.
ApiMaker generates model classes from your recipes. The classes inherit BaseModel and expose relationship helpers based on your server-side associations.
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:
`js
const user = await User.find("a0f3842b-1e4c-4e9d-8f2d-cd021e5a9b6a")
const tasksCollection = user.tasks()
const tasks = await tasksCollection.toArray()
`
- 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: "sam@example.com"}, {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})
- constructor(args): Creates a model instance. Example: const user = new User({id: "123"})modelClass()
- : Returns the model class. Example: user.modelClass().modelName().namemodelClassData()
- : Returns the recipe data for the model. Example: user.modelClassData().nameprimaryKey()
- : 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: "sam@example.com"})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: "sam@example.com"})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: "new@example.com"})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"})
- 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()`
The server-side setup, resource definitions, and serializers live in the main ApiMaker repository README.