code-docs/packages/api.md
Server-side API handler for Lowdefy applications. Executes requests, manages connections, and handles authentication context.
This package provides the server-side logic that:
import {
callEndpoint, // Execute custom API endpoints
callRequest, // Execute data requests
createApiContext, // Create server context with auth info
getHomeAndMenus, // Fetch menu configuration
getNextAuthConfig, // Auth.js configuration
getPageConfig, // Fetch page configuration
getRootConfig, // Fetch app root configuration
logClientError, // Process client errors with schema validation
ConfigurationError,
RequestError,
ServerError,
} from '@lowdefy/api';
Client Action (Request)
│
▼
┌───────────────────┐
│ callRequest() │
└───────────────────┘
│
▼
┌───────────────────┐ ┌────────────────────────┐
│ getRequestConfig │────▶│ Read from build output │
└───────────────────┘ └────────────────────────┘
│
▼
┌───────────────────┐
│authorizeRequest() │ ◀── Check user roles/permissions
└───────────────────┘
│
▼
┌───────────────────┐
│ getConnection() │ ◀── Load connection handler (MongoDB, HTTP, etc.)
└───────────────────┘
│
▼
┌───────────────────┐
│evaluateOperators()│ ◀── Resolve _secret, _user, etc. in connection/request
└───────────────────┘
│
▼
┌───────────────────┐
│ validateSchemas │ ◀── Validate connection/request properties
└───────────────────┘
│
▼
┌───────────────────┐
│callRequestResolver│ ◀── Execute the actual database/API call
└───────────────────┘
│
▼
Response
Endpoints allow multi-step server-side routines:
Client Action (Endpoint)
│
▼
┌───────────────────┐
│ callEndpoint() │
└───────────────────┘
│
▼
┌───────────────────┐
│getEndpointConfig()│
└───────────────────┘
│
▼
InternalApi? ──yes──▶ throw "does not exist"
│ no
▼
┌───────────────────┐
│authorizeEndpoint()│
└───────────────────┘
│
▼
┌───────────────────┐
│ runRoutine() │ ◀── Dispatch by prefix: request:, endpoint:, control
└───────────────────┘
│ │
▼ ▼
handleRequest handleEndpointCall ──▶ runRoutine (child)
│
▼
Response/Error
/context/| Module | Purpose |
|---|---|
createApiContext.js | Initializes context with user session, state, and helper functions (steps/payload live on routineContext, not here) |
createAuthorize.js | Creates authorization checker for role-based access |
createReadConfigFile.js | Utility to read build output files |
createEvaluateOperators.js | Server-side operator evaluation; accepts steps/payload per call for routine isolation |
errors.js | Error types: ConfigurationError, RequestError, ServerError |
/routes/request/| Module | Purpose |
|---|---|
callRequest.js | Main entry point for request execution |
authorizeRequest.js | Check if user can execute this request |
getRequestConfig.js | Load request definition from build output |
getConnectionConfig.js | Load connection definition |
getConnection.js | Get the connection handler (e.g., MongoDB client) |
evaluateOperators.js | Resolve operators in connection/request properties |
checkConnectionRead.js | Verify read permissions on connection |
checkConnectionWrite.js | Verify write permissions on connection |
validateSchemas.js | Validate properties against connection/request schemas |
callRequestResolver.js | Execute the actual resolver function |
/routes/endpoints/| Module | Purpose |
|---|---|
callEndpoint.js | HTTP entry point for endpoint execution; blocks InternalApi |
runRoutine.js | Dispatch steps by ID prefix: request:, endpoint:, control |
handleRequest.js | Execute a database/API request step |
handleEndpointCall.js | Execute a CallApi step (server-side endpoint-to-endpoint) |
addStepResult.js | Store step results in routineContext.steps |
getEndpointConfig.js | Load endpoint config from build artifacts |
authorizeApiEndpoint.js | Check user access to endpoint |
control/ | Control flow handlers (if, for, try, switch, return, etc.) |
/routes/auth/Handles Auth.js (NextAuth) configuration retrieval.
| Module | Purpose |
|---|---|
callbacks/createSessionCallback.js | Assembles session from OIDC claims, userFields, and plugins |
callbacks/validateSessionRoles.js | Validates session.user.roles is an array of strings |
callbacks/createJWTCallback.js | JWT token assembly |
callbacks/addUserFieldsToSession.js | Maps provider fields to session via auth.userFields |
callbacks/addUserFieldsToToken.js | Maps provider fields to JWT token via auth.userFields |
callbacks/createCallbackPlugins.js | Filters callback plugins by type |
/routes/page/Serves page configuration to the client.
/routes/rootConfig/Serves app configuration, menus, and home page info.
Operators like _secret and _user must run server-side because:
Requests are simple data operations:
Endpoints are programmable APIs:
Each request gets a fresh connection context. Connections are:
Three error types for different scenarios:
| Error | When Used |
|---|---|
ConfigurationError | Invalid config (wrong schema, missing connection) |
RequestError | Expected errors (validation failed, unauthorized) |
ServerError | Unexpected errors (connection failed, bug) |
All error classes support optional configKey parameter for build artifact tracing:
import { ConfigurationError, RequestError, ServerError } from '@lowdefy/api';
// Throw error with config location tracking
throw new ConfigurationError({
message: 'Connection "mongoDB" not found',
configKey: request['~k'], // Links error to source YAML location
});
// Error without location (still valid)
throw new ServerError({ message: 'Database connection failed' });
Implementation: packages/api/src/context/errors.js
The error classes accept an options object:
message (string, required): Error messageconfigKey (string, optional): The ~k value for error tracingWhen errors reach the client or logs, the configKey can be resolved to show file:line location using resolveConfigLocation from @lowdefy/helpers.
Client-side errors are sent to the server for centralized logging via the logClientError route. When errors carry received data (the params/properties that caused the failure), the server validates them against plugin schemas to produce more helpful error messages.
Client-side: lowdefy._internal.handleError(error) serializes the error with serializer.serialize() (using the ~e marker) and POSTs to /api/client-error. The received property is preserved in the payload for server-side schema validation.
Server route: packages/api/src/routes/log/logClientError.js
Processes client errors:
serializer.deserialize() — restores correct Lowdefy error classBlockError, ActionError, or OperatorError with received data, validates against plugin schemas (see below)loadAndResolveErrorLocation() — reads keyMap/refMap from build artifactserror.source and error.config on the error objectConfigError, logs that (with cause chain preserving the original error){ source, configError } to client — configError is the serialized validation error if schema validation failed/routes/log/ — Plugin Schema Validation| Module | Purpose |
|---|---|
validatePluginSchema.js | Validates data against a plugin's JSON schema using @lowdefy/ajv |
formatValidationError.js | Converts AJV errors into human-readable messages |
logClientError.js | Orchestrates error logging with optional schema validation |
Validation flow in logClientError:
Client sends error (e.g., BlockError with received: { title: 123 })
│
▼
┌──────────────────────────┐
│ Look up schema for type │ ◀── Read from plugins/blockSchemas.json
└──────────────────────────┘
│
▼
┌──────────────────────────┐
│ validatePluginSchema() │ ◀── Validate received data against schema
└──────────────────────────┘
│ (if invalid)
▼
┌──────────────────────────┐
│ formatValidationError() │ ◀── Convert AJV errors to readable messages
└──────────────────────────┘
│
▼
ConfigError with cause chain → logged to terminal
Schema map files (generated at build time):
| Error Type | Schema File | Schema Key | Field Label |
|---|---|---|---|
BlockError | plugins/blockSchemas.json | properties | property |
ActionError | plugins/actionSchemas.json | params | param |
OperatorError | plugins/operatorSchemas.json | params | param |
Example output:
/Users/dev/app/pages/home.yaml:15
[ConfigError] Block "Button" property "title" must be type "string".
Caused by: [BlockError] Error rendering block "submitBtn".
Operator method names: For operators with method-qualified names (e.g., _yaml.parse), the validation extracts params from the method-style received key (e.g., { '_yaml.parse': { on: ... } }) and uses the display name _yaml.parse in error messages.
Graceful degradation: If schema files are missing, the plugin has no schema, or validation itself fails, the original error is logged unchanged. Schema validation never prevents error logging.
See Error Tracing System for complete documentation.