{"id":13872156,"url":"https://github.com/brightdigit/MistKit","last_synced_at":"2025-07-16T01:33:08.971Z","repository":{"id":45628571,"uuid":"292557726","full_name":"brightdigit/MistKit","owner":"brightdigit","description":"Swift Package for Server-Side and Command-Line Access to CloudKit Web Services","archived":false,"fork":false,"pushed_at":"2023-05-29T23:46:10.000Z","size":4250,"stargazers_count":207,"open_issues_count":34,"forks_count":11,"subscribers_count":13,"default_branch":"main","last_synced_at":"2024-11-16T17:03:27.009Z","etag":null,"topics":["cloudkit","server-side-swift","swift","vapor"],"latest_commit_sha":null,"homepage":"","language":"Swift","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/brightdigit.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null}},"created_at":"2020-09-03T12:03:03.000Z","updated_at":"2024-11-12T16:35:40.000Z","dependencies_parsed_at":"2024-01-16T10:09:27.978Z","dependency_job_id":null,"html_url":"https://github.com/brightdigit/MistKit","commit_stats":{"total_commits":73,"total_committers":3,"mean_commits":"24.333333333333332","dds":0.0821917808219178,"last_synced_commit":"adecfe3abfeccca3273fbbf97b62eb275d445510"},"previous_names":[],"tags_count":14,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/brightdigit%2FMistKit","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/brightdigit%2FMistKit/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/brightdigit%2FMistKit/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/brightdigit%2FMistKit/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/brightdigit","download_url":"https://codeload.github.com/brightdigit/MistKit/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":226090030,"owners_count":17572114,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["cloudkit","server-side-swift","swift","vapor"],"created_at":"2024-08-05T23:00:35.337Z","updated_at":"2024-11-23T19:31:40.083Z","avatar_url":"https://github.com/brightdigit.png","language":"Swift","readme":"\n\u003cp align=\"center\"\u003e\n    \u003cimg alt=\"MistKit\" title=\"MistKit\" src=\"Assets/logo.svg\" height=\"200\"\u003e\n\u003c/p\u003e\n\u003ch1 align=\"center\"\u003e MistKit \u003c/h1\u003e\n\nSwift Package for Server-Side and Command-Line Access to CloudKit Web Services\n\n[![SwiftPM](https://img.shields.io/badge/SPM-Linux%20%7C%20iOS%20%7C%20macOS%20%7C%20watchOS%20%7C%20tvOS-success?logo=swift)](https://swift.org)\n[![Twitter](https://img.shields.io/badge/twitter-@brightdigit-blue.svg?style=flat)](http://twitter.com/brightdigit)\n![GitHub](https://img.shields.io/github/license/brightdigit/MistKit)\n![GitHub issues](https://img.shields.io/github/issues/brightdigit/MistKit)\n\n[![macOS](https://github.com/brightdigit/MistKit/workflows/macOS/badge.svg)](https://github.com/brightdigit/MistKit/actions?query=workflow%3AmacOS)\n[![ubuntu](https://github.com/brightdigit/MistKit/workflows/ubuntu/badge.svg)](https://github.com/brightdigit/MistKit/actions?query=workflow%3Aubuntu)\n[![Travis (.com)](https://img.shields.io/travis/com/brightdigit/MistKit?logo=travis\u0026?label=travis-ci)](https://travis-ci.com/brightdigit/MistKit)\n[![Bitrise](https://img.shields.io/bitrise/b2595eab70c25d1b?logo=bitrise\u0026?label=bitrise\u0026token=rHUhEUFkU2RUL-KGmrKX1Q)](https://app.bitrise.io/app/b2595eab70c25d1b)\n[![CircleCI](https://img.shields.io/circleci/build/github/brightdigit/MistKit?logo=circleci\u0026?label=circle-ci\u0026token=45c9ff6a86f9ac6c1ec8c85c3bc02f4d8859aa6b)](https://app.circleci.com/pipelines/github/brightdigit/MistKit)\n\n[![](https://img.shields.io/endpoint?url=https%3A%2F%2Fswiftpackageindex.com%2Fapi%2Fpackages%2Fbrightdigit%2FMistKit%2Fbadge%3Ftype%3Dswift-versions)](https://swiftpackageindex.com/brightdigit/MistKit)\n[![](https://img.shields.io/endpoint?url=https%3A%2F%2Fswiftpackageindex.com%2Fapi%2Fpackages%2Fbrightdigit%2FMistKit%2Fbadge%3Ftype%3Dplatforms)](https://swiftpackageindex.com/brightdigit/MistKit)\n\n\n[![Codecov](https://img.shields.io/codecov/c/github/brightdigit/MistKit)](https://codecov.io/gh/brightdigit/MistKit)\n[![CodeFactor Grade](https://img.shields.io/codefactor/grade/github/brightdigit/MistKit)](https://www.codefactor.io/repository/github/brightdigit/MistKit)\n[![codebeat badge](https://codebeat.co/badges/c47b7e58-867c-410b-80c5-57e10140ba0f)](https://codebeat.co/projects/github-com-brightdigit-mistkit-main)\n[![Code Climate maintainability](https://img.shields.io/codeclimate/maintainability/brightdigit/MistKit)](https://codeclimate.com/github/brightdigit/MistKit)\n[![Code Climate technical debt](https://img.shields.io/codeclimate/tech-debt/brightdigit/MistKit?label=debt)](https://codeclimate.com/github/brightdigit/MistKit)\n[![Code Climate issues](https://img.shields.io/codeclimate/issues/brightdigit/MistKit)](https://codeclimate.com/github/brightdigit/MistKit)\n[![Reviewed by Hound](https://img.shields.io/badge/Reviewed_by-Hound-8E64B0.svg)](https://houndci.com)\n\n![Demonstration of MistKit via Command-Line App `mistdemoc`](Assets/MistKitDemo.gif)\n\n\n# Table of Contents\n\n   * [**Introduction**](#introduction)\n   * [**Features**](#features)\n   * [**Installation**](#installation)\n   * [**Usage**](#usage)\n      * [Composing Web Service Requests](#composing-web-service-requests)\n        * [Setting Up Authenticated Requests](#setting-up-authenticated-requests)\n        * [CloudKit and Vapor](#cloudkit-and-vapor)\n      * [Fetching Records Using a Query (records/query)](#fetching-records-using-a-query-recordsquery)\n      * [Fetching Records by Record Name (records/lookup)](#fetching-records-by-record-name-recordslookup)\n      * [Fetching Current User Identity (users/caller)](#fetching-current-user-identity-userscaller)\n      * [Modifying Records (records/modify)](#modifying-records-recordsmodify)\n      * [Using SwiftNIO](#using-swiftnio)\n         * [Using EventLoops](#using-eventloops)\n         * [Choosing an HTTP Client](#choosing-an-http-client)\n      * [Examples](#examples)\n      * [Further Code Documentation](#further-code-documentation)\n   * [**Roadmap**](#roadmap)\n      * [~~0.1.0~~](#010)\n      * [~~0.2.0~~](#020)\n      * [**0.4.0**](#040)\n      * [0.6.0](#060)\n      * [0.8.0](#080)\n      * [0.9.0](#090)\n      * [v1.0.0](#v100)\n   * [**License**](#license)\n\n# Introduction\n\nRather than the CloudKit framework this Swift package uses [CloudKit Web Services.](https://developer.apple.com/library/archive/documentation/DataManagement/Conceptual/CloudKitWebServicesReference/index.html#//apple_ref/doc/uid/TP40015240-CH41-SW1). Why?\n\n* Building a **Command Line Application**\n* Use on **Linux** (or any other non-Apple OS)\n* Required for **Server-Side Integration (via Vapor)**\n* Access via **AWS Lambda**\n* **Migrating Data from/to CloudKit**\n\n... and more\n\nIn my case, I was using this for **the Vapor back-end for my Apple Watch app [Heartwitch](https://heartwitch.app)**. Here's some example code showing how to setup and use **MistKit** with CloudKit container.\n\n### Demo Example\n\n#### CloudKit Dashboard Schema\n\n![Sample Schema for Todo List](Assets/CloudKitDB-Demo-Schema.jpg)\n\n#### Sample Code using **MistKit**\n\n```swift\n// Example for pulling a todo list from CloudKit\nimport MistKit\nimport MistKitNIOHTTP1Token\n\n// setup your connection to CloudKit\nlet connection = MKDatabaseConnection(\n  container: \"iCloud.com.brightdigit.MistDemo\", \n  apiToken: \"****\", \n  environment: .development\n)\n\n// setup how to manager your user's web authentication token \nlet manager = MKTokenManager(storage: MKUserDefaultsStorage(), client: MKNIOHTTP1TokenClient())\n\n// setup your database manager\nlet database = MKDatabase(\n  connection: connection,\n  tokenManager: manager\n)\n\n// create your request to CloudKit\nlet query = MKQuery(recordType: TodoListItem.self)\n\nlet request = FetchRecordQueryRequest(\n  database: .private, \n  query: FetchRecordQuery(query: query))\n\n// handle the result\ndatabase.query(request) { result in\n  dump(result)\n}\n\n// wait for query here...\n```\n\nTo wait for the CloudKit query to complete synchronously, you can use [CFRunLoop](https://developer.apple.com/documentation/corefoundation/cfrunloop-rht):\n\n```swift\n...\n// handle the result\ndatabase.query(request) { result in\n  dump(result)\n\n  // nessecary if you need run this synchronously\n  CFRunLoopStop(CFRunLoopGetMain())\n}\n\n// nessecary if you need run this synchronously\nCFRunLoopRun()\n```\n# Features \n\nHere's what's currently implemented with this library:\n\n- [x] Composing Web Service Requests\n- [x] Modifying Records (records/modify)\n- [x] Fetching Records Using a Query (records/query)\n- [x] Fetching Records by Record Name (records/lookup)\n- [x] Fetching Current User Identity (users/caller)\n\n# Installation\n\nSwift Package Manager is Apple's decentralized dependency manager to integrate libraries to your Swift projects. It is now fully integrated with Xcode 11.\n\nTo integrate **MistKit** into your project using SPM, specify it in your Package.swift file:\n\n```swift    \nlet package = Package(\n  ...\n  dependencies: [\n    .package(url: \"https://github.com/brightdigit/MistKit\", from: \"0.2.0\")\n  ],\n  targets: [\n      .target(\n          name: \"YourTarget\",\n          dependencies: [\"MistKit\", ...]),\n      ...\n  ]\n)\n```\n\nThere are also products for SwiftNIO as well as Vapor if you are building server-side implmentation:\n\n```swift      \n      .target(\n          name: \"YourTarget\",\n          dependencies: [\"MistKit\", \n            .product(name: \"MistKitNIO\", package: \"MistKit\"),  // if you are building a server-side application\n            .product(name: \"MistKitVapor\", package: \"MistKit\") // if you are building a Vapor application\n            ...]\n      ),\n```\n\n# Usage \n\n## Composing Web Service Requests\n\n**MistKit** requires a connection be setup with the following properties:\n\n* `container` name in the format of `iCloud.com.*.*` such as `iCloud.com.brightdigit.MistDemo`\n* `apiToken` which can be [created through the CloudKit Dashboard](https://developer.apple.com/library/archive/documentation/DataManagement/Conceptual/CloudKitWebServicesReference/SettingUpWebServices.html#//apple_ref/doc/uid/TP40015240-CH24-SW1)\n* `environment` which can be either `development` or `production`\n\nHere's an example of how to setup an `MKDatabase`:\n\n```swift\nlet connection = MKDatabaseConnection(\n  container: options.container, \n  apiToken: options.apiKey, \n  environment: options.environment)\n\n// setup your database manager\nlet database = MKDatabase(\n  connection: connection,\n  tokenManager: manager\n)\n```\n\nBefore getting into make an actual request, you should probably know how to make authenticated request for `private` or `shared` databases.\n\n### Setting Up Authenticated Requests\n\nIn order to have access to `private` or `shared` databases, the Cloud Web Services API require a web authentication token. In order for the MistKit to obtain this, an http server is setup to listen to the callback from CloudKit.\n\nTherefore when you setup your API token, make sure to setup a url for the Sign-In Callback:\n\n![CloudKit Dashboard](Assets/CloudKitDB-APIToken.png)\n\nOnce that's setup, you can setup a `MKTokenManager`.\n\n![CloudKit Dashboard Callback](Assets/CloudKitDB-APIToken-Callback.png)\n\n#### Managing Web Authentication Tokens\n\n`MKTokenManager` requires a `MKTokenStorage` for storing the token for later.\nThere are a few implementations you can use:\n  * `MKFileStorage` stores the token as a simple text file\n  * `MKUserDefaultsStorage` stores the token using `UserDefaults`\n  * `MKVaporModelStorage` stores the token in a database `Model` object via `Fluent`\n  * `MKVaporSessionStorage` stores the token the Vapor `Session` data\n\nOptionally **MistKit** can setup a web server for you if needed to listen to web authentication via a `MKTokenClient`:\nThere are a few implementations you can use:\n  * `MKNIOHTTP1TokenClient` sets up an http server using SwiftNIO\n\nHere's an example of how you `MKDatabase`:\n\n```swift\nlet connection = MKDatabaseConnection(\n  container: options.container, \n  apiToken: options.apiKey, \n  environment: options.environment\n )\n\n// setup how to manager your user's web authentication token\nlet manager = MKTokenManager(\n  // store the token in UserDefaults\n  storage: MKUserDefaultsStorage(), \n  // setup an http server at localhost for port 7000\n  client: MKNIOHTTP1TokenClient(bindTo: .ipAddress(host: \"127.0.0.1\", port: 7000))\n)\n\n// setup your database manager\nlet database = MKDatabase(\n  connection: connection,\n  tokenManager: manager\n)\n```\n\n##### Using `MKNIOHTTP1TokenClient`\n\nIf you are not building a server-side application, you can use `MKNIOHTTP1TokenClient`, by adding `MistKitNIO` to your package dependency:\n\n```swift\nlet package = Package(\n  ...\n  dependencies: [\n    .package(url: \"https://github.com/brightdigit/MistKit\", .branch(\"main\")\n  ],\n  targets: [\n      .target(\n          name: \"YourTarget\",\n          dependencies: [\"MistKit\", \"MistKitNIOHTTP1Token\", ...]),\n      ...\n  ]\n)\n```\n\nWhen a request fails due to authentication failure, `MKNIOHTTP1TokenClient` will start an http server to begin listening to web authentication token. By default, `MKNIOHTTP1TokenClient` will simply print the url but you can override the `onRequestURL`:\n\n```swift\npublic class MKNIOHTTP1TokenClient: MKTokenClient {\n  \n  public init(bindTo: BindTo, onRedirectURL : ((URL) -\u003e Void)? = nil) {\n    self.bindTo = bindTo\n    self.onRedirectURL = onRedirectURL ?? {print($0)}\n  }\n  ...\n}\n```\n\n### CloudKit and Vapor\n\n#### Static Web Authentication Tokens\n\nIf you may already have a `webAuthenticationToken`, you can use `MKStaticTokenManager`. This is a read-only implementation of `MKTokenManagerProtocol` which takes a read-only `String?` for the `webAuthenticationToken`.\n\nHere's some sample code I use in my Vapor app **[Heartwitch](https://heartwitch.app)** for pulling the `webAuthenticationToken` from my database and using that token when I create a `MKDatabase` instance.\n\n```swift\nimport MistKit\nimport MistKitVapor\n\nextension Application {\n  ...\n  var cloudKitConnection: MKDatabaseConnection {\n    MKDatabaseConnection(\n      container: configuration.cloudkitContainer,\n      apiToken: configuration.cloudkitAPIKey,\n      environment: environment.cloudKitEnvironment\n    )\n  }\n\n  func cloudKitDatabase(using client: Client, withWebAuthenticationToken webAuthenticationToken: String? = nil) -\u003e MKDatabase\u003cMKVaporClient\u003e {\n    MKDatabase(\n      connection: cloudKitConnection,\n      client: MKVaporClient(client: client),\n      tokenManager: MKStaticTokenManager(token: webAuthenticationToken, client: nil)\n    )\n  }\n}\n\nstruct DeviceController {\n\n  func fetch(_ request: Request) throws -\u003e EventLoopFuture\u003cMKServerResponse\u003c[DeviceResponseItem]\u003e\u003e {\n    let user = try request.auth.require(User.self)\n    let userID = try user.requireID()\n    let token = user.$appleUsers.query(on: request.db).field(\\.$webAuthenticationToken).first().map { $0?.webAuthenticationToken }\n\n    let cloudKitDatabase: EventLoopFuture\u003cMKDatabase\u003e = token.map {\n      request.application.cloudKitDatabase(using: request.client, withWebAuthenticationToken: $0)\n    }\n    \n    let cloudKitRequest = FetchRecordQueryRequest(\n      database: .private,\n      query: FetchRecordQuery(query: query)\n    )\n    \n    let newEntries = cloudKitDatabase.flatMap {\n      let cloudKitResult = cloudKitDatabase.query(cloudKitRequest, on: request.eventLoop)\n    }\n\n    return newEntries.mistKitResponse()\n  }\n  \n  ...\n}\n```\n\nBesides static strings, you can store your tokens in the session or in your database.\n\n#### Storing Web Authentication Tokens in Databases and Sessions\n\nIn the `mistdemod` demo Vapor application, there's an example of how to create an `MKDatabase` based on the request using both `MKVaporModelStorage` and `MKVaporSessionStorage`:\n\n```swift\nextension MKDatabase where HttpClient == MKVaporClient {\n  init(request: Request) {\n    let storage: MKTokenStorage\n    if let user = request.auth.get(User.self) {\n      storage = MKVaporModelStorage(model: user)\n    } else {\n      storage = MKVaporSessionStorage(session: request.session)\n    }\n    let manager = MKTokenManager(storage: storage, client: nil)\n\n    let options = MistDemoDefaultConfiguration(apiKey: request.application.cloudKitAPIKey)\n    let connection = MKDatabaseConnection(container: options.container, apiToken: options.apiKey, environment: options.environment)\n\n    // use the webAuthenticationToken which is passed\n    if let token = options.token {\n      manager.webAuthenticationToken = token\n    }\n\n    self.init(connection: connection, factory: nil, client: MKVaporClient(client: request.client), tokenManager: manager)\n  }\n}\n```\n\nIn this case, for the `User` model needs to implement `MKModelStorable`.\n\n```swift\nfinal class User: Model, Content {\n  ...\n\n  @Field(key: \"cloudKitToken\")\n  var cloudKitToken: String?\n}\n\nextension User: MKModelStorable {\n  static var tokenKey: KeyPath\u003cUser, Field\u003cString?\u003e\u003e = \\User.$cloudKitToken\n}\n```\n\nThe `MKModelStorable` protocol ensures that the `Model` contains the properties needed for storing the web authentication token.\n\nWhile the command line tool needs a `MKTokenClient` to listen for the callback from CloudKit, with a server-side application you can just add a API call. Here's an example which listens for the `ckWebAuthToken` and saves it to the `User`:\n\n```swift\nstruct CloudKitController: RouteCollection {\n  func token(_ request: Request) -\u003e EventLoopFuture\u003cHTTPStatus\u003e {\n    guard let token: String = request.query[\"ckWebAuthToken\"] else {\n      return request.eventLoop.makeSucceededFuture(.notFound)\n    }\n\n    guard let user = request.auth.get(User.self) else {\n      request.cloudKitAPI.webAuthenticationToken = token\n      return request.eventLoop.makeSucceededFuture(.accepted)\n    }\n\n    user.cloudKitToken = token\n    return user.save(on: request.db).transform(to: .accepted)\n  }\n\n  func boot(routes: RoutesBuilder) throws {\n    routes.get([\"token\"], use: token)\n  }\n}\n```\n\nIf you have an app which already uses Apple's existing CloudKit API, you can also [save the webAuthenticationToken to your database with a `CKFetchWebAuthTokenOperation`](https://developer.apple.com/documentation/cloudkit/ckfetchwebauthtokenoperation).\n\n## Fetching Records Using a Query (records/query)\n\nThere are two ways to fetch records:\n\n* using an `MKAnyQuery` to fetch `MKAnyRecord` items\n* using a custom type which implements `MKQueryRecord`\n\n### Setting Up Queries\n\nTo fetch as `MKAnyRecord`, simply create `MKAnyQuery` with the matching `recordType` (i.e. schema name). \n\n```swift\n// create your request to CloudKit\nlet query = MKAnyQuery(recordType: \"TodoListItem\")\n\nlet request = FetchRecordQueryRequest(\n  database: .private,\n  query: FetchRecordQuery(query: query)\n)\n\n// handle the result\ndatabase.perform(request: request) { result in\n  do {\n    try print(result.get().records.information)\n  } catch {\n    completed(error)\n    return\n  }\n  completed(nil)\n}\n```\n\nThis will give you `MKAnyRecord` items which contain a `fields` property with your values:\n\n```swift\npublic struct MKAnyRecord: Codable {\n  public let recordType: String\n  public let recordName: UUID?\n  public let recordChangeTag: String?\n  public let fields: [String: MKValue]\n  ...\n```\n\nThe `MKValue` type is an enum which contains the type and value of the field.\n\n### Strong-Typed Queries\n\nIn order to use a custom type for requests, you need to implement `MKQueryRecord`. Here's an example of a todo item which contains a title property:\n\n```swift\npublic class TodoListItem: MKQueryRecord {\n  // required property and methods for MKQueryRecord\n  public static var recordType: String = \"TodoItem\"\n  public static var desiredKeys: [String]? = [\"title\"]\n\n  public let recordName: UUID?\n  public let recordChangeTag: String?\n  \n  public required init(record: MKAnyRecord) throws {\n    recordName = record.recordName\n    recordChangeTag = record.recordChangeTag\n    title = try record.string(fromKey: \"title\")\n  }\n  \n  public var fields: [String: MKValue] {\n    return [\"title\": .string(title)]\n  }\n  \n  // custom fields and methods to `TodoListItem`\n  public var title: String\n  \n  public init(title: String) {\n    self.title = title\n    recordName = nil\n    recordChangeTag = nil\n  }\n}\n```\n\nNow you can create an `MKQuery` using your custom type.\n\n```swift\n// create your request to CloudKit\nlet query = MKQuery(recordType: TodoListItem.self)\n\nlet request = FetchRecordQueryRequest(\n  database: .private,\n  query: FetchRecordQuery(query: query)\n)\n\n// handle the result\ndatabase.query(request) { result in\n  do {\n    try print(result.get().information)\n  } catch {\n    completed(error)\n    return\n  }\n  completed(nil)\n}\n```\n\nRather than using `MKDatabase.perform(request:)`, use `MKDatabase.query(_ query:)` and `MKDatabase` will decode the value to your custom type.\n\n### Filters \n\n_Coming Soon_\n\n## Fetching Records by Record Name (records/lookup)\n\n```swift\nlet recordNames : [UUID] = [...]\n\nlet query = LookupRecordQuery(TodoListItem.self, recordNames: recordNames)\n\nlet request = LookupRecordQueryRequest(database: .private, query: query)\n\ndatabase.lookup(request) { result in\n  try? print(result.get().count)\n}\n```\n\n_Coming Soon_\n\n## Fetching Current User Identity (users/caller)\n\n```swift\nlet request = GetCurrentUserIdentityRequest()\ndatabase.perform(request: request) { (result) in\n  try? print(result.get().userRecordName)\n}\n```\n\n_Coming Soon_\n\n## Modifying Records (records/modify)\n\n### Creating Records\n\n```swift\nlet item = TodoListItem(title: title)\n\nlet operation = ModifyOperation(operationType: .create, record: item)\n\nlet query = ModifyRecordQuery(operations: [operation])\n\nlet request = ModifyRecordQueryRequest(database: .private, query: query)\n\ndatabase.perform(operations: request) { result in\n  do {\n    try print(result.get().updated.information)\n  } catch {\n    completed(error)\n    return\n  }\n  completed(nil)\n}\n```\n\n### Deleting Records\n\nIn order to delete and update records, you are required to already have the object fetched from CloudKit. Therefore you'll need to run a `LookupRecordQueryRequest` or `FetchRecordQueryRequest` to get access to the record. Once you have access to the records, simply create a delete operation with your record:\n\n```swift\nlet query = LookupRecordQuery(TodoListItem.self, recordNames: recordNames)\n\nlet request = LookupRecordQueryRequest(database: .private, query: query)\n\ndatabase.lookup(request) { result in\n  let items: [TodoListItem]\n  \n  do {\n    items = try result.get()\n  } catch {\n    completed(error)\n    return\n  }\n  \n  let operations = items.map { (item) in\n    ModifyOperation(operationType: .delete, record: item)\n  }\n\n  let query = ModifyRecordQuery(operations: operations)\n\n  let request = ModifyRecordQueryRequest(database: .private, query: query)\n  \n  database.perform(operations: request) { result in\n    do {\n      try print(\"Deleted \\(result.get().deleted.count) items.\")\n    } catch {\n      completed(error)\n      return\n    }\n    completed(nil)\n  }\n}\n```\n\n### Updating Records\n\nSimilarly with updating records, you are required to already have the object fetched from CloudKit. Again, run a `LookupRecordQueryRequest` or `FetchRecordQueryRequest` to get access to the record. Once you have access to the records, simply create a update operation with your record:\n\n```swift\nlet query = LookupRecordQuery(TodoListItem.self, recordNames: [recordName])\n\nlet request = LookupRecordQueryRequest(database: .private, query: query)\n\ndatabase.lookup(request) { result in\n  let items: [TodoListItem]\n  do {\n    items = try result.get()\n\n  } catch {\n    completed(error)\n    return\n  }\n  let operations = items.map { (item) -\u003e ModifyOperation\u003cTodoListItem\u003e in\n    item.title = self.newTitle\n    return ModifyOperation(operationType: .update, record: item)\n  }\n\n  let query = ModifyRecordQuery(operations: operations)\n\n  let request = ModifyRecordQueryRequest(database: .private, query: query)\n  database.perform(operations: request) { result in\n    do {\n      try print(\"Updated \\(result.get().updated.count) items.\")\n    } catch {\n      completed(error)\n      return\n    }\n    completed(nil)\n  }\n}\n```\n\n## Using SwiftNIO\n\nIf you are building a server-side application and already using [SwiftNIO](https://github.com/apple/swift-nio), you might want to take advantage of some helpers which will work already existing patterns and APIs available. Primarily **[EventLoops](https://apple.github.io/swift-nio/docs/current/NIO/Protocols/EventLoop.html)** from [SwiftNIO](https://github.com/apple/swift-nio) and the respective **HTTP clients** from [SwiftNIO](https://github.com/apple/swift-nio) and [Vapor](https://vapor.codes/).\n\n### Using EventLoops\n\nIf you are building a server-side application in [SwiftNIO](https://github.com/apple/swift-nio) (or [Vapor](https://vapor.codes/)), you are likely using [EventLoops](https://apple.github.io/swift-nio/docs/current/NIO/Protocols/EventLoop.html) and [EventLoopFuture](https://apple.github.io/swift-nio/docs/current/NIO/Classes/EventLoopFuture.html) for asyncronous programming. EventLoopFutures are essentially the Future/Promise implementation of [SwiftNIO](https://github.com/apple/swift-nio). Luckily there are helper methods in MistKit which provide [EventLoopFutures](https://apple.github.io/swift-nio/docs/current/NIO/Classes/EventLoopFuture.html) similar to the way they implmented in [SwiftNIO](https://github.com/apple/swift-nio). These implementations augment the already existing callback:\n\n\n```swift\npublic extension MKDatabase {\n  func query\u003cRecordType\u003e(\n    _ query: FetchRecordQueryRequest\u003cMKQuery\u003cRecordType\u003e\u003e,\n    on eventLoop: EventLoop\n  ) -\u003e EventLoopFuture\u003c[RecordType]\u003e\n\n  func perform\u003cRecordType\u003e(\n    operations: ModifyRecordQueryRequest\u003cRecordType\u003e,\n    on eventLoop: EventLoop\n  ) -\u003e EventLoopFuture\u003cModifiedRecordQueryResult\u003cRecordType\u003e\u003e\n  \n  func lookup\u003cRecordType\u003e(\n    _ lookup: LookupRecordQueryRequest\u003cRecordType\u003e,\n    on eventLoop: EventLoop\n  ) -\u003e EventLoopFuture\u003c[RecordType]\u003e\n\n  func perform\u003cRequestType: MKRequest, ResponseType\u003e(\n    request: RequestType,\n    on eventLoop: EventLoop\n  ) -\u003e EventLoopFuture\u003cResponseType\u003e -\u003e EventLoopFuture\u003cResponseType\u003e\n    where RequestType.Response == ResponseType\n}\n```\n\nAlso if you are using the results as `Content` for a [Vapor](https://vapor.codes/) HTTP response, **MistKit** provides a `MKServerResponse` enum type which distinguishes between an authentication failure (with the redirect URL) and an actual success. \n\n```swift\npublic enum MKServerResponse\u003cSuccess\u003e: Codable where Success: Codable {\n  public init(attemptRecoveryFrom error: Error) throws\n\n  case failure(URL)\n  case success(Success)\n}\n```\n\nBesides [EventLoopFuture](https://apple.github.io/swift-nio/docs/current/NIO/Classes/EventLoopFuture.html), you can also use a different HTTP client for calling CloudKit Web Services.  \n\n### Choosing an HTTP Client\n\nBy default, MistKit uses `URLSession` for making HTTP calls to the CloudKit Web Service via the `MKURLSessionClient`:\n\n```swift\npublic struct MKURLSessionClient: MKHttpClient {\n  public init(session: URLSession) {\n    self.session = session\n  }\n\n  public func request(withURL url: URL, data: Data?) -\u003e MKURLRequest\n}\n```\n\nHowever if you are using [SwiftNIO](https://github.com/apple/swift-nio) or [Vapor](https://vapor.codes/), it makes more sense the use their HTTP clients for making those calls:\n* For **SwiftNIO**, there's **`MKAsyncClient`** which uses an `HTTPClient` provided by the `AsyncHTTPClient` library\n* For **Vapor**, there's **`MKVaporClient`** which uses an `Client` provided by the `Vapor` library\n\nIn the mistdemod example, you can see how to use a Vapor `Request` to create an `MKDatabase` with the `client` property of the `Request`:\n\n```swift\nextension MKDatabase where HttpClient == MKVaporClient {\n  init(request: Request) {\n    let manager: MKTokenManager    \n    let connection : MKDatabaseConnection\n    self.init(\n      connection: connection, \n      factory: nil, \n      client: MKVaporClient(client: request.client), \n      tokenManager: manager\n    )\n  }\n}\n```\n\n## Examples\n\nThere are two examples on how to do basic CRUD methods in CloudKit via MistKit: \n* As a command line tool using Swift Argument Parser checkout [the `mistdemoc` Swift package executable here](https://github.com/brightdigit/MistKit/tree/main/Sources/mistdemoc)\n* And a server-side Vapor application [`mistdemod` here](https://github.com/brightdigit/MistKit/tree/main/Sources/mistdemoc)\n\n## Further Code Documentation\n\n[Documentation Here](/Documentation/Reference/README.md)\n\n# Roadmap\n\n\u003c!-- https://developer.apple.com/library/archive/documentation/DataManagement/Conceptual/CloudKitWebServicesReference/index.html#//apple_ref/doc/uid/TP40015240-CH41-SW1 --\u003e\n\n## 0.1.0\n\n- [x] Composing Web Service Requests\n- [x] Modifying Records (records/modify)\n- [x] Fetching Records Using a Query (records/query)\n- [x] Fetching Records by Record Name (records/lookup)\n- [x] Fetching Current User Identity (users/caller)\n\n## 0.2.0 \n\n- [x] Vapor Token Client\n- [x] Vapor Token Storage\n- [x] Vapor URL Client\n- [x] Swift NIO URL Client\n\n## 0.4.0 \n\n- [X] Date Field Types\n- [X] Location Field Types\n- [ ] List Field Types\n- [ ] System Field Integration\n\n## 0.6.0\n\n- [ ] Name Component Types\n- [ ] Discovering User Identities (POST users/discover)\n- [ ] Discovering All User Identities (GET users/discover)\n- [ ] Support `postMessage` for Authentication Requests\n\n## 0.8.0\n\n- [ ] Uploading Assets (assets/upload)\n- [ ] Referencing Existing Assets (assets/rereference)\n- [ ] Fetching Records Using a Query (records/query) w/ basic filtering\n\n## 0.9.0\n\n- [ ] Fetching Contacts (users/lookup/contacts)\n- [ ] Fetching Users by Email (users/lookup/email)\n- [ ] Fetching Users by Record Name (users/lookup/id)\n\n## v1.0.0\n\n- [ ] Reference Field Types\n- [ ] Error Codes\n- [ ] Handle Data Size Limits\n\n## v1.x.x+\n\n- [ ] Fetching Record Changes (records/changes)\n- [ ] Fetching Record Information (records/resolve)\n- [ ] Accepting Share Records (records/accept)\n- [ ] Fetching Zones (zones/list)\n- [ ] Fetching Zones by Identifier (zones/lookup)\n- [ ] Modifying Zones (zones/modify)\n- [ ] Fetching Database Changes (changes/database)\n- [ ] Fetching Record Zone Changes (changes/zone)\n- [ ] Fetching Zone Changes (zones/changes)\n- [ ] Fetching Subscriptions (subscriptions/list)\n- [ ] Fetching Subscriptions by Identifier (subscriptions/lookup)\n- [ ] Modifying Subscriptions (subscriptions/modify)\n- [ ] Creating APNs Tokens (tokens/create)\n- [ ] Registering Tokens (tokens/register)\n\n\u003c!-- Explain Demo Application --\u003e\n\n## Not Planned\n\n- [ ] Fetching Current User (users/current) _deprecated_\n\n# License \n\nThis code is distributed under the MIT license. See the [LICENSE](LICENSE) file for more info.\n","funding_links":[],"categories":["Swift"],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fbrightdigit%2FMistKit","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fbrightdigit%2FMistKit","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fbrightdigit%2FMistKit/lists"}