[{"data":1,"prerenderedAt":-1},["ShallowReactive",2],{"project-7123":3},{"id":4,"name":5,"fullName":6,"owner":7,"repo":5,"description":8,"homepage":9,"htmlUrl":10,"language":11,"languages":10,"totalLinesOfCode":10,"stars":12,"forks":13,"watchers":14,"openIssues":15,"contributorsCount":16,"subscribersCount":16,"size":16,"stars1d":16,"stars7d":16,"stars30d":16,"stars90d":16,"forks30d":16,"starsTrendScore":16,"compositeScore":17,"rankGlobal":10,"rankLanguage":10,"license":18,"archived":19,"fork":19,"defaultBranch":20,"hasWiki":19,"hasPages":19,"topics":21,"createdAt":10,"pushedAt":10,"updatedAt":30,"readmeContent":31,"aiSummary":32,"trendingCount":16,"starSnapshotCount":16,"syncStatus":33,"lastSyncTime":34,"discoverSource":35},7123,"SwiftSync","3lvis\u002FSwiftSync","3lvis","JSON to SwiftData and back. SwiftData Sync. ","",null,"Swift",2541,261,57,9,0,59.25,"MIT License",false,"master",[22,23,24,25,26,27,28,29],"carthage","cocoapods","core-data","coredata","json","restkit","swift","sync","2026-06-12 04:00:32","![SwiftSync](Images\u002Flogo-v3.png)\n\nSwiftSync is a sync layer for SwiftData apps.\n\nDefine your models once, read from local SwiftData, and let SwiftSync handle the repetitive sync and export work in between.\n\n## Features\n\n- Convention-first JSON -> SwiftData mapping\n- Deterministic diffing for inserts, updates, and deletes\n- Automatic relationship syncing for nested objects and foreign keys\n- Export back into API-ready JSON\n- Reactive local reads for SwiftUI and UIKit\n\n## Quick Start\n\n### One-to-many\n\n![Relationship model example](Images\u002Fone-to-many-swift.png)\n\n```swift\nimport SwiftData\nimport SwiftSync\n\n@Syncable\n@Model\nfinal class User {\n  @Attribute(.unique) var id: Int\n  var email: String?\n  var createdAt: Date?\n  var updatedAt: Date?\n  var notes: [Note]\n\n  init(id: Int, email: String? = nil, createdAt: Date? = nil, updatedAt: Date? = nil, notes: [Note] = []) {\n    self.id = id\n    self.email = email\n    self.createdAt = createdAt\n    self.updatedAt = updatedAt\n    self.notes = notes\n  }\n}\n\n@Syncable\n@Model\nfinal class Note {\n  @Attribute(.unique) var id: Int\n  var text: String\n  var user: User?\n\n  init(id: Int, text: String, user: User? = nil) {\n    self.id = id\n    self.text = text\n    self.user = user\n  }\n}\n```\n\n#### JSON\n\n```json\n[\n  {\n    \"id\": 6,\n    \"email\": \"shawn@ovium.com\",\n    \"created_at\": \"2014-02-14T04:30:10+00:00\",\n    \"updated_at\": \"2014-02-17T10:01:12+00:00\",\n    \"notes\": [\n      {\n        \"id\": 301,\n        \"text\": \"Call supplier before Friday\"\n      },\n      {\n        \"id\": 302,\n        \"text\": \"Prepare Q1 budget review\"\n      }\n    ]\n  }\n]\n```\n\n#### Sync\n\nCreate the container:\n\n```swift\nlet syncContainer = try SyncContainer(for: User.self, Note.self)\n```\n\nThen sync the payload:\n\n```swift\nlet payload = try await getUsers()\ntry await syncContainer.sync(payload: payload, as: User.self)\n```\n\n#### SwiftUI reacts automatically using @SyncQuery\n\n```swift\nimport SwiftUI\nimport SwiftSync\n\nstruct UsersView: View {\n  let syncContainer: SyncContainer\n\n  @SyncQuery(\n    User.self,\n    in: syncContainer,\n    sortBy: [SortDescriptor(\\User.id)]\n  )\n  private var users: [User]\n\n  var body: some View {\n    List {\n      ForEach(users) { user in\n        Section(user.email ?? \"User \\\\(user.id)\") {\n          ForEach(user.notes) { note in\n            Text(note.text)\n          }\n        }\n      }\n    }\n  }\n}\n```\n\n### One-to-one\n\n![One-to-one relationship model example](Images\u002Fone-to-one-v2.png)\n\n#### Model\n\n```swift\nimport SwiftData\nimport SwiftSync\n\n@Syncable\n@Model\nfinal class User {\n  @Attribute(.unique) var id: Int\n  var name: String\n  var company: Company?\n\n  init(id: Int, name: String, company: Company? = nil) {\n    self.id = id\n    self.name = name\n    self.company = company\n  }\n}\n\n@Syncable\n@Model\nfinal class Company {\n  @Attribute(.unique) var id: Int\n  var name: String\n  var user: User?\n\n  init(id: Int, name: String, user: User? = nil) {\n    self.id = id\n    self.name = name\n    self.user = user\n  }\n}\n```\n\n#### JSON\n\n```json\n[\n  {\n    \"id\": 6,\n    \"name\": \"Shawn Merrill\",\n    \"company\": {\n      \"id\": 0,\n      \"name\": \"Facebook\"\n    }\n  }\n]\n```\n\n#### Sync\n\n```swift\nlet syncContainer = try SyncContainer(for: User.self, Company.self)\n\nlet payload = try await getUsers()\ntry await syncContainer.sync(payload: payload, as: User.self)\n```\n\n#### SwiftUI reacts automatically using @SyncQuery\n\n```swift\nimport SwiftUI\nimport SwiftSync\n\nstruct UsersView: View {\n  let syncContainer: SyncContainer\n\n  @SyncQuery(\n    User.self,\n    in: syncContainer,\n    sortBy: [SortDescriptor(\\User.id)]\n  )\n  private var users: [User]\n\n  var body: some View {\n    List(users) { user in\n      VStack(alignment: .leading, spacing: 4) {\n        Text(user.name)\n        Text(user.company?.name ?? \"No company\")\n          .foregroundStyle(.secondary)\n      }\n    }\n  }\n}\n```\n\n## Install\n\nAdd the package in Xcode:\n\n1. `File` -> `Add Package Dependencies...`\n2. Use this URL:\n\n```text\nhttps:\u002F\u002Fgithub.com\u002F3lvis\u002FSwiftSync.git\n```\n\n3. Add the `SwiftSync` library product to your app target.\n\nIf you use `Package.swift` directly:\n\n```swift\n.package(url: \"https:\u002F\u002Fgithub.com\u002F3lvis\u002FSwiftSync.git\", from: \"1.0.0\")\n```\n\nThen import:\n\n```swift\nimport SwiftSync\n```\n\nRequirements: Xcode 17+, Swift 6.2, iOS 17+ \u002F macOS 14+\n\n## Full Overview\n\nQuick Start already covers the default root-collection path.\n\nThe sections below cover the next cases you are likely to hit in a real app:\n\n- One-to-many relationships linked by IDs instead of nested child objects\n- Parent-scoped sync for child collections returned under one parent\n- Single-item sync for detail payloads and mutation responses\n- Relationship payload shapes beyond the simple nested to-many example\n- Customization points for mapping, reads, export, and dates\n\nTable of contents:\n\n- [One-to-Many w\u002F child IDs](#one-to-many-w-child-ids)\n- [Parent-Scoped Sync](#parent-scoped-sync)\n- [Single-Item Sync](#single-item-sync)\n- [Property Mapping and Customization](#property-mapping-and-customization)\n- [Reactive Reads](#reactive-reads)\n- [Exporting JSON](#exporting-json)\n- [Date Handling](#date-handling)\n- [Demo App](#demo-app)\n- [Further Reading](#further-reading)\n- [License](#license)\n\n## One-to-Many w\u002F child IDs\n\nUse this shape when the parent JSON does not include full child objects and only sends their IDs.\n\n### Model\n\n```swift\n@Syncable\n@Model\npublic final class Project {\n  @Attribute(.unique) public var id: String\n  public var name: String\n  public var tasks: [Task]\n}\n\n@Syncable\n@Model\npublic final class Task {\n  @Attribute(.unique) public var id: String\n  public var title: String\n\n  @NotExport\n  public var project: Project?\n}\n```\n\n### JSON\n\n```json\n[\n  {\n    \"id\": \"C3E7A1B2-1001-0000-0000-000000000001\",\n    \"name\": \"Account Security Controls\",\n    \"task_ids\": [\n      \"C3E7A1B2-3001-0000-0000-000000000001\",\n      \"C3E7A1B2-3001-0000-0000-000000000002\"\n    ]\n  }\n]\n```\n\n### Sync\n\n```swift\ntry await syncContainer.sync(payload: payload, as: Project.self)\n```\n\nThis works when those tasks already exist in SwiftData, or are synced elsewhere. SwiftSync uses the `*_ids` list to connect the relationship without needing full task objects in the same JSON.\n\n### Read\n\n```swift\n@SyncModel(Project.self, id: projectID, in: syncContainer)\nprivate var project: Project?\n```\n\nThis pattern is useful for APIs that return lightweight parent objects and keep the full child records on a separate endpoint.\n\n## Parent-Scoped Sync\n\nUse parent-scoped sync when an endpoint returns the children for a single parent, such as `\u002Fprojects\u002F{id}\u002Ftasks`.\n\n### Model\n\n```swift\n@Syncable\n@Model\npublic final class Task {\n  @Attribute(.unique) public var id: String\n  public var title: String\n  public var projectID: String\n\n  @NotExport\n  public var project: Project?\n}\n```\n\n### JSON\n\n```json\n[\n  {\n    \"id\": \"C3E7A1B2-3001-0000-0000-000000000001\",\n    \"title\": \"Add session timeout controls to account settings\",\n    \"project_id\": \"C3E7A1B2-1001-0000-0000-000000000001\"\n  }\n]\n```\n\n### Sync\n\n```swift\ntry await syncContainer.sync(\n  payload: payload,\n  as: Task.self,\n  parent: project,\n  relationship: \\Task.project\n)\n```\n\nThe explicit `relationship:` key path tells SwiftSync which parent these rows belong to. It compares changes only inside that parent’s set of rows, not across the whole table.\n\n### Read\n\n```swift\nlet taskPublisher = SyncQueryPublisher(\n  Task.self,\n  relationship: \\Task.project,\n  relationshipID: projectID,\n  in: syncContainer\n)\n```\n\nSee [Parent Scope](docs\u002Fproject\u002Fparent-scope.md) for the full rules.\n\n## Single-Item Sync\n\nUse `sync(item:)` when an endpoint returns one model at a time, such as a detail screen or the response from saving an edit.\n\nIt also works well when that same response includes child data that belongs to that one model.\n\n### Model\n\n```swift\n@Syncable\n@Model\npublic final class Item {\n  @Attribute(.unique) public var id: String\n  public var title: String\n  public var taskID: String\n\n  @NotExport\n  public var task: Task?\n}\n```\n\n### JSON\n\n```json\n{\n  \"id\": \"C3E7A1B2-3001-0000-0000-000000000001\",\n  \"title\": \"Add session timeout controls to account settings\",\n  \"items\": [\n    {\n      \"id\": \"C3E7A1B2-4001-0000-0000-000000000001\",\n      \"task_id\": \"C3E7A1B2-3001-0000-0000-000000000001\",\n      \"title\": \"Document requirements\"\n    }\n  ]\n}\n```\n\n### Sync\n\n```swift\ntry await syncContainer.sync(item: payload, as: Task.self)\ntry await syncContainer.sync(\n  payload: itemPayload,\n  as: Item.self,\n  parent: task,\n  relationship: \\Item.task\n)\n```\n\n### Read\n\n```swift\n@SyncModel(Task.self, id: taskID, in: syncContainer)\nprivate var task: Task?\n```\n\nThis keeps the detail flow simple:\n\n- Update the task from the single-object response\n- Update that task's checklist items from the nested array\n- Let both list and detail screens keep reading from the same SwiftData data\n\n## Property Mapping\n\nConvention-first mapping is the default. Reach for overrides only when local naming intentionally differs from the backend.\n\nLet's say you have a `Task` model. The backend sends a top-level `description` field and a nested `state` object, but you want the local model to stay straightforward and Swift-friendly:\n\n```json\n{\n  \"id\": 42,\n  \"title\": \"Ship README rewrite\",\n  \"description\": \"Tighten up the property mapping docs and examples.\",\n  \"state\": {\n    \"id\": \"in_progress\",\n    \"label\": \"In Progress\"\n  }\n}\n```\n\nYou can keep `title` convention-based, rename `description` locally, and flatten the nested `state` object into plain properties on `Task`:\n\n```swift\n@Syncable\n@Model\nfinal class Task {\n  @Attribute(.unique) var id: Int\n  var title: String\n\n  @RemoteKey(\"description\")\n  var details: String?\n\n  @RemoteKey(\"state.id\")\n  var state: String\n\n  @RemoteKey(\"state.label\")\n  var stateLabel: String\n\n  init(\n    id: Int,\n    title: String,\n    details: String? = nil,\n    state: String,\n    stateLabel: String\n  ) {\n    self.id = id\n    self.title = title\n    self.details = details\n    self.state = state\n    self.stateLabel = stateLabel\n  }\n}\n```\n\nIn that example:\n\n- `title` needs no annotation because the local name already matches the backend key\n- `details` maps back to `description`, which avoids overloading a common Swift model property name\n- `state` reads from `state.id`, so your app can work with a flat status identifier\n- `stateLabel` reads from `state.label`, which keeps the display string available without storing a nested type locally\n\nUse these annotations when you need them:\n\n- Rely on convention when names already line up\n- Use `@RemoteKey` when the local property name intentionally differs\n- Use deep paths when the backend nests values but your local model should stay flat\n- Use `@PrimaryKey` or `@PrimaryKey(remote:)` when identity is not `id`\n- Use `@NotExport` when a property should not be written back out\n\nSee [Property Mapping Contract](docs\u002Fproject\u002Fproperty-mapping-contract.md) for the complete mapping rules.\n\n## Reactive Reads\n\nSwiftSync is built around local reactive reads. That means your views do not fetch directly from the network and then hold onto that response as UI state. Instead, sync writes backend changes into SwiftData, and the UI reads from SwiftData as its source of truth.\n\nThe \"reactive\" part is that those reads stay fresh automatically. When a sync updates the local store, `@SyncQuery` and `@SyncModel` observe the relevant changes, refetch from the local container, and let SwiftUI re-render with current data.\n\nThis is the core pattern:\n\n- Sync or mutation code writes backend results into the local `SyncContainer`\n- Screens read from the local store instead of reading from transport responses\n- The UI updates when the local rows it depends on change\n\nUse `@SyncQuery` for list reads and `@SyncModel` for detail reads:\n\n```swift\n@SyncQuery(\n  Task.self,\n  in: syncContainer,\n  sortBy: [\n    SortDescriptor(\\Task.updatedAt, order: .reverse),\n    SortDescriptor(\\Task.id)\n  ]\n)\nvar tasks: [Task]\n```\n\nFor one row by ID, use `@SyncModel`:\n\n```swift\n@SyncModel(Task.self, id: taskID, in: syncContainer)\nvar task: Task?\n```\n\nFor rows that belong to one parent, pass `relationship` and `relationshipID` so the query stays scoped to that relationship path:\n\n```swift\n@SyncQuery(\n  Task.self,\n  relationship: \\.project,\n  relationshipID: projectID,\n  in: syncContainer,\n  sortBy: [SortDescriptor(\\Task.id)]\n)\nvar tasks: [Task]\n```\n\nUIKit is supported through `SyncQueryPublisher` and `SyncModelPublisher`, with the same idea: read locally, let sync update the store, and let the UI react to store changes.\n\nSee [Reactive Reads](docs\u002Fproject\u002Freactive-reads.md) for the full patterns and tradeoffs.\n\n## Exporting JSON\n\nUse export when a local draft needs to become a request body, usually for create and update flows.\n\nThe main surface is object export:\n\n```swift\nlet body = syncContainer.export(draft)\n```\n\nThat gives you a JSON-ready dictionary using the same key mapping, nested relationship handling, and date formatting rules that sync uses in the other direction.\n\nTypical flow:\n\n- Build or edit a draft locally\n- Export that draft right before save\n- Send the exported body to your backend\n- Sync the backend response back into the container so the UI keeps reading from local state\n\nThis is the pattern used in the demo task form as well. Keep the draft isolated while the user edits, export it when they tap save, and let the post-save sync update the shared store.\n\nIf your backend expects a different key style or date format, configure the container first:\n\n```swift\nlet syncContainer = SyncContainer(\n  modelContainer,\n  keyStyle: .camelCase,\n  dateFormatter: formatter\n)\n\nlet body = syncContainer.export(draft)\n```\n\nDefaults:\n\n- Snake_case keys\n- Relationships included as inline arrays\u002Fobjects\n- ISO-style UTC dates\n- Nils exported as `null`\n\nBest practices:\n\n- Export a draft object, not the live screen state from network responses\n- Prefer exporting right before create or update requests so the payload reflects the latest local edits\n- Treat the exported object as a transport body, then re-sync the confirmed backend response into SwiftData\n- Use `@RemoteKey`, `@NotExport`, and container formatting options to keep transport concerns out of your UI code\n\nSee [FAQ](docs\u002Fproject\u002Ffaq.md) and [Property Mapping Contract](docs\u002Fproject\u002Fproperty-mapping-contract.md) for more on export.\n\n## Date Handling\n\nSwiftSync handles common API date formats out of the box, so most apps do not need any date setup to get started.\n\nOn import, it accepts common ISO8601 variants, date-only strings, `YYYY-MM-DD HH:mm:ss`, fractional seconds, and unix timestamps.\n\nOn export, it uses an ISO-style UTC formatter by default. That means the same `SyncContainer` can usually parse backend dates on the way in and emit API-ready dates on the way back out without extra configuration.\n\nIf your backend expects a different outbound format, configure the container with a custom `DateFormatter`:\n\n```swift\nlet syncContainer = SyncContainer(\n  modelContainer,\n  dateFormatter: formatter\n)\n```\n\nUse that when the server expects a specific non-default string format for create or update payloads.\n\n## Demo App\n\nThe demo app shows the full workflow end to end. It is there to show the pieces working together, not to explain every concept in the README.\n\nIt includes:\n\n- Project-scoped task sync\n- Task detail sync with nested items\n- To-one and to-many relationship updates\n- Local reactive reads in SwiftUI and UIKit\n- Create\u002Fedit flows that export local models back into payloads\n\nOpen `SwiftSync.xcworkspace` if you want to see those cases working together in a single app.\n\n## Further Reading\n\n- [Parent Scope](docs\u002Fproject\u002Fparent-scope.md)\n- [Property Mapping Contract](docs\u002Fproject\u002Fproperty-mapping-contract.md)\n- [Reactive Reads](docs\u002Fproject\u002Freactive-reads.md)\n- [Backend Contract](docs\u002Fproject\u002Fbackend-contract.md)\n- [FAQ](docs\u002Fproject\u002Ffaq.md)\n\n## License\n\nSwiftSync is released under the [MIT License](LICENSE).\n","SwiftSync 是一个用于 SwiftData 应用的数据同步层，支持 JSON 与 SwiftData 之间的双向转换。其核心功能包括基于约定的 JSON 到 SwiftData 映射、确定性的差异处理（插入、更新和删除）、自动关系同步（嵌套对象和外键）以及将数据导出为 API 就绪的 JSON 格式。此外，它还提供了对 SwiftUI 和 UIKit 的响应式本地读取支持。此工具特别适用于需要频繁进行客户端-服务器间数据同步的应用场景，如移动应用中的用户数据管理、笔记应用等，能够显著减少开发者在数据同步逻辑上的开发工作量。",2,"2026-06-11 03:10:38","top_language"]