Choose How Your App Supplies Records
Your app remains the source of truth. Primordial keeps a searchable projection of that data and does not replace your app database.
| App data | Use |
|---|---|
| A complete in-memory dataset | replaceAll(with:) |
| Explicit inserts, updates, and removals | apply(_:) |
| A durable database or another paged source | synchronize(from:options:) |
| SwiftData models | PrimordialSwiftDataSource |
Replace a Complete Dataset
Use replaceAll(with:) when your app already has the complete set of records. Records omitted
from the new snapshot are removed from the collection.
let currentRecords = [record]
for try await event in notes.replaceAll(with: currentRecords) {
switch event {
case .progress(let progress):
print(progress.phase, progress.changedRecords)
case .completed(let report):
print(report.generatedEmbeddings)
print(report.reusedEmbeddings)
}
}
Search continues to use the previous complete snapshot until the replacement commits. A failed or cancelled replacement does not expose a partial dataset, and unchanged text can reuse its embedding.
Apply Individual Changes
Use apply(_:) when your app already knows which complete records changed. Update your source
of truth first, then apply an upsert or removal to its searchable projection.
let changes: [PrimordialRecordChange] = [
.upsert(record),
.remove(id: "old-record")
]
for try await event in notes.apply(changes) {
if case .completed(let report) = event {
print(report.changedRecords, report.removedRecords)
}
}
Partial field updates are intentionally not part of the collection API. Change the application record,
then pass its complete current fields with .upsert.
Synchronize SwiftData
Use PrimordialSwiftDataSource to map a SwiftData model into searchable records. Mark the stable
record ID with .preserveValueOnDeletion so removals can be read from SwiftData history.
import Primordial
import SwiftData
@Model
final class Note {
@Attribute(.unique, .preserveValueOnDeletion)
var id: UUID
var title: String
var body: String
var modifiedAt: Date
}
let source = try PrimordialSwiftDataSource<Note>(
identifier: "app.notes",
schemaVersion: "1",
container: modelContainer,
recordID: \Note.id,
encodeID: { $0.uuidString }
) { note in
[
"title": .text(note.title),
"body": .text(note.body),
"modifiedAt": .date(note.modifiedAt)
]
}
for try await event in notes.synchronize(from: source) {
switch event {
case .progress(let progress):
print(progress.phase, progress.changedRecords)
case .completed(let report):
print(report.changedRecords, report.removedRecords)
}
}
Continue using your normal ModelContext and @Query code. Primordial reads the
supplied ModelContainer, creates the first snapshot, and uses saved history checkpoints on
later synchronizations.
- Keep
identifierstable for the same logical data source. - Increase
schemaVersionwhen the record or field mapping changes meaning. - The encoded record ID must be nonempty and unique.
- Filtered SwiftData fetch descriptors are not supported.
Choose a Synchronization Mode
let options = PrimordialSynchronizationOptions(
mode: .automatic,
batchSize: 100
)
for try await _ in notes.synchronize(
from: source,
options: options
) { }
| Mode | Behavior |
|---|---|
.automatic |
Uses incremental changes when possible and falls back to a complete snapshot when required. |
.incremental |
Requires a valid saved checkpoint and fails instead of rebuilding automatically. |
.snapshot |
Forces a complete snapshot from the source. |
The default batch size is 100. Valid values are 1...1000; Primordial may reduce
work per batch when the device is under resource pressure.
Inspect Synchronization Status
let status = try await notes.synchronizationStatus()
print(status.sourceIdentifier)
print(status.sourceSchemaVersion)
print(status.hasCheckpoint)
print(status.requiresSnapshot)
print(status.lastCompletedAt)
print(status.lastReport)
Use the status for diagnostics or application UI. Your app decides when to synchronize—for example, after model availability, after committing meaningful source changes, when the app becomes active, or from an app-owned background task when the system grants execution time.
Reset or Change the Source
A synchronized collection stays bound to its source identifier. Reset it before binding that collection name to a different logical source.
try await notes.reset()
Reset removes the collection's searchable records, synchronization checkpoint, and source binding.