Skip to content

Conversation

@finnvoor
Copy link

@finnvoor finnvoor commented Dec 20, 2025

// All fields encrypted (default)
try SyncEngine(for: database, tables: Reminder.self)

// No fields encrypted
try SyncEngine(for: database, tables: Reminder.self, encryptedFields: .none)

// Only specific fields encrypted
try SyncEngine(for: database, tables: Reminder.self, encryptedFields: .only(Reminder.notes, Reminder.title))

I originally implemented unencryptedFields: RemindersList.title, Reminder.title with an inverse encryptedFields: RemindersList.title, Reminder.title, but I think the API was very confusing since there also needed to be an init for all/none encryption.

added unit tests and tested in a WIP app

see #244

@lukaskubanek
Copy link
Contributor

@finnvoor I like the ergonomics of the EncryptedFields struct, but there are two issues worth considering:

First, as I understand it, this approach naively switches between record[field] and record.encryptedValues[field]. That becomes problematic if the app runs against an environment with existing data and the encryption flag is flipped for a field. CloudKit treats it as a programmer error when you try to populate both and throws a runtime exception that crashes the app. As discussed here, it may be worth introducing an additional layer that caches these flags as the source of truth. SQLiteData could then detect deviations and, for example, emit a purple warning while continuing to use the cached value.

Second, this approach would rule out the macro-based solution that @pfandrade and I proposed here. One advantage of that approach is that it can easily house additional CloudKit-related field configuration, such as custom field names or merge policies for custom conflict resolution, while keeping it close to the field definitions in the model types, rather than specifying everything in the detached SyncEngine initializer.

There is still an unresolved clash with the type-level macro, which would need to be aware of the sync capability and therefore differ from @Table, which is being used now. In any case, it would be interesting to hear from Brandon and Stephen which direction they foresee for the library.

@finnvoor
Copy link
Author

@lukaskubanek RE your first point I think pretty much any changes to SyncEngine config after deploying a schema are going to cause huge issues with your app (containerIdentifier/defaultZone) and I think it's a bit overkill to try to avoid user error like this (it's easier to clearly warn in the docs against this).

RE macros I definitely think it's worth considering/experimenting with, though I'd like to not have to flag every single one of my fields as unencrypted with a macro (IMO all fields should be unencrypted by default to match SwiftData/CoreData, but that would change the existing defaults).

open to all feedback, definitely a big change!

@lukaskubanek
Copy link
Contributor

I think pretty much any changes to SyncEngine config after deploying a schema are going to cause huge issues with your app (containerIdentifier/defaultZone) and I think it's a bit overkill to try to avoid user error like this (it's easier to clearly warn in the docs against this)

That’s fair. Though, from the discussions, it sounds like some devs might want to switch fields from encrypted to unencrypted retroactively, e.g. after a schema is already deployed and they later realize they need indices or want to access the data via JS. I’m on thin ice here since I don’t have this use case myself. Just throwing in my two cents…

IMO all fields should be unencrypted by default to match SwiftData/CoreData, but that would change the existing defaults

Yep, I’d also prefer this over the current default in the library, but I agree it’s probably not something that can be easily changed at this point.

(Using unencrypted fields by default would also likely have avoided the CloudKit bug #315 that I struggled with for quite a while. Thankfully, a workaround is now in place as of version 1.4.1.)

RE macros I definitely think it's worth considering/experimenting with, though I'd like to not have to flag every single one of my fields as unencrypted with a macro

I see. Another option could be a type-level master switch, which could even be used to change the default behavior should that be desired in the library.

For example, a plain @Table might become @SyncedTable(encryptFields: true), while @SyncedTable itself would default to unencrypted fields. Individual fields could still override this via @SyncedField(encrypted: true/false).

That said, this might be leaning too far. It really depends on the overall direction of the library. My main motivation was to unify CloudKit configuration with the focus on custom conflict resolution, which is the area I’m most interested in (see #272).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants