Skip to main content
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.

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;

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.

EnvironmentEntity

A named variable scope belonging to a workspace collection.Key fields: id, name, collectionId (nullable — null = user-global), isActive, 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.

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.

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.

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.

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.

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.
RunOptions controls run-time behaviour and is serialised to runOptionsJson in CollectionRuns so past runs can be replayed exactly:

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.
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.
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.
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.