Youll
Modules

YoullNetwork

The dual-transport networking layer providing GraphQL and REST APIs, token management, caching, and data models for the entire Youll platform.

Why YoullNetwork

Every screen in the platform needs data from the backend: content feeds, user profiles, journey progress, event schedules, meal plans, and more. YoullNetwork is the single module that handles all of these network operations. It sits at the bottom of the dependency graph as a leaf module with zero Bricks dependencies. Every other module depends on it, but it depends on nothing from Bricks.

YoullNetwork provides two transport layers: Apollo GraphQL for complex, structured data queries and REST for simpler operations like user registration, content likes, and file handling. Both transports share the same token management, error handling, and configuration. API classes choose which transport to use per method, and consumers never need to know or care which one is being used under the hood.

The module also owns the platform's data models. Raw API responses are parsed into internal response models, then transformed into public domain models (prefixed BN* or YN*) that every other module consumes. This two-tier model system keeps parsing logic isolated from business logic.

Architecture Overview

All network operations flow through BricksNetwork.shared, which is created once during the bootstrap sequence via Config.setup(). The dual-transport architecture looks like this:

BricksNetwork.shared
  ├── Apollo GraphQL
  │   ├── ApolloMission (query/mutation wrapper)
  │   ├── LandingModule (JSON parsing bridge)
  │   ├── Response Models (DecodableResponse protocol)
  │   └── 55 queries, 23 mutations, 22+ fragments

  └── REST (async/await HTTP methods)
      ├── get / post / patch / put / delete
      └── URL construction + token injection

When each transport is used:

TransportUsed ForExamples
GraphQLComplex data queries, feed sections, content with relationships, batch operationsContent queries, home/explore feeds, journeys, events, meals, playlists, search, paywall config, quiz, activity, explore tags
RESTSimple CRUD, user management, file-related operations, trackingUser registration/profile, likes/dislikes, badge earning, timer sessions, tracking sessions, promo offers, account deletion, vector search

Some API classes use both transports. For example, ContentAPI uses GraphQL for content queries but REST for likes, timer sessions, and vector search.

BricksNetwork Entry Point

BricksNetwork is the central networking client. It is created via a static factory method and accessed as a singleton:

// BricksNetwork.swift
public class BricksNetwork: @unchecked Sendable {

    public nonisolated(unsafe) static var shared: BricksNetwork?

    let environment: Environment
    lazy var apollo: ApolloClient = makeApolloClient()
    lazy var cachePolicy: CachePolicy = .fetchIgnoringCacheCompletely
    weak var dataSource: BricksNetworkDataSource?
    var queue = DispatchQueue.global(qos: .userInitiated)
    public var extraLang: String = ""
    public var coreDataStore: CoreDataStore

    public static func buildNetwork(
        environment: Environment,
        dataSource: BricksNetworkDataSource? = nil,
        extraLang: String? = nil
    ) {
        BricksNetwork.shared = BricksNetwork(
            environment: environment,
            dataSource: dataSource,
            extraLang: extraLang
        )
    }
}

Environment

The Environment struct holds the two endpoint URLs:

public struct Environment {
    let endpointURL: URL  // GraphQL (Hasura) endpoint
    let restURL: URL?     // REST API base URL

    public init(apiUrl: URL, restUrl: URL? = nil)
}

The customer app provides these URLs through its Keys struct during the bootstrap sequence. See Bootstrap Sequence for the full initialization flow.

BricksNetworkDataSource Protocol

The BricksNetworkDataSource protocol is the contract between the networking layer and the authentication system. The customer app (via Config) implements this protocol to provide token management:

public protocol BricksNetworkDataSource: AnyObject {
    typealias Completion = (_ token: String?) -> Void

    var isLoggedIn: Bool { get }
    var accessToken: String? { get }
    var currentUserInfoHeaders: UserInfoHeaders { get }

    func requiresTokenForNetworkRequest(operation: String) -> Bool
    func refreshToken(completion: Completion?)
}
Property/MethodPurpose
isLoggedInWhether a user is currently authenticated
accessTokenThe current JWT access token (nil if not logged in)
currentUserInfoHeadersHeaders containing user ID, device ID, and role
requiresTokenForNetworkRequest(operation:)Per-operation decision: does this request need authentication?
refreshToken(completion:)Called when the token is expired; must provide a fresh token via the callback

requiresTokenForNetworkRequest is what enables anonymous browsing. When it returns false, the request proceeds without a bearer token. The interceptor still sends x-hasura-role: guest headers so the backend knows the request is unauthenticated. When it returns true and no token is available, the request fails with missingAccessToken.

Token Management

YoullNetwork handles JWT token validation and refresh transparently. Consumers never need to manage tokens directly.

JWT Validation

The String.isValidJWTToken extension decodes the JWT payload and checks the exp claim:

// Utils/String+Token.swift
extension String {
    var isValidJWTToken: Bool {
        // Decode base64url payload, extract "exp" claim, compare to Date()
    }
}

If the token is expired, the interceptor automatically triggers a refresh before proceeding with the request.

GraphQL Token Flow

The UserManagementInterceptor is an Apollo interceptor that runs before every GraphQL request:

class UserManagementInterceptor: ApolloInterceptor {
    enum UserError: Error {
        case noUserLoggedIn
        case refreshTokenFailed
    }

    func interceptAsync<Operation: GraphQLOperation>(
        chain: RequestChain,
        request: HTTPRequest<Operation>,
        response: HTTPResponse<Operation>?,
        completion: @escaping (Result<GraphQLResult<Operation.Data>, Error>) -> Void
    ) {
        // 1. Check if this operation requires a token
        if dataSource?.requiresTokenForNetworkRequest(operation: Operation.operationName) == false {
            // Add token if available (optional), add user info headers, proceed
            chain.proceedAsync(...)
            return
        }

        // 2. Token required but missing -> error
        guard let token = dataSource?.accessToken else {
            chain.handleErrorAsync(UserError.noUserLoggedIn, ...)
            return
        }

        // 3. Token valid -> add and proceed
        if token.isValidJWTToken {
            addTokenAndProceed(token, ...)
        } else {
            // 4. Token expired -> refresh, then retry
            refreshTokenAndRetry(...)
        }
    }
}

REST Token Flow

REST methods use the same BricksNetworkDataSource but handle token refresh inline via async/await:

private func createURLRequest(token: String?, path: String, ...) async throws -> URLRequest? {
    var accessToken = token ?? dataSource?.accessToken

    // If token required but missing, throw
    if accessToken == nil && requiresToken {
        throw NetworkError.missingAccessToken
    }

    // If token expired and required, refresh via async continuation
    if let isValidToken = accessToken?.isValidJWTToken, !isValidToken && requiresToken {
        accessToken = await refreshToken()
    }

    // Build request with token and headers
    ...
}

Request Headers

Each transport sends a different set of headers:

HeaderGraphQLREST
Authorization: Bearer {token}Yes (via interceptor)Yes (via createURLRequest)
x-hasura-role: user/guestYesNo
x-hasura-user-id: {id}Yes (if logged in)No
device-id: {deviceID}YesNo
X-Youll-Platform: iosNoYes
X-Youll-App-Version: {version}NoYes
X-Youll-User-Id: {id}NoYes
Content-Type: application/jsonNo (Apollo handles it)Yes

The UserInfoHeaders struct constructs the GraphQL headers. It automatically sets the role to "user" or "guest" based on whether a user ID is present:

public struct UserInfoHeaders {
    let userID: String?
    let deviceID: String
    let role: String  // "user" if userID != nil, "guest" otherwise
    let appVersion: String?

    public func getUserInfoHeaders() -> [String: String] {
        var headers = ["x-hasura-role": role, "device-id": deviceID]
        if let userID { headers["x-hasura-user-id"] = userID }
        return headers
    }
}

GraphQL Layer

The GraphQL layer wraps Apollo iOS (v1.15.0+) with two internal classes: ApolloMission for executing operations and LandingModule for parsing responses.

ApolloMission

ApolloMission provides static methods that wrap Apollo's fetch and perform calls, returning Combine Future publishers:

final class ApolloMission {
    typealias Response<T> = Future<T, Error>

    // Query returning array of Decodable items
    static func launch<Q: Query, T: Decodable>(
        query: Q,
        into network: BricksNetwork,
        cachePolicy: CachePolicy? = nil
    ) -> Response<[T]>

    // Query returning array via DecodableResponse transformation
    static func launch<Q: Query, T: DecodableResponse>(
        query: Q,
        into network: BricksNetwork,
        cachePolicy: CachePolicy? = nil,
        landingModel: T.Type
    ) -> Response<[T.Model]>

    // Combined query returning single Decodable object
    static func launch<Q: Query, T: Decodable>(
        combinedQuery: Q,
        into network: BricksNetwork
    ) -> Response<T>

    // Void mutation (fire-and-forget)
    static func execute<M: Mutation>(
        mutation: M,
        into network: BricksNetwork
    ) -> Response<Void>

    // Mutation returning array
    static func execute<M: Mutation, T: Decodable>(
        mutation: M,
        into network: BricksNetwork
    ) -> Response<[T]>
}

The void mutation variant (execute(mutation:into:) -> Response<Void>) always returns .success(()) without checking the GraphQL response for errors. Mutations like SetViewedContentMutation and DismissHomeSliceMutation will appear to succeed even if the server returned an error. Keep this in mind when debugging mutation failures.

LandingModule

LandingModule bridges Apollo's raw GraphQLResult to the application's Decodable response models. It extracts Apollo's DataDict, converts it to a standard JSON dictionary, then decodes it:

final class LandingModule<T: Decodable> {
    static func parseQuery<Q: Query>(_ query: Q, result: QResult<Q>) -> Output<[T]> {
        // 1. Check for GraphQL errors
        if let error = resultData.errors?.first {
            return .failure(NetworkError.operationFailed)
        }
        // 2. Convert Apollo DataDict to JSON
        guard let json = resultData.data?.__data.convertToJSON() else {
            return .failure(NetworkError.invalidResponse)
        }
        // 3. Decode JSON to [T]
        return decodeObjects(from: json)
    }
}

The DataDict.convertToJSON() extension recursively converts Apollo's internal data representation to standard [String: AnyHashable] dictionaries that JSONDecoder can handle.

DecodableResponse Protocol

The DecodableResponse protocol defines the transformation contract between response models and domain models:

protocol DecodableResponse where Self: Decodable {
    associatedtype Model: Any
    func model() -> Model
}

Response models conform to this protocol to provide a model() method that creates the corresponding public domain model. The Array extension provides mapToModels() for batch transformation:

extension Array where Element: DecodableResponse {
    func mapToModels() -> [Element.Model] {
        return self.map { $0.model() }
    }
}

GraphQL Operations Catalog

The auto-generated GraphQL/ package contains all operations against the Hasura backend:

TypeCountExamples
Queries55GetContentQuery, GetHomeScreenSectionsQuery, GetJourneyQuery, GetAllEventsQuery, ApplySearchQuery, GetPaywallConfigQuery
Mutations23SetViewedContentMutation, CreatePlaylistMutation, InsertQuizAnswersMutation, RegisterTransactionMutation, SetEventResponseMutation
Fragments22+ContentFields, AssetFields, CategoryFields, JourneyField, PaywallConfigFields, MealContentFields

Operations are auto-generated from .graphql files in Resources/GraphQL/ against the schema in Resources/Schema/Youll.graphqls. The schema uses a @cached directive for caching hints.

REST Layer

The REST layer provides typed async/await HTTP methods directly on BricksNetwork:

extension BricksNetwork {
    // With return value
    public func get<T: Codable>(path: String, queryParams: [String: String]? = nil) async throws -> T
    public func post<T: Codable>(token: String? = nil, path: String, body: Data? = nil) async throws -> T
    public func patch<T: Codable>(path: String, body: Data? = nil) async throws -> T
    public func put<T: Codable>(path: String, queryParams: [String: String], body: Data?) async throws -> T
    public func delete<T: Codable>(path: String) async throws -> T

    // Void variants (no return value)
    public func get(path: String, queryParams: [String: String]? = nil) async throws
    public func post(token: String? = nil, path: String, body: Data? = nil) async throws
    public func patch(path: String, body: Data? = nil) async throws
    public func delete(path: String) async throws
}

All REST methods follow the same pattern: construct a URLRequest via createURLRequest (which handles token injection and headers), execute via URLSession.shared, validate the HTTP status code (200-299), and decode the response.

For empty responses, the EmptyResponse struct is used as a passthrough type:

public struct EmptyResponse: Codable { }

API Classes Catalog

All 18 API classes extend ApiClient and provide static methods. The ApiClient base class defines a single type alias:

public class ApiClient {
    public typealias Publisher<T> = AnyPublisher<T, Error>
}
API ClassDomainTransportKey Methods
ContentAPIContent, categories, subcategories, favorites, reviewsMixedgetCategories, getContent, getFavoriteContent, likeContent (REST), sendTimerSession (REST), getContentsVectorSearch (REST)
UserAPIUser profile, registration, badges, notificationsMixedregisterNewUser (REST), getProfile (REST), getEarnedBadges (REST), deleteUserAccount (REST), updateProfilePicture (REST)
FeedAPIHome, explore, and search feedsGraphQLgetHomeFeedSections, getExploreFeedSections, getSearchFeed, dismissHomeSliceMutation
JourneyAPILinear and timeline journeysGraphQLgetJourney, getLinearJourney, setActiveLinearJourney, restartJourney
ActivitiesAPIWorkouts, activity programs, feedbackGraphQLgetActivityPageSlices, setWorkoutCompleted, getNextWorkout, updateActivityFeedback
ExploreAPIExplore tags, tagged itemsGraphQLgetExploreTags, getExploreTag, getExploreTaggedItems
EventsAPIEvents managementGraphQLgetAllEvents, getEventById, getUserEvents, setEventResponse
LibraryAPIPlaylists managementGraphQLgetPlaylists, createPlaylist, addContentToPlaylist, deletePlaylist
MealsAPIMeal programs, detailsGraphQLgetMeals, getMealDetailCards, markMealAsChecked
SearchAPISearch operationsGraphQLgetSearchFilters, applySearch
QuizAPIQuiz operationsGraphQLgetQuiz, insertQuizAnswers, saveQuizAnswers
PaymentsAPITransaction registrationGraphQLregisterTransaction
SettingsAPIApp configuration queriesGraphQLgetPlayerConfig, getOnboardingConfig, getQuizConfig
ConfigAPIApp config, social proofGraphQLgetAppConfig, getSocialProofDetails
FacilitatorsAPIFacilitator detailsGraphQLgetFacilitatorByID, getFacilitatorsByIDs
PromoOffersAPIPromotional offersRESTgetPromoOffers
WidgetsAPIWidget configurationGraphQLgetWidgetConfig

UserAPI holds a static currentUser publisher (CurrentValueSubject<YNProfile?, Never>) that bridges the network layer to the service layer. When getProfile returns, it updates UserAPI.currentUser, which UserService in Core subscribes to. See Communication Patterns for the full subscription chain.

Data Models

YoullNetwork uses a three-tier model pipeline that separates parsing concerns from the public API surface:

graph LR
    A["GraphQL Response<br/>(Apollo DataDict)"] --> B["Response Model<br/>(internal, Decodable)"]
    B --> C["Domain Model<br/>(public, BN*/YN*)"]
    D["REST Response<br/>(JSON)"] --> C

Tier 1: Response Models (Internal)

67 files in Network/API Response Models/. These are internal Decodable structs that mirror the GraphQL schema structure. They conform to DecodableResponse to provide a model() transformation:

// Network/API Response Models/ContentResponse.swift
class ContentResponse: Decodable {
    let id: String
    let title: String
    let body: String?
    let asset: AssetResponse
    let thumbnail: AssetResponse?
    let isLiked: Bool
    let isLocked: Bool
    // ... more fields
}

extension ContentResponse: DecodableResponse {
    typealias Model = BNItem

    func model() -> BNItem {
        let content = BNContent(
            id: id,
            title: extraLanguages?.first?.title ?? title,
            // ... maps all fields to the domain model
        )
        return .content(content)
    }
}

Tier 2: Intermediate Models (Factory)

4 files that handle complex transformations between response models and domain models when simple one-to-one mapping is not sufficient:

  • HomeFeedSection+Factory transforms feed response into section models
  • ExploreFeedSection+Factory transforms explore response into section models
  • EnvoyURL wraps URL handling for share link generation
  • FeedTag represents tags in the feed context

Tier 3: Public Domain Models

55 files in Public/Models/. These are the models consumed by Core, Client, and all other modules:

PrefixPurposeExamples
BN*General domain modelsBNContent, BNCategory, BNSubcategory, BNJourney, BNEvent, BNPlaylist, BNHomeFeedSection, BNWorkoutModel
YN*User and profile modelsYNProfile, YNBadge, YNProfileNotifications

REST responses decode directly into domain models (Tier 3) since they are already flat JSON structures. GraphQL responses go through all three tiers.

Combine and async/await Patterns

YoullNetwork exposes APIs using both Combine publishers and async/await. Both patterns coexist in the codebase:

Combine pattern (used by most GraphQL-based methods):

// Returns AnyPublisher<[BNCategory], Error>
public static func getCategories(from network: BricksNetwork) -> Publisher<[BNCategory]> {
    let query = GetContentCategoriesQuery(extraLang: network.extraLang)
    return ApolloMission.launch(query: query, into: network, landingModel: CategoryResponse.self)
        .map({ $0.mapToCategory() })
        .eraseToAnyPublisher()
}

async/await pattern (used by all REST methods and newer APIs):

// async throws -> YNProfile
public static func getProfile(stats: [String], from network: BricksNetwork) async throws -> YNProfile {
    let path = "/users/me"
    let profile: YNProfile = try await network.get(path: path, queryParams: [...])
    currentUser.value = profile
    return profile
}

Dual signatures (some methods offer both):

// Combine version
public static func getProfile(stats: [String], from network: BricksNetwork) -> Publisher<YNProfile>

// async/await version
public static func getProfile(stats: [String], from network: BricksNetwork) async throws -> YNProfile

The Combine versions of dual-signature methods typically wrap the async version in a Future:

public static func getProfile(stats: [String], from network: BricksNetwork) -> Publisher<YNProfile> {
    return Future<YNProfile, Error> { promise in
        Task { @MainActor in
            do {
                let profile = try await getProfile(stats: stats, from: network)
                promise(.success(profile))
            } catch {
                promise(.failure(error))
            }
        }
    }.eraseToAnyPublisher()
}

All Combine publishers use Never or Error as the failure type. Subscribers typically use .sink with [weak self] and store subscriptions in a Set<AnyCancellable>.

Error Handling

NetworkError

The NetworkError enum covers all failure cases:

public enum NetworkError: Error {
    case operationFailed      // GraphQL returned errors in the response
    case invalidRequest       // Could not construct the request (missing REST URL, etc.)
    case invalidResponse      // HTTP status code outside 200-299
    case invalidData          // JSON decoding failed
    case missingAccessToken   // Token required but not available
    case emptyResponse        // Empty data received (unused in practice)
}

Error Propagation by Transport

TransportError SourceHandling
GraphQLresultData.errors?.firstLandingModule checks for GraphQL errors before parsing. Returns .failure(NetworkError.operationFailed)
GraphQLApollo network failurePropagated as-is through the Result.failure path
GraphQLToken missing/expiredUserManagementInterceptor throws UserError.noUserLoggedIn or UserError.refreshTokenFailed
RESTHTTP status outside 200-299executeRequest throws NetworkError.invalidResponse
RESTJSON decode failureJSONDecoder.decode throws, propagated via async/await
RESTToken missingcreateURLRequest throws NetworkError.missingAccessToken

Caching and Persistence

Apollo SQLite Cache

YoullNetwork initializes an Apollo SQLiteNormalizedCache backed by a SQLite database:

let sqliteFileURL = documentsURL.appendingPathComponent("test_apollo_db.sqlite")
if let sqliteCache = try? SQLiteNormalizedCache(fileURL: sqliteFileURL) {
    store = ApolloStore(cache: sqliteCache)
} else {
    store = ApolloStore()  // Falls back to in-memory cache
}

The default cache policy is .fetchIgnoringCacheCompletely, which means the Apollo cache is not used for reads by default. The SQLite database exists and stores normalized data, but queries bypass it unless they explicitly pass a different cache policy. Currently only PaymentsAPI.getSuperPaywallConfig uses .returnCacheDataAndFetch. Per-query overrides are supported via the cachePolicy parameter on ApolloMission.launch.

CoreData Persistence

CoreDataStore provides a general-purpose CoreData stack for local persistence:

public class CoreDataStore {
    public func fetch<T>(_ type: T.Type, predicate: NSPredicate?, ...) -> [T]?
    public func saveInBackground(task: @escaping (NSManagedObjectContext) -> Void)
    public func deleteAll()
}

The DataApiCache utility uses CoreData to cache user profiles. When UserAPI.getProfile returns, it stores the profile via DataApiCache.save(object:in:). On next launch, UserService loads the cached profile immediately via DataApiCache.get(network:) before making a network request.

Cache Clearing

BricksNetwork.clearAppCache() calls coreDataStore.deleteAll() to wipe all locally persisted data. This is typically called during logout.

NetworkReachability

NetworkReachability is a singleton that monitors network connectivity and exposes the status via a Combine publisher:

public class NetworkReachability {
    public var reachablePublisher = CurrentValueSubject<Bool, Never>(false)
    public var reachable: Bool { reachablePublisher.value }

    nonisolated(unsafe) public static let shared = NetworkReachability()
}

It uses an internal Reachability class (bundled third-party library) to detect WiFi and cellular connectivity changes. The publisher emits true when any connection is available and false when offline.

Services and ViewModels subscribe to NetworkReachability.shared.reachablePublisher to show/hide offline indicators or defer network operations. See Communication Patterns for how this publisher fits into the platform's reactive state system.

Configuration

Environment URLs

The customer app provides two URLs via BricksNetwork.Environment:

  • endpointURL (required): The Hasura GraphQL endpoint
  • restURL (optional): The REST API base URL

Localization via extraLang

BricksNetwork.extraLang is a language code string set at initialization and passed to nearly every GraphQL content query:

let query = GetContentCategoriesQuery(extraLang: network.extraLang)

This parameter tells the backend to return content in the specified language when translations are available. If not set (empty string), the backend returns content in the default language.

Cache Policy Overrides

Individual queries can override the default cache policy:

ApolloMission.launch(query: query, into: network, cachePolicy: .returnCacheDataAndFetch)

Available Apollo cache policies include .fetchIgnoringCacheCompletely (default), .returnCacheDataAndFetch, .returnCacheDataElseFetch, and .returnCacheDataDontFetch.

What's Next

For Agents

Key Files

ConceptFile Path
Package manifestBricks/modules/YoullNetwork/Package.swift
Main entry pointBricks/modules/YoullNetwork/Network/Sources/BricksNetwork.swift
Data source protocolBricks/modules/YoullNetwork/Network/Sources/BricksNetworkDataSource.swift
Token interceptorBricks/modules/YoullNetwork/Network/Sources/Network/UserManagementInterceptor.swift
Network errorsBricks/modules/YoullNetwork/Network/Sources/NetworkError.swift
GraphQL query wrapperBricks/modules/YoullNetwork/Network/Sources/Apollo/ApolloMission+Query.swift
GraphQL mutation wrapperBricks/modules/YoullNetwork/Network/Sources/Apollo/ApolloMission+Mutation.swift
GraphQL parsingBricks/modules/YoullNetwork/Network/Sources/Apollo/LandingModule+Query.swift
DecodableResponse protocolBricks/modules/YoullNetwork/Network/Sources/Apollo/DecodableResponse.swift
ReachabilityBricks/modules/YoullNetwork/Network/Sources/Public/NetworkReachability.swift
API classesBricks/modules/YoullNetwork/Network/Sources/Public/Data Task/*.swift
Response modelsBricks/modules/YoullNetwork/Network/Sources/Network/API Response Models/*.swift
Public domain modelsBricks/modules/YoullNetwork/Network/Sources/Public/Models/*.swift
CoreData storeBricks/modules/YoullNetwork/Network/Sources/CoreData/CoreDataStore.swift
JWT validationBricks/modules/YoullNetwork/Network/Sources/Utils/String+Token.swift
GraphQL schemaBricks/modules/YoullNetwork/Resources/Schema/Youll.graphqls
GraphQL operationsBricks/modules/YoullNetwork/Resources/GraphQL/*.graphql

Reading Order

  1. Package.swift for dependencies (Apollo, ApolloSQLite, local GraphQL package)
  2. BricksNetwork.swift for the singleton, environment setup, and REST methods
  3. BricksNetworkDataSource.swift for the auth contract
  4. UserManagementInterceptor.swift for the GraphQL token flow
  5. ApolloMission+Query.swift and ApolloMission+Mutation.swift for the Apollo wrapper
  6. LandingModule+Query.swift and LandingModule+Decode.swift for JSON parsing
  7. DecodableResponse.swift for the model transformation protocol
  8. Any API class (e.g., ContentAPI.swift) for usage patterns
  9. The corresponding response model (e.g., ContentResponse.swift) for the transformation pipeline

Tips

  • To find which API class handles a feature: search for the feature name (e.g., "journey", "meal", "event") in Public/Data Task/.
  • To trace a query to its response model: look at the landingModel: parameter in the ApolloMission.launch call. That type's model() method shows the transformation.
  • To add a new GraphQL query: add the .graphql file to Resources/GraphQL/, run Apollo codegen, then call it via ApolloMission.launch in the appropriate API class.
  • To add a new REST endpoint: call network.get/post/patch/delete directly in the API class. REST methods are all on BricksNetwork.
  • To understand the auth flow for a request: check requiresTokenForNetworkRequest in the customer app's Config class. Operations listed there require a valid JWT.
  • GraphQL operations are in GraphQL/Sources/Operations/ (auto-generated). Queries, mutations, and fragments are in separate subdirectories.
  • extraLang is passed to almost every content query. If content is returning in the wrong language, check that BricksNetwork.extraLang is set correctly during initialization.

On this page