Skip to main content

Documentation Index

Fetch the complete documentation index at: https://mintlify.com/JonathanHerSa/xolo-api-hub/llms.txt

Use this file to discover all available pages before exploring further.

A clean domain layer is what makes Xolo’s architecture genuinely maintainable. By housing the data-access contract, all entity definitions, and the business-logic services in a package that has zero dependency on Flutter widgets, Drift, or Riverpod, every piece of behaviour can be unit-tested in isolation against a plain mock. Adding a new feature begins with the domain — extend the contract, write a test, then wire up the database implementation — rather than the other way around.

XoloRepository Interface

XoloRepository is the single gateway between the presentation layer and all persistent data. It lives in lib/domain/repositories/xolo_repository.dart and contains no imports from package:drift. Every widget, provider, and service depends only on this abstract class, which means the underlying storage engine can be swapped without touching a single screen.
Simple key-value configuration store. watchSetting emits reactively whenever the stored value changes.
Future<void> setSetting(String key, String value);
Future<String?> getSetting(String key);
Stream<String?> watchSetting(String key);

Domain Entities

All domain entities are plain Dart classes with const constructors. They carry no Drift annotations, no Flutter imports, and no JSON serialisation logic (that lives in entity_mappers.dart). This makes them trivial to construct in tests and safe to pass across isolates.

CollectionEntity

Represents a collection node (root workspace or nested folder).Key fields: id, name, description (nullable), parentId (nullable FK — null means root), authType, authData, createdAt.Helper: bool get isRoot => parentId == null;
class CollectionEntity {
  final int id;
  final String name;
  final String? description;
  final int? parentId;   // null = root workspace
  final String? authType;
  final String? authData;
  final DateTime createdAt;

  bool get isRoot => parentId == null;
}

SavedRequestEntity

A reusable HTTP request with optional collection membership, auth override, schema hint, scripts, and assertions.Key fields: id, name, method, url, headersJson, paramsJson, body, collectionId (nullable), authType, authData, schemaJson, preScriptsJson, scriptsJson, assertionsJson, createdAt, updatedAt, isDeleted.
class SavedRequestEntity {
  final int id;
  final String name;
  final String method;
  final String url;
  final String? headersJson;
  final String? paramsJson;
  final String? body;
  final String? authType;
  final String? authData;
  final String? schemaJson;
  final String? preScriptsJson;
  final String? scriptsJson;
  final String? assertionsJson;
  final int? collectionId;
  final DateTime createdAt;
  final DateTime updatedAt;
  final bool isDeleted;
}

EnvironmentEntity

A named variable scope belonging to a workspace collection.Key fields: id, name, collectionId (nullable — null = user-global), isActive, createdAt.
class EnvironmentEntity {
  final int id;
  final String name;
  final int? collectionId;
  final bool isActive;
  final DateTime createdAt;
}

EnvVariableEntity

A single variable key-value pair. scope drives resolution priority: 'env' entries override 'global' entries with the same key.Key fields: id, key, value, environmentId (nullable), collectionId (nullable), scope ('global' or 'env'), createdAt.
class EnvVariableEntity {
  final int id;
  final String key;
  final String value;
  final int? environmentId;
  final int? collectionId;
  final String scope;
  final DateTime createdAt;
}

HistoryEntryEntity

An immutable snapshot of one HTTP execution, including auth credentials at the time of the call for replay fidelity.Key fields: id, savedRequestId (nullable), workspaceId (nullable), method, url, originalUrl (nullable template), headersJson, paramsJson, body, authType, authData, statusCode, responseBody, durationMs, executedAt.
class HistoryEntryEntity {
  final int id;
  final int? savedRequestId;
  final int? workspaceId;
  final String method;
  final String url;          // resolved URL
  final String? originalUrl; // template URL with variables
  final int? statusCode;
  final String? responseBody;
  final int? durationMs;
  final DateTime executedAt;
  // ... auth snapshot fields
}

AssertionRuleEntity

A single declarative validation rule attached to a SavedRequestEntity. Rules are serialised to assertionsJson and evaluated by AssertionEvaluator.Key fields: type (AssertionType enum), target (nullable — JSONPath or field name), expected (expected value/threshold), operator (defaults 'equals').AssertionType values: statusCode, responseTimeMs, jsonPathExists, jsonPathEquals, bodyContains.
class AssertionRuleEntity {
  final AssertionType type;
  final String? target;
  final String expected;
  final String operator; // 'equals', 'lessThan', 'in', etc.
}

CollectionRunEntity

Header record for one automated collection run. allPassed is a convenience getter used by the UI to decide which badge colour to render.Key fields: id, collectionId, workspaceId (nullable), environmentId (nullable), status (RunStatus enum), totalSteps, passedSteps, failedSteps, skippedSteps, startedAt, finishedAt (nullable), stopOnFailure, variablesSnapshotJson.RunStatus values: running, completed, failed, cancelled.
class CollectionRunEntity {
  final RunStatus status;
  final int totalSteps;
  final int passedSteps;
  final int failedSteps;
  final int skippedSteps;
  final DateTime startedAt;
  final DateTime? finishedAt;

  bool get allPassed => failedSteps == 0 && passedSteps > 0;
}

RunStepResultEntity

Per-step execution record. assertionResults is a List<AssertionResultEntity> deserialised from the JSON column by the mapper.Key fields: stepIndex, savedRequestId (nullable), name, method, url, status (RunStepStatus enum), statusCode, durationMs, passed, assertionResults, errorMessage, responseBodySnippet, extractedVariables.RunStepStatus values: passed, failed, skipped, error.
class RunStepResultEntity {
  final int stepIndex;
  final int? savedRequestId;
  final String name;
  final RunStepStatus status;
  final bool passed;
  final List<AssertionResultEntity> assertionResults;
  final Map<String, String> extractedVariables;
  // ...
}

RunPlanItem and RunOptions

RunPlanItem is the atomic unit of a collection run plan. It wraps a SavedRequestEntity with its zero-based stepIndex and the owning collectionId, giving CollectionRunnerService everything it needs to dispatch the request.
class RunPlanItem {
  final int stepIndex;
  final SavedRequestEntity request;
  final int? collectionId;
}
RunOptions controls run-time behaviour and is serialised to runOptionsJson in CollectionRuns so past runs can be replayed exactly:
class RunOptions {
  final bool stopOnFailure;          // abort after first failed step
  final List<String> skipIfVariableEmpty; // skip steps when listed vars are unset
  final int delayBetweenStepsMs;     // pause between steps (rate limiting)
  final int startFromIndex;          // resume mid-run from a given step index
}

Pure Domain Services

The three service classes in lib/domain/services/ contain all the execution intelligence. They depend only on XoloRepository and standard Dart libraries — no Flutter, no Drift, no Riverpod — making them fully unit-testable with a simple mock repository.
RunPlanBuilder converts a collection tree into a flat, ordered List<RunPlanItem>. The algorithm is a depth-first walk: for each node it first enqueues that node’s direct requests (in DB order), then recurses into each sub-collection.Inputs: collectionId (root of the tree to run), XoloRepository (injected at construction).Output: Future<List<RunPlanItem>> — items are numbered sequentially from index 0.
class RunPlanBuilder {
  RunPlanBuilder(this._repo);
  final XoloRepository _repo;

  Future<List<RunPlanItem>> build(int collectionId) async {
    final items = <RunPlanItem>[];
    var index = 0;
    await _walk(collectionId, (request) {
      items.add(RunPlanItem(
        stepIndex: index++,
        request: request,
        collectionId: request.collectionId,
      ));
    });
    return items;
  }

  Future<void> _walk(
    int collectionId,
    void Function(SavedRequestEntity) onRequest,
  ) async {
    // 1. Enqueue all requests in this collection
    final requests = await _repo.fetchRequestsInCollection(collectionId);
    for (final request in requests) {
      onRequest(request);
    }
    // 2. Recurse into sub-collections
    final subCollections = await _repo.fetchSubCollections(collectionId);
    for (final sub in subCollections) {
      await _walk(sub.id, onRequest);
    }
  }
}
Because RunPlanBuilder only calls fetchRequestsInCollection and fetchSubCollections, it can be tested with a two-method mock without touching a real database.
CollectionRunnerService drives the full execution lifecycle. It is constructed with three dependencies — RequestPipeline (HTTP), AuthResolverService (auth), and XoloRepository (persistence) — and exposes a single execute method.The service steps through the plan sequentially:
1

Create run record

Calls repository.createCollectionRun(...) to open a CollectionRuns row with status running.
2

Skip check

If RunOptions.skipIfVariableEmpty lists a variable that is absent or empty in the current workingVars map, the step is marked skipped and an explanatory message is stored as errorMessage.
3

Pre-scripts

ScriptExecutor.executePreScripts evaluates preScriptsJson rules, adding any extracted values to workingVars before the request is sent.
4

HTTP dispatch

RequestPipeline.send(...) fires the request with resolved headers, query params, body, and auth. If the CancelToken is cancelled mid-run, execution stops and status becomes cancelled.
5

Assertion evaluation

AssertionEvaluator.evaluate(...) is called with the response data and the deserialized List<AssertionRuleEntity>. The overall stepPassed flag is true only when output.error == null and every assertion passes.
6

Post-scripts (variable chaining)

scriptsJson rules are applied with JSONPath to extract values from the response body and write them back to workingVars and the repository via upsertVariable. These variables are then available to subsequent steps.
7

Persist step result

A RunStepResultEntity is constructed, emitted via the onStep callback (for live UI updates), and persisted via repository.insertRunStepResult(...).
8

Finish run

After the plan is exhausted (or aborted), repository.finishCollectionRun(...) closes the run record with final pass/fail/skip counts and a snapshot of the variable state.
final run = await collectionRunnerService.execute(
  collectionId: collection.id,
  collectionName: collection.name,
  plan: planItems,           // from RunPlanBuilder
  baseVariables: variables,  // from watchResolvedVariables
  options: RunOptions(stopOnFailure: true, delayBetweenStepsMs: 250),
  environmentId: activeEnvId,
  workspaceId: workspaceId,
  onStep: (step) {
    // emit to UI in real-time
    ref.read(runStepsProvider.notifier).addStep(step);
  },
  cancelToken: cancelToken,
  onRunCreated: (runId) => ref.read(activeRunIdProvider.notifier).set(runId),
);
CollectionRunnerService has no import 'package:flutter/...' — it can run in any Dart environment, including unit tests and background isolates.
AssertionEvaluator is a static-only class (private constructor) that maps a list of AssertionRuleEntity rules against a response and returns a List<AssertionResultEntity>. It has no mutable state and no I/O.Inputs: rules, statusCode, durationMs, responseData (parsed JSON or string), errorMessage.Output: List<AssertionResultEntity> — one result per rule. If rules is empty, a single implicit passed: true result is returned so the step is not penalised.
AssertionTypeBehaviour
statusCodeExact match (equals) or membership check (in operator with comma-separated list)
responseTimeMsdurationMs < limit (lessThan) or durationMs <= limit
jsonPathExistsVerifies at least one non-null match at the given JSONPath
jsonPathEqualsCompares the first JSONPath match (as string) against the expected value (quotes stripped)
bodyContainsString.contains check on the serialised response body
final rules = AssertionRuleEntity.listFromJson(request.assertionsJson);
final results = AssertionEvaluator.evaluate(
  rules: rules,
  statusCode: 200,
  durationMs: 143,
  responseData: {'id': 42, 'name': 'Alice'},
);

final allOk = AssertionEvaluator.allPassed(results); // true / false
Because AssertionEvaluator is a pure function with no side effects, testing it requires nothing more than calling evaluate(...) and asserting on the returned list.
When adding a new feature, start by extending XoloRepository with the method contract and writing a test against a mock implementation before writing DriftXoloRepository. This enforces the clean boundary, surfaces awkward API shapes early, and means you always have a passing test suite before touching the database layer.

Build docs developers (and LLMs) love