Documentation
¶
Overview ¶
Package core is the reusable Mealie client and workflow layer that sits beneath the CLI. It exposes a small, stable, hand-crafted API surface (clean public DTOs in this package) over the Mealie HTTP API, deliberately hiding upstream quirks such as the camelCase-query / snake_case-pagination mismatch and the households-vs-top-level path split.
The CLI commands and any future MCP server are intended to call this package rather than the HTTP API directly, so there is a single implementation of each workflow. The bundled OpenAPI spec under api/specs is the source of truth for the contract tests that guard the DTO field names against upstream drift.
Index ¶
- Constants
- Variables
- func FetchAll[T any](ctx context.Context, pageSize int, ...) ([]T, error)
- func IsConflict(err error) bool
- func IsForbidden(err error) bool
- func IsNotFound(err error) bool
- func IsTransport(err error) bool
- func IsUnauthorized(err error) bool
- func IsValidation(err error) bool
- type APIError
- type About
- type AddRecipeToList
- type Client
- func (c *Client) About(ctx context.Context) (*About, error)
- func (c *Client) AddRecipesToShoppingList(ctx context.Context, listID string, recipes []AddRecipeToList) (*ShoppingList, error)
- func (c *Client) AddShoppingItem(ctx context.Context, in CreateShoppingItem) (*ShoppingListItem, error)
- func (c *Client) BaseURL() string
- func (c *Client) CreateMealPlan(ctx context.Context, in CreateMealPlan) (*MealPlan, error)
- func (c *Client) CreateRecipe(ctx context.Context, name string) (string, error)
- func (c *Client) CreateRecipeFromURL(ctx context.Context, recipeURL string, includeTags bool) (string, error)
- func (c *Client) CreateShoppingList(ctx context.Context, name string) (*ShoppingList, error)
- func (c *Client) DeleteMealPlan(ctx context.Context, id int) error
- func (c *Client) DeleteRecipe(ctx context.Context, slug string) error
- func (c *Client) DeleteShoppingItem(ctx context.Context, id string) error
- func (c *Client) DeleteShoppingList(ctx context.Context, id string) error
- func (c *Client) ExportRecipe(ctx context.Context, slug string) (json.RawMessage, error)
- func (c *Client) GetRecipe(ctx context.Context, slug string) (*Recipe, error)
- func (c *Client) GetShoppingItem(ctx context.Context, id string) (*ShoppingListItem, error)
- func (c *Client) GetShoppingList(ctx context.Context, id string) (*ShoppingList, error)
- func (c *Client) ListMealPlans(ctx context.Context, opts ListOptions, start, end string) (*Page[MealPlan], error)
- func (c *Client) ListRecipes(ctx context.Context, opts RecipeListOptions) (*Page[RecipeSummary], error)
- func (c *Client) ListShoppingLists(ctx context.Context, opts ListOptions) (*Page[ShoppingList], error)
- func (c *Client) Login(ctx context.Context, username, password, tokenName string) (string, error)
- func (c *Client) SetItemChecked(ctx context.Context, id string, checked bool) (*ShoppingListItem, error)
- func (c *Client) TodayMealPlans(ctx context.Context) ([]MealPlan, error)
- func (c *Client) Whoami(ctx context.Context) (*User, error)
- type CreateMealPlan
- type CreateShoppingItem
- type FieldError
- type ListOptions
- type MealPlan
- type NamedRef
- type Option
- type OrganizerRef
- type Page
- type Recipe
- type RecipeIngredient
- type RecipeListOptions
- type RecipeNote
- type RecipeStep
- type RecipeSummary
- type ShoppingList
- type ShoppingListItem
- type TransportError
- type User
Constants ¶
const ( EntryBreakfast = "breakfast" EntryLunch = "lunch" EntryDinner = "dinner" EntrySide = "side" EntrySnack = "snack" EntryDrink = "drink" EntryDessert = "dessert" )
Meal plan entry types accepted by the API (the PlanEntryType enum).
Variables ¶
var EntryTypes = []string{EntryBreakfast, EntryLunch, EntryDinner, EntrySide, EntrySnack, EntryDrink, EntryDessert}
EntryTypes lists the valid meal plan entry types, in display order.
Functions ¶
func FetchAll ¶ added in v0.3.0
func FetchAll[T any](ctx context.Context, pageSize int, fetch func(page, perPage int) (*Page[T], error)) ([]T, error)
FetchAll walks a paginated endpoint to completion and returns every item. It is the bounded, client-side replacement for the perPage=-1 "no pagination" sentinel: instead of asking the server for everything in a single response (which a large instance can fail to deliver under the response-size cap), it fetches steady pages until the result set is exhausted.
pageSize is the per-request batch size: 0 falls back to defaultPageSize, and a value < 1 (such as the old -1 sentinel) is rejected with an error rather than being passed back to the server. fetch is invoked once per 1-based page with the resolved batch size and must return that page; any error it returns aborts the whole walk and is propagated unchanged.
func IsTransport ¶
IsTransport reports whether err is a transport-level failure.
func IsUnauthorized ¶
IsUnauthorized reports a 401 response (missing/invalid token).
Types ¶
type APIError ¶
type APIError struct {
StatusCode int
// Code is a stable machine token derived from the status (e.g. "not_found").
Code string
// Message is a single-line human description, extracted from the API body.
Message string
// RequestID echoes the server correlation id when present.
RequestID string
// Retryable indicates a transient failure (429/502/503/504).
Retryable bool
// RetryAfter is the server's suggested wait before retrying, parsed from the
// Retry-After header (zero when absent).
RetryAfter time.Duration
// Fields carries per-field validation errors for 4xx validation responses.
Fields []FieldError
}
APIError is a non-2xx response from the Mealie API, normalised into a stable shape the CLI can map onto exit codes and the JSON error envelope.
func AsAPIError ¶
AsAPIError extracts an *APIError from err, if present.
type About ¶
type About struct {
Version string `json:"version"`
Production bool `json:"production"`
Demo bool `json:"demoStatus"`
AllowSignup bool `json:"allowSignup"`
DefaultGroupSlug string `json:"defaultGroupSlug,omitempty"`
EnableOIDC bool `json:"enableOidc"`
}
About is the public server information from GET /api/app/about. It needs no authentication and is used by `mealie doctor` and `mealie version` to verify connectivity and the server version.
type AddRecipeToList ¶ added in v0.3.0
type AddRecipeToList struct {
RecipeID string `json:"recipeId"`
Scale float64 `json:"recipeIncrementQuantity,omitempty"` // 0/unset → server default (1)
}
AddRecipeToList is one entry in the bulk "add recipe to a shopping list" request body. Scale carries omitempty so an unset (zero) value is dropped from the request, letting the server apply its documented default of 1 rather than us forcing a scale of 0. pkg/core is a public surface, so the tag — not just the CLI — has to honour that default.
type Client ¶
type Client struct {
// contains filtered or unexported fields
}
Client is a Mealie API client. It is safe for concurrent use once constructed; all configuration happens via options in New.
func New ¶
New constructs a Client for the given server base URL (e.g. https://mealie.example.com, without the /api suffix) and API token. The token may be empty for unauthenticated calls such as Login or the public About.
func (*Client) AddRecipesToShoppingList ¶ added in v0.3.0
func (c *Client) AddRecipesToShoppingList(ctx context.Context, listID string, recipes []AddRecipeToList) (*ShoppingList, error)
AddRecipesToShoppingList pushes one or more recipes' ingredients onto a shopping list via the bulk endpoint (the non-deprecated .../recipe path; the .../recipe/{recipe_id} variant is deprecated). The request body is a JSON array of AddRecipeToList.
The 200 response is a ShoppingListOut, decoded into the curated ShoppingList. That is deliberately display-lossy — it drops recipeReferences, groupId, userId and the like that the curated struct does not model — because the confirmation render only needs the list name and its listItems, matching the trade-off the rest of the SDK makes.
The POST is intentionally not retried (c.do skips POST): re-running would duplicate the added ingredients, so a single attempt is correct.
func (*Client) AddShoppingItem ¶
func (c *Client) AddShoppingItem(ctx context.Context, in CreateShoppingItem) (*ShoppingListItem, error)
AddShoppingItem adds an item to a list. The create endpoint returns a ShoppingListItemsCollectionOut; the newly created item is returned.
func (*Client) CreateMealPlan ¶
CreateMealPlan adds a meal plan entry.
func (*Client) CreateRecipe ¶
CreateRecipe creates an empty recipe with the given name and returns its slug.
func (*Client) CreateRecipeFromURL ¶
func (c *Client) CreateRecipeFromURL(ctx context.Context, recipeURL string, includeTags bool) (string, error)
CreateRecipeFromURL scrapes the given URL into a new recipe and returns its slug.
func (*Client) CreateShoppingList ¶
CreateShoppingList creates a new shopping list with the given name.
func (*Client) DeleteMealPlan ¶
DeleteMealPlan removes a meal plan entry by id.
func (*Client) DeleteRecipe ¶
DeleteRecipe deletes a recipe by slug.
func (*Client) DeleteShoppingItem ¶
DeleteShoppingItem removes an item by id.
func (*Client) DeleteShoppingList ¶
DeleteShoppingList deletes a shopping list by id.
func (*Client) ExportRecipe ¶ added in v0.3.0
ExportRecipe fetches a single recipe by slug and returns the response body verbatim. Unlike GetRecipe, which decodes into the curated Recipe struct (and silently drops fields it does not model), this preserves every field the server sends — it is the lossless source for the `recipe export` backup flow.
func (*Client) GetShoppingItem ¶
GetShoppingItem fetches a single shopping list item by id.
func (*Client) GetShoppingList ¶
GetShoppingList fetches a single shopping list, including its items.
func (*Client) ListMealPlans ¶
func (c *Client) ListMealPlans(ctx context.Context, opts ListOptions, start, end string) (*Page[MealPlan], error)
ListMealPlans returns a page of meal plan entries, optionally restricted to a date range (inclusive, YYYY-MM-DD). Empty start/end disable that bound.
func (*Client) ListRecipes ¶
func (c *Client) ListRecipes(ctx context.Context, opts RecipeListOptions) (*Page[RecipeSummary], error)
ListRecipes returns a page of recipe summaries.
func (*Client) ListShoppingLists ¶
func (c *Client) ListShoppingLists(ctx context.Context, opts ListOptions) (*Page[ShoppingList], error)
ListShoppingLists returns a page of shopping lists.
func (*Client) Login ¶
Login authenticates with a username and password against POST /api/auth/token and, when tokenName is non-empty, mints a long-lived API token named tokenName via POST /api/users/api-tokens. The returned string is the long-lived token (or the short-lived access token if tokenName is empty). The client's own token is not used or modified.
func (*Client) SetItemChecked ¶
func (c *Client) SetItemChecked(ctx context.Context, id string, checked bool) (*ShoppingListItem, error)
SetItemChecked toggles the checked state of an item. It round-trips the full raw item (as a map) so fields outside the curated struct — labelId, foodId, unitId, extras, recipe references, position — are preserved on update, then decodes the ShoppingListItemsCollectionOut update envelope.
func (*Client) TodayMealPlans ¶
TodayMealPlans returns the meal plan entries scheduled for the current day.
type CreateMealPlan ¶
type CreateMealPlan struct {
Date string `json:"date"`
EntryType string `json:"entryType"`
Title string `json:"title,omitempty"`
Text string `json:"text,omitempty"`
RecipeID string `json:"recipeId,omitempty"`
}
CreateMealPlan is the payload for adding a meal plan entry. Provide either a RecipeID (to plan an existing recipe) or a Title/Text (for a free-text entry).
type CreateShoppingItem ¶
type CreateShoppingItem struct {
ShoppingListID string `json:"shoppingListId"`
Note string `json:"note,omitempty"`
Quantity *float64 `json:"quantity,omitempty"`
}
CreateShoppingItem is the payload for adding a free-text item to a list. Only ShoppingListID is required by Mealie; a note-only item is created by sending just the note (and an optional quantity).
type FieldError ¶
FieldError is a single validation problem reported by the API.
type ListOptions ¶
type ListOptions struct {
Page int
PerPage int
OrderBy string
OrderDirection string
QueryFilter string
Search string
}
ListOptions are the pagination/ordering/search parameters common to all list endpoints. Zero-valued fields are omitted from the request.
func All ¶
func All() ListOptions
All returns ListOptions that request every result in a single page (Mealie interprets perPage=-1 as "no pagination").
type MealPlan ¶
type MealPlan struct {
ID int `json:"id"`
Date string `json:"date"`
EntryType string `json:"entryType"`
Title string `json:"title,omitempty"`
Text string `json:"text,omitempty"`
RecipeID *string `json:"recipeId,omitempty"`
Recipe *RecipeSummary `json:"recipe,omitempty"`
}
MealPlan is a single meal plan entry from /api/households/mealplans. Entry ids are integers in Mealie (unlike most resources which use UUID strings).
type Option ¶
type Option func(*Client)
Option configures a Client.
func WithHTTPClient ¶
WithHTTPClient supplies a custom *http.Client (e.g. for proxies or test transports). When provided, the client is used as-is and is never mutated; WithTimeout is then ignored (set the timeout on your own client instead).
func WithMaxRetries ¶
WithMaxRetries sets how many times transient failures are retried for idempotent methods (GET/HEAD/PUT/DELETE). POST is never auto-retried.
func WithTimeout ¶
WithTimeout sets the per-request timeout applied to the client New builds. It has no effect when a custom client is supplied via WithHTTPClient.
func WithUserAgent ¶
WithUserAgent sets the User-Agent header, e.g. "mealie-cli/1.2.0".
type OrganizerRef ¶
type OrganizerRef struct {
ID string `json:"id,omitempty"`
Name string `json:"name"`
Slug string `json:"slug,omitempty"`
}
OrganizerRef is a category, tag or tool reference attached to a recipe.
type Page ¶
type Page[T any] struct { Page int `json:"page"` PerPage int `json:"per_page"` Total int `json:"total"` TotalPages int `json:"total_pages"` Items []T `json:"items"` Next *string `json:"next"` Previous *string `json:"previous"` }
Page is the normalised pagination envelope. Upstream Mealie uses snake_case keys here (per_page/total_pages) even though list query parameters are camelCase; this type pins the response contract regardless.
type Recipe ¶
type Recipe struct {
RecipeSummary
RecipeIngredient []RecipeIngredient `json:"recipeIngredient,omitempty"`
RecipeInstructions []RecipeStep `json:"recipeInstructions,omitempty"`
Tools []OrganizerRef `json:"tools,omitempty"`
Notes []RecipeNote `json:"notes,omitempty"`
OrgURL string `json:"orgURL,omitempty"`
}
Recipe is the full recipe detail. It embeds RecipeSummary so summary fields appear inline in JSON output.
type RecipeIngredient ¶
type RecipeIngredient struct {
Quantity *float64 `json:"quantity,omitempty"`
Unit *NamedRef `json:"unit,omitempty"`
Food *NamedRef `json:"food,omitempty"`
Note string `json:"note,omitempty"`
Display string `json:"display,omitempty"`
Title string `json:"title,omitempty"`
OriginalText string `json:"originalText,omitempty"`
}
RecipeIngredient is one ingredient line. Display/OriginalText carry the human-readable rendering; Food/Unit are the structured references when parsed.
type RecipeListOptions ¶
type RecipeListOptions struct {
ListOptions
Categories []string
Tags []string
Tools []string
Cookbook string
}
RecipeListOptions extends ListOptions with recipe-specific filters that map to the convenience query parameters on GET /api/recipes.
type RecipeNote ¶
type RecipeNote struct {
Title string `json:"title,omitempty"`
Text string `json:"text,omitempty"`
}
RecipeNote is a free-text note attached to a recipe.
type RecipeStep ¶
type RecipeStep struct {
Title string `json:"title,omitempty"`
Text string `json:"text,omitempty"`
}
RecipeStep is one instruction step.
type RecipeSummary ¶
type RecipeSummary struct {
ID string `json:"id,omitempty"`
Slug string `json:"slug"`
Name string `json:"name"`
Description string `json:"description,omitempty"`
Image string `json:"image,omitempty"`
RecipeYield string `json:"recipeYield,omitempty"`
TotalTime string `json:"totalTime,omitempty"`
PrepTime string `json:"prepTime,omitempty"`
CookTime string `json:"cookTime,omitempty"`
Rating *float64 `json:"rating,omitempty"`
Categories []OrganizerRef `json:"recipeCategory,omitempty"`
Tags []OrganizerRef `json:"tags,omitempty"`
DateAdded string `json:"dateAdded,omitempty"`
DateUpdated string `json:"dateUpdated,omitempty"`
}
RecipeSummary is the list/summary view of a recipe. Field names mirror the Mealie API (camelCase) so the curated set is a faithful, stable subset.
type ShoppingList ¶
type ShoppingList struct {
ID string `json:"id"`
Name string `json:"name"`
CreatedAt string `json:"createdAt,omitempty"`
UpdatedAt string `json:"updatedAt,omitempty"`
ListItems []ShoppingListItem `json:"listItems,omitempty"`
}
ShoppingList is a household shopping list. ListItems is populated by GetShoppingList but is typically empty in the list/summary view.
type ShoppingListItem ¶
type ShoppingListItem struct {
ID string `json:"id"`
ShoppingListID string `json:"shoppingListId"`
Checked bool `json:"checked"`
Position int `json:"position"`
Note string `json:"note,omitempty"`
Quantity *float64 `json:"quantity,omitempty"`
FoodID *string `json:"foodId,omitempty"`
Food *NamedRef `json:"food,omitempty"`
UnitID *string `json:"unitId,omitempty"`
Unit *NamedRef `json:"unit,omitempty"`
LabelID *string `json:"labelId,omitempty"`
Display string `json:"display,omitempty"`
}
ShoppingListItem is one entry on a shopping list. Field names mirror the Mealie ShoppingListItemOut schema.
type TransportError ¶
type TransportError struct{ Err error }
TransportError wraps a network/transport-level failure (no HTTP response).
func (*TransportError) Error ¶
func (e *TransportError) Error() string
func (*TransportError) Unwrap ¶
func (e *TransportError) Unwrap() error
type User ¶
type User struct {
ID string `json:"id"`
Username string `json:"username"`
FullName string `json:"fullName,omitempty"`
Email string `json:"email,omitempty"`
Admin bool `json:"admin"`
Group string `json:"group,omitempty"`
Household string `json:"household,omitempty"`
}
User is the authenticated user from GET /api/users/self.