Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -257,6 +257,7 @@ The `users/lookup/email` and `users/lookup/id` primitives (`lookupUsersByEmail`
In MistDemo, integration runs targeting these endpoints use `PhaseContext.userContextService` (a public+web-auth `CloudKitService`) which is built from `CLOUDKIT_API_TOKEN` + `CLOUDKIT_WEB_AUTH_TOKEN` regardless of the primary `--database` selection. The `DatabaseConfiguration` / `AuthenticationCredentials` types in `Examples/MistDemo/Sources/MistDemoKit/Configuration/` enforce valid database+auth combinations at construction time.

**Result Types (Sources/MistKit/Models/ and Sources/MistKit/Models/Zones/):**
- `RecordName` — string-backed struct for a record's identity within a zone (UUID or custom string). Encodes as a JSON string. Not a `typealias` (that would not distinguish record IDs from owner/zone names) and not an enum (the set of names is open). Distinct from `UserRecordName`.
- `QueryResult` — `records: [RecordInfo]`, `continuationMarker: String?`
- `RecordChangesResult` — `records: [RecordInfo]`, `syncToken: String?`, `moreComing: Bool`
- `ZoneChangesResult` — `zones: [ZoneInfo]`, `syncToken: String?`, `moreComing: Bool` *(deprecated `zones/changes`; `syncToken` rides the wire as `metaSyncToken` — see #430 above)*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ internal enum ExportCommand {
let fields: [String: String]

init(from recordInfo: RecordInfo) {
self.recordName = recordInfo.recordName
self.recordName = recordInfo.recordName.rawValue
self.recordType = recordInfo.recordType
self.fields = recordInfo.fields.mapValues { fieldValue in
String(describing: fieldValue)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -100,7 +100,7 @@ public struct BushelCloudKitService: Sendable, RecordManaging, CloudKitRecordCol
desiredKeys: [],
database: .public(.prefers(.serverToServer))
)
let recordNames = Set(records.map(\.recordName))
let recordNames = Set(records.map(\.recordName.rawValue))

Self.logger.debug("Found \(recordNames.count) existing \(recordType) records")
return recordNames
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -231,16 +231,16 @@ public struct SyncEngine: Sendable {

// Classify operations for each type
let swiftClassification = OperationClassification(
proposedRecordNames: fetchResult.swiftVersions.map(\.recordName),
existingRecordNames: swiftNames
proposedRecordNames: fetchResult.swiftVersions.map { RecordName($0.recordName) },
existingRecordNames: Set(swiftNames.map(RecordName.init(rawValue:)))
)
let restoreClassification = OperationClassification(
proposedRecordNames: fetchResult.restoreImages.map(\.recordName),
existingRecordNames: restoreNames
proposedRecordNames: fetchResult.restoreImages.map { RecordName($0.recordName) },
existingRecordNames: Set(restoreNames.map(RecordName.init(rawValue:)))
)
let xcodeClassification = OperationClassification(
proposedRecordNames: fetchResult.xcodeVersions.map(\.recordName),
existingRecordNames: xcodeNames
proposedRecordNames: fetchResult.xcodeVersions.map { RecordName($0.recordName) },
existingRecordNames: Set(xcodeNames.map(RecordName.init(rawValue:)))
)

Self.logger.debug(
Expand All @@ -252,15 +252,24 @@ public struct SyncEngine: Sendable {
// XcodeVersion last (references the other two)
let swiftResult = try await syncRecords(
fetchResult.swiftVersions,
classification: swiftClassification
classification: swiftClassification,
recordType: SwiftVersionRecord.cloudKitRecordType,
name: \.recordName,
fields: { $0.toCloudKitFields() }
)
let restoreResult = try await syncRecords(
fetchResult.restoreImages,
classification: restoreClassification
classification: restoreClassification,
recordType: RestoreImageRecord.cloudKitRecordType,
name: \.recordName,
fields: { $0.toCloudKitFields() }
)
let xcodeResult = try await syncRecords(
fetchResult.xcodeVersions,
classification: xcodeClassification
classification: xcodeClassification,
recordType: XcodeVersionRecord.cloudKitRecordType,
name: \.recordName,
fields: { $0.toCloudKitFields() }
)

print("\n" + String(repeating: "=", count: 60))
Expand Down Expand Up @@ -305,9 +314,12 @@ public struct SyncEngine: Sendable {
/// - records: Records to sync
/// - classification: Classification of operations as creates vs updates
/// - Returns: Sync result for this record type
private func syncRecords<T: CloudKitRecord>(
private func syncRecords<T>(
_ records: [T],
classification: OperationClassification
classification: OperationClassification,
recordType: String,
name: KeyPath<T, String>,
fields: (T) -> [String: FieldValue]
) async throws -> TypeSyncResult {
guard !records.isEmpty else {
return TypeSyncResult(created: 0, updated: 0, failed: 0, failedRecordNames: [])
Expand All @@ -316,15 +328,15 @@ public struct SyncEngine: Sendable {
let operations = records.map { record in
RecordOperation(
operationType: .forceReplace,
recordType: T.cloudKitRecordType,
recordName: record.recordName,
fields: record.toCloudKitFields()
recordType: recordType,
recordName: RecordName(record[keyPath: name]),
fields: fields(record)
)
}

return try await cloudKitService.executeBatchOperations(
operations,
recordType: T.cloudKitRecordType,
recordType: recordType,
classification: classification
)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,10 @@ public import MistKit
// MARK: - CloudKitRecord Conformance

extension DataSourceMetadata: CloudKitRecord {
@_implements(CloudKitRecord, recordName)
public var cloudKitRecordName: RecordName {
RecordName(rawValue: recordName)
}
public static var cloudKitRecordType: String { "DataSourceMetadata" }

public static func from(recordInfo: RecordInfo) -> Self? {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,10 @@ public import MistKit
// MARK: - CloudKitRecord Conformance

extension RestoreImageRecord: @retroactive CloudKitRecord {
@_implements(CloudKitRecord, recordName)
public var cloudKitRecordName: RecordName {
RecordName(rawValue: recordName)
}
public static var cloudKitRecordType: String { "RestoreImage" }

public static func from(recordInfo: RecordInfo) -> Self? {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,10 @@ public import MistKit
// MARK: - CloudKitRecord Conformance

extension SwiftVersionRecord: @retroactive CloudKitRecord {
@_implements(CloudKitRecord, recordName)
public var cloudKitRecordName: RecordName {
RecordName(rawValue: recordName)
}
public static var cloudKitRecordType: String { "SwiftVersion" }

public static func from(recordInfo: RecordInfo) -> Self? {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,10 @@ public import MistKit
// MARK: - CloudKitRecord Conformance

extension XcodeVersionRecord: @retroactive CloudKitRecord {
@_implements(CloudKitRecord, recordName)
public var cloudKitRecordName: RecordName {
RecordName(rawValue: recordName)
}
public static var cloudKitRecordType: String { "XcodeVersion" }

public static func from(recordInfo: RecordInfo) -> Self? {
Expand All @@ -52,8 +56,9 @@ extension XcodeVersionRecord: @retroactive CloudKitRecord {
downloadURL: recordInfo.fields["downloadURL"]?.urlValue,
fileSize: recordInfo.fields["fileSize"]?.intValue,
isPrerelease: recordInfo.fields["isPrerelease"]?.boolValue ?? false,
minimumMacOS: recordInfo.fields["minimumMacOS"]?.referenceValue?.recordName,
includedSwiftVersion: recordInfo.fields["includedSwiftVersion"]?.referenceValue?.recordName,
minimumMacOS: recordInfo.fields["minimumMacOS"]?.referenceValue?.recordName.rawValue,
includedSwiftVersion: recordInfo.fields["includedSwiftVersion"]?.referenceValue?.recordName
.rawValue,
sdkVersions: recordInfo.fields["sdkVersions"]?.stringValue,
notes: recordInfo.fields["notes"]?.stringValue
)
Expand Down Expand Up @@ -93,7 +98,7 @@ extension XcodeVersionRecord: @retroactive CloudKitRecord {
if let minimumMacOS {
fields["minimumMacOS"] = .reference(
Reference(
recordName: minimumMacOS,
recordName: RecordName(minimumMacOS),
action: nil
)
)
Expand All @@ -102,7 +107,7 @@ extension XcodeVersionRecord: @retroactive CloudKitRecord {
if let includedSwiftVersion {
fields["includedSwiftVersion"] = .reference(
Reference(
recordName: includedSwiftVersion,
recordName: RecordName(includedSwiftVersion),
action: nil
)
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ internal struct MockCloudKitServiceTests {
let operation = RecordOperation(
operationType: .create,
recordType: "RestoreImage",
recordName: "RestoreImage-\(record.buildNumber)",
recordName: RecordName("RestoreImage-\(record.buildNumber)"),
fields: record.toCloudKitFields()
)

Expand All @@ -76,7 +76,7 @@ internal struct MockCloudKitServiceTests {
let createOp = RecordOperation(
operationType: .create,
recordType: "RestoreImage",
recordName: recordName,
recordName: RecordName(recordName),
fields: initialRecord.toCloudKitFields()
)
try await service.executeBatchOperations([createOp])
Expand All @@ -100,7 +100,7 @@ internal struct MockCloudKitServiceTests {
let replaceOp = RecordOperation(
operationType: .forceReplace,
recordType: "RestoreImage",
recordName: recordName,
recordName: RecordName(recordName),
fields: updatedRecord.toCloudKitFields()
)
try await service.executeBatchOperations([replaceOp])
Expand All @@ -127,7 +127,7 @@ internal struct MockCloudKitServiceTests {
let createOp = RecordOperation(
operationType: .create,
recordType: "RestoreImage",
recordName: recordName,
recordName: RecordName(recordName),
fields: record.toCloudKitFields()
)
try await service.executeBatchOperations([createOp])
Expand All @@ -136,7 +136,7 @@ internal struct MockCloudKitServiceTests {
let deleteOp = RecordOperation(
operationType: .delete,
recordType: "RestoreImage",
recordName: recordName
recordName: RecordName(recordName)
)
try await service.executeBatchOperations([deleteOp])

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -166,7 +166,7 @@ internal actor MockCloudKitService: RecordManaging {

private func createRecordInfo(from operation: RecordOperation) -> RecordInfo {
RecordInfo(
recordName: operation.recordName ?? UUID().uuidString,
recordName: operation.recordName ?? RecordName(UUID().uuidString),
recordType: operation.recordType,
recordChangeTag: UUID().uuidString,
fields: operation.fields
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,7 @@ extension FieldValue {
Issue.record("Expected .reference, got \(self)")
return
}
#expect(ref.recordName == expectedRecordName)
#expect(ref.recordName.rawValue == expectedRecordName)
}

/// Asserts that this FieldValue is a date (does not validate the exact value)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ public enum MockRecordInfo: Sendable {
fields: [String: FieldValue]
) -> RecordInfo {
RecordInfo(
recordName: recordName,
recordName: RecordName(recordName),
recordType: recordType,
recordChangeTag: nil,
fields: fields
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ extension Article: CloudKitConvertible {
let tags = record.stringArray(forKey: "tags")

self.init(
recordName: record.recordName,
recordName: record.recordName.rawValue,
recordChangeTag: record.recordChangeTag,
feedRecordName: feedRecordName,
guid: guid,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@ extension Feed: CloudKitConvertible {
let tags = record.stringArray(forKey: "tags")

self.init(
recordName: record.recordName,
recordName: record.recordName.rawValue,
recordChangeTag: record.recordChangeTag,
feedURL: feedURL,
title: title,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ public struct ArticleOperationBuilder: Sendable {
articles.map { article in
RecordOperation.create(
recordType: "Article",
recordName: UUID().uuidString,
recordName: RecordName(UUID().uuidString),
fields: article.toFieldsDict()
)
}
Expand All @@ -68,7 +68,7 @@ public struct ArticleOperationBuilder: Sendable {

return RecordOperation.update(
recordType: "Article",
recordName: recordName,
recordName: RecordName(recordName),
fields: article.toFieldsDict(),
recordChangeTag: article.recordChangeTag
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ extension CloudKitService {

let operation = RecordOperation.create(
recordType: "Feed",
recordName: UUID().uuidString,
recordName: RecordName(UUID().uuidString),
fields: feed.toFieldsDict()
)
let results = try await self.modifyRecords([operation])
Expand All @@ -58,7 +58,7 @@ extension CloudKitService {

let operation = RecordOperation.update(
recordType: "Feed",
recordName: recordName,
recordName: RecordName(recordName),
fields: feed.toFieldsDict(),
recordChangeTag: feed.recordChangeTag
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ public struct FeedCloudKitService: Sendable {

let operation = RecordOperation.create(
recordType: "Feed",
recordName: UUID().uuidString,
recordName: RecordName(UUID().uuidString),
fields: feed.toFieldsDict()
)
let results = try await recordOperator.modifyRecords([operation])
Expand All @@ -74,7 +74,7 @@ public struct FeedCloudKitService: Sendable {

let operation = RecordOperation.update(
recordType: "Feed",
recordName: recordName,
recordName: RecordName(recordName),
fields: feed.toFieldsDict(),
recordChangeTag: feed.recordChangeTag
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -161,7 +161,7 @@ extension ArticleConversion {

// Create a record
let record = RecordInfo(
recordName: originalArticle.recordName ?? "roundtrip-article",
recordName: RecordName(originalArticle.recordName ?? "roundtrip-article"),
recordType: "Article",
recordChangeTag: originalArticle.recordChangeTag,
fields: fields
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,7 @@ extension FeedConversion {

// Create a record
let record = RecordInfo(
recordName: originalFeed.recordName ?? "round-trip",
recordName: RecordName(originalFeed.recordName ?? "round-trip"),
recordType: "Feed",
recordChangeTag: originalFeed.recordChangeTag,
fields: fields
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -193,7 +193,7 @@ internal struct BatchOperationResultTests {
private func createTestRecords(count: Int) -> [RecordInfo] {
(0..<count).map { index in
RecordInfo(
recordName: "record-\(index)",
recordName: RecordName("record-\(index)"),
recordType: "Article",
recordChangeTag: "tag-\(index)",
fields: [
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ extension ArticleCloudKitService {
fields: [String: FieldValue] = [:]
) -> RecordInfo {
RecordInfo(
recordName: recordName,
recordName: RecordName(recordName),
recordType: "Article",
recordChangeTag: "tag-123",
fields: fields
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ extension ArticleCloudKitService {
fields: [String: FieldValue] = [:]
) -> RecordInfo {
RecordInfo(
recordName: recordName,
recordName: RecordName(recordName),
recordType: "Article",
recordChangeTag: "tag-123",
fields: fields
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@ extension ArticleSyncService {
fields: [String: FieldValue] = [:]
) -> RecordInfo {
RecordInfo(
recordName: recordName,
recordName: RecordName(recordName),
recordType: "Article",
recordChangeTag: "tag-123",
fields: fields
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ extension FeedCloudKitService {
fields: [String: FieldValue] = [:]
) -> RecordInfo {
RecordInfo(
recordName: recordName,
recordName: RecordName(recordName),
recordType: "Feed",
recordChangeTag: "tag-123",
fields: fields
Expand Down
Loading
Loading