Contents

naviapps/preset-kit

PresetKit is a small Swift package for encoding and decoding portable, versioned preset

Requirements

  • Swift 6.0 or later
  • No Apple-platform minimum is declared; PresetKit uses Foundation-only APIs.

Installation

Add this package to your Swift Package dependencies:

.package(url: "https://github.com/naviapps/preset-kit.git", from: "2.0.0")

Then add the library product to your target:

.product(name: "PresetKit", package: "preset-kit")

Documentation

Basic Usage

In the examples below, your app owns EditorLayout. PresetKit owns the document wrapper, metadata, codecs, and import error categories.

import PresetKit

struct EditorLayout: Codable, Equatable {
  var columns: Int
  var showsInspector: Bool
}

Define one host-owned PresetFormat for that app-owned payload:

import PresetKit

let layoutFormat = try PresetFormat(
  identifier: "com.example.editor-layout"
)

Use a host-owned format identifier such as a reverse-DNS string. Identifiers are dot-separated segments, and each segment may contain ASCII letters, numbers, hyphens, or underscores. Payloads only need Encodable for export, Decodable for import, or Codable when the same type is used for both. When the document format changes, set currentVersion to the version this app writes and minimumSupportedVersion to the oldest version it can still import. These versions describe the preset document format. Host apps decide how payload values migrate between versions.

Create a document and export it as JSON:

import PresetKit

let layoutDocument = layoutFormat.makeDocument(
  payload: EditorLayout(columns: 2, showsInspector: true),
  metadata: PresetMetadata(
    id: "focus",
    title: "Focus",
    producerVersion: "2026.1"
  )
)

let jsonData = try PresetJSONCodec.encode(layoutDocument)

PresetMetadata.id is an optional host-defined identifier. PresetKit stores it without assigning sync, storage, or uniqueness semantics.

Import untrusted data with a byte limit. In these examples, importedJSONData is data read from a user-selected file or another external source:

import PresetKit

let importedDocument = try PresetJSONCodec.decodeDocument(
  EditorLayout.self,
  from: importedJSONData,
  matching: layoutFormat,
  maximumByteCount: 1_000_000
)
let importedLayout = importedDocument.payload

PresetKit ignores unknown fields in its package-owned document envelope, format version, and metadata so additive producers remain readable. Payload field handling remains the host payload type's Decodable responsibility.

Use decodeDocument when the caller also needs document metadata or stored format identity:

import PresetKit

let importedDocument = try PresetJSONCodec.decodeDocument(
  EditorLayout.self,
  from: importedJSONData,
  matching: layoutFormat,
  maximumByteCount: 1_000_000
)

let importedTitle = importedDocument.metadata.title
let importedFormatVersion = importedDocument.formatVersion
let importedFormatIdentifier = importedFormatVersion.identifier
let importedVersionNumber = importedFormatVersion.version

Use decodeDocuments for document arrays and map their payload properties when only payloads are needed. Foundation accepts JSON imports as UTF-8 or UTF-16 text.

Property Lists

Use PresetPropertyListCodec when a host app needs property list data instead of JSON:

import PresetKit

let propertyListData = try PresetPropertyListCodec.encode(layoutDocument)
let propertyListDocument = try PresetPropertyListCodec.decodeDocument(
  EditorLayout.self,
  from: propertyListData,
  matching: layoutFormat,
  maximumByteCount: 1_000_000
)
let propertyListLayout = propertyListDocument.payload

JSON remains the default interchange format. Property lists are useful for Apple-platform workflows that already store or inspect plist data.

Import Errors

Decode methods throw PresetImportError directly, providing canonical categories for host UI and logging:

import PresetKit

do {
  let importedDocument = try PresetJSONCodec.decodeDocument(
    EditorLayout.self,
    from: importedJSONData,
    matching: layoutFormat,
    maximumByteCount: 1_000_000
  )

  saveImportedLayout(importedDocument.payload)
} catch {
  switch error {
  case .invalidDocument:
    presentImportError("The selected file is not a valid preset.")
  case .formatMismatch:
    presentImportError("The selected file contains a different kind of preset.")
  case .unsupportedFormatVersion:
    presentImportError("This preset was created by an unsupported app version.")
  case .inputTooLarge:
    presentImportError("The selected file is too large.")
  }
}

Responsibility Boundary

PresetKit intentionally does not own:

  • preset payload schemas
  • payload schema evolution between host-app payload versions
  • import/export UI
  • persistence, syncing, or sharing flows
  • validation beyond document format identity and version range
  • YAML, TOML, or other formats that require external dependencies or host-specific schema choices

Those concerns should live in the host app or in a package with that direct responsibility.

Development

Run the package check with:

make check

GitHub Actions runs the same check on pull requests and pushes to main.

Contributing

See CONTRIBUTING.md. Release notes are in CHANGELOG.md.

Security

Report vulnerabilities privately. See SECURITY.md.

License

PresetKit is released under the MIT License. See LICENSE.

Package Metadata

Repository: naviapps/preset-kit

Default branch: main

README: README.md