Core Concepts

This page explains the fundamental concepts that define how TheCompanyApp works. Understanding these concepts is essential for both users and developers.

1. Company-Scoped Multi-Tenancy

The Problem

Users often need to manage multiple businesses or participate in different organizations — but mixing data between them creates chaos.

The Solution

Every piece of data in TheCompanyApp (except user credentials) belongs to exactly one Company. The companyID attribute acts as a tenant identifier, ensuring complete data isolation.

How It Works

User's Device
├── Company A (Owner)
│   ├── Inventory items scoped to Company A
│   ├── Orders scoped to Company A
│   └── Contacts scoped to Company A
├── Company B (Participant)
│   ├── Inventory items scoped to Company B
│   ├── Orders scoped to Company B
│   └── Contacts scoped to Company B
└── Company C (Owner)
    └── Data scoped to Company C

All queries filter by the currently selected company:

Why This Design Exists

  • Security: Prevents accidental data leakage between companies

  • Performance: Queries return only relevant data

  • Scalability: Support unlimited companies per user

  • Flexibility: Users can be owners of some companies, participants in others

2. Dual Persistent Store Architecture

The Private Store

File: TheCompanyApp.sqlite CloudKit Database: Private Database Contains:

  • Companies created by this user (as owner)

  • UserPass credentials for all companies

  • AccessControl records

  • All company-scoped data for owned companies

The Shared Store

File: TheCompanyApp-shared.sqlite CloudKit Database: Shared Database Contains:

  • Companies shared by other users (as participant)

  • All company-scoped data for shared companies

  • Read from the CKShare zone

Why Two Stores?

CloudKit distinguishes between:

  • Private data: Accessible only by the user across their devices

  • Shared data: Accessible by multiple users via CKShare

The dual-store architecture mirrors this model, ensuring:

  • Private credentials never sync to shared zones

  • Owned companies stay in private database

  • Shared companies automatically appear in shared database

3. CloudKit Sharing Model

Sharing Lifecycle

Owner Creates Company (Private Store)

  1. User creates a company via CreatCUOC.swift

  2. Company is saved to Private Store

  3. CloudKit syncs to Private Database

  4. UserPass is created for login (never shared)

Owner Shares Company

  1. Owner taps "Share Company" in ShareAppView.swift

  2. App creates a CKShare with Companies as root record

  3. All related entities (inventory, orders, etc.) are enlisted into the share

  4. UICloudSharingController generates a share URL

  5. Owner sends URL to participants via Messages/Email

Participant Accepts Share

  1. Participant opens share URL in Safari

  2. URL opens TheCompanyApp via universal link

  3. App calls acceptShare() on CloudKit

  4. CloudKit adds company to participant's Shared Database

  5. App syncs data to Shared Store

  6. Company appears in participant's company list

Share Permissions

  • Owner: Full read/write access, can modify share participants

  • Participant: Read/write access to company data (based on AccessControl)

  • Read-Only Participant: View-only access (future enhancement)

4. Identity and Authentication

iCloud Identity

  • Primary authentication via user's Apple ID

  • No separate account creation required

  • CloudKit uses CKRecord.Reference for user identification

UserPass Credentials

  • Secondary authentication for company access

  • Stored in Private Store only (never shared)

  • Each user creates their own username/password per company

  • Enables access control even among invited participants

Access Control

The AccessControl entity manages:

  • userID: UUID identifying the user

  • companyID: Which company this access applies to

  • isOwner: Boolean flag for company ownership

  • deptMask: Department-based permissions (bitmask)

  • managerMask: Manager-level permissions (bitmask)

5. Company Selection and Context

@AppStorage State

The currently selected company is persisted via:

Context Resolution

When the user switches companies, the app:

  1. Reads selectedCompanyIDString from AppStorage

  2. Resolves the Companies object via resolveCompanyAndStore()

  3. Determines if the company is in Private or Shared Store

  4. Sets up filtered fetch requests for all views

Preferred Store Logic

For participants who also own the same company (edge case):

This prefers the Shared Store version to ensure participants see real-time updates from the owner.

6. Data Enlistment

What is Enlistment?

When new records are created for a shared company, they must be enlisted into the CKShare zone so other participants can see them.

How It Works

This method:

  1. Checks if the company has an associated CKShare

  2. If yes, enlists the new object into that share's zone

  3. If no, it's a no-op (company isn't shared)

When Enlistment Happens

  • Creating inventory items

  • Adding orders

  • Creating dispatch records

  • Adding contacts

  • Any company-scoped entity creation

7. Store Assignment

The Challenge

When creating a new object, Core Data must know which persistent store to save it to.

The Solution

This explicitly assigns the object to the same store as its parent company, ensuring:

  • Owner-created data stays in Private Store

  • Participant-created data goes to Shared Store

  • No cross-store relationship issues

8. Sync and Conflict Resolution

Merge Policy

When conflicts occur (same record modified on two devices):

  • CloudKit's server state wins

  • Local changes are merged at the property level

  • Recent writes typically take precedence

Remote Change Handling

When CloudKit pushes changes:

  1. Core Data posts .NSPersistentStoreRemoteChange notification

  2. App refreshes the view context

  3. SwiftUI views automatically update via @FetchRequest

9. Asynchronous Save Strategy

Blocking Saves (Traditional)

Waits for CloudKit sync, blocks UI thread.

Async Saves (Optimized)

Returns immediately, CloudKit syncs in background.

Why This Matters

  • Prevents UI freezes during save operations

  • Improves user experience, especially on slow networks

  • CloudKit sync happens transparently

10. Subscription Tiers and Permissions

Free Tier

  • tier = "Free" on Companies entity

  • Owner access only (no sharing)

  • Full feature access

Business Tier

  • tier = "Business" on Companies entity

  • Up to 10 participants

  • CloudKit sharing enabled

  • AccessControl enforcement

Enterprise Tier

  • tier = "Enterprise" on Companies entity

  • Unlimited participants

  • Advanced features (analytics, AI)

Enforcement

Tier limits are checked in:

  • ShareAppView.swift: Prevents sharing on Free tier

  • CompanyView.swift: Shows upgrade prompts

  • Backend validation (future): Server-side enforcement

Summary

These core concepts form the foundation of TheCompanyApp's architecture:

Concept
Benefit

Company-Scoped Multi-Tenancy

Data isolation and security

Dual Persistent Store

Proper CloudKit private/shared separation

CloudKit Sharing Model

Real-time multi-user collaboration

Identity via iCloud + UserPass

Simple yet secure authentication

Store Assignment & Enlistment

Correct data placement and sharing

Async Save Strategy

Responsive UI without blocking

Understanding these concepts will help you effectively use, extend, and troubleshoot TheCompanyApp.

Next Steps

Last updated