@kaspernj/api-maker
v1.0.2092
Published
My new module
Maintainers
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-makerQuick 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_onerelationships). - An array of models (for
has_manyrelationships). nullwhen 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 aModelNamehelper 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 aselectclause 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 forransack(). Example:User.all().toArray()parseValidationErrors({error, model, options}): Converts server validation errors toValidationErrors. 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().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: "[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 onisNewRecord(). 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 aResultwrapper. 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.
