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.payloadPresetKit 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.versionUse 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.payloadJSON 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 checkGitHub 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