InventoryKit is a Swift Package Manager (SPM) library for modeling, validating, and persisting complex asset inventories (e.g., vintage computers, automotive parts). It provides a YAML/JSON-backed schema, relationship modeling, high-volume catalog indexing, pagination, and pluggable storage so apps can manage hundreds of thousands of assets safely.
- Protocol-First Architecture: Core definitions (
InventoryCore) are separated from concrete implementations (InventoryKit), enabling a flexible, modular design. - Rich Models: Schema-versioned
InventoryDocumentwith assets, lifecycle, MRO, health, embedded components, and relationship requirements. - Relationship + Compatibility Checks: Define relationships via
InventoryRelationshipRequirementProtocoland evaluate compliance (e.g., peripherals required for a computer). - Storage Abstractions: Use
InventoryStorageProviderto plug in any backend (CloudKit, Databases, FileSystemKit). - Transformers: Default YAML/JSON transformers built on Yams + JSONEncoder.
- High-Volume Catalog:
InventoryCatalogactor indexes assets, supports identifier lookup, and paginated queries. - Tag Registry System: Domain-based tag registration with code execution support.
- SDK Entry Point:
InventoryServicebootstraps provider + catalog with configurable logging. - CI + Tests: Validated with >90% code coverage.
InventoryKit targets Swift 6 toolchains and is validated on:
- macOS (Xcode toolchain, macOS 13+)
- Linux (swift.org toolchains, Ubuntu 22.04+ via CI)
Because the package only depends on Foundation and Yams, it can be embedded in server-side Swift services or Apple-platform apps alike.
import InventoryKit
// Implement a storage provider (e.g., using FileSystemKit for file operations)
struct MyStorageProvider: InventoryStorageProvider {
let identifier = "my-provider"
let transformer = InventoryKit.transformer(for: .yaml)
// Vendor support is optional, defaults to nil/unsupported
func loadInventory(validatingAgainst version: InventorySchemaVersion) async throws -> any InventoryDocumentProtocol {
// Use FileSystemKit to read file data
let data = try await FileSystemKit.readData(from: self.url)
return try transformer.decode(data, validatingAgainst: version)
}
func saveInventory(_ document: any InventoryDocumentProtocol) async throws {
// Use FileSystemKit to write file data
let data = try transformer.encode(document)
try await FileSystemKit.writeData(data, to: self.url)
}
}
let provider = MyStorageProvider()
let configuration = InventoryConfiguration(
provider: provider,
schemaVersion: .current,
logLevel: .info
)
let service = try await InventoryService.bootstrap(configuration: configuration)
let newAsset = AnyInventoryAsset(name: "IBM PC/AT")
await service.catalog.upsert(newAsset)
try await service.persistChanges()
let page = await service.listAssets(page: InventoryPageRequest(offset: 0, limit: 20))
print("Loaded \(page.items.count) assets (total \(page.total))")Implement the InventoryStorageProvider protocol inside your app or backend. For example, a CloudKit provider would:
- Fetch CloudKit records and build an
InventoryDocument. - Use
transformer.decode/encodefor YAML/JSON serialization if storing blobs. - Return its identifier and transformer so InventoryKit can log and encode consistently.
struct CloudKitInventoryProvider: InventoryStorageProvider {
let identifier = "cloudkit-provider"
let transformer = InventoryKit.transformer(for: .json)
func loadInventory(validatingAgainst version: InventorySchemaVersion) async throws -> any InventoryDocumentProtocol {
let data = try await fetchDataFromCloudKit()
return try transformer.decode(data, validatingAgainst: version)
}
func saveInventory(_ document: any InventoryDocumentProtocol) async throws {
let data = try transformer.encode(document)
try await saveDataToCloudKit(data)
}
func replaceInventory(with document: any InventoryDocumentProtocol) async throws {
try await saveInventory(document)
}
}Pass your provider into InventoryConfiguration to bootstrap InventoryService.
InventoryKit provides a tag registry system that enables domain-specific tag resolution through code execution handlers. This allows clients to register custom tags that execute code when encountered, enabling powerful tag-based processing workflows.
import InventoryKit
// Create a service (tag registry is automatically created if not provided)
let service = try await InventoryService.bootstrap(configuration: configuration)
// Access the tag registry
let registry = service.tagRegistry
// Register a tag with a code execution handler
try await registry.register(tag: "dsk", domain: "retroboxfs") { tag in
return "AppleDiskImage" // Returns type identifier
}
// Check if a tag is registered
let isRegistered = try await registry.isRegistered(tag: "dsk", domain: "retroboxfs")
print(isRegistered) // true
// Execute handler when tag is encountered
if let result = try await registry.execute(tag: "dsk", domain: "retroboxfs") {
print("Resolved type: \(result)") // "AppleDiskImage"
}
// Get all tags for a domain
let tags = try await registry.tags(for: "retroboxfs")
print(tags) // ["dsk", "woz", "a2r", ...]
// Resolve tag to domain
if let domain = try await registry.domain(for: "dsk") {
print("Domain: \(domain)") // "retroboxfs"
}Tags are organized by domain, allowing multiple clients to register tags without conflicts:
// Register tags for different domains
try await registry.register(tag: "dsk", domain: "retroboxfs") { _ in "AppleDiskImage" }
try await registry.register(tag: "verified", domain: "acme") { _ in "true" }
// Tags with the same name can exist in different domains
let retroboxfsTags = try await registry.tags(for: "retroboxfs")
let acmeTags = try await registry.tags(for: "acme")You can provide a custom tag registry implementation:
struct CustomTagRegistry: InventoryTagRegistry {
// Implement protocol methods
// ...
}
let customRegistry = CustomTagRegistry()
let configuration = InventoryConfiguration(
provider: provider,
tagRegistry: customRegistry
)
let service = try await InventoryService.bootstrap(configuration: configuration)The tag registry system is designed to work with RetroboxFS for disk image type resolution:
// RetroboxFS registers tags during disk image processing
// Tags are stored in InventoryAsset.tags
// Later, RetroboxFS can resolve tags to internal types
let asset = await service.asset(identifierType: .uuid, value: assetID)
if let asset = asset {
for tag in asset.tags {
if let resolvedType = try await registry.execute(tag: tag, domain: "retroboxfs") {
print("Tag \(tag) resolved to: \(resolvedType)")
}
}
}The default DefaultTagRegistry implementation uses Swift actors for thread-safe operations. All tag registry operations are safe for concurrent access.
Run the full suite via:
swift testCI enforces the same command on GitHub Actions (macOS runners) for every push and pull request to main.
InventoryKit follows Semantic Versioning (SemVer) for the library API:
- MAJOR: breaking API changes. +- MINOR: backwards-compatible feature additions.
- PATCH: backwards-compatible bug fixes.
Schema evolution is tracked separately via InventorySchemaVersion. Each release documents both the package version and the current schema version. When introducing schema changes:
- Bump
InventorySchemaVersion.current. - Provide migration helpers or validation logic.
- Publish release notes explaining schema compatibility expectations.
Recommended release workflow:
- Update
Package.swift(if needed) and documentation for the new version. - Tag the commit
vMAJOR.MINOR.PATCH. - Publish release notes summarizing API + schema changes.
IMPORTANT: Client applications must NOT use internal services or actors directly. Always use the public Facade API.
- Client Integration Guide - Detailed guide on using the Facade API.
let services = InventoryKit.shared.services
// Use services.transactions, services.locations, etc.InventoryKit supports client-side localization overrides. Define keys in your main app bundle's Localizable.strings to override default text.
The docs/ directory contains detailed guides for architecture and usage:
- Terminology: Definitions of core concepts (Asset, Product, Relationship).
- Services: Guide to the service layer (Import, Enrichment, Relationship).
- Client Integration: How to integrate InventoryKit into an app.
- Standards: Code standards and implementation patterns.
- Contribution Design: Workflow for user submissions to the public catalog.
- Ship additional storage providers (CloudKit, SQLite).
- Publish DocC documentation with schema samples.
- Bundle JSON/YAML schema definitions for validation tooling.
Contributions and feedback are welcome—please open issues or pull requests!